Skip to content

Commit

Permalink
vertex array object fix for gl 3.3
Browse files Browse the repository at this point in the history
Signed-off-by: operel <[email protected]>

Extra robustness to missing gl / cugl features

Signed-off-by: operel <[email protected]>
  • Loading branch information
operel committed Feb 7, 2023
1 parent 1f4203a commit 12a7239
Show file tree
Hide file tree
Showing 3 changed files with 39 additions and 16 deletions.
2 changes: 1 addition & 1 deletion wisp/framework/state.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ class InteractiveRendererState:
'none' - No inherent antialising mode will be activated.
"""

gl_version: str = "GL 3.3"
gl_version: str = "GL 3.3 core"
""" Wisp applications rely on glumpy + OpenGL to render specific features and blit content to the window.
This setting configures glumpy to load with a specific OpenGL backend.
OpenGL 3.3 is widely supported and is therefore assumed to be the default.
Expand Down
49 changes: 36 additions & 13 deletions wisp/renderer/app/wisp_app.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

from __future__ import annotations
from abc import ABC
import logging
import numpy as np
import torch
from glumpy import app, gloo, gl, ext
Expand Down Expand Up @@ -111,7 +112,8 @@ def __init__(self, wisp_state, window_name="wisp app"):
# There we generate a simple billboard GL program (normally with a shared CUDA resource)
# Canvas content will be blitted onto it
self.canvas_program: Optional[gloo.Program] = None # GL program used to paint a single billboard
self.cugl_rgb_handle = None # CUDA buffer, as a shared resource with OpenGL
self.vao: Optional[gloo.VertexArray] = None # Vertex array object to hold GL buffers
self.cugl_rgb_handle = None # CUDA buffer, as a shared resource with OpenGL
self.cugl_depth_handle = None

try:
Expand All @@ -133,7 +135,7 @@ def __init__(self, wisp_state, window_name="wisp app"):
self.change_user_mode(self.default_user_mode())

self.redraw() # Refresh RendererCore

def add_pipeline(self, name, pipeline, transform=None):
"""Register a neural fields pipeline into the scene graph.
Expand All @@ -143,7 +145,7 @@ def add_pipeline(self, name, pipeline, transform=None):
transform (wisp.core.ObjectTransform): The transform for the pipeline.
"""
add_pipeline_to_scene_graph(self.wisp_state, name, pipeline, transform=transform)

def add_widget(self, widget):
""" Adds a widget to the list of widgets.
Expand Down Expand Up @@ -242,10 +244,10 @@ def run(self):
"""
app.run() # App clock should always run as frequently as possible (background tasks should not be limited)

def _create_window(self, width, height, window_name, gl_version):
def _create_window(self, width, height, window_name, gl_version) -> app.Window:
# Currently assume glfw backend due to integration with imgui
app.use(f"glfw_imgui ({gl_version})")
win_config = app.configuration.Configuration()
win_config = app.configuration.get_default()
if self.wisp_state.renderer.antialiasing == 'msaa_4x':
win_config.samples = 4

Expand All @@ -267,7 +269,7 @@ def _create_window(self, width, height, window_name, gl_version):
return window

@staticmethod
def _create_gl_depth_billboard_program(texture: np.ndarray, depth_texture: np.ndarray):
def _create_gl_depth_billboard_program(texture: np.ndarray, depth_texture: np.ndarray) -> gloo.Program:
vertex = """
uniform float scale;
attribute vec2 position;
Expand Down Expand Up @@ -301,8 +303,22 @@ def _create_gl_depth_billboard_program(texture: np.ndarray, depth_texture: np.nd
canvas['depth_tex'] = depth_texture
return canvas

def _create_vao(self, gl_config: app.Configuration) -> gloo.VertexArray:
""" Creates a "default" VertexBufferObject to be used by the GL Programs. """
# OpenGL 3.3+ requires that a VertexArrayObject is always bound.
# Since glumpy's glfw_imgui backend doesn't guarantee one, and imgui expects a GL context with OpenGL >= 3.3,
# we create a default one here for all programs and buffers which gloo will bind its buffers to.
# 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
# case, at least once per gizmo). However, the following glumpy issue needs to be sorted out first, see:
# https://github.com/glumpy/glumpy/issues/310
vao = None
if gl_config.major_version >= 3:
vao = np.zeros(0, np.float32).view(gloo.VertexArray) # Keep GL happy by binding with some VAO handle
vao.activate() # Actual vao created here
return vao

@staticmethod
def _create_screen_texture(res_h, res_w, channel_depth, dtype=np.uint8):
def _create_screen_texture(res_h, res_w, channel_depth, dtype=np.uint8) -> gloo.Texture2D:
""" Create and return a Texture2D with gloo and a cuda handle. """
if issubclass(dtype, np.integer):
tex = np.zeros((res_h, res_w, channel_depth), dtype).view(gloo.Texture2D)
Expand All @@ -317,8 +333,14 @@ def _create_screen_texture(res_h, res_w, channel_depth, dtype=np.uint8):

def _register_cugl_shared_texture(self, tex):
if self.blitdevice2device:
# Create shared GL / CUDA resource
handle = cuda_register_gl_image(image=int(tex.handle), target=tex.target)
try:
# Create shared GL / CUDA resource
handle = cuda_register_gl_image(image=int(tex.handle), target=tex.target)
except RuntimeError as e:
logging.warning('cugl device2device interface is not available in this env, '
'wisp will fallback & memcopy cuda output to gl canvas through cpu.')
self.blitdevice2device = False
handle = None
else:
# No shared resource required, as we copy from cuda buffer -> cpu -> GL texture
handle = None
Expand Down Expand Up @@ -385,7 +407,7 @@ def render_canvas(self, render_core, time_delta, force_render):

return img, depth_img

def _blit_to_gl_renderbuffer(self, img, depth_img, canvas_program, cugl_rgb_handle, cugl_depth_handle, height):
def _blit_to_gl_renderbuffer(self, img, depth_img, canvas_program, vao, cugl_rgb_handle, cugl_depth_handle, height):
if self.blitdevice2device:
# Device to device copy: Copy CUDA buffer to GL Texture mem
shared_tex = canvas_program['tex']
Expand All @@ -402,6 +424,8 @@ def _blit_to_gl_renderbuffer(self, img, depth_img, canvas_program, cugl_rgb_hand
canvas_program['tex'] = img.cpu().numpy()
canvas_program['depth_tex'] = depth_img.cpu().numpy()

if vao is not None:
vao.activate()
canvas_program.draw(gl.GL_TRIANGLE_STRIP)

def update_renderer_state(self, wisp_state, dt):
Expand Down Expand Up @@ -487,7 +511,7 @@ def render(self):

# glumpy code injected within the pyimgui render loop to blit the renderer core output to the actual canvas
# The torch buffers are copied by with cuda, connected as shared resources as 2d GL textures
self._blit_to_gl_renderbuffer(img, depth_img, self.canvas_program, self.cugl_rgb_handle,
self._blit_to_gl_renderbuffer(img, depth_img, self.canvas_program, self.vao, self.cugl_rgb_handle,
self.cugl_depth_handle, self.height)

# Finally, render OpenGL gizmos on the canvas.
Expand Down Expand Up @@ -544,6 +568,7 @@ def on_resize(self, width, height):
self.cugl_depth_handle = self._register_cugl_shared_texture(depth_tex)
if self.canvas_program is None:
self.canvas_program = self._create_gl_depth_billboard_program(texture=tex, depth_texture=depth_tex)
self.vao = self._create_vao(self.window.config)
else:
if self.canvas_program['tex'] is not None:
self.canvas_program['tex'].delete()
Expand Down Expand Up @@ -708,7 +733,6 @@ def dump_framebuffer(self, path='./framebuffer'):
framebuffer = np.flip(framebuffer, 0)
ext.png.from_array(framebuffer, 'L').save(path + '_depth.png')


def register_io_mappings(self):
WispMouseButton.register_symbol(WispMouseButton.LEFT_BUTTON, app.window.mouse.LEFT)
WispMouseButton.register_symbol(WispMouseButton.MIDDLE_BUTTON, app.window.mouse.MIDDLE)
Expand All @@ -720,4 +744,3 @@ def register_io_mappings(self):
WispKey.register_symbol(WispKey.DOWN, app.window.key.DOWN)

# TODO: Take care of remaining mappings, and verify the event handlers of glumpy were not overriden

4 changes: 2 additions & 2 deletions wisp/renderer/gizmos/ogl/world_grid.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ def __init__(self, squares_per_axis: int = 20, grid_size: float = 1.0,
gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MAG_FILTER, gl.GL_LINEAR)
try:
gl.glTexParameterf(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MAX_ANISOTROPY, 16)
except AttributeError as e:
logging.warning('GL_TEXTURE_MAX_ANISOTROPY not available; appearance may suffer')
except Exception as e:
logging.warning('GL_TEXTURE_MAX_ANISOTROPY not available; world grid cosmetic appearance may degrade.')
gl.glGenerateMipmap(gl.GL_TEXTURE_2D)
tex.deactivate()

Expand Down

0 comments on commit 12a7239

Please sign in to comment.