Skip to content

Commit c9b4c29

Browse files
committed
Addressing Review
1 parent 4a7f0b7 commit c9b4c29

2 files changed

Lines changed: 85 additions & 53 deletions

File tree

newton/_src/viewer/gl/opengl.py

Lines changed: 64 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -1368,7 +1368,7 @@ def render_texture(
13681368
spacing_px: float = 2.0,
13691369
clear_color: tuple[float, float, float, float] = (0.0, 0.0, 0.0, 1.0),
13701370
):
1371-
"""Draw a texture directly to the window without rendering the 3D scene.
1371+
"""Draw a texture to the frame buffer without rendering the 3D scene.
13721372
13731373
Args:
13741374
texture_id: OpenGL texture id to draw, or ``None`` to only clear.
@@ -1381,15 +1381,17 @@ def render_texture(
13811381
``texture_height`` for single-image textures.
13821382
atlas_cols: Number of atlas columns used to pack the source tiles.
13831383
spacing_px: Spacing between displayed tiles in pixels.
1384-
clear_color: Window clear color.
1384+
clear_color: Frame clear color.
13851385
"""
13861386
gl = RendererGL.gl
13871387
self._make_current()
13881388

13891389
screen_w = max(int(self._screen_width), 1)
13901390
screen_h = max(int(self._screen_height), 1)
13911391

1392-
gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, 0)
1392+
assert self._frame_fbo is not None
1393+
gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, self._frame_fbo)
1394+
gl.glDrawBuffer(gl.GL_COLOR_ATTACHMENT0)
13931395
gl.glClearColor(*clear_color)
13941396
gl.glDisable(gl.GL_DEPTH_TEST)
13951397
gl.glDepthMask(True)
@@ -1398,61 +1400,70 @@ def render_texture(
13981400
gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT)
13991401
gl.glDepthMask(False)
14001402

1401-
if texture_id is None or texture_id == 0 or texture_width <= 0 or texture_height <= 0:
1402-
gl.glDepthMask(True)
1403-
return
1403+
if texture_id is not None and texture_id != 0 and texture_width > 0 and texture_height > 0:
1404+
from .image_logger import compute_grid_layout # noqa: PLC0415
1405+
1406+
tile_count = max(1, int(tile_count))
1407+
tile_width = int(tile_width or texture_width)
1408+
tile_height = int(tile_height or texture_height)
1409+
atlas_cols = max(1, int(atlas_cols))
1410+
spacing_px = max(0.0, float(spacing_px if tile_count > 1 else 0.0))
1411+
1412+
rows, cols, cell_w, cell_h = compute_grid_layout(
1413+
tile_count,
1414+
tile_height / float(max(tile_width, 1)),
1415+
float(screen_w),
1416+
float(screen_h),
1417+
spacing_x=spacing_px,
1418+
spacing_y=spacing_px,
1419+
)
1420+
grid_w = cols * cell_w + max(0, cols - 1) * spacing_px
1421+
grid_h = rows * cell_h + max(0, rows - 1) * spacing_px
1422+
origin_x = (screen_w - grid_w) * 0.5
1423+
origin_y = (screen_h - grid_h) * 0.5
14041424

1405-
from .image_logger import compute_grid_layout # noqa: PLC0415
1406-
1407-
tile_count = max(1, int(tile_count))
1408-
tile_width = int(tile_width or texture_width)
1409-
tile_height = int(tile_height or texture_height)
1410-
atlas_cols = max(1, int(atlas_cols))
1411-
spacing_px = max(0.0, float(spacing_px if tile_count > 1 else 0.0))
1412-
1413-
rows, cols, cell_w, cell_h = compute_grid_layout(
1414-
tile_count,
1415-
tile_height / float(max(tile_width, 1)),
1416-
float(screen_w),
1417-
float(screen_h),
1418-
spacing_x=spacing_px,
1419-
spacing_y=spacing_px,
1420-
)
1421-
grid_w = cols * cell_w + max(0, cols - 1) * spacing_px
1422-
grid_h = rows * cell_h + max(0, rows - 1) * spacing_px
1423-
origin_x = (screen_w - grid_w) * 0.5
1424-
origin_y = (screen_h - grid_h) * 0.5
1425-
1426-
gl.glActiveTexture(gl.GL_TEXTURE0)
1427-
gl.glBindTexture(gl.GL_TEXTURE_2D, int(texture_id))
1428-
with self._frame_shader:
1429-
for i in range(tile_count):
1430-
display_row, display_col = divmod(i, cols)
1431-
draw_x = max(0, int(round(origin_x + display_col * (cell_w + spacing_px))))
1432-
draw_y = max(
1433-
0,
1434-
int(round(origin_y + (rows - 1 - display_row) * (cell_h + spacing_px))),
1435-
)
1436-
draw_w = max(1, int(round(cell_w)))
1437-
draw_h = max(1, int(round(cell_h)))
1438-
uv_rect = _texture_tile_uv_rect(
1439-
i,
1440-
tile_width,
1441-
tile_height,
1442-
texture_width,
1443-
texture_height,
1444-
atlas_cols,
1445-
)
1425+
gl.glActiveTexture(gl.GL_TEXTURE0)
1426+
gl.glBindTexture(gl.GL_TEXTURE_2D, int(texture_id))
1427+
with self._frame_shader:
1428+
for i in range(tile_count):
1429+
display_row, display_col = divmod(i, cols)
1430+
draw_x = max(0, int(round(origin_x + display_col * (cell_w + spacing_px))))
1431+
draw_y = max(
1432+
0,
1433+
int(round(origin_y + (rows - 1 - display_row) * (cell_h + spacing_px))),
1434+
)
1435+
draw_w = max(1, int(round(cell_w)))
1436+
draw_h = max(1, int(round(cell_h)))
1437+
uv_rect = _texture_tile_uv_rect(
1438+
i,
1439+
tile_width,
1440+
tile_height,
1441+
texture_width,
1442+
texture_height,
1443+
atlas_cols,
1444+
)
1445+
1446+
gl.glViewport(draw_x, draw_y, draw_w, draw_h)
1447+
self._frame_shader.update(0, uv_rect)
1448+
gl.glBindVertexArray(self._frame_vao)
1449+
gl.glDrawElements(gl.GL_TRIANGLES, len(self._frame_indices), gl.GL_UNSIGNED_INT, None)
1450+
gl.glBindVertexArray(0)
1451+
gl.glBindTexture(gl.GL_TEXTURE_2D, 0)
1452+
1453+
gl.glDepthMask(True)
1454+
gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, 0)
1455+
gl.glViewport(0, 0, screen_w, screen_h)
14461456

1447-
gl.glViewport(draw_x, draw_y, draw_w, draw_h)
1448-
self._frame_shader.update(0, uv_rect)
1457+
if self._frame_texture is not None:
1458+
gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT)
1459+
gl.glActiveTexture(gl.GL_TEXTURE0)
1460+
gl.glBindTexture(gl.GL_TEXTURE_2D, self._frame_texture)
1461+
with self._frame_shader:
1462+
self._frame_shader.update(0)
14491463
gl.glBindVertexArray(self._frame_vao)
14501464
gl.glDrawElements(gl.GL_TRIANGLES, len(self._frame_indices), gl.GL_UNSIGNED_INT, None)
14511465
gl.glBindVertexArray(0)
1452-
gl.glBindTexture(gl.GL_TEXTURE_2D, 0)
1453-
1454-
gl.glViewport(0, 0, screen_w, screen_h)
1455-
gl.glDepthMask(True)
1466+
gl.glBindTexture(gl.GL_TEXTURE_2D, 0)
14561467

14571468
err = gl.glGetError()
14581469
assert err == gl.GL_NO_ERROR, hex(err)

newton/tests/test_viewer_get_frame.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,27 @@ def test_headless_frame_capture_across_devices(self):
112112
finally:
113113
viewer.close()
114114

115+
def test_headless_capture_main_image_frame(self):
116+
"""Verify get_frame captures a main image rendered headlessly."""
117+
try:
118+
viewer = newton.viewer.ViewerGL(width=64, height=48, headless=True)
119+
except Exception as exc:
120+
self.skipTest(f"ViewerGL not available: {exc}")
121+
return
122+
123+
try:
124+
width = viewer.renderer._screen_width
125+
height = viewer.renderer._screen_height
126+
image = np.full((height, width, 3), (37, 113, 191), dtype=np.uint8)
127+
128+
viewer.begin_frame(0.0)
129+
viewer.log_main_image("color", image)
130+
viewer.end_frame()
131+
132+
np.testing.assert_array_equal(viewer.get_frame().numpy(), image)
133+
finally:
134+
viewer.close()
135+
115136
def test_cpu_viewer_uses_host_pbo_readback(self):
116137
pixels = np.array(
117138
[

0 commit comments

Comments
 (0)