Created
August 23, 2023 21:18
-
-
Save kwahoo2/8e3232eecdfdb3d8b11e44e96c50da4c to your computer and use it in GitHub Desktop.
Paste in FreeCAD console to get OpenXR viewer
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# based on gl_example.py https://github.com/cmbruns/pyopenxr_examples | |
import ctypes | |
import logging | |
from threading import Thread | |
import sdl2 | |
import platform | |
from OpenGL import GL | |
if platform.system() == "Windows": | |
from OpenGL import WGL | |
elif platform.system() == "Linux": | |
from OpenGL import GLX | |
import xr | |
from pivy.coin import SoSeparator | |
from pivy.coin import SoBaseColor | |
from pivy.coin import SbColor | |
from pivy.coin import SoSceneManager | |
from pivy.coin import SbViewportRegion | |
from pivy.coin import SoFrustumCamera | |
from pivy.coin import SoPerspectiveCamera | |
from pivy.coin import SbVec3f | |
from pivy.coin import SoCamera | |
from pivy.coin import SoDirectionalLight | |
from pivy.coin import SoScale | |
from pivy.coin import SoTranslation | |
from pivy.coin import SbRotation | |
from pivy.coin import SoGroup | |
from pivy.coin import SoRotationXYZ | |
import FreeCADGui as Gui | |
from math import tan, pi | |
ALL_SEVERITIES = ( | |
xr.DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | |
| xr.DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT | |
| xr.DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | |
| xr.DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT | |
) | |
ALL_TYPES = ( | |
xr.DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | |
| xr.DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | |
| xr.DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT | |
| xr.DEBUG_UTILS_MESSAGE_TYPE_CONFORMANCE_BIT_EXT | |
) | |
def py_log_level(severity_flags: int): | |
if severity_flags & 0x0001: # VERBOSE | |
return logging.DEBUG | |
if severity_flags & 0x0010: # INFO | |
return logging.INFO | |
if severity_flags & 0x0100: # WARNING | |
return logging.WARNING | |
if severity_flags & 0x1000: # ERROR | |
return logging.ERROR | |
return logging.CRITICAL | |
stringForFormat = { | |
GL.GL_COMPRESSED_R11_EAC: "COMPRESSED_R11_EAC", | |
GL.GL_COMPRESSED_RED_RGTC1: "COMPRESSED_RED_RGTC1", | |
GL.GL_COMPRESSED_RG_RGTC2: "COMPRESSED_RG_RGTC2", | |
GL.GL_COMPRESSED_RG11_EAC: "COMPRESSED_RG11_EAC", | |
GL.GL_COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT: "COMPRESSED_RGB_BPTC_UNSIGNED_FLOAT", | |
GL.GL_COMPRESSED_RGB8_ETC2: "COMPRESSED_RGB8_ETC2", | |
GL.GL_COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2: "COMPRESSED_RGB8_PUNCHTHROUGH_ALPHA1_ETC2", | |
GL.GL_COMPRESSED_RGBA8_ETC2_EAC: "COMPRESSED_RGBA8_ETC2_EAC", | |
GL.GL_COMPRESSED_SIGNED_R11_EAC: "COMPRESSED_SIGNED_R11_EAC", | |
GL.GL_COMPRESSED_SIGNED_RG11_EAC: "COMPRESSED_SIGNED_RG11_EAC", | |
GL.GL_COMPRESSED_SRGB_ALPHA_BPTC_UNORM: "COMPRESSED_SRGB_ALPHA_BPTC_UNORM", | |
GL.GL_COMPRESSED_SRGB8_ALPHA8_ETC2_EAC: "COMPRESSED_SRGB8_ALPHA8_ETC2_EAC", | |
GL.GL_COMPRESSED_SRGB8_ETC2: "COMPRESSED_SRGB8_ETC2", | |
GL.GL_COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2: "COMPRESSED_SRGB8_PUNCHTHROUGH_ALPHA1_ETC2", | |
GL.GL_DEPTH_COMPONENT16: "DEPTH_COMPONENT16", | |
GL.GL_DEPTH_COMPONENT24: "DEPTH_COMPONENT24", | |
GL.GL_DEPTH_COMPONENT32: "DEPTH_COMPONENT32", | |
GL.GL_DEPTH_COMPONENT32F: "DEPTH_COMPONENT32F", | |
GL.GL_DEPTH24_STENCIL8: "DEPTH24_STENCIL8", | |
GL.GL_R11F_G11F_B10F: "R11F_G11F_B10F", | |
GL.GL_R16_SNORM: "R16_SNORM", | |
GL.GL_R16: "R16", | |
GL.GL_R16F: "R16F", | |
GL.GL_R16I: "R16I", | |
GL.GL_R16UI: "R16UI", | |
GL.GL_R32F: "R32F", | |
GL.GL_R32I: "R32I", | |
GL.GL_R32UI: "R32UI", | |
GL.GL_R8_SNORM: "R8_SNORM", | |
GL.GL_R8: "R8", | |
GL.GL_R8I: "R8I", | |
GL.GL_R8UI: "R8UI", | |
GL.GL_RG16_SNORM: "RG16_SNORM", | |
GL.GL_RG16: "RG16", | |
GL.GL_RG16F: "RG16F", | |
GL.GL_RG16I: "RG16I", | |
GL.GL_RG16UI: "RG16UI", | |
GL.GL_RG32F: "RG32F", | |
GL.GL_RG32I: "RG32I", | |
GL.GL_RG32UI: "RG32UI", | |
GL.GL_RG8_SNORM: "RG8_SNORM", | |
GL.GL_RG8: "RG8", | |
GL.GL_RG8I: "RG8I", | |
GL.GL_RG8UI: "RG8UI", | |
GL.GL_RGB10_A2: "RGB10_A2", | |
GL.GL_RGB8: "RGB8", | |
GL.GL_RGB9_E5: "RGB9_E5", | |
GL.GL_RGBA16_SNORM: "RGBA16_SNORM", | |
GL.GL_RGBA16: "RGBA16", | |
GL.GL_RGBA16F: "RGBA16F", | |
GL.GL_RGBA16I: "RGBA16I", | |
GL.GL_RGBA16UI: "RGBA16UI", | |
GL.GL_RGBA2: "RGBA2", | |
GL.GL_RGBA32F: "RGBA32F", | |
GL.GL_RGBA32I: "RGBA32I", | |
GL.GL_RGBA32UI: "RGBA32UI", | |
GL.GL_RGBA8_SNORM: "RGBA8_SNORM", | |
GL.GL_RGBA8: "RGBA8", | |
GL.GL_RGBA8I: "RGBA8I", | |
GL.GL_RGBA8UI: "RGBA8UI", | |
GL.GL_SRGB8_ALPHA8: "SRGB8_ALPHA8", | |
GL.GL_SRGB8: "SRGB8", | |
GL.GL_RGB16F: "RGB16F", | |
GL.GL_DEPTH32F_STENCIL8: "DEPTH32F_STENCIL8", | |
GL.GL_BGR: "BGR (Out of spec)", | |
GL.GL_BGRA: "BGRA (Out of spec)", | |
} | |
class OpenXRTest(object): | |
def __init__(self, log_level=logging.WARNING): | |
self._running = True | |
logging.basicConfig() | |
self.logger = logging.getLogger("gl_example") | |
self.logger.setLevel(log_level) | |
self.debug_callback = xr.PFN_xrDebugUtilsMessengerCallbackEXT(self.debug_callback_py) | |
self.mirror_window = True | |
self.instance = None | |
self.system_id = None | |
self.pxrCreateDebugUtilsMessengerEXT = None | |
self.pxrDestroyDebugUtilsMessengerEXT = None | |
self.pxrGetOpenGLGraphicsRequirementsKHR = None | |
self.graphics_requirements = xr.GraphicsRequirementsOpenGLKHR() | |
if platform.system() == 'Windows': | |
self.graphics_binding = xr.GraphicsBindingOpenGLWin32KHR() | |
elif platform.system() == 'Linux': | |
self.graphics_binding = xr.GraphicsBindingOpenGLXlibKHR() | |
else: | |
raise NotImplementedError('Unsupported platform') | |
self.render_target_size = None | |
self.window = None | |
self.session = None | |
self.projection_layer_views = (xr.CompositionLayerProjectionView * 2)( | |
*([xr.CompositionLayerProjectionView()] * 2)) | |
self.projection_layer = xr.CompositionLayerProjection( | |
views=self.projection_layer_views) | |
self.swapchain_create_info = xr.SwapchainCreateInfo() | |
self.swapchain = None | |
self.swapchain_images = None | |
self.fbo_id = None | |
self.fbo_depth_buffer = None | |
self.quit = False | |
self.session_state = xr.SessionState.IDLE | |
self.frame_state = xr.FrameState() | |
self.eye_view_states = None | |
self.window_size = None | |
self.enable_debug = True | |
self.linux_steamvr_broken_destroy_instance = False | |
self.nearPlane = 0.01 | |
self.farPlane = 10000.0 | |
self.prepare_xr_instance() | |
self.prepare_xr_system() | |
self.prepare_window() | |
self.prepare_xr_session() | |
self.prepare_xr_swapchain() | |
self.prepare_xr_composition_layers() | |
self.prepare_gl_framebuffer() | |
self.setup_cameras() | |
self.setup_scene() | |
def debug_callback_py( | |
self, | |
severity: xr.DebugUtilsMessageSeverityFlagsEXT, | |
_type: xr.DebugUtilsMessageTypeFlagsEXT, | |
data: ctypes.POINTER(xr.DebugUtilsMessengerCallbackDataEXT), | |
_user_data: ctypes.c_void_p, | |
) -> bool: | |
d = data.contents | |
# TODO structure properties to return unicode strings | |
self.logger.log(py_log_level(severity), f"{d.function_name.decode()}: {d.message.decode()}") | |
return True | |
def run(self): | |
while self._running: | |
self.frame() | |
def setup_cameras(self): | |
self.camera = [SoFrustumCamera(), SoFrustumCamera()] | |
# 0 - left eye, 1 - right eye | |
for eye_index in range (2): | |
self.camera[eye_index].viewportMapping.setValue(SoCamera.LEAVE_ALONE) | |
def setup_scene(self): | |
# coin3d setup | |
self.vpReg = SbViewportRegion(self.render_target_size[0], self.render_target_size[1]) | |
self.m_sceneManager = SoSceneManager() #scene manager overhead over render manager seems to be pretty #small | |
self.m_sceneManager.setViewportRegion(self.vpReg) | |
self.m_sceneManager.setBackgroundColor(SbColor(0.0, 0.0, 0.8)) | |
light = SoDirectionalLight() | |
light2 = SoDirectionalLight() | |
light2.direction.setValue(-1,-1,-1) | |
light2.intensity.setValue(0.6) | |
light2.color.setValue(0.8,0.8,1) | |
scale = SoScale() | |
scale.scaleFactor.setValue(0.001, 0.001, 0.001) #OpenXR uses meters not milimeters | |
sg = Gui.ActiveDocument.ActiveView.getSceneGraph()#get active scenegraph | |
rot = SoRotationXYZ() # rotate scene to set Z as vertical | |
rot.axis.setValue(SoRotationXYZ.X) | |
rot.angle.setValue(-pi/2) | |
self.camtrans = [SoTranslation(), SoTranslation()] | |
self.cgrp = [SoGroup(), SoGroup()] # group for camera | |
self.sgrp = [SoGroup(), SoGroup()] # group for scenegraph | |
self.rootScene = [SoSeparator(), SoSeparator()] | |
for eye_index in range (2): | |
self.rootScene[eye_index].ref() | |
self.rootScene[eye_index].addChild(self.cgrp[eye_index]) | |
self.cgrp[eye_index].addChild(self.camtrans[eye_index]) | |
self.cgrp[eye_index].addChild(self.camera[eye_index]) | |
self.rootScene[eye_index].addChild(self.sgrp[eye_index]) | |
self.sgrp[eye_index].addChild(light) | |
self.sgrp[eye_index].addChild(light2) | |
self.sgrp[eye_index].addChild(scale) | |
self.sgrp[eye_index].addChild(rot) | |
self.sgrp[eye_index].addChild(sg) # add scenegraph | |
def prepare_xr_instance(self): | |
discovered_extensions = xr.enumerate_instance_extension_properties() | |
if xr.EXT_DEBUG_UTILS_EXTENSION_NAME not in discovered_extensions: | |
self.enable_debug = False | |
requested_extensions = [xr.KHR_OPENGL_ENABLE_EXTENSION_NAME] | |
if self.enable_debug: | |
requested_extensions.append(xr.EXT_DEBUG_UTILS_EXTENSION_NAME) | |
for extension in requested_extensions: | |
assert extension in discovered_extensions | |
app_info = xr.ApplicationInfo("gl_example", 0, "pyopenxr", 0, xr.XR_CURRENT_API_VERSION) | |
ici = xr.InstanceCreateInfo( | |
application_info=app_info, | |
enabled_extension_names=requested_extensions, | |
) | |
dumci = xr.DebugUtilsMessengerCreateInfoEXT() | |
if self.enable_debug: | |
dumci.message_severities = ALL_SEVERITIES | |
dumci.message_types = ALL_TYPES | |
dumci.user_data = None # TODO | |
dumci.user_callback = self.debug_callback | |
ici.next = ctypes.cast(ctypes.pointer(dumci), ctypes.c_void_p) # TODO: yuck | |
self.instance = xr.create_instance(ici) | |
# TODO: pythonic wrapper | |
self.pxrGetOpenGLGraphicsRequirementsKHR = ctypes.cast( | |
xr.get_instance_proc_addr( | |
self.instance, | |
"xrGetOpenGLGraphicsRequirementsKHR", | |
), | |
xr.PFN_xrGetOpenGLGraphicsRequirementsKHR | |
) | |
instance_props = xr.get_instance_properties(self.instance) | |
if platform.system() == 'Linux' and instance_props.runtime_name == b"SteamVR/OpenXR": | |
print("SteamVR/OpenXR on Linux detected, enabling workarounds") | |
# Enabling workaround for https://github.com/ValveSoftware/SteamVR-for-Linux/issues/422, | |
# and https://github.com/ValveSoftware/SteamVR-for-Linux/issues/479 | |
# destroy_instance() causes SteamVR to hang and never recover | |
self.linux_steamvr_broken_destroy_instance = True | |
def prepare_xr_system(self): | |
get_info = xr.SystemGetInfo(xr.FormFactor.HEAD_MOUNTED_DISPLAY) | |
self.system_id = xr.get_system(self.instance, get_info) # TODO: not a pointer | |
view_configs = xr.enumerate_view_configurations(self.instance, self.system_id) | |
assert view_configs[0] == xr.ViewConfigurationType.PRIMARY_STEREO.value # TODO: equality... | |
view_config_views = xr.enumerate_view_configuration_views( | |
self.instance, self.system_id, xr.ViewConfigurationType.PRIMARY_STEREO) | |
assert len(view_config_views) == 2 | |
assert view_config_views[0].recommended_image_rect_height == view_config_views[1].recommended_image_rect_height | |
self.render_target_size = ( | |
view_config_views[0].recommended_image_rect_width * 2, | |
view_config_views[0].recommended_image_rect_height) | |
result = self.pxrGetOpenGLGraphicsRequirementsKHR( | |
self.instance, self.system_id, ctypes.byref(self.graphics_requirements)) # TODO: pythonic wrapper | |
result = xr.exception.check_result(xr.Result(result)) | |
if result.is_exception(): | |
raise result | |
def prepare_window(self): | |
if sdl2.SDL_Init(sdl2.SDL_INIT_VIDEO) != 0: | |
raise RuntimeError("SDL initialization failed") | |
self.window_size = [s // 4 for s in self.render_target_size] | |
self.window = sdl2.SDL_CreateWindow (b"test", | |
sdl2.SDL_WINDOWPOS_CENTERED, sdl2.SDL_WINDOWPOS_CENTERED, | |
self.window_size[0], self.window_size[1], sdl2.SDL_WINDOW_SHOWN|sdl2.SDL_WINDOW_OPENGL) | |
if self.window is None: | |
raise RuntimeError("Failed to create SDL window") | |
self.context = sdl2.SDL_GL_CreateContext(self.window) | |
sdl2.SDL_GL_MakeCurrent(self.window, self.context) | |
# Attempt to disable vsync on the desktop window or | |
# it will interfere with the OpenXR frame loop timing | |
sdl2.SDL_GL_SetSwapInterval(0) | |
def prepare_xr_session(self): | |
if platform.system() == 'Windows': | |
self.graphics_binding.h_dc = WGL.wglGetCurrentDC() | |
self.graphics_binding.h_glrc = WGL.wglGetCurrentContext() | |
else: | |
self.graphics_binding.x_display = GLX.glXGetCurrentDisplay() | |
self.graphics_binding.glx_context = GLX.glXGetCurrentContext() | |
self.graphics_binding.glx_drawable = GLX.glXGetCurrentDrawable() | |
pp = ctypes.cast(ctypes.pointer(self.graphics_binding), ctypes.c_void_p) | |
sci = xr.SessionCreateInfo(0, self.system_id, next=pp) | |
self.session = xr.create_session(self.instance, sci) | |
reference_spaces = xr.enumerate_reference_spaces(self.session) | |
for rs in reference_spaces: | |
self.logger.debug(f"Session supports reference space {xr.ReferenceSpaceType(rs)}") | |
# TODO: default constructors for Quaternion, Vector3f, Posef, ReferenceSpaceCreateInfo | |
rsci = xr.ReferenceSpaceCreateInfo( | |
xr.ReferenceSpaceType.STAGE, | |
xr.Posef(xr.Quaternionf(0, 0, 0, 1), xr.Vector3f(0, 0, 0)) | |
) | |
self.projection_layer.space = xr.create_reference_space(self.session, rsci) | |
swapchain_formats = xr.enumerate_swapchain_formats(self.session) | |
for scf in swapchain_formats: | |
self.logger.debug(f"Session supports swapchain format {stringForFormat[scf]}") | |
def prepare_xr_swapchain(self): | |
self.swapchain_create_info.usage_flags = xr.SWAPCHAIN_USAGE_TRANSFER_DST_BIT | |
self.swapchain_create_info.format = GL.GL_SRGB8_ALPHA8 | |
self.swapchain_create_info.sample_count = 1 | |
self.swapchain_create_info.array_size = 1 | |
self.swapchain_create_info.face_count = 1 | |
self.swapchain_create_info.mip_count = 1 | |
self.swapchain_create_info.width = self.render_target_size[0] | |
self.swapchain_create_info.height = self.render_target_size[1] | |
self.swapchain = xr.create_swapchain(self.session, self.swapchain_create_info) | |
self.swapchain_images = xr.enumerate_swapchain_images(self.swapchain, xr.SwapchainImageOpenGLKHR) | |
for i, si in enumerate(self.swapchain_images): | |
self.logger.debug(f"Swapchain image {i} type = {xr.StructureType(si.type)}") | |
def prepare_xr_composition_layers(self): | |
self.projection_layer.view_count = 2 | |
self.projection_layer.views = self.projection_layer_views | |
for eye_index in range(2): | |
layer_view = self.projection_layer_views[eye_index] | |
layer_view.sub_image.swapchain = self.swapchain | |
layer_view.sub_image.image_rect.extent = xr.Extent2Di( | |
self.render_target_size[0] // 2, | |
self.render_target_size[1], | |
) | |
if eye_index == 1: | |
layer_view.sub_image.image_rect.offset.x = layer_view.sub_image.image_rect.extent.width | |
def prepare_gl_framebuffer(self): | |
sdl2.SDL_GL_MakeCurrent(self.window, self.context) | |
self.fbo_depth_buffer = GL.glGenRenderbuffers(1) | |
GL.glBindRenderbuffer(GL.GL_RENDERBUFFER, self.fbo_depth_buffer) | |
if self.swapchain_create_info.sample_count == 1: | |
GL.glRenderbufferStorage( | |
GL.GL_RENDERBUFFER, | |
GL.GL_DEPTH24_STENCIL8, | |
self.swapchain_create_info.width, | |
self.swapchain_create_info.height, | |
) | |
else: | |
GL.glRenderbufferStorageMultisample( | |
GL.GL_RENDERBUFFER, | |
self.swapchain_create_info.sample_count, | |
GL.GL_DEPTH24_STENCIL8, | |
self.swapchain_create_info.width, | |
self.swapchain_create_info.height, | |
) | |
self.fbo_id = GL.glGenFramebuffers(1) | |
GL.glBindFramebuffer(GL.GL_DRAW_FRAMEBUFFER, self.fbo_id) | |
GL.glFramebufferRenderbuffer( | |
GL.GL_DRAW_FRAMEBUFFER, | |
GL.GL_DEPTH_STENCIL_ATTACHMENT, | |
GL.GL_RENDERBUFFER, | |
self.fbo_depth_buffer, | |
) | |
GL.glBindFramebuffer(GL.GL_DRAW_FRAMEBUFFER, 0) | |
def frame(self): | |
# Gui.updateGui() # update FreeCAD mainwindow event loop | |
self.poll_sdl_events() | |
self.poll_xr_events() | |
if self.quit: | |
return | |
if self.start_xr_frame(): | |
self.update_xr_views() | |
if self.frame_state.should_render: | |
self.render() | |
self.end_xr_frame() | |
def poll_xr_events(self): | |
while True: | |
try: | |
event_buffer = xr.poll_event(self.instance) | |
event_type = xr.StructureType(event_buffer.type) | |
if event_type == xr.StructureType.EVENT_DATA_SESSION_STATE_CHANGED: | |
self.on_session_state_changed(event_buffer) | |
except xr.EventUnavailable: | |
break | |
def poll_sdl_events(self): | |
event = sdl2.SDL_Event() | |
while sdl2.SDL_PollEvent(ctypes.byref(event)) != 0: | |
if event.type == sdl2.SDL_QUIT: | |
self._running = False | |
self.terminate() | |
def on_session_state_changed(self, session_state_changed_event): | |
# TODO: it would be nice to avoid this horrible cast... | |
event = ctypes.cast( | |
ctypes.byref(session_state_changed_event), | |
ctypes.POINTER(xr.EventDataSessionStateChanged)).contents | |
# TODO: enum property | |
self.session_state = xr.SessionState(event.state) | |
if self.session_state == xr.SessionState.READY: | |
if not self.quit: | |
sbi = xr.SessionBeginInfo(xr.ViewConfigurationType.PRIMARY_STEREO) | |
xr.begin_session(self.session, sbi) | |
elif self.session_state == xr.SessionState.STOPPING: | |
xr.end_session(self.session) | |
self.session = None | |
self.quit = True | |
def start_xr_frame(self) -> bool: | |
if self.session_state in [ | |
xr.SessionState.READY, | |
xr.SessionState.FOCUSED, | |
xr.SessionState.SYNCHRONIZED, | |
xr.SessionState.VISIBLE, | |
]: | |
frame_wait_info = xr.FrameWaitInfo(None) | |
try: | |
self.frame_state = xr.wait_frame(self.session, frame_wait_info) | |
xr.begin_frame(self.session, None) | |
return True | |
except xr.ResultException: | |
return False | |
return False | |
def end_xr_frame(self): | |
frame_end_info = xr.FrameEndInfo( | |
self.frame_state.predicted_display_time, | |
xr.EnvironmentBlendMode.OPAQUE | |
) | |
if self.frame_state.should_render: | |
for eye_index in range(2): | |
layer_view = self.projection_layer_views[eye_index] | |
eye_view = self.eye_view_states[eye_index] | |
layer_view.fov = eye_view.fov | |
layer_view.pose = eye_view.pose | |
frame_end_info.layers = [ctypes.byref(self.projection_layer), ] | |
xr.end_frame(self.session, frame_end_info) | |
def update_xr_views(self): | |
nearPlane = self.nearPlane | |
farPlane = self.farPlane | |
vi = xr.ViewLocateInfo( | |
xr.ViewConfigurationType.PRIMARY_STEREO, | |
self.frame_state.predicted_display_time, | |
self.projection_layer.space, | |
) | |
vs, self.eye_view_states = xr.locate_views(self.session, vi) | |
for eye_index, view_state in enumerate(self.eye_view_states): | |
hmdrot = SbRotation(view_state.pose.orientation.x, view_state.pose.orientation.y, view_state.pose.orientation.z, view_state.pose.orientation.w) | |
hmdpos = SbVec3f(view_state.pose.position.x, view_state.pose.position.y, view_state.pose.position.z) #get global position and orientation for both cameras | |
pfLeft = tan(view_state.fov.angle_left) | |
pfRight = tan(view_state.fov.angle_right) | |
pfTop = tan(view_state.fov.angle_up) | |
pfBottom = tan(view_state.fov.angle_down) | |
self.camera[eye_index].orientation.setValue(hmdrot) | |
self.camera[eye_index].position.setValue(hmdpos) | |
self.camera[eye_index].aspectRatio.setValue((pfTop - pfBottom)/(pfRight - pfLeft)) | |
self.camera[eye_index].nearDistance.setValue(nearPlane) | |
self.camera[eye_index].farDistance.setValue(farPlane) | |
self.camera[eye_index].left.setValue(nearPlane * pfLeft) | |
self.camera[eye_index].right.setValue(nearPlane * pfRight) | |
self.camera[eye_index].top.setValue(nearPlane * pfTop) | |
self.camera[eye_index].bottom.setValue(nearPlane * pfBottom) | |
def render(self): | |
ai = xr.SwapchainImageAcquireInfo(None) | |
swapchain_index = xr.acquire_swapchain_image(self.swapchain, ai) | |
wi = xr.SwapchainImageWaitInfo(xr.INFINITE_DURATION) | |
xr.wait_swapchain_image(self.swapchain, wi) | |
sdl2.SDL_GL_MakeCurrent(self.window, self.context) | |
GL.glUseProgram(0) | |
GL.glBindFramebuffer(GL.GL_FRAMEBUFFER, self.fbo_id) | |
sw_image = self.swapchain_images[swapchain_index] | |
GL.glFramebufferTexture( | |
GL.GL_FRAMEBUFFER, | |
GL.GL_COLOR_ATTACHMENT0, | |
sw_image.image, | |
0, | |
) | |
w, h = self.render_target_size | |
# "render" to the swapchain image | |
GL.glEnable(GL.GL_SCISSOR_TEST) | |
GL.glScissor(0, 0, w // 2, h) | |
self.vpReg.setViewportPixels(0, 0, w // 2, h) | |
self.m_sceneManager.setViewportRegion(self.vpReg) | |
self.m_sceneManager.setSceneGraph(self.rootScene[0]) | |
GL.glEnable(GL.GL_CULL_FACE) | |
GL.glEnable(GL.GL_DEPTH_TEST) | |
self.m_sceneManager.render() | |
GL.glDisable(GL.GL_CULL_FACE) | |
GL.glDisable(GL.GL_DEPTH_TEST) | |
GL.glClearDepth(1.0) | |
GL.glScissor(w // 2, 0, w // 2, h) | |
self.vpReg.setViewportPixels(w // 2, 0, w // 2, h) | |
self.m_sceneManager.setViewportRegion(self.vpReg) | |
self.m_sceneManager.setSceneGraph(self.rootScene[1]) | |
GL.glEnable(GL.GL_CULL_FACE) | |
GL.glEnable(GL.GL_DEPTH_TEST) | |
self.m_sceneManager.render() | |
GL.glDisable(GL.GL_CULL_FACE) | |
GL.glDisable(GL.GL_DEPTH_TEST) | |
GL.glClearDepth(1.0) | |
GL.glDisable(GL.GL_SCISSOR_TEST) | |
if self.mirror_window: | |
# fast blit from the fbo to the window surface | |
GL.glBindFramebuffer(GL.GL_DRAW_FRAMEBUFFER, 0) | |
GL.glBlitFramebuffer( | |
0, 0, w, h, 0, 0, | |
*self.window_size, | |
GL.GL_COLOR_BUFFER_BIT, | |
GL.GL_NEAREST | |
) | |
GL.glFramebufferTexture(GL.GL_READ_FRAMEBUFFER, GL.GL_COLOR_ATTACHMENT0, 0, 0) | |
GL.glBindFramebuffer(GL.GL_READ_FRAMEBUFFER, 0) | |
ri = xr.SwapchainImageReleaseInfo() | |
xr.release_swapchain_image(self.swapchain, ri) | |
sdl2.SDL_GL_SwapWindow(self.window) | |
def terminate(self): | |
self._running = False | |
if self.window is not None: | |
sdl2.SDL_GL_MakeCurrent(self.window, self.context) | |
if self.fbo_id is not None: | |
GL.glDeleteFramebuffers(1, [self.fbo_id]) | |
self.fbo_id = None | |
if self.fbo_depth_buffer is not None: | |
GL.glDeleteRenderbuffers(1, [self.fbo_depth_buffer]) | |
self.fbo_depth_buffer = None | |
sdl2.SDL_GL_DeleteContext(self.context) | |
sdl2.SDL_DestroyWindow(self.window) | |
sdl2.SDL_Quit() | |
if self.swapchain is not None: | |
xr.destroy_swapchain(self.swapchain) | |
self.swapchain = None | |
if self.session is not None: | |
xr.destroy_session(self.session) | |
self.session = None | |
self.system_id = None | |
if self.instance is not None: | |
# Workaround for https://github.com/ValveSoftware/SteamVR-for-Linux/issues/422 | |
# and https://github.com/ValveSoftware/SteamVR-for-Linux/issues/479 | |
if not self.linux_steamvr_broken_destroy_instance: | |
xr.destroy_instance(self.instance) | |
self.instance = None | |
for rs in range(self.rootScene): | |
self.rootScene[rs].unref() | |
if __name__ == "__main__": | |
oxrtest = OpenXRTest() | |
t = Thread(target=oxrtest.run) | |
t.start() | |
t.join() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment