Skip to content

Commit dc2d32b

Browse files
Add support for storing last rendered frame in ViewerGL
1 parent 7c64bf5 commit dc2d32b

File tree

2 files changed

+44
-4
lines changed

2 files changed

+44
-4
lines changed

newton/_src/viewer/gl/opengl.py

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import sys
2020

2121
import numpy as np
22+
import numpy.typing as npt
2223
import warp as wp
2324

2425
from newton.utils import create_sphere_mesh
@@ -922,7 +923,7 @@ def update(self):
922923
self.app.platform_event_loop.step(0.001) # 1ms app polling latency
923924
self.window.dispatch_events()
924925

925-
def render(self, camera, objects, lines=None):
926+
def render(self, camera, objects, lines=None, return_frame: bool = False) -> npt.NDArray[np.uint8] | None:
926927
gl = RendererGL.gl
927928
self._make_current()
928929

@@ -991,6 +992,21 @@ def render(self, camera, objects, lines=None):
991992
gl.GL_NEAREST,
992993
)
993994

995+
# -----------------------------------
996+
# Read back frame buffer if requested
997+
# -----------------------------------
998+
999+
frame: npt.NDArray[np.uint8] | None = None
1000+
1001+
if return_frame:
1002+
gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, self._frame_fbo)
1003+
buffer = (gl.GLubyte * (4 * self._screen_width * self._screen_height))()
1004+
gl.glReadPixels(0, 0, self._screen_width, self._screen_height, gl.GL_RGBA, gl.GL_UNSIGNED_BYTE, buffer)
1005+
gl.glBindFramebuffer(gl.GL_FRAMEBUFFER, 0)
1006+
1007+
frame = np.frombuffer(buffer, dtype=np.uint8).reshape((self._screen_height, self._screen_width, 4))
1008+
frame = np.flipud(frame)
1009+
9941010
# ------------------------------------------------------------------
9951011
# Draw resolved texture to the screen
9961012
# ------------------------------------------------------------------
@@ -1018,6 +1034,8 @@ def render(self, camera, objects, lines=None):
10181034
err = gl.glGetError()
10191035
assert err == gl.GL_NO_ERROR, hex(err)
10201036

1037+
return frame
1038+
10211039
def present(self):
10221040
if not self.headless:
10231041
self.window.flip()

newton/_src/viewer/viewer_gl.py

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import time
1919

2020
import numpy as np
21+
import numpy.typing as npt
2122
import warp as wp
2223

2324
import newton as nt
@@ -53,7 +54,7 @@ class ViewerGL(ViewerBase):
5354
- Extensible logging of meshes, lines, points, and arrays for custom visualization.
5455
"""
5556

56-
def __init__(self, width=1920, height=1080, vsync=False, headless=False):
57+
def __init__(self, width=1920, height=1080, vsync=False, headless=False, save_frame: bool = False):
5758
"""
5859
Initialize the OpenGL viewer and UI.
5960
@@ -62,6 +63,7 @@ def __init__(self, width=1920, height=1080, vsync=False, headless=False):
6263
height (int): Window height in pixels.
6364
vsync (bool): Enable vertical sync.
6465
headless (bool): Run in headless mode (no window).
66+
save_frame (bool): If True, store internally the last rendered frame.
6567
"""
6668
super().__init__()
6769

@@ -127,6 +129,10 @@ def __init__(self, width=1920, height=1080, vsync=False, headless=False):
127129
# positions: "side", "stats", "free"
128130
self._ui_callbacks = {"side": [], "stats": [], "free": []}
129131

132+
# Buffer to store the last rendered frame
133+
self._save_frame = save_frame
134+
self._frame = np.empty(0, dtype=np.uint8)
135+
130136
self.set_model(None)
131137

132138
def register_ui_callback(self, callback, position="side"):
@@ -425,7 +431,7 @@ def end_frame(self):
425431
destroyed results in a crash (access violation). Therefore we check
426432
whether an exit was requested and early-out before touching GL if so.
427433
"""
428-
self._update()
434+
return self._update()
429435

430436
@override
431437
def apply_forces(self, state):
@@ -459,7 +465,10 @@ def _update(self):
459465
return
460466

461467
# Render the scene and present it
462-
self.renderer.render(self.camera, self.objects, self.lines)
468+
frame = self.renderer.render(self.camera, self.objects, self.lines, return_frame=self._save_frame)
469+
470+
if self._save_frame:
471+
self._frame = frame
463472

464473
# Always update FPS tracking, even if UI is hidden
465474
self._update_fps()
@@ -475,6 +484,19 @@ def _update(self):
475484

476485
self.renderer.present()
477486

487+
def get_frame(self) -> npt.NDArray[np.uint8]:
488+
"""
489+
Get the last rendered frame as a numpy array.
490+
491+
Returns:
492+
np.ndarray: The last rendered frame in RGBA format.
493+
"""
494+
495+
if not self._save_frame:
496+
raise RuntimeError("Frame saving is not enabled. Initialize with `save_frame=True`.")
497+
498+
return self._frame
499+
478500
@override
479501
def is_running(self) -> bool:
480502
"""

0 commit comments

Comments
 (0)