From 89edccbf4cd88ab8910825228995bc517726ef25 Mon Sep 17 00:00:00 2001 From: bjorn Date: Sun, 5 Feb 2023 19:51:12 -0800 Subject: [PATCH] Add lovr.headset.isPassthroughEnabled and lovr.headset.setPassthroughEnabled; --- etc/webxr.js | 18 +++- src/api/l_headset.c | 14 +++ src/modules/headset/headset.h | 2 + src/modules/headset/headset_desktop.c | 20 +++- src/modules/headset/headset_openxr.c | 137 ++++++++++++++++++++------ src/modules/headset/headset_webxr.c | 4 +- 6 files changed, 154 insertions(+), 41 deletions(-) diff --git a/etc/webxr.js b/etc/webxr.js index a7af3a11..8ca597ba 100644 --- a/etc/webxr.js +++ b/etc/webxr.js @@ -182,6 +182,11 @@ var webxr = { return 1; /* ORIGIN_FLOOR */ }, + webxr_getDisplayDimensions: function(width, height) { + HEAPU32[width >> 2] = state.layer.framebufferWidth; + HEAPU32[height >> 2] = state.layer.framebufferHeight; + }, + webxr_getDisplayTime: function() { return state.displayTime / 1000.0; }, @@ -190,11 +195,6 @@ var webxr = { return (state.displayTime - state.lastDisplayTime) / 1000.0; }, - webxr_getDisplayDimensions: function(width, height) { - HEAPU32[width >> 2] = state.layer.framebufferWidth; - HEAPU32[height >> 2] = state.layer.framebufferHeight; - }, - webxr_getDisplayFrequency: function() { return 0.0; }, @@ -411,6 +411,14 @@ var webxr = { return true; }, + webxr_isPassthroughEnabled: function() { + return false; + }, + + webxr_setPassthroughEnabled: function() { + return false; + }, + webxr_update: function() { return (state.displayTime - state.lastDisplayTime) / 1000.0; } diff --git a/src/api/l_headset.c b/src/api/l_headset.c index 9dc50b08..6c4c4423 100644 --- a/src/api/l_headset.c +++ b/src/api/l_headset.c @@ -539,6 +539,18 @@ static int l_lovrHeadsetIsFocused(lua_State* L) { return 1; } +static int l_lovrHeadsetIsPassthroughEnabled(lua_State* L) { + lua_pushboolean(L, lovrHeadsetInterface->isPassthroughEnabled()); + return 1; +} + +static int l_lovrHeadsetSetPassthroughEnabled(lua_State* L) { + bool enable = lua_toboolean(L, 1); + bool success = lovrHeadsetInterface->setPassthroughEnabled(enable); + lua_pushboolean(L, success); + return 1; +} + static int l_lovrHeadsetUpdate(lua_State* L) { double dt = 0.; @@ -620,6 +632,8 @@ static const luaL_Reg lovrHeadset[] = { { "getPass", l_lovrHeadsetGetPass }, { "submit", l_lovrHeadsetSubmit }, { "isFocused", l_lovrHeadsetIsFocused }, + { "isPassthroughEnabled", l_lovrHeadsetIsPassthroughEnabled }, + { "setPassthroughEnabled", l_lovrHeadsetSetPassthroughEnabled }, { "update", l_lovrHeadsetUpdate }, { "getTime", l_lovrHeadsetGetTime }, { "getDeltaTime", l_lovrHeadsetGetDeltaTime }, diff --git a/src/modules/headset/headset.h b/src/modules/headset/headset.h index 86eaa5aa..cd50d791 100644 --- a/src/modules/headset/headset.h +++ b/src/modules/headset/headset.h @@ -155,6 +155,8 @@ typedef struct HeadsetInterface { struct Pass* (*getPass)(void); void (*submit)(void); bool (*isFocused)(void); + bool (*isPassthroughEnabled)(void); + bool (*setPassthroughEnabled)(bool enable); double (*update)(void); } HeadsetInterface; diff --git a/src/modules/headset/headset_desktop.c b/src/modules/headset/headset_desktop.c index 9b225055..e8af5e01 100644 --- a/src/modules/headset/headset_desktop.c +++ b/src/modules/headset/headset_desktop.c @@ -75,6 +75,10 @@ static HeadsetOrigin desktop_getOriginType(void) { return ORIGIN_HEAD; } +static void desktop_getDisplayDimensions(uint32_t* width, uint32_t* height) { + os_window_get_size(width, height); +} + static double desktop_getDisplayTime(void) { return state.nextDisplayTime - state.epoch; } @@ -83,10 +87,6 @@ static double desktop_getDeltaTime(void) { return state.nextDisplayTime - state.prevDisplayTime; } -static void desktop_getDisplayDimensions(uint32_t* width, uint32_t* height) { - os_window_get_size(width, height); -} - static uint32_t desktop_getViewCount(void) { return 1; } @@ -225,6 +225,14 @@ static bool desktop_isFocused(void) { return state.focused; } +static bool desktop_isPassthroughEnabled(void) { + return false; +} + +static bool desktop_setPassthroughEnabled(bool enable) { + return false; +} + static double desktop_update(void) { bool front = os_is_key_down(KEY_W) || os_is_key_down(KEY_UP); bool back = os_is_key_down(KEY_S) || os_is_key_down(KEY_DOWN); @@ -325,9 +333,9 @@ HeadsetInterface lovrHeadsetDesktopDriver = { .destroy = desktop_destroy, .getName = desktop_getName, .getOriginType = desktop_getOriginType, + .getDisplayDimensions = desktop_getDisplayDimensions, .getDisplayTime = desktop_getDisplayTime, .getDeltaTime = desktop_getDeltaTime, - .getDisplayDimensions = desktop_getDisplayDimensions, .getViewCount = desktop_getViewCount, .getViewPose = desktop_getViewPose, .getViewAngles = desktop_getViewAngles, @@ -348,5 +356,7 @@ HeadsetInterface lovrHeadsetDesktopDriver = { .getPass = desktop_getPass, .submit = desktop_submit, .isFocused = desktop_isFocused, + .isPassthroughEnabled = desktop_isPassthroughEnabled, + .setPassthroughEnabled = desktop_setPassthroughEnabled, .update = desktop_update }; diff --git a/src/modules/headset/headset_openxr.c b/src/modules/headset/headset_openxr.c index 93687e88..cbd8bcc1 100644 --- a/src/modules/headset/headset_openxr.c +++ b/src/modules/headset/headset_openxr.c @@ -98,7 +98,13 @@ uintptr_t gpu_vk_get_queue(uint32_t* queueFamilyIndex, uint32_t* queueIndex); X(xrEnumerateDisplayRefreshRatesFB)\ X(xrRequestDisplayRefreshRateFB)\ X(xrQuerySystemTrackedKeyboardFB)\ - X(xrCreateKeyboardSpaceFB) + X(xrCreateKeyboardSpaceFB)\ + X(xrCreatePassthroughFB)\ + X(xrDestroyPassthroughFB)\ + X(xrPassthroughStartFB)\ + X(xrPassthroughPauseFB)\ + X(xrCreatePassthroughLayerFB)\ + X(xrDestroyPassthroughLayerFB) #define XR_DECLARE(fn) static PFN_##fn fn; #define XR_LOAD(fn) xrGetInstanceProcAddr(state.instance, #fn, (PFN_xrVoidFunction*) &fn); @@ -156,6 +162,7 @@ static struct { XrCompositionLayerProjection layers[1]; XrCompositionLayerProjectionView layerViews[2]; XrCompositionLayerDepthInfoKHR depthInfo[2]; + XrCompositionLayerPassthroughFB passthroughLayer; XrFrameState frameState; TextureFormat depthFormat; Texture* textures[2][MAX_IMAGES]; @@ -174,6 +181,9 @@ static struct { XrPath actionFilters[MAX_DEVICES]; XrHandTrackerEXT handTrackers[2]; XrControllerModelKeyMSFT controllerModelKeys[2]; + XrPassthroughFB passthrough; + XrPassthroughLayerFB passthroughLayerHandle; + bool passthroughActive; struct { bool controllerModel; bool depth; @@ -185,6 +195,7 @@ static struct { bool headless; bool keyboardTracking; bool overlay; + bool passthrough; bool refreshRate; bool viveTrackers; } features; @@ -371,6 +382,7 @@ static bool openxr_init(HeadsetConfig* config) { { "XR_FB_hand_tracking_aim", &state.features.handTrackingAim, true }, { "XR_FB_hand_tracking_mesh", &state.features.handTrackingMesh, true }, { "XR_FB_keyboard_tracking", &state.features.keyboardTracking, true }, + { "XR_FB_passthrough", &state.features.passthrough, true }, { "XR_MND_headless", &state.features.headless, true }, { "XR_MSFT_controller_model", &state.features.controllerModel, true }, { "XR_ULTRALEAP_hand_tracking_forearm", &state.features.handTrackingElbow, true }, @@ -413,24 +425,11 @@ static bool openxr_init(HeadsetConfig* config) { XR_INIT(xrGetSystem(state.instance, &info, &state.system)); - XrSystemEyeGazeInteractionPropertiesEXT eyeGazeProperties = { - .type = XR_TYPE_SYSTEM_EYE_GAZE_INTERACTION_PROPERTIES_EXT, - .supportsEyeGazeInteraction = false - }; - - XrSystemHandTrackingPropertiesEXT handTrackingProperties = { - .type = XR_TYPE_SYSTEM_HAND_TRACKING_PROPERTIES_EXT, - .supportsHandTracking = false - }; - - XrSystemKeyboardTrackingPropertiesFB keyboardTrackingProperties = { - .type = XR_TYPE_SYSTEM_KEYBOARD_TRACKING_PROPERTIES_FB, - .supportsKeyboardTracking = false - }; - - XrSystemProperties properties = { - .type = XR_TYPE_SYSTEM_PROPERTIES - }; + XrSystemEyeGazeInteractionPropertiesEXT eyeGazeProperties = { .type = XR_TYPE_SYSTEM_EYE_GAZE_INTERACTION_PROPERTIES_EXT }; + XrSystemHandTrackingPropertiesEXT handTrackingProperties = { .type = XR_TYPE_SYSTEM_HAND_TRACKING_PROPERTIES_EXT }; + XrSystemKeyboardTrackingPropertiesFB keyboardTrackingProperties = { .type = XR_TYPE_SYSTEM_KEYBOARD_TRACKING_PROPERTIES_FB }; + XrSystemPassthroughProperties2FB passthroughProperties = { .type = XR_TYPE_SYSTEM_PASSTHROUGH_PROPERTIES2_FB }; + XrSystemProperties properties = { .type = XR_TYPE_SYSTEM_PROPERTIES }; if (state.features.gaze) { eyeGazeProperties.next = properties.next; @@ -447,10 +446,16 @@ static bool openxr_init(HeadsetConfig* config) { properties.next = &keyboardTrackingProperties; } + if (state.features.passthrough) { + passthroughProperties.next = properties.next; + properties.next = &passthroughProperties; + } + XR_INIT(xrGetSystemProperties(state.instance, state.system, &properties)); state.features.gaze = eyeGazeProperties.supportsEyeGazeInteraction; state.features.handTracking = handTrackingProperties.supportsHandTracking; state.features.keyboardTracking = keyboardTrackingProperties.supportsKeyboardTracking; + state.features.passthrough = passthroughProperties.capabilities & XR_PASSTHROUGH_CAPABILITY_BIT_FB; uint32_t viewConfigurationCount; XrViewConfigurationType viewConfigurations[2]; @@ -1042,17 +1047,10 @@ static void openxr_start(void) { } } - XrCompositionLayerFlags layerFlags = 0; - - if (state.features.overlay) { - layerFlags = XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT | XR_COMPOSITION_LAYER_UNPREMULTIPLIED_ALPHA_BIT; - } - // Pre-init composition layer state.layers[0] = (XrCompositionLayerProjection) { .type = XR_TYPE_COMPOSITION_LAYER_PROJECTION, .space = state.referenceSpace, - .layerFlags = layerFlags, .viewCount = 2, .views = state.layerViews }; @@ -1122,6 +1120,9 @@ static void openxr_stop(void) { if (state.handTrackers[0]) xrDestroyHandTrackerEXT(state.handTrackers[0]); if (state.handTrackers[1]) xrDestroyHandTrackerEXT(state.handTrackers[1]); + if (state.passthrough) xrDestroyPassthroughFB(state.passthrough); + if (state.passthroughLayerHandle) xrDestroyPassthroughLayerFB(state.passthroughLayerHandle); + for (size_t i = 0; i < MAX_DEVICES; i++) { if (state.spaces[i]) { xrDestroySpace(state.spaces[i]); @@ -2055,13 +2056,13 @@ static void openxr_submit(void) { return; } + XrCompositionLayerBaseHeader const* layers[2]; + XrFrameEndInfo info = { .type = XR_TYPE_FRAME_END_INFO, .displayTime = state.frameState.predictedDisplayTime, .environmentBlendMode = XR_ENVIRONMENT_BLEND_MODE_OPAQUE, - .layers = (const XrCompositionLayerBaseHeader*[1]) { - (XrCompositionLayerBaseHeader*) &state.layers[0] - } + .layers = layers }; if (state.features.depth) { @@ -2081,7 +2082,20 @@ static void openxr_submit(void) { XR(xrReleaseSwapchainImage(state.swapchain[DEPTH], NULL)); } - info.layerCount = 1; + if (state.passthroughActive) { + layers[0] = (const XrCompositionLayerBaseHeader*) &state.passthroughLayer; + layers[1] = (const XrCompositionLayerBaseHeader*) &state.layers[0]; + info.layerCount = 2; + } else { + layers[0] = (const XrCompositionLayerBaseHeader*) &state.layers[0]; + info.layerCount = 1; + } + + if (state.features.overlay || state.passthroughActive) { + state.layers[0].layerFlags = XR_COMPOSITION_LAYER_BLEND_TEXTURE_SOURCE_ALPHA_BIT | XR_COMPOSITION_LAYER_UNPREMULTIPLIED_ALPHA_BIT; + } else { + state.layers[0].layerFlags = 0; + } } XR(xrEndFrame(state.session, &info)); @@ -2093,6 +2107,67 @@ static bool openxr_isFocused(void) { return state.sessionState == XR_SESSION_STATE_FOCUSED; } +static bool openxr_isPassthroughEnabled(void) { + return state.passthroughActive; +} + +static bool openxr_setPassthroughEnabled(bool enable) { + if (!state.features.passthrough) return false; + + XrResult result = XR_SUCCESS; + + if (!state.passthrough) { + XrPassthroughCreateInfoFB info = { .type = XR_TYPE_PASSTHROUGH_CREATE_INFO_FB }; + result = xrCreatePassthroughFB(state.session, &info, &state.passthrough); + + if (XR_FAILED(result)) { + return false; + } + + XrPassthroughLayerCreateInfoFB layerInfo = { + .type = XR_TYPE_PASSTHROUGH_LAYER_CREATE_INFO_FB, + .passthrough = state.passthrough, + .purpose = XR_PASSTHROUGH_LAYER_PURPOSE_RECONSTRUCTION_FB, + .flags = XR_PASSTHROUGH_IS_RUNNING_AT_CREATION_BIT_FB + }; + + result = xrCreatePassthroughLayerFB(state.session, &layerInfo, &state.passthroughLayerHandle); + + if (XR_FAILED(result)) { + xrDestroyPassthroughFB(state.passthrough); + state.passthrough = NULL; + return false; + } + + state.passthroughLayer = (XrCompositionLayerPassthroughFB) { + .type = XR_TYPE_COMPOSITION_LAYER_PASSTHROUGH_FB, + .layerHandle = state.passthroughLayerHandle + }; + } + + if (enable == state.passthroughActive) { + return true; + } + + if (enable) { + result = xrPassthroughStartFB(state.passthrough); + + if (XR_SUCCEEDED(result)) { + state.passthroughActive = true; + return true; + } + } else { + result = xrPassthroughPauseFB(state.passthrough); + + if (XR_SUCCEEDED(result)) { + state.passthroughActive = false; + return true; + } + } + + return false; +} + static double openxr_update(void) { if (state.waited) return openxr_getDeltaTime(); @@ -2202,5 +2277,7 @@ HeadsetInterface lovrHeadsetOpenXRDriver = { .getPass = openxr_getPass, .submit = openxr_submit, .isFocused = openxr_isFocused, + .isPassthroughEnabled = openxr_isPassthroughEnabled, + .setPassthroughEnabled = openxr_setPassthroughEnabled, .update = openxr_update }; diff --git a/src/modules/headset/headset_webxr.c b/src/modules/headset/headset_webxr.c index 255bf7ed..fda1acd7 100644 --- a/src/modules/headset/headset_webxr.c +++ b/src/modules/headset/headset_webxr.c @@ -5,9 +5,9 @@ extern void webxr_start(void); extern void webxr_destroy(void); extern bool webxr_getName(char* name, size_t length); extern HeadsetOrigin webxr_getOriginType(void); +extern void webxr_getDisplayDimensions(uint32_t* width, uint32_t* height); extern double webxr_getDisplayTime(void); extern double webxr_getDeltaTime(void); -extern void webxr_getDisplayDimensions(uint32_t* width, uint32_t* height); extern uint32_t webxr_getViewCount(void); extern bool webxr_getViewPose(uint32_t view, float* position, float* orientation); extern bool webxr_getViewAngles(uint32_t view, float* left, float* right, float* up, float* down); @@ -26,6 +26,8 @@ extern struct ModelData* webxr_newModelData(Device device, bool animated); extern bool webxr_animate(struct Model* model); extern void webxr_renderTo(void (*callback)(void*), void* userdata); extern bool webxr_isFocused(void); +extern bool webxr_isPassthroughEnabled(void); +extern bool webxr_setPassthroughEnabled(bool enable); extern double webxr_update(void); static bool webxrAttached = false;