1010
1111from __future__ import annotations
1212from abc import ABC
13+ import logging
1314import numpy as np
1415import torch
1516from glumpy import app , gloo , gl , ext
@@ -111,7 +112,8 @@ def __init__(self, wisp_state, window_name="wisp app"):
111112 # There we generate a simple billboard GL program (normally with a shared CUDA resource)
112113 # Canvas content will be blitted onto it
113114 self .canvas_program : Optional [gloo .Program ] = None # GL program used to paint a single billboard
114- self .cugl_rgb_handle = None # CUDA buffer, as a shared resource with OpenGL
115+ self .vao : Optional [gloo .VertexArray ] = None # Vertex array object to hold GL buffers
116+ self .cugl_rgb_handle = None # CUDA buffer, as a shared resource with OpenGL
115117 self .cugl_depth_handle = None
116118
117119 try :
@@ -133,7 +135,7 @@ def __init__(self, wisp_state, window_name="wisp app"):
133135 self .change_user_mode (self .default_user_mode ())
134136
135137 self .redraw () # Refresh RendererCore
136-
138+
137139 def add_pipeline (self , name , pipeline , transform = None ):
138140 """Register a neural fields pipeline into the scene graph.
139141
@@ -143,7 +145,7 @@ def add_pipeline(self, name, pipeline, transform=None):
143145 transform (wisp.core.ObjectTransform): The transform for the pipeline.
144146 """
145147 add_pipeline_to_scene_graph (self .wisp_state , name , pipeline , transform = transform )
146-
148+
147149 def add_widget (self , widget ):
148150 """ Adds a widget to the list of widgets.
149151
@@ -242,10 +244,10 @@ def run(self):
242244 """
243245 app .run () # App clock should always run as frequently as possible (background tasks should not be limited)
244246
245- def _create_window (self , width , height , window_name , gl_version ):
247+ def _create_window (self , width , height , window_name , gl_version ) -> app . Window :
246248 # Currently assume glfw backend due to integration with imgui
247249 app .use (f"glfw_imgui ({ gl_version } )" )
248- win_config = app .configuration .Configuration ()
250+ win_config = app .configuration .get_default ()
249251 if self .wisp_state .renderer .antialiasing == 'msaa_4x' :
250252 win_config .samples = 4
251253
@@ -267,7 +269,7 @@ def _create_window(self, width, height, window_name, gl_version):
267269 return window
268270
269271 @staticmethod
270- def _create_gl_depth_billboard_program (texture : np .ndarray , depth_texture : np .ndarray ):
272+ def _create_gl_depth_billboard_program (texture : np .ndarray , depth_texture : np .ndarray ) -> gloo . Program :
271273 vertex = """
272274 uniform float scale;
273275 attribute vec2 position;
@@ -301,8 +303,22 @@ def _create_gl_depth_billboard_program(texture: np.ndarray, depth_texture: np.nd
301303 canvas ['depth_tex' ] = depth_texture
302304 return canvas
303305
306+ def _create_vao (self , gl_config : app .Configuration ) -> gloo .VertexArray :
307+ """ Creates a "default" VertexBufferObject to be used by the GL Programs. """
308+ # OpenGL 3.3+ requires that a VertexArrayObject is always bound.
309+ # Since glumpy's glfw_imgui backend doesn't guarantee one, and imgui expects a GL context with OpenGL >= 3.3,
310+ # we create a default one here for all programs and buffers which gloo will bind its buffers to.
311+ # This isn't how VAOs are meant to be used: it is more correct to keep a VAO per group of buffers (in Wisp's
312+ # case, at least once per gizmo). However, the following glumpy issue needs to be sorted out first, see:
313+ # https://github.com/glumpy/glumpy/issues/310
314+ vao = None
315+ if gl_config .major_version >= 3 :
316+ vao = np .zeros (0 , np .float32 ).view (gloo .VertexArray ) # Keep GL happy by binding with some VAO handle
317+ vao .activate () # Actual vao created here
318+ return vao
319+
304320 @staticmethod
305- def _create_screen_texture (res_h , res_w , channel_depth , dtype = np .uint8 ):
321+ def _create_screen_texture (res_h , res_w , channel_depth , dtype = np .uint8 ) -> gloo . Texture2D :
306322 """ Create and return a Texture2D with gloo and a cuda handle. """
307323 if issubclass (dtype , np .integer ):
308324 tex = np .zeros ((res_h , res_w , channel_depth ), dtype ).view (gloo .Texture2D )
@@ -317,8 +333,14 @@ def _create_screen_texture(res_h, res_w, channel_depth, dtype=np.uint8):
317333
318334 def _register_cugl_shared_texture (self , tex ):
319335 if self .blitdevice2device :
320- # Create shared GL / CUDA resource
321- handle = cuda_register_gl_image (image = int (tex .handle ), target = tex .target )
336+ try :
337+ # Create shared GL / CUDA resource
338+ handle = cuda_register_gl_image (image = int (tex .handle ), target = tex .target )
339+ except RuntimeError as e :
340+ logging .warning ('cugl device2device interface is not available in this env, '
341+ 'wisp will fallback & memcopy cuda output to gl canvas through cpu.' )
342+ self .blitdevice2device = False
343+ handle = None
322344 else :
323345 # No shared resource required, as we copy from cuda buffer -> cpu -> GL texture
324346 handle = None
@@ -385,7 +407,7 @@ def render_canvas(self, render_core, time_delta, force_render):
385407
386408 return img , depth_img
387409
388- def _blit_to_gl_renderbuffer (self , img , depth_img , canvas_program , cugl_rgb_handle , cugl_depth_handle , height ):
410+ def _blit_to_gl_renderbuffer (self , img , depth_img , canvas_program , vao , cugl_rgb_handle , cugl_depth_handle , height ):
389411 if self .blitdevice2device :
390412 # Device to device copy: Copy CUDA buffer to GL Texture mem
391413 shared_tex = canvas_program ['tex' ]
@@ -402,6 +424,8 @@ def _blit_to_gl_renderbuffer(self, img, depth_img, canvas_program, cugl_rgb_hand
402424 canvas_program ['tex' ] = img .cpu ().numpy ()
403425 canvas_program ['depth_tex' ] = depth_img .cpu ().numpy ()
404426
427+ if vao is not None :
428+ vao .activate ()
405429 canvas_program .draw (gl .GL_TRIANGLE_STRIP )
406430
407431 def update_renderer_state (self , wisp_state , dt ):
@@ -487,7 +511,7 @@ def render(self):
487511
488512 # glumpy code injected within the pyimgui render loop to blit the renderer core output to the actual canvas
489513 # The torch buffers are copied by with cuda, connected as shared resources as 2d GL textures
490- self ._blit_to_gl_renderbuffer (img , depth_img , self .canvas_program , self .cugl_rgb_handle ,
514+ self ._blit_to_gl_renderbuffer (img , depth_img , self .canvas_program , self .vao , self . cugl_rgb_handle ,
491515 self .cugl_depth_handle , self .height )
492516
493517 # Finally, render OpenGL gizmos on the canvas.
@@ -544,6 +568,7 @@ def on_resize(self, width, height):
544568 self .cugl_depth_handle = self ._register_cugl_shared_texture (depth_tex )
545569 if self .canvas_program is None :
546570 self .canvas_program = self ._create_gl_depth_billboard_program (texture = tex , depth_texture = depth_tex )
571+ self .vao = self ._create_vao (self .window .config )
547572 else :
548573 if self .canvas_program ['tex' ] is not None :
549574 self .canvas_program ['tex' ].delete ()
@@ -708,7 +733,6 @@ def dump_framebuffer(self, path='./framebuffer'):
708733 framebuffer = np .flip (framebuffer , 0 )
709734 ext .png .from_array (framebuffer , 'L' ).save (path + '_depth.png' )
710735
711-
712736 def register_io_mappings (self ):
713737 WispMouseButton .register_symbol (WispMouseButton .LEFT_BUTTON , app .window .mouse .LEFT )
714738 WispMouseButton .register_symbol (WispMouseButton .MIDDLE_BUTTON , app .window .mouse .MIDDLE )
@@ -720,4 +744,3 @@ def register_io_mappings(self):
720744 WispKey .register_symbol (WispKey .DOWN , app .window .key .DOWN )
721745
722746 # TODO: Take care of remaining mappings, and verify the event handlers of glumpy were not overriden
723-
0 commit comments