// SPDX-License-Identifier: MIT // The authors below grant copyright rights under the MIT license: // Copyright (c) 2024 Nick Klingensmith // Copyright (c) 2024 Qualcomm Technologies, Inc. // This requires an addition to the Android Manifest to work on quest: // // And adding this to the application section can also improve the passthrough // experience: // // // To work on Quest+Link, you may need to enable beta features in the Oculus // app's settings. using System; using System.Runtime.InteropServices; namespace StereoKit.Framework { class PassthroughFBExt : IStepper { bool extAvailable; bool enabled; bool enableOnInitialize; XrPassthroughFB activePassthrough = new XrPassthroughFB(); XrPassthroughLayerFB activeLayer = new XrPassthroughLayerFB(); Color oldColor; bool oldSky; public bool Available => extAvailable; public bool Enabled { get => enabled; set { if (extAvailable == false || enabled == value) return; if (value) { enabled = StartPassthrough(); } else { PausePassthrough(); enabled = false; } } } public PassthroughFBExt() : this(true) { } public PassthroughFBExt(bool enabled = true) { if (SK.IsInitialized) Log.Err("PassthroughFBExt must be constructed before StereoKit is initialized!"); Backend.OpenXR.RequestExt("XR_FB_passthrough"); enableOnInitialize = enabled; } public bool Initialize() { extAvailable = Backend.XRType == BackendXRType.OpenXR && Backend.OpenXR.ExtEnabled("XR_FB_passthrough") && LoadBindings() && InitPassthrough(); return true; } public void Step() { if (Enabled == false) return; XrCompositionLayerPassthroughFB layer = new XrCompositionLayerPassthroughFB( XrCompositionLayerFlags.BLEND_TEXTURE_SOURCE_ALPHA_BIT, activeLayer); Backend.OpenXR.AddCompositionLayer(layer, -1); } public void Shutdown() { if (!Enabled) return; Enabled = false; DestroyPassthrough(); } bool InitPassthrough() { XrPassthroughFlagsFB flags = enableOnInitialize ? XrPassthroughFlagsFB.IS_RUNNING_AT_CREATION_BIT_FB : XrPassthroughFlagsFB.None; XrResult result = xrCreatePassthroughFB( Backend.OpenXR.Session, new XrPassthroughCreateInfoFB(flags), out activePassthrough); if (result != XrResult.Success) { Log.Err($"xrCreatePassthroughFB failed: {result}"); return false; } result = xrCreatePassthroughLayerFB( Backend.OpenXR.Session, new XrPassthroughLayerCreateInfoFB(activePassthrough, flags, XrPassthroughLayerPurposeFB.RECONSTRUCTION_FB), out activeLayer); if (result != XrResult.Success) { Log.Err($"xrCreatePassthroughLayerFB failed: {result}"); return false; } enabled = enableOnInitialize; StartSky(); return true; } void DestroyPassthrough() { xrDestroyPassthroughLayerFB(activeLayer); xrDestroyPassthroughFB(activePassthrough); } bool StartPassthrough() { XrResult result = xrPassthroughStartFB(activePassthrough); if (result != XrResult.Success) { Log.Err($"xrPassthroughStartFB failed: {result}"); return false; } result = xrPassthroughLayerResumeFB(activeLayer); if (result != XrResult.Success) { Log.Err($"xrPassthroughLayerResumeFB failed: {result}"); return false; } StartSky(); return true; } void StartSky() { oldColor = Renderer.ClearColor; oldSky = Renderer.EnableSky; Renderer.ClearColor = Color.BlackTransparent; Renderer.EnableSky = false; } void PausePassthrough() { XrResult result = xrPassthroughLayerPauseFB(activeLayer); if (result != XrResult.Success) { Log.Err($"xrPassthroughLayerPauseFB failed: {result}"); return; } result = xrPassthroughPauseFB(activePassthrough); if (result != XrResult.Success) { Log.Err($"xrPassthroughPauseFB failed: {result}"); return; } Renderer.ClearColor = oldColor; Renderer.EnableSky = oldSky; } #region OpenXR native bindings and types enum XrStructureType : UInt64 { XR_TYPE_PASSTHROUGH_CREATE_INFO_FB = 1000118001, XR_TYPE_PASSTHROUGH_LAYER_CREATE_INFO_FB = 1000118002, XR_TYPE_PASSTHROUGH_STYLE_FB = 1000118020, XR_TYPE_COMPOSITION_LAYER_PASSTHROUGH_FB = 1000118003, } enum XrPassthroughFlagsFB : UInt64 { None = 0, IS_RUNNING_AT_CREATION_BIT_FB = 0x00000001, LAYER_DEPTH_BIT_FB = 0x00000002 } enum XrCompositionLayerFlags : UInt64 { None = 0, CORRECT_CHROMATIC_ABERRATION_BIT = 0x00000001, BLEND_TEXTURE_SOURCE_ALPHA_BIT = 0x00000002, UNPREMULTIPLIED_ALPHA_BIT = 0x00000004, } enum XrPassthroughLayerPurposeFB : UInt32 { RECONSTRUCTION_FB = 0, PROJECTED_FB = 1, TRACKED_KEYBOARD_HANDS_FB = 1000203001, MAX_ENUM_FB = 0x7FFFFFFF, } enum XrResult : Int32 { Success = 0, } #pragma warning disable 0169 // handle is not "used", but required for interop [StructLayout(LayoutKind.Sequential)] struct XrPassthroughFB { ulong handle; } [StructLayout(LayoutKind.Sequential)] struct XrPassthroughLayerFB { ulong handle; } #pragma warning restore 0169 [StructLayout(LayoutKind.Sequential)] struct XrPassthroughCreateInfoFB { private XrStructureType type; public IntPtr next; public XrPassthroughFlagsFB flags; public XrPassthroughCreateInfoFB(XrPassthroughFlagsFB passthroughFlags) { type = XrStructureType.XR_TYPE_PASSTHROUGH_CREATE_INFO_FB; next = IntPtr.Zero; flags = passthroughFlags; } } [StructLayout(LayoutKind.Sequential)] struct XrPassthroughLayerCreateInfoFB { private XrStructureType type; public IntPtr next; public XrPassthroughFB passthrough; public XrPassthroughFlagsFB flags; public XrPassthroughLayerPurposeFB purpose; public XrPassthroughLayerCreateInfoFB(XrPassthroughFB passthrough, XrPassthroughFlagsFB flags, XrPassthroughLayerPurposeFB purpose) { type = XrStructureType.XR_TYPE_PASSTHROUGH_LAYER_CREATE_INFO_FB; next = IntPtr.Zero; this.passthrough = passthrough; this.flags = flags; this.purpose = purpose; } } [StructLayout(LayoutKind.Sequential)] struct XrPassthroughStyleFB { public XrStructureType type; public IntPtr next; public float textureOpacityFactor; public Color edgeColor; public XrPassthroughStyleFB(float textureOpacityFactor, Color edgeColor) { type = XrStructureType.XR_TYPE_PASSTHROUGH_STYLE_FB; next = IntPtr.Zero; this.textureOpacityFactor = textureOpacityFactor; this.edgeColor = edgeColor; } } [StructLayout(LayoutKind.Sequential)] struct XrCompositionLayerPassthroughFB { public XrStructureType type; public IntPtr next; public XrCompositionLayerFlags flags; public ulong space; public XrPassthroughLayerFB layerHandle; public XrCompositionLayerPassthroughFB(XrCompositionLayerFlags flags, XrPassthroughLayerFB layerHandle) { type = XrStructureType.XR_TYPE_COMPOSITION_LAYER_PASSTHROUGH_FB; next = IntPtr.Zero; space = 0; this.flags = flags; this.layerHandle = layerHandle; } } delegate XrResult del_xrCreatePassthroughFB (ulong session, [In] XrPassthroughCreateInfoFB createInfo, out XrPassthroughFB outPassthrough); delegate XrResult del_xrDestroyPassthroughFB (XrPassthroughFB passthrough); delegate XrResult del_xrPassthroughStartFB (XrPassthroughFB passthrough); delegate XrResult del_xrPassthroughPauseFB (XrPassthroughFB passthrough); delegate XrResult del_xrCreatePassthroughLayerFB (ulong session, [In] XrPassthroughLayerCreateInfoFB createInfo, out XrPassthroughLayerFB outLayer); delegate XrResult del_xrDestroyPassthroughLayerFB (XrPassthroughLayerFB layer); delegate XrResult del_xrPassthroughLayerPauseFB (XrPassthroughLayerFB layer); delegate XrResult del_xrPassthroughLayerResumeFB (XrPassthroughLayerFB layer); delegate XrResult del_xrPassthroughLayerSetStyleFB(XrPassthroughLayerFB layer, [In] XrPassthroughStyleFB style); del_xrCreatePassthroughFB xrCreatePassthroughFB; del_xrDestroyPassthroughFB xrDestroyPassthroughFB; del_xrPassthroughStartFB xrPassthroughStartFB; del_xrPassthroughPauseFB xrPassthroughPauseFB; del_xrCreatePassthroughLayerFB xrCreatePassthroughLayerFB; del_xrDestroyPassthroughLayerFB xrDestroyPassthroughLayerFB; del_xrPassthroughLayerPauseFB xrPassthroughLayerPauseFB; del_xrPassthroughLayerResumeFB xrPassthroughLayerResumeFB; del_xrPassthroughLayerSetStyleFB xrPassthroughLayerSetStyleFB; bool LoadBindings() { xrCreatePassthroughFB = Backend.OpenXR.GetFunction ("xrCreatePassthroughFB"); xrDestroyPassthroughFB = Backend.OpenXR.GetFunction ("xrDestroyPassthroughFB"); xrPassthroughStartFB = Backend.OpenXR.GetFunction ("xrPassthroughStartFB"); xrPassthroughPauseFB = Backend.OpenXR.GetFunction ("xrPassthroughPauseFB"); xrCreatePassthroughLayerFB = Backend.OpenXR.GetFunction ("xrCreatePassthroughLayerFB"); xrDestroyPassthroughLayerFB = Backend.OpenXR.GetFunction ("xrDestroyPassthroughLayerFB"); xrPassthroughLayerPauseFB = Backend.OpenXR.GetFunction ("xrPassthroughLayerPauseFB"); xrPassthroughLayerResumeFB = Backend.OpenXR.GetFunction ("xrPassthroughLayerResumeFB"); xrPassthroughLayerSetStyleFB = Backend.OpenXR.GetFunction("xrPassthroughLayerSetStyleFB"); return xrCreatePassthroughFB != null && xrDestroyPassthroughFB != null && xrPassthroughStartFB != null && xrPassthroughPauseFB != null && xrCreatePassthroughLayerFB != null && xrDestroyPassthroughLayerFB != null && xrPassthroughLayerPauseFB != null && xrPassthroughLayerResumeFB != null && xrPassthroughLayerSetStyleFB != null; } #endregion } }