diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml
index 5509b8b350f..aae2c855d52 100644
--- a/.github/workflows/dotnet.yml
+++ b/.github/workflows/dotnet.yml
@@ -62,6 +62,8 @@ jobs:
run: dotnet build Kni.Platform.iOS.GL.sln --property:WarningLevel=1
- name: Build Android.GL
run: dotnet build Kni.Platform.Android.GL.sln --property:WarningLevel=1
+ # - name: Build Oculus.GL
+ # run: dotnet build Kni.Platform.Oculus.GL.sln --property:WarningLevel=1
# - name: Build Cardboard.GL
# run: dotnet build Kni.Platform.Cardboard.GL.sln --property:WarningLevel=1
# - name: Build Windows UAP.DX11
diff --git a/BuildNuget.bat b/BuildNuget.bat
index c212d1a8b36..b1229c2699c 100644
--- a/BuildNuget.bat
+++ b/BuildNuget.bat
@@ -21,6 +21,7 @@ dotnet pack src\Xna.Framework.Content.Pipeline.Media\Xna.Framework.Content.Pipel
"C:\Program Files (x86)\NuGet3\nuget.exe" pack NuGetPackages/MonoGame.Framework.WindowsUniversal.nuspec -OutputDirectory NuGetPackages\Output\ -BasePath . -Version 3.14.9001.0 -Properties Configuration=Release
dotnet pack Platforms\Kni.Platform.Android.GL.csproj --output NuGetPackages\Output\ /t:Build /p:Configuration=Release
+dotnet pack Platforms\Kni.Platform.Oculus.GL.csproj --output NuGetPackages\Output\ /t:Build /p:Configuration=Release
dotnet pack Platforms\Kni.Platform.iOS.GL.csproj --output NuGetPackages\Output\ /t:Build /p:Configuration=Release
dotnet pack Platforms\Kni.Platform.WinForms.DX11.csproj --output NuGetPackages\Output\ /t:Build /p:Configuration=Release
dotnet pack Platforms\Kni.Platform.SDL2.GL.csproj --output NuGetPackages\Output\ /t:Build /p:Configuration=Release
diff --git a/KNI.sln b/KNI.sln
index 63c42f36e92..07fc29c254d 100644
--- a/KNI.sln
+++ b/KNI.sln
@@ -77,6 +77,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Kni.Tests.SDL2.GL", "Tests\
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Kni.Tests.WinForms.DX11", "Tests\Kni.Tests.WinForms.DX11.csproj", "{DED2DDB6-D4BE-656D-2E54-657374732E57}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Kni.Platform.Oculus.GL", "Platforms\Kni.Platform.Oculus.GL.csproj", "{3472997A-CD26-4D0D-B64A-795F913E6777}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -207,6 +209,10 @@ Global
{DED2DDB6-D4BE-656D-2E54-657374732E57}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DED2DDB6-D4BE-656D-2E54-657374732E57}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DED2DDB6-D4BE-656D-2E54-657374732E57}.Release|Any CPU.Build.0 = Release|Any CPU
+ {3472997A-CD26-4D0D-B64A-795F913E6777}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {3472997A-CD26-4D0D-B64A-795F913E6777}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3472997A-CD26-4D0D-B64A-795F913E6777}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {3472997A-CD26-4D0D-B64A-795F913E6777}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -243,6 +249,7 @@ Global
{5212C44E-1573-43C2-85E8-5751A12FBBFD} = {753EFCC6-42AB-433E-A013-B7B5D1FE50E0}
{DED2DDB6-D4BE-B1B4-2E54-657374732E57} = {94670BA5-3B36-4EBA-A3E0-27912F6BD11A}
{DED2DDB6-D4BE-656D-2E54-657374732E57} = {94670BA5-3B36-4EBA-A3E0-27912F6BD11A}
+ {3472997A-CD26-4D0D-B64A-795F913E6777} = {9E8F9D1A-DEF1-4FA1-8DE9-0A82B7BC99D9}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {48F20664-456B-4FE0-AE0A-7F5AC695B13C}
diff --git a/Kni.Platform.Oculus.GL.sln b/Kni.Platform.Oculus.GL.sln
new file mode 100644
index 00000000000..ea08444379a
--- /dev/null
+++ b/Kni.Platform.Oculus.GL.sln
@@ -0,0 +1,98 @@
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.9.34723.18
+MinimumVisualStudioVersion = 17.9.34723.18
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Framework", "Framework", "{A5A52329-49D0-4CE0-BA3E-DBD2BA90988F}"
+EndProject
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Platforms", "Platforms", "{9E8F9D1A-DEF1-4FA1-8DE9-0A82B7BC99D9}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Xna.Framework", "src\Xna.Framework\Xna.Framework.csproj", "{741B4B1E-89E4-434C-8867-6129838AFD51}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Xna.Framework.Content", "src\Xna.Framework.Content\Xna.Framework.Content.csproj", "{1DC4C439-A8A6-4A11-AB3B-A88DCBA05449}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Xna.Framework.Graphics", "src\Xna.Framework.Graphics\Xna.Framework.Graphics.csproj", "{4B8D3F73-BBD2-4057-B86B-8B73B957DC0F}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Xna.Framework.Audio", "src\Xna.Framework.Audio\Xna.Framework.Audio.csproj", "{3F81F76D-F0F3-44FE-A256-40AF153C33F7}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Xna.Framework.Media", "src\Xna.Framework.Media\Xna.Framework.Media.csproj", "{6E0E6284-13FF-4DC7-8FC2-B6D756EAF1FD}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Xna.Framework.Input", "src\Xna.Framework.Input\Xna.Framework.Input.csproj", "{8FB8B257-C091-4C41-B221-75C37B68CD8F}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Xna.Framework.Game", "src\Xna.Framework.Game\Xna.Framework.Game.csproj", "{90BBD6EF-F386-4F47-88CD-BF386C7D1705}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Xna.Framework.Devices", "src\Xna.Framework.Devices\Xna.Framework.Devices.csproj", "{6B3E56F7-C567-463C-9746-0244FD959322}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Xna.Framework.Storage", "src\Xna.Framework.Storage\Xna.Framework.Storage.csproj", "{7AE82BAB-5F52-427A-8F6F-DA829261FF9C}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Xna.Framework.XR", "src\Xna.Framework.XR\Xna.Framework.XR.csproj", "{6D0D985D-B256-4208-9E78-77897D461698}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Kni.Platform.Oculus.GL", "Platforms\Kni.Platform.Oculus.GL.csproj", "{3472997A-CD26-4D0D-B64A-795F913E6777}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {741B4B1E-89E4-434C-8867-6129838AFD51}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {741B4B1E-89E4-434C-8867-6129838AFD51}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {741B4B1E-89E4-434C-8867-6129838AFD51}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {741B4B1E-89E4-434C-8867-6129838AFD51}.Release|Any CPU.Build.0 = Release|Any CPU
+ {1DC4C439-A8A6-4A11-AB3B-A88DCBA05449}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {1DC4C439-A8A6-4A11-AB3B-A88DCBA05449}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1DC4C439-A8A6-4A11-AB3B-A88DCBA05449}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {1DC4C439-A8A6-4A11-AB3B-A88DCBA05449}.Release|Any CPU.Build.0 = Release|Any CPU
+ {4B8D3F73-BBD2-4057-B86B-8B73B957DC0F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {4B8D3F73-BBD2-4057-B86B-8B73B957DC0F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {4B8D3F73-BBD2-4057-B86B-8B73B957DC0F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {4B8D3F73-BBD2-4057-B86B-8B73B957DC0F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {3F81F76D-F0F3-44FE-A256-40AF153C33F7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {3F81F76D-F0F3-44FE-A256-40AF153C33F7}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3F81F76D-F0F3-44FE-A256-40AF153C33F7}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {3F81F76D-F0F3-44FE-A256-40AF153C33F7}.Release|Any CPU.Build.0 = Release|Any CPU
+ {6E0E6284-13FF-4DC7-8FC2-B6D756EAF1FD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {6E0E6284-13FF-4DC7-8FC2-B6D756EAF1FD}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6E0E6284-13FF-4DC7-8FC2-B6D756EAF1FD}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6E0E6284-13FF-4DC7-8FC2-B6D756EAF1FD}.Release|Any CPU.Build.0 = Release|Any CPU
+ {8FB8B257-C091-4C41-B221-75C37B68CD8F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {8FB8B257-C091-4C41-B221-75C37B68CD8F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {8FB8B257-C091-4C41-B221-75C37B68CD8F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {8FB8B257-C091-4C41-B221-75C37B68CD8F}.Release|Any CPU.Build.0 = Release|Any CPU
+ {90BBD6EF-F386-4F47-88CD-BF386C7D1705}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {90BBD6EF-F386-4F47-88CD-BF386C7D1705}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {90BBD6EF-F386-4F47-88CD-BF386C7D1705}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {90BBD6EF-F386-4F47-88CD-BF386C7D1705}.Release|Any CPU.Build.0 = Release|Any CPU
+ {6B3E56F7-C567-463C-9746-0244FD959322}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {6B3E56F7-C567-463C-9746-0244FD959322}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6B3E56F7-C567-463C-9746-0244FD959322}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6B3E56F7-C567-463C-9746-0244FD959322}.Release|Any CPU.Build.0 = Release|Any CPU
+ {7AE82BAB-5F52-427A-8F6F-DA829261FF9C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {7AE82BAB-5F52-427A-8F6F-DA829261FF9C}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {7AE82BAB-5F52-427A-8F6F-DA829261FF9C}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {7AE82BAB-5F52-427A-8F6F-DA829261FF9C}.Release|Any CPU.Build.0 = Release|Any CPU
+ {6D0D985D-B256-4208-9E78-77897D461698}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {6D0D985D-B256-4208-9E78-77897D461698}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {6D0D985D-B256-4208-9E78-77897D461698}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {6D0D985D-B256-4208-9E78-77897D461698}.Release|Any CPU.Build.0 = Release|Any CPU
+ {3472997A-CD26-4D0D-B64A-795F913E6777}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {3472997A-CD26-4D0D-B64A-795F913E6777}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {3472997A-CD26-4D0D-B64A-795F913E6777}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {3472997A-CD26-4D0D-B64A-795F913E6777}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {741B4B1E-89E4-434C-8867-6129838AFD51} = {A5A52329-49D0-4CE0-BA3E-DBD2BA90988F}
+ {1DC4C439-A8A6-4A11-AB3B-A88DCBA05449} = {A5A52329-49D0-4CE0-BA3E-DBD2BA90988F}
+ {4B8D3F73-BBD2-4057-B86B-8B73B957DC0F} = {A5A52329-49D0-4CE0-BA3E-DBD2BA90988F}
+ {3F81F76D-F0F3-44FE-A256-40AF153C33F7} = {A5A52329-49D0-4CE0-BA3E-DBD2BA90988F}
+ {6E0E6284-13FF-4DC7-8FC2-B6D756EAF1FD} = {A5A52329-49D0-4CE0-BA3E-DBD2BA90988F}
+ {8FB8B257-C091-4C41-B221-75C37B68CD8F} = {A5A52329-49D0-4CE0-BA3E-DBD2BA90988F}
+ {90BBD6EF-F386-4F47-88CD-BF386C7D1705} = {A5A52329-49D0-4CE0-BA3E-DBD2BA90988F}
+ {6B3E56F7-C567-463C-9746-0244FD959322} = {A5A52329-49D0-4CE0-BA3E-DBD2BA90988F}
+ {7AE82BAB-5F52-427A-8F6F-DA829261FF9C} = {A5A52329-49D0-4CE0-BA3E-DBD2BA90988F}
+ {6D0D985D-B256-4208-9E78-77897D461698} = {A5A52329-49D0-4CE0-BA3E-DBD2BA90988F}
+ {3472997A-CD26-4D0D-B64A-795F913E6777} = {9E8F9D1A-DEF1-4FA1-8DE9-0A82B7BC99D9}
+ EndGlobalSection
+EndGlobal
diff --git a/NuGetPackages/RegisterLocalNuget.bat b/NuGetPackages/RegisterLocalNuget.bat
index 97f65b228ac..4067a3dce1c 100644
--- a/NuGetPackages/RegisterLocalNuget.bat
+++ b/NuGetPackages/RegisterLocalNuget.bat
@@ -25,6 +25,7 @@ set username=username
"C:\Program Files (x86)\nuget\nuget.exe" delete nkast.Kni.Platform.UAP.DX11 3.14.9001 -Source "C:\Users\%username%\.nuget\localPackages" -NonInteractive
"C:\Program Files (x86)\nuget\nuget.exe" delete nkast.Kni.Platform.SDL2.GL 3.14.9001 -Source "C:\Users\%username%\.nuget\localPackages" -NonInteractive
"C:\Program Files (x86)\nuget\nuget.exe" delete nkast.Kni.Platform.Android.GL 3.14.9001 -Source "C:\Users\%username%\.nuget\localPackages" -NonInteractive
+"C:\Program Files (x86)\nuget\nuget.exe" delete nkast.Kni.Platform.Oculus.GL 3.14.9001 -Source "C:\Users\%username%\.nuget\localPackages" -NonInteractive
"C:\Program Files (x86)\nuget\nuget.exe" delete nkast.Kni.Platform.WinForms.DX11.OculusOVR 3.14.9001 -Source "C:\Users\%username%\.nuget\localPackages" -NonInteractive
@@ -54,6 +55,7 @@ set username=username
"C:\Program Files (x86)\nuget\nuget.exe" add output\nkast.Kni.Platform.UAP.DX11.3.14.9001.nupkg -Source "C:\Users\%username%\.nuget\localPackages"
"C:\Program Files (x86)\nuget\nuget.exe" add output\nkast.Kni.Platform.SDL2.GL.3.14.9001.nupkg -Source "C:\Users\%username%\.nuget\localPackages"
"C:\Program Files (x86)\nuget\nuget.exe" add output\nkast.Kni.Platform.Android.GL.3.14.9001.nupkg -Source "C:\Users\%username%\.nuget\localPackages"
+"C:\Program Files (x86)\nuget\nuget.exe" add output\nkast.Kni.Platform.Oculus.GL.3.14.9001.nupkg -Source "C:\Users\%username%\.nuget\localPackages"
"C:\Program Files (x86)\nuget\nuget.exe" add output\nkast.Kni.Platform.WinForms.DX11.OculusOVR.3.14.9001.nupkg -Source "C:\Users\%username%\.nuget\localPackages"
diff --git a/Platforms/Game/.Android/ConcreteGraphicsDeviceManager.cs b/Platforms/Game/.Android/ConcreteGraphicsDeviceManager.cs
index 1052a109c4a..f0759f4ac95 100644
--- a/Platforms/Game/.Android/ConcreteGraphicsDeviceManager.cs
+++ b/Platforms/Game/.Android/ConcreteGraphicsDeviceManager.cs
@@ -220,6 +220,10 @@ public override bool BeginDraw()
public override void EndDraw()
{
+#if OCULUS
+ return; // On Oculus, do not Present() the backbuffer.
+#endif
+
//base.EndDraw();
GraphicsDevice device = this.GraphicsDevice;
diff --git a/Platforms/Game/.Oculus/AndroidCompatibility.cs b/Platforms/Game/.Oculus/AndroidCompatibility.cs
new file mode 100644
index 00000000000..6f798c7008c
--- /dev/null
+++ b/Platforms/Game/.Oculus/AndroidCompatibility.cs
@@ -0,0 +1,139 @@
+using System;
+using System.Linq;
+using Android.App;
+using Android.Content.Res;
+using Android.OS;
+using Android.Views;
+
+namespace Microsoft.Xna.Framework
+{
+ ///
+ /// Properties that change from how XNA works by default
+ ///
+ public class AndroidCompatibility
+ {
+ private static AndroidCompatibility _current;
+
+ ///
+ /// Because the Kindle Fire devices default orientation is fliped by 180 degrees from all the other android devices
+ /// on the market we need to do some special processing to make sure that LandscapeLeft is the correct way round.
+ /// This list contains all the Build.Model strings of the effected devices, it should be added to if and when
+ /// more devices exhibit the same issues.
+ ///
+ private readonly string[] Kindles = new[] { "KFTT", "KFJWI", "KFJWA", "KFSOWI", "KFTHWA", "KFTHWI", "KFAPWA", "KFAPWI" };
+
+ public bool FlipLandscape { get; private set; }
+
+ [CLSCompliant(false)]
+ public Orientation NaturalOrientation { get; private set; }
+
+
+ [CLSCompliant(false)]
+ public static AndroidCompatibility Current
+ {
+ get
+ {
+ if (_current != null)
+ return _current;
+ else
+ throw new InvalidOperationException("Not initialized.");
+ }
+ }
+
+ internal static void Initialize(Activity activity)
+ {
+ if (_current == null)
+ _current = new AndroidCompatibility(activity);
+ }
+
+ private AndroidCompatibility(Activity activity)
+ {
+ FlipLandscape = Kindles.Contains(Build.Model);
+ NaturalOrientation = GetDeviceNaturalOrientation(activity);
+ }
+
+ private Orientation GetDeviceNaturalOrientation(Activity activity)
+ {
+ var orientation = activity.Resources.Configuration.Orientation;
+ SurfaceOrientation rotation = activity.WindowManager.DefaultDisplay.Rotation;
+
+ // check if MainActivity setup is correct.
+ var screenOrientation = activity.RequestedOrientation;
+ if (screenOrientation != Android.Content.PM.ScreenOrientation.Landscape)
+ throw new InvalidOperationException("Invalid orientation. Set ScreenOrientation in MainActivity to Landscape.");
+
+ if (((rotation == SurfaceOrientation.Rotation0 || rotation == SurfaceOrientation.Rotation180) &&
+ orientation == Orientation.Landscape)
+ || ((rotation == SurfaceOrientation.Rotation90 || rotation == SurfaceOrientation.Rotation270) &&
+ orientation == Orientation.Portrait))
+ {
+ return Orientation.Landscape;
+ }
+ else
+ {
+ return Orientation.Portrait;
+ }
+ }
+
+ internal DisplayOrientation GetAbsoluteOrientation(int degrees)
+ {
+ // Orientation is reported by the device in degrees compared to the natural orientation
+ // Some tablets have a natural landscape orientation, which we need to account for
+ if (NaturalOrientation == Orientation.Landscape)
+ degrees += 270;
+
+ // Round orientation into one of 8 positions, either 0, 45, 90, 135, 180, 225, 270, 315.
+ degrees = ( ((degrees + 22) / 45) * 45) % 360;
+
+ // Surprisingly 90 degree is landscape right, except on Kindle devices
+ var disporientation = DisplayOrientation.Unknown;
+ switch (degrees)
+ {
+ case 90: disporientation = FlipLandscape ? DisplayOrientation.LandscapeLeft : DisplayOrientation.LandscapeRight;
+ break;
+ case 270: disporientation = FlipLandscape ? DisplayOrientation.LandscapeRight : DisplayOrientation.LandscapeLeft;
+ break;
+ case 0: disporientation = DisplayOrientation.Portrait;
+ break;
+ case 180: disporientation = DisplayOrientation.PortraitDown;
+ break;
+ default:
+ disporientation = DisplayOrientation.Unknown;
+ break;
+ }
+
+ return disporientation;
+ }
+
+ ///
+ /// Get the absolute orientation of the device, accounting for platform differences.
+ ///
+ ///
+ [CLSCompliant(false)]
+ public DisplayOrientation GetAbsoluteOrientation(Activity activity)
+ {
+ var orientation = activity.WindowManager.DefaultDisplay.Rotation;
+
+ // Landscape degrees (provided by the OrientationListener) are swapped by default
+ // Since we use the code used by OrientationListener, we have to swap manually
+ int degrees;
+ switch (orientation)
+ {
+ case SurfaceOrientation.Rotation90:
+ degrees = 270;
+ break;
+ case SurfaceOrientation.Rotation180:
+ degrees = 180;
+ break;
+ case SurfaceOrientation.Rotation270:
+ degrees = 90;
+ break;
+ default:
+ degrees = 0;
+ break;
+ }
+
+ return GetAbsoluteOrientation(degrees);
+ }
+ }
+}
diff --git a/Platforms/Input/.OpenXR/XR/ConcreteTouchControllerStrategy.cs b/Platforms/Input/.OpenXR/XR/ConcreteTouchControllerStrategy.cs
new file mode 100644
index 00000000000..c8c352346c7
--- /dev/null
+++ b/Platforms/Input/.OpenXR/XR/ConcreteTouchControllerStrategy.cs
@@ -0,0 +1,336 @@
+// Copyright (C)2022-24 Nick Kastellanos
+
+using System;
+using System.Collections.Generic;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Input;
+using Microsoft.Xna.Framework.Input.XR;
+using Microsoft.Xna.Framework.XR;
+using Microsoft.Xna.Platform.XR;
+using nkast.LibOXR;
+using Silk.NET.OpenXR;
+using XrAction = Silk.NET.OpenXR.Action;
+
+namespace Microsoft.Xna.Platform.Input.XR
+{
+ public sealed class ConcreteTouchControllerStrategy : ITouchControllerInput
+ {
+ private ConcreteXRDevice _xrDevice;
+
+ unsafe void ITouchControllerInput.GetCapabilities(TouchControllerType controllerType,
+ ref GamePadType gamePadType, ref string displayName, ref string identifier, ref bool isConnected,
+ ref Buttons buttons,
+ ref bool hasLeftVibrationMotor, ref bool hasRightVibrationMotor,
+ ref bool hasVoiceSupport
+ )
+ {
+ var session = _xrDevice.Session;
+ if (session == null)
+ {
+ gamePadType = GamePadType.Unknown;
+ displayName = String.Empty;
+ identifier = String.Empty;
+ isConnected = false;
+ buttons = (Buttons)0;
+ hasLeftVibrationMotor = false;
+ hasRightVibrationMotor = false;
+ hasVoiceSupport = false;
+ }
+
+ //OvrControllerType connectedControllerTypes = session.GetConnectedControllerTypes();
+ TouchControllerType connectedControllerTypes = default;
+
+ Result xrResult;
+
+ ActionPath leftHandPath, rightHandPath;
+ _xrDevice.Instance.StringToPath("/user/hand/left", out leftHandPath);
+ _xrDevice.Instance.StringToPath("/user/hand/right", out rightHandPath);
+
+ ActionPath oculusTouchProfilePath;
+ _xrDevice.Instance.StringToPath("/interaction_profiles/oculus/touch_controller", out oculusTouchProfilePath);
+
+ session.GetCurrentInteractionProfile(leftHandPath, out InteractionProfileState leftProfileState);
+ session.GetCurrentInteractionProfile(rightHandPath, out InteractionProfileState rightProfileState);
+
+ if (leftProfileState.InteractionProfile == oculusTouchProfilePath.Handle)
+ connectedControllerTypes |= TouchControllerType.LTouch;
+
+ if (rightProfileState.InteractionProfile == oculusTouchProfilePath.Handle)
+ connectedControllerTypes |= TouchControllerType.RTouch;
+
+
+ isConnected = ((int)connectedControllerTypes & (int)controllerType) != 0;
+
+ connectedControllerTypes = (TouchControllerType)((int)connectedControllerTypes & (int)controllerType);
+
+ if (((int)connectedControllerTypes & (int)TouchControllerType.LTouch) == (int)TouchControllerType.LTouch)
+ {
+ buttons |= Buttons.X;
+ buttons |= Buttons.Y;
+ buttons |= Buttons.LeftThumbstickLeft & Buttons.LeftThumbstickRight;
+ buttons |= Buttons.LeftThumbstickDown & Buttons.LeftThumbstickUp;
+ buttons |= Buttons.LeftTrigger & Buttons.LeftGrip;
+ //buttons |= Buttons.LeftStick;
+ //buttons |= Buttons.Start;
+ }
+
+ if (((int)connectedControllerTypes & (int)TouchControllerType.RTouch) == (int)TouchControllerType.RTouch)
+ {
+ buttons |= Buttons.A;
+ buttons |= Buttons.B;
+ buttons |= Buttons.RightThumbstickLeft & Buttons.RightThumbstickRight;
+ buttons |= Buttons.RightThumbstickDown & Buttons.RightThumbstickUp;
+ buttons |= Buttons.RightTrigger & Buttons.RightGrip;
+ //buttons |= Buttons.RightStick;
+ //buttons |= Buttons.Back;
+ }
+
+ }
+
+ Buttons _virtualButtons;
+
+ internal ConcreteTouchControllerStrategy(ConcreteXRDevice xrDevice)
+ {
+ this._xrDevice = xrDevice;
+ }
+
+ GamePadState ITouchControllerInput.GetState(TouchControllerType controllerType)
+ {
+ var session = _xrDevice.Session;
+ if (session == null)
+ return new GamePadState();
+
+
+ //OvrControllerType connectedControllerTypes = session.GetConnectedControllerTypes();
+ TouchControllerType connectedControllerTypes = default;
+
+ Result xrResult;
+
+ ActionPath leftHandPath, rightHandPath;
+ _xrDevice.Instance.StringToPath("/user/hand/left", out leftHandPath);
+ _xrDevice.Instance.StringToPath("/user/hand/right", out rightHandPath);
+
+ ActionPath oculusTouchProfilePath;
+ _xrDevice.Instance.StringToPath("/interaction_profiles/oculus/touch_controller", out oculusTouchProfilePath);
+
+ session.GetCurrentInteractionProfile(leftHandPath, out InteractionProfileState leftProfileState);
+ session.GetCurrentInteractionProfile(rightHandPath, out InteractionProfileState rightProfileState);
+
+ if (leftProfileState.InteractionProfile == oculusTouchProfilePath.Handle)
+ connectedControllerTypes |= TouchControllerType.LTouch;
+
+ if (rightProfileState.InteractionProfile == oculusTouchProfilePath.Handle)
+ connectedControllerTypes |= TouchControllerType.RTouch;
+
+
+ var isConnected = ((int)connectedControllerTypes & (int)controllerType) != 0;
+ if (!isConnected)
+ return new GamePadState();
+
+ //OvrInputState ovrInputState;
+ //var ovrResult = session.GetInputState(ovrControllerType, out ovrInputState);
+
+ GamePadThumbSticks thumbSticks = default(GamePadThumbSticks);
+ GamePadTriggers triggers = default(GamePadTriggers);
+ GamePadTriggers grips = default(GamePadTriggers);
+ Buttons buttons = default(Buttons);
+ Buttons touches = default(Buttons);
+
+
+ ActionStateVector2f ThumbStickL = session.GetActionStateVector2(_xrDevice._moveOnJoystickActionL);
+ ActionStateVector2f ThumbStickR = session.GetActionStateVector2(_xrDevice._moveOnJoystickActionR);
+
+ thumbSticks = new GamePadThumbSticks(
+ leftPosition: new Vector2(ThumbStickL.CurrentState.X, ThumbStickL.CurrentState.Y),
+ rightPosition: new Vector2(ThumbStickR.CurrentState.X, ThumbStickR.CurrentState.Y));
+
+ ActionStateFloat moveYStateL = session.GetActionStateFloat(_xrDevice._moveOnYActionL);
+ ActionStateFloat moveYStateR = session.GetActionStateFloat(_xrDevice._moveOnYActionR);
+
+ triggers = new GamePadTriggers(
+ leftTrigger: moveYStateL.CurrentState,
+ rightTrigger: moveYStateR.CurrentState);
+
+ ActionStateFloat moveXStateL = session.GetActionStateFloat(_xrDevice._moveOnXActionL);
+ ActionStateFloat moveXStateR = session.GetActionStateFloat(_xrDevice._moveOnXActionR);
+
+ grips = new GamePadTriggers(
+ leftTrigger: moveXStateL.CurrentState,
+ rightTrigger: moveXStateR.CurrentState);
+
+ //// left buttons
+ ActionStateBoolean _stateButtonX = session.GetActionStateBoolean(_xrDevice._actionButtonX);
+ ActionStateBoolean _stateButtonY = session.GetActionStateBoolean(_xrDevice._actionButtonY);
+ ActionStateBoolean _stateButtonLTS = session.GetActionStateBoolean(_xrDevice._actionButtonLeftThumbstick);
+
+ if (_stateButtonX.CurrentState != 0)
+ buttons |= Buttons.X;
+ if (_stateButtonY.CurrentState != 0)
+ buttons |= Buttons.Y;
+ if (_stateButtonLTS.CurrentState != 0)
+ buttons |= Buttons.LeftStick;
+ //if (((int)ovrInputState.Buttons & (int)OvrButton.Enter) != 0) // Menu
+ // buttons |= Buttons.Start;
+
+ //// right buttons
+ ActionStateBoolean _stateButtonA = session.GetActionStateBoolean(_xrDevice._actionButtonA);
+ ActionStateBoolean _stateButtonB = session.GetActionStateBoolean(_xrDevice._actionButtonB);
+ ActionStateBoolean _stateButtonRTS = session.GetActionStateBoolean(_xrDevice._actionButtonRightThumbstick);
+
+ if (_stateButtonA.CurrentState!=0)
+ buttons |= Buttons.A;
+ if (_stateButtonB.CurrentState != 0)
+ buttons |= Buttons.B;
+ if (_stateButtonRTS.CurrentState != 0)
+ buttons |= Buttons.RightStick;
+ //if (((int)ovrInputState.Buttons & (int)OvrButton.Home) != 0) // Oculus button
+ // buttons |= Buttons.Back;
+
+ float TriggerThresholdOn = 0.6f;
+ float TriggerThresholdOff = 0.7f;
+ float ThumbstickThresholdOn = 0.5f;
+ float ThumbstickThresholdOff = 0.4f;
+ //// virtual trigger buttons
+ if (triggers.Left > TriggerThresholdOn && (_virtualButtons & Buttons.LeftTrigger) == 0)
+ _virtualButtons |= Buttons.LeftTrigger;
+ else if (triggers.Left < TriggerThresholdOff && (_virtualButtons & Buttons.LeftTrigger) != 0)
+ _virtualButtons &= ~Buttons.LeftTrigger;
+ if (triggers.Right > TriggerThresholdOn && (_virtualButtons & Buttons.RightTrigger) == 0)
+ _virtualButtons |= Buttons.RightTrigger;
+ else if (triggers.Right < TriggerThresholdOff && (_virtualButtons & Buttons.RightTrigger) != 0)
+ _virtualButtons &= ~Buttons.RightTrigger;
+ //// virtual grip buttons
+ if (grips.Left > TriggerThresholdOn && (_virtualButtons & Buttons.LeftGrip) == 0)
+ _virtualButtons |= Buttons.LeftGrip;
+ else if (grips.Left < TriggerThresholdOff && (_virtualButtons & Buttons.LeftGrip) != 0)
+ _virtualButtons &= ~Buttons.LeftGrip;
+ if (grips.Right > TriggerThresholdOn && (_virtualButtons & Buttons.RightGrip) == 0)
+ _virtualButtons |= Buttons.RightGrip;
+ else if (grips.Right < TriggerThresholdOff && (_virtualButtons & Buttons.RightGrip) != 0)
+ _virtualButtons &= ~Buttons.RightGrip;
+ //// virtual thumbstick buttons
+ if (ThumbStickL.CurrentState.X < -ThumbstickThresholdOn && (_virtualButtons & Buttons.LeftThumbstickLeft) == 0)
+ _virtualButtons |= Buttons.LeftThumbstickLeft;
+ else if (ThumbStickL.CurrentState.X > -ThumbstickThresholdOff && (_virtualButtons & Buttons.LeftThumbstickLeft) != 0)
+ _virtualButtons &= ~Buttons.LeftThumbstickLeft;
+ if (ThumbStickR.CurrentState.X < -ThumbstickThresholdOn && (_virtualButtons & Buttons.RightThumbstickLeft) == 0)
+ _virtualButtons |= Buttons.RightThumbstickLeft;
+ else if (ThumbStickR.CurrentState.X > -ThumbstickThresholdOff && (_virtualButtons & Buttons.RightThumbstickLeft) != 0)
+ _virtualButtons &= ~Buttons.RightThumbstickLeft;
+ if (ThumbStickL.CurrentState.X > ThumbstickThresholdOn && (_virtualButtons & Buttons.LeftThumbstickRight) == 0)
+ _virtualButtons |= Buttons.LeftThumbstickRight;
+ else if (ThumbStickL.CurrentState.X < ThumbstickThresholdOff && (_virtualButtons & Buttons.LeftThumbstickRight) != 0)
+ _virtualButtons &= ~Buttons.LeftThumbstickRight;
+ if (ThumbStickR.CurrentState.X > ThumbstickThresholdOn && (_virtualButtons & Buttons.RightThumbstickRight) == 0)
+ _virtualButtons |= Buttons.RightThumbstickRight;
+ else if (ThumbStickR.CurrentState.X < ThumbstickThresholdOff && (_virtualButtons & Buttons.RightThumbstickRight) != 0)
+ _virtualButtons &= ~Buttons.RightThumbstickRight;
+
+ if (ThumbStickL.CurrentState.Y < -ThumbstickThresholdOn && (_virtualButtons & Buttons.LeftThumbstickDown) == 0)
+ _virtualButtons |= Buttons.LeftThumbstickDown;
+ else if (ThumbStickL.CurrentState.Y > -ThumbstickThresholdOff && (_virtualButtons & Buttons.LeftThumbstickDown) != 0)
+ _virtualButtons &= ~Buttons.LeftThumbstickDown;
+ if (ThumbStickR.CurrentState.Y < -ThumbstickThresholdOn && (_virtualButtons & Buttons.RightThumbstickDown) == 0)
+ _virtualButtons |= Buttons.RightThumbstickDown;
+ else if (ThumbStickR.CurrentState.Y > -ThumbstickThresholdOff && (_virtualButtons & Buttons.RightThumbstickDown) != 0)
+ _virtualButtons &= ~Buttons.RightThumbstickDown;
+ if (ThumbStickL.CurrentState.Y > ThumbstickThresholdOn && (_virtualButtons & Buttons.LeftThumbstickUp) == 0)
+ _virtualButtons |= Buttons.LeftThumbstickUp;
+ else if (ThumbStickL.CurrentState.Y < ThumbstickThresholdOff && (_virtualButtons & Buttons.LeftThumbstickUp) != 0)
+ _virtualButtons &= ~Buttons.LeftThumbstickUp;
+ if (ThumbStickR.CurrentState.Y > ThumbstickThresholdOn && (_virtualButtons & Buttons.RightThumbstickUp) == 0)
+ _virtualButtons |= Buttons.RightThumbstickUp;
+ else if (ThumbStickR.CurrentState.Y < ThumbstickThresholdOff && (_virtualButtons & Buttons.RightThumbstickUp) != 0)
+ _virtualButtons &= ~Buttons.RightThumbstickUp;
+
+ buttons |= _virtualButtons;
+
+ //// left touches
+ ActionStateBoolean _stateButtonXtouch = session.GetActionStateBoolean(_xrDevice._actionButtonXtouch);
+ ActionStateBoolean _stateButtonYtouch = session.GetActionStateBoolean(_xrDevice._actionButtonYtouch);
+ ActionStateBoolean _stateButtonLTStouch = session.GetActionStateBoolean(_xrDevice._actionButtonLeftThumbsticktouch);
+ ActionStateFloat _stateTriggerLTouch = session.GetActionStateFloat(_xrDevice._actionTriggerLeftTouch);
+ // BUG: Grip touch doesn't work on native OpenXR.
+ // https://communityforums.atmeta.com/t5/OpenXR-Development/unable-to-get-grip-button-input-on-quest-1-getting-path/td-p/833021
+ //ActionStateFloat _stateGripLTouch = session.GetActionStateFloat(_xrDevice._actionGripLeftTouch);
+
+ if (_stateButtonXtouch.CurrentState != 0)
+ touches |= Buttons.X;
+ if (_stateButtonYtouch.CurrentState != 0)
+ touches |= Buttons.Y;
+ if (_stateButtonLTStouch.CurrentState != 0)
+ touches |= Buttons.LeftStick;
+ //if (((int)ovrInputState.Buttons & (int)OvrButton.Enter) != 0) // Menu
+ // touches |= Buttons.Start;
+ if (_stateTriggerLTouch.CurrentState != 0)
+ touches |= Buttons.LeftTrigger;
+ //if (_stateGripLTouch.CurrentState != 0)
+ // touches |= Buttons.LeftGrip;
+
+ //// right touches
+ ActionStateBoolean _stateButtonAtouch = session.GetActionStateBoolean(_xrDevice._actionButtonAtouch);
+ ActionStateBoolean _stateButtonBtouch = session.GetActionStateBoolean(_xrDevice._actionButtonBtouch);
+ ActionStateBoolean _stateButtonRTStouch = session.GetActionStateBoolean(_xrDevice._actionButtonRightThumbsticktouch);
+ ActionStateFloat _stateTriggerRTouch = session.GetActionStateFloat(_xrDevice._actionTriggerRightTouch);
+ // BUG: Grip touch doesn't work on native OpenXR.
+ // https://communityforums.atmeta.com/t5/OpenXR-Development/unable-to-get-grip-button-input-on-quest-1-getting-path/td-p/833021
+ //ActionStateFloat _stateGripRTouch = session.GetActionStateFloat(_xrDevice._actionGripRightTouch);
+
+ if (_stateButtonAtouch.CurrentState != 0)
+ touches |= Buttons.A;
+ if (_stateButtonBtouch.CurrentState != 0)
+ touches |= Buttons.B;
+ if (_stateButtonRTStouch.CurrentState != 0)
+ touches |= Buttons.RightStick;
+ //if (((int)ovrInputState.Buttons & (int)OvrButton.Home) != 0) // Oculus button
+ // touches |= Buttons.Back;
+ if (_stateTriggerRTouch.CurrentState != 0)
+ touches |= Buttons.RightTrigger;
+ //if (_stateGripRTouch.CurrentState != 0)
+ // touches |= Buttons.RightGrip;
+
+
+ var state = new GamePadState(
+ thumbSticks: thumbSticks,
+ triggers: triggers,
+ grips: grips,
+ touchButtons: new GamePadTouchButtons(buttons, touches),
+ dPad: default(GamePadDPad)
+ );
+
+ return state;
+ }
+
+ const int XR_MIN_HAPTIC_DURATION = -1;
+
+ private long ToXrTime(double sec)
+ {
+ return (long)(sec * 1000 * 1000 * 1000); //nSec
+ }
+
+ bool ITouchControllerInput.SetVibration(TouchControllerType controllerType, float amplitude)
+ {
+ var session = _xrDevice.Session;
+ if (session == null)
+ return false;
+
+ if ((controllerType & TouchControllerType.LTouch) == TouchControllerType.LTouch)
+ {
+ Result xrResult = session.ApplyHapticFeedback(
+ _xrDevice._vibrateLeftFeedback, amplitude: amplitude, frequency: 3000f, duration: ToXrTime(0.5));
+ if (xrResult != Result.Success)
+ return false;
+ }
+
+ if ((controllerType & TouchControllerType.RTouch) == TouchControllerType.RTouch)
+ {
+ Result xrResult = session.ApplyHapticFeedback(
+ _xrDevice._vibrateRightFeedback, amplitude: amplitude, frequency: 3000f, duration: ToXrTime(0.5));
+ if (xrResult != Result.Success)
+ return false;
+ }
+
+ return true;
+ }
+ }
+}
diff --git a/Platforms/Kni.Platform.Oculus.GL.csproj b/Platforms/Kni.Platform.Oculus.GL.csproj
new file mode 100644
index 00000000000..34c88c6168a
--- /dev/null
+++ b/Platforms/Kni.Platform.Oculus.GL.csproj
@@ -0,0 +1,286 @@
+
+
+
+
+ ..\Artifacts\Platforms\Oculus
+ True
+
+ 3.14.9001.0
+ 3.14.9001.0
+
+
+
+
+ false
+ false
+ net8.0-android32.0
+ Default
+ True
+ {3472997A-CD26-4D0D-B64A-795F913E6777}
+ Kni.Platform
+ Microsoft.Xna.Framework
+ ANDROID;OCULUS;OPENAL;OPENGL;GLES;STBSHARP_INTERNAL
+ true
+ CS0067;CS1591;CS1574;CS0419
+ true
+ KNI backend for Oculus platform (GLES).
+ KNI;monogame;.net core;core;.net standard;standard;android
+ nkast.Kni.Platform.Oculus.GL
+
+
+
+
+ ILLink.Descriptors.xml
+
+
+
+
+
+ {741B4B1E-89E4-434C-8867-6129838AFD51}
+ Xna.Framework
+ False
+
+
+ {1DC4C439-A8A6-4A11-AB3B-A88DCBA05449}
+ Xna.Framework.Content
+ False
+
+
+ {4B8D3F73-BBD2-4057-B86B-8B73B957DC0F}
+ Xna.Framework.Graphics
+ False
+
+
+ {3F81F76D-F0F3-44FE-A256-40AF153C33F7}
+ Xna.Framework.Audio
+ False
+
+
+ {6E0E6284-13FF-4DC7-8FC2-B6D756EAF1FD}
+ Xna.Framework.Media
+ False
+
+
+ {8FB8B257-C091-4C41-B221-75C37B68CD8F}
+ Xna.Framework.Input
+ False
+
+
+ {90BBD6EF-F386-4F47-88CD-BF386C7D1705}
+ Xna.Framework.Game
+ False
+
+
+ {6B3E56F7-C567-463C-9746-0244FD959322}
+ Xna.Framework.Devices
+ False
+
+
+ {7AE82BAB-5F52-427A-8F6F-DA829261FF9C}
+ Xna.Framework.Storage
+ False
+
+
+ {6D0D985D-B256-4208-9E78-77897D461698}
+ Xna.Framework.XR
+ False
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ libs\arm64-v8a\libopenxr_loader.so
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ libs\armeabi-v7a\libopenal32.so
+
+
+ libs\arm64-v8a\libopenal32.so
+
+
+ libs\x86\libopenal32.so
+
+
+ libs\x86_64\libopenal32.so
+
+
+
+
+
+ Resources.AlphaTestEffect.fxo
+
+
+ Resources.BasicEffect.fxo
+
+
+ Resources.DualTextureEffect.fxo
+
+
+ Resources.EnvironmentMapEffect.fxo
+
+
+ Resources.SkinnedEffect.fxo
+
+
+ Resources.SpriteEffect.fxo
+
+
+
+
\ No newline at end of file
diff --git a/Platforms/XR/.Oculus/ConcreteOxrSwapChainData.cs b/Platforms/XR/.Oculus/ConcreteOxrSwapChainData.cs
new file mode 100644
index 00000000000..0b7b597bbef
--- /dev/null
+++ b/Platforms/XR/.Oculus/ConcreteOxrSwapChainData.cs
@@ -0,0 +1,103 @@
+// Copyright (C)2024 Nick Kastellanos
+
+using System;
+using System.Collections.Generic;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+using Microsoft.Xna.Framework.XR;
+using Microsoft.Xna.Platform.XR;
+using Silk.NET.OpenXR;
+
+namespace Microsoft.Xna.Platform.Graphics
+{
+ // Concrete SharpDX.Direct3D11
+ internal sealed class ConcreteOxrSwapChainData : ConcreteXRDevice.OxrSwapChainDataBase
+ {
+
+ //private OvrTextureSwapChain _swapChain;
+ //public D3D11.Texture2D[] _buckBuffers;
+ private RenderTarget2D _renderTarget;
+
+ //internal override OvrTextureSwapChain SwapChain { get { return _swapChain; } }
+
+ internal override RenderTarget2D GetRenderTarget(int eye)
+ {
+ return _renderTarget;
+ }
+
+ //internal static int CreateSwapChain(
+ // GraphicsDevice graphicsDevice, OvrSession ovrSession,
+ // int w, int h,
+ // SurfaceFormat preferredFormat, DepthFormat preferredDepthFormat, int preferredMultiSampleCount,
+ // out OvrDevice.OvrSwapChainDataBase outSwapChainData)
+ //{
+ // int ovrResult = 0;
+
+ // outSwapChainData = null;
+ // ConcreteOvrSwapChainData swapChainData = new ConcreteOvrSwapChainData();
+
+ // OvrTextureSwapChainDesc desc = default(OvrTextureSwapChainDesc);
+ // desc.Type = OvrTextureType.Texture2D;
+ // desc.Format = OvrTextureFormat.R8G8B8A8_UNORM_SRGB;
+ // //desc.Format = OVR_FORMAT_R8G8B8A8_UNORM;
+ // desc.ArraySize = 1;
+ // desc.Width = w;
+ // desc.Height = h;
+ // desc.MipLevels = 1;
+ // desc.SampleCount = 1;
+ // desc.SampleCount = 1;
+ // desc.StaticImage = OvrBool.False;
+ // desc.MiscFlags = OvrTextureMiscFlags.None;
+ // desc.BindFlags = OvrTextureBindFlags.DX_RenderTarget;
+
+ // OvrTextureSwapChain swapChain;
+
+ // D3D11.Device d3dDevice = (D3D11.Device)graphicsDevice.GetD3D11Device();
+ // ovrResult = ovrSession.CreateTextureSwapChainDX(d3dDevice.NativePointer, desc, out swapChain);
+ // if (ovrResult < 0)
+ // return ovrResult;
+ // swapChainData._swapChain = swapChain;
+
+ // // create backBuffers
+ // int texSwapChainCount;
+ // ovrResult = swapChain.GetTextureSwapChainLength(out texSwapChainCount);
+
+ // swapChainData._renderTarget = new RenderTarget2D(
+ // graphicsDevice, w, h, false,
+ // preferredFormat, preferredDepthFormat, preferredMultiSampleCount,
+ // RenderTargetUsage.DiscardContents);
+
+ // swapChainData._buckBuffers = new D3D11.Texture2D[texSwapChainCount];
+ // for (int i = 0; i < texSwapChainCount; i++)
+ // {
+ // IntPtr pDxTexture3D;
+ // ovrResult = swapChain.GetTextureSwapChainBufferDX(i, IID_ID3D11Texture2D, out pDxTexture3D);
+ // swapChainData._buckBuffers[i] = new D3D11.Texture2D(pDxTexture3D);
+ // }
+
+ // outSwapChainData = swapChainData;
+
+ // return 0;
+ //}
+
+ internal override int SubmitRenderTarget(GraphicsDevice graphicsDevice, RenderTarget2D rt)
+ {
+ int ovrResult;
+ int index = 0;
+ //ovrResult = SwapChain.GetCurrentIndex(out index);
+ //if (ovrResult < 0)
+ // return ovrResult;
+
+ //D3D11.Texture2D dstResource = _buckBuffers[index];
+ //D3D11.Resource srcResource = (D3D11.Resource)rt.GetD3D11Resource();
+//
+ //D3D11.Device d3dDevice = (D3D11.Device)graphicsDevice.GetD3D11Device();
+ //D3D11.DeviceContext d3dContext = d3dDevice.ImmediateContext;
+ //
+ //d3dContext.CopyResource(srcResource, dstResource);
+
+ return 0;
+ }
+ }
+}
+
diff --git a/Platforms/XR/.Oculus/ConcreteXRDevice.cs b/Platforms/XR/.Oculus/ConcreteXRDevice.cs
new file mode 100644
index 00000000000..176d42311f2
--- /dev/null
+++ b/Platforms/XR/.Oculus/ConcreteXRDevice.cs
@@ -0,0 +1,2680 @@
+// Copyright (C)2024 Nick Kastellanos
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Text;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Graphics;
+using Microsoft.Xna.Framework.Input.XR;
+using Microsoft.Xna.Platform;
+using Microsoft.Xna.Platform.Input.XR;
+using Microsoft.Xna.Framework.XR;
+using nkast.LibOXR;
+using Silk.NET.Core;
+using Silk.NET.OpenXR;
+using SilkXR = Silk.NET.OpenXR.XR;
+using XrAction = Silk.NET.OpenXR.Action;
+//using OpenGL = Microsoft.Xna.Platform.Graphics.OpenGL;
+
+namespace Microsoft.Xna.Platform.XR
+{
+ public interface IOculusDeviceStrategy
+ {
+ SessionState SessionState { get; }
+ }
+
+ internal class ConcreteXRDevice : XRDeviceStrategy
+ , IOculusDeviceStrategy
+ {
+ const string _engineName = "KNI Engine";
+
+ private string _applicationName;
+ private IServiceProvider _services;
+ private IGraphicsDeviceService _graphics;
+ XRSessionMode _sessionMode;
+ XRDeviceState _deviceState;
+ bool _isTrackFloorLevelEnabled = false;
+
+ OxrAPI _xrAPI;
+ IList _extensions;
+ OxrInstance _oxrInstance;
+ OxrSession _oxrSession;
+
+ int _CpuLevel;
+ int _GpuLevel;
+
+ float[] _SupportedDisplayRefreshRates;
+ uint _RequestedDisplayRefreshRateIndex;
+ uint _NumSupportedDisplayRefreshRates;
+ xrGetDisplayRefreshRateFB _pfnxrGetDisplayRefreshRateFB;
+ xrRequestDisplayRefreshRateFB _pfnxrRequestDisplayRefreshRateFB;
+
+ OxrSpace _HeadSpace;
+
+ OxrSpace _LocalSpace;
+ OxrSpace _LocalFloorSpace;
+ OxrSpace _FakeStageSpace;
+ OxrSpace _StageSpace;
+
+ bool _isLocalFloorSupported;
+ bool _isStageSupported;
+ OxrSpace _CurrentStageSpace;
+
+ OxrPassthroughFB _passthroughFB;
+ OxrPassthroughLayerFB _passthroughLayerFB;
+
+ bool _useSimpleProfile;
+
+ OxrActionSet _runningActionSet;
+ public XrAction _actionButtonA;
+ public XrAction _actionButtonB;
+ public XrAction _actionButtonRightThumbstick;
+ public XrAction _actionButtonX;
+ public XrAction _actionButtonY;
+ public XrAction _actionButtonLeftThumbstick;
+
+ public XrAction _actionButtonAtouch;
+ public XrAction _actionButtonBtouch;
+ public XrAction _actionButtonRightThumbsticktouch;
+ public XrAction _actionButtonXtouch;
+ public XrAction _actionButtonYtouch;
+ public XrAction _actionButtonLeftThumbsticktouch;
+
+ public XrAction _moveOnXActionL;
+ public XrAction _moveOnXActionR;
+ public XrAction _moveOnYActionL;
+ public XrAction _moveOnYActionR;
+ public XrAction _moveOnJoystickActionL;
+ public XrAction _moveOnJoystickActionR;
+
+ public XrAction _actionTriggerLeftTouch;
+ public XrAction _actionTriggerRightTouch;
+ // BUG: Grip touch doesn't work on native OpenXR.
+ // https://communityforums.atmeta.com/t5/OpenXR-Development/unable-to-get-grip-button-input-on-quest-1-getting-path/td-p/833021
+ //public XrAction _actionGripLeftTouch;
+ //public XrAction _actionGripRightTouch;
+
+ public XrAction _vibrateLeftFeedback;
+ public XrAction _vibrateRightFeedback;
+
+ XrAction _aimPoseAction;
+ XrAction _gripPoseAction;
+ ActionPath _leftHandPath;
+ ActionPath _rightHandPath;
+
+ OxrSpace _leftControllerAimSpace;
+ OxrSpace _rightControllerAimSpace;
+ OxrSpace _leftControllerGripSpace;
+ OxrSpace _rightControllerGripSpace;
+
+
+ const int FRAMEBUFFER_SRGB = 0x8DB9;
+ const long SRGB8_ALPHA8 = (long)Graphics.OpenGL.PixelInternalFormat.Srgba;
+
+ long _frameIndex = 0;
+
+ private HeadsetState _headsetState;
+ private HandsState _handsState;
+
+ OxrSwapChainDataBase[] _swapChainData = new OxrSwapChainDataBase[2];
+
+ Posef _xfLocalFromHead;
+ Posef _xfLocalFloorFromHead;
+ Posef _xfStageFromHead;
+ Posef _xfStageFromLocal;
+ View[] _projections = new View[ovrMaxNumEyes];
+
+ Pose3[] _viewTransform = new Pose3[ovrMaxNumEyes];
+
+ FrameState _frameState;
+ ovrRenderer _Renderer = new ovrRenderer();
+
+ ovrCompositorLayer_Union[] _Layers = new ovrCompositorLayer_Union[ovrMaxLayerCount];
+
+ internal OxrSession Session { get { return _oxrSession; } }
+
+ public OxrInstance Instance { get { return _oxrInstance; } }
+
+ //OvrPosef[] _hmdToEyePose = new OvrPosef[2];
+ //OvrLayerEyeFov _layer;
+
+ //OvrStatusBits _statusFlags;
+
+ public override bool IsVRSupported
+ {
+ get { return true; }
+ }
+
+ public override bool IsARSupported
+ {
+ get
+ {
+ if (_extensions.Contains(nameof(OxrExtensions.XR_FB_passthrough)))
+ return true;
+
+ return false;
+ }
+ }
+
+ public override XRSessionMode SessionMode
+ {
+ get { return _sessionMode; }
+ }
+
+ public override XRDeviceState DeviceState
+ {
+ get { return _deviceState; }
+ }
+
+ public override bool IsTrackFloorLevelEnabled
+ {
+ get { return _isTrackFloorLevelEnabled; }
+ }
+
+
+ public ConcreteXRDevice(string applicationName, Game game)
+ : this(applicationName, game.Services)
+ {
+ }
+
+ public unsafe ConcreteXRDevice(string applicationName, IServiceProvider services)
+ {
+ this._applicationName = applicationName;
+ this._services = services;
+
+ _graphics = _services.GetService(typeof(IGraphicsDeviceService)) as IGraphicsDeviceService;
+
+ if (_graphics == null)
+ throw new ArgumentNullException("IGraphicsDeviceService not found in services.");
+
+ _graphics.DeviceResetting += GraphicsDeviceResetting;
+ _graphics.DeviceReset += GraphicsDeviceReset;
+ _graphics.DeviceDisposing += GraphicsDeviceDisposing;
+
+
+ Android.App.Activity activity = _services.GetService(typeof(Android.App.Activity)) as Android.App.Activity;
+ if (activity == null)
+ throw new InvalidOperationException("Activity not found in services.");
+
+ LoaderInitInfoAndroidKHR loaderInitInfo = new LoaderInitInfoAndroidKHR(
+ applicationVM: (void*)Java.Interop.JniRuntime.CurrentRuntime.InvocationPointer,
+ applicationContext: (void*)Android.Runtime.JNIEnv.ToJniHandle(activity)
+ );
+ Result xrResult;
+
+ xrResult = OxrAPI.InitializeLoader((LoaderInitInfoBaseHeaderKHR*)(&loaderInitInfo), out _xrAPI);
+ if (xrResult != Result.Success)
+ {
+ Console.WriteLine($"Failed to initialize OpenXR Loader: {xrResult}");
+ this._deviceState = XRDeviceState.NoPermissions;
+ }
+ else
+ Console.WriteLine("OpenXR Loader initialized successfully.");
+
+ // Log available layers.
+ {
+ IList layers = _xrAPI.GetLayers();
+ foreach (string layerName in layers)
+ {
+ Console.WriteLine("Found layer {0}", layerName);
+ }
+ }
+
+ // Log available extensions.
+ _extensions = _xrAPI.GetExtensions();
+ for (int i = 0; i < _extensions.Count; i++)
+ {
+ string extensionName = _extensions[i];
+ Console.WriteLine("Extension {0} = {1}", i, extensionName);
+ }
+
+ this._deviceState = XRDeviceState.Disabled;
+ }
+
+ public override int BeginSessionAsync(XRSessionMode sessionMode)
+ {
+ if (sessionMode == XRSessionMode.AR)
+ {
+ if (!this.IsARSupported)
+ throw new ArgumentException("mode");
+ }
+
+ _sessionMode = sessionMode;
+
+ GraphicsDevice graphicsDevice = _graphics.GraphicsDevice;
+ if (graphicsDevice == null)
+ {
+ throw new ArgumentNullException("graphics.GraphicsDevice");
+ //_graphics.DeviceCreated += GraphicsDeviceCreated;
+ }
+
+ int result = Initialize(graphicsDevice);
+ if (result == 0)
+ {
+ ((IFrameworkDispatcher)FrameworkDispatcher.Current).OnUpdate += ovrApp_HandleXrEvents;
+ }
+
+ return result;
+ }
+
+ bool stageBoundsDirty = true;
+ public unsafe override int BeginFrame()
+ {
+ ovrApp_HandleXrEvents();
+
+ bool notVisible;
+
+ if (_SessionActive == false)
+ {
+ //return -1;
+ }
+
+ Result xrResult;
+
+ if (_leftControllerAimSpace == null)
+ {
+ _leftControllerAimSpace = _oxrSession.CreateActionSpace(_aimPoseAction, _leftHandPath);
+ }
+ if (_rightControllerAimSpace == null)
+ {
+ _rightControllerAimSpace = _oxrSession.CreateActionSpace(_aimPoseAction, _rightHandPath);
+ }
+ if (_leftControllerGripSpace == null)
+ {
+ _leftControllerGripSpace = _oxrSession.CreateActionSpace(_gripPoseAction, _leftHandPath);
+ }
+ if (_rightControllerGripSpace == null)
+ {
+ _rightControllerGripSpace = _oxrSession.CreateActionSpace(_gripPoseAction, _rightHandPath);
+ }
+
+ // Create the scene if not yet created.
+ // The scene is created here to be able to show a loading icon.
+ if (!ovrScene_IsCreated(ref _Scene))
+ {
+ ovrScene_Create(ref _Scene);
+ }
+
+ if (stageBoundsDirty)
+ {
+ UpdateStageBounds();
+ stageBoundsDirty = false;
+ }
+
+ // NOTE: OpenXR does not use the concept of frame indices. Instead,
+ // XrWaitFrame returns the predicted display time.
+
+ //return 0;
+
+ xrResult = _oxrSession.WaitFrame(out FrameWaitInfo waitFrameInfo, out _frameState);
+ Debug.Assert(xrResult == Result.Success, "WaitFrame");
+
+ // Get the HMD pose, predicted for the middle of the time period during which
+ // the new eye images will be displayed. The number of frames predicted ahead
+ // depends on the pipeline depth of the engine and the synthesis rate.
+ // The better the prediction, the less black will be pulled in at the edges.
+ xrResult = _oxrSession.BeginFrame(out FrameBeginInfo beginFrameDesc);
+ Debug.Assert(xrResult == Result.Success, "WaitFrame");
+
+ long time = _frameState.PredictedDisplayTime;
+ //double time = _ovrSession.GetPredictedDisplayTime(_frameIndex);
+
+ xrResult = _oxrInstance.LocateSpace(
+ _HeadSpace, _LocalSpace, _frameState.PredictedDisplayTime, out _xfLocalFromHead, out SpaceLocationFlags localLocationFlags);
+ Debug.Assert(xrResult == Result.Success, "LocateSpace");
+
+ if (_LocalFloorSpace != null)
+ {
+ xrResult = _oxrInstance.LocateSpace(
+ _HeadSpace, _LocalFloorSpace, _frameState.PredictedDisplayTime, out _xfLocalFloorFromHead, out SpaceLocationFlags localFloorLocationFlags);
+ Debug.Assert(xrResult == Result.Success, "LocateSpace");
+ }
+
+ xrResult = _oxrInstance.LocateSpace(
+ _HeadSpace, _CurrentStageSpace, _frameState.PredictedDisplayTime, out _xfStageFromHead, out SpaceLocationFlags stageLocationFlags);
+ Debug.Assert(xrResult == Result.Success, "LocateSpace");
+
+ xrResult = _oxrInstance.LocateSpace(
+ _LocalSpace, _CurrentStageSpace, _frameState.PredictedDisplayTime, out _xfStageFromLocal, out SpaceLocationFlags stage2LocationFlags);
+ Debug.Assert(xrResult == Result.Success, "LocateSpace");
+
+ uint projectionCapacityInput = ovrMaxNumEyes;
+ uint projectionCountOutput = projectionCapacityInput;
+
+ xrResult = _oxrSession.LocateView(
+ _viewportConfig.ViewConfigurationType,
+ _frameState.PredictedDisplayTime,
+ _HeadSpace,
+ out ViewState viewState,
+ projectionCapacityInput,
+ out projectionCountOutput,
+ _projections);
+ Debug.Assert(xrResult == Result.Success, "xrLocateView");
+
+
+ Pose3 HeadPose = (_isTrackFloorLevelEnabled) ? _xfLocalFloorFromHead.ToPose3() : _xfLocalFromHead.ToPose3();
+ _headsetState.HeadPose = HeadPose;
+
+ for (int eye = 0; eye < ovrMaxNumEyes; eye++)
+ {
+ Pose3 xfHeadFromEye = _projections[eye].Pose.ToPose3();
+ Pose3 xfStageFromEye = xfHeadFromEye * HeadPose;
+
+ _viewTransform[eye] = xfStageFromEye;
+
+ switch (eye)
+ {
+ case 0:
+ _headsetState.LEyePose = _viewTransform[eye];
+ break;
+ case 1:
+ _headsetState.REyePose = _viewTransform[eye];
+ break;
+ }
+ }
+
+ // update input information
+ XrAction[] controller = new XrAction[] {_aimPoseAction, _gripPoseAction, _aimPoseAction, _gripPoseAction};
+ ActionPath[] subactionPath = new ActionPath[] { _leftHandPath, _leftHandPath, _rightHandPath, _rightHandPath };
+ OxrSpace[] controllerSpace = new OxrSpace[]
+ {
+ _leftControllerAimSpace,
+ _leftControllerGripSpace,
+ _rightControllerAimSpace,
+ _rightControllerGripSpace,
+ };
+
+ for (int i = 0; i < 4; i++)
+ {
+ // ActionPoseIsActive
+ xrResult = _oxrSession.GetActionStatePose(controller[i], subactionPath[i], out ActionStatePose state);
+ Debug.Assert(xrResult == Result.Success, "GetActionStatePose");
+ if (state.IsActive != 0)
+ {
+ //Console.WriteLine("Controller {0} is active." + i);
+
+ LocVel lv = GetSpaceLocVel(controllerSpace[i], _frameState.PredictedDisplayTime);
+ _Scene.TrackedController[i].Active =
+ (lv.LocationFlags & SpaceLocationFlags.PositionValidBit) != 0;
+ _Scene.TrackedController[i].Pose = lv.Pose;
+ _Scene.TrackedController[i].LinearVelocity = lv.LinearVelocity;
+
+ if (i == 0)
+ {
+ if ((lv.LocationFlags & SpaceLocationFlags.PositionValidBit) != 0)
+ {
+ _handsState.LHandPose = _Scene.TrackedController[i].Pose;
+ _handsState.LHandLinearVelocity = _Scene.TrackedController[i].LinearVelocity;
+ }
+ else
+ {
+ _handsState.LHandPose = Pose3.Identity;
+ _handsState.LHandLinearVelocity = Vector3.Zero;
+ }
+ }
+ if (i == 1)
+ {
+ if ((lv.LocationFlags & SpaceLocationFlags.PositionValidBit) != 0)
+ {
+ _handsState.LGripPose = _Scene.TrackedController[i].Pose;
+ }
+ else
+ {
+ _handsState.LGripPose = Pose3.Identity;
+ }
+ }
+ if (i == 2)
+ {
+ if ((lv.LocationFlags & SpaceLocationFlags.PositionValidBit) != 0)
+ {
+ _handsState.RHandPose = _Scene.TrackedController[i].Pose;
+ _handsState.RHandLinearVelocity = _Scene.TrackedController[i].LinearVelocity;
+ }
+ else
+ {
+ _handsState.RHandPose = Pose3.Identity;
+ _handsState.RHandLinearVelocity = Vector3.Zero;
+ }
+ }
+ if (i == 3)
+ {
+ if ((lv.LocationFlags & SpaceLocationFlags.PositionValidBit) != 0)
+ {
+ _handsState.RGripPose = _Scene.TrackedController[i].Pose;
+ }
+ else
+ {
+ _handsState.RGripPose = Pose3.Identity;
+ }
+ }
+
+
+ float dt = 0.01f; // use 0.2f for testing velocity vectors
+ {
+ _Scene.TrackedController[i].Pose.Translation += lv.LinearVelocity * dt;
+ }
+ }
+ else
+ {
+ //Console.WriteLine("Controller {0} is inactive."+i);
+
+ _Scene.TrackedController[i].Clear();
+ }
+ }
+
+ // OpenXR input
+ {
+ // sync action data
+ xrResult = _oxrSession.SyncActions(_runningActionSet, 0);
+ if (xrResult != Result.SessionNotFocused)
+ Debug.Assert(xrResult == Result.Success, "SyncActions");
+
+ // query input action states
+ ActionStateGetInfo getInfo = new ActionStateGetInfo(StructureType.ActionStateGetInfo);
+ getInfo.SubactionPath = 0;
+
+ ActionStateBoolean thumbstickLClickState = _oxrSession.GetActionStateBoolean(_actionButtonLeftThumbstick);
+ ActionStateBoolean thumbstickRClickState = _oxrSession.GetActionStateBoolean(_actionButtonRightThumbstick);
+
+ // Update app logic based on input
+ if ((thumbstickLClickState.ChangedSinceLastSync != 0
+ && thumbstickLClickState.CurrentState != 0)
+ || (thumbstickRClickState.ChangedSinceLastSync != 0
+ && thumbstickRClickState.CurrentState != 0)
+ )
+ {
+ float currentRefreshRate = 0.0f;
+ xrResult = _pfnxrGetDisplayRefreshRateFB(_oxrSession.Session, ¤tRefreshRate);
+ Debug.Assert(xrResult == Result.Success, "_pfnxrGetDisplayRefreshRateFB");
+ Console.WriteLine("Current Display Refresh Rate: {0}", currentRefreshRate);
+
+ int requestedRateIndex = (int) (_RequestedDisplayRefreshRateIndex++ %
+ _NumSupportedDisplayRefreshRates);
+
+ float requestRefreshRate =
+ _SupportedDisplayRefreshRates[requestedRateIndex];
+ Console.WriteLine("Requesting Display Refresh Rate: {0}", requestRefreshRate);
+ xrResult = _pfnxrRequestDisplayRefreshRateFB(_oxrSession.Session, requestRefreshRate);
+ Debug.Assert(xrResult == Result.Success, "_pfnxrRequestDisplayRefreshRateFB");
+ }
+
+ // The KHR simple profile doesn't have these actions, so the getters will fail
+ // and flood the log with errors.
+ if (_useSimpleProfile == false)
+ {
+ ActionStateFloat moveXStateL = _oxrSession.GetActionStateFloat(_moveOnXActionL);
+ ActionStateFloat moveXStateR = _oxrSession.GetActionStateFloat(_moveOnXActionR);
+
+ ActionStateFloat moveYStateL = _oxrSession.GetActionStateFloat(_moveOnYActionL);
+ ActionStateFloat moveYStateR = _oxrSession.GetActionStateFloat(_moveOnYActionR);
+
+ if (moveXStateL.ChangedSinceLastSync != 0)
+ {
+ //appQuadPositionX = moveYStateL.CurrentState;
+ Console.WriteLine("L Grip " + moveXStateL.CurrentState);
+ }
+ if (moveYStateR.ChangedSinceLastSync != 0)
+ {
+ //appQuadPositionX = moveXStateR.CurrentState;
+ Console.WriteLine("R Grip " + moveXStateR.CurrentState);
+ }
+
+ if (moveYStateL.ChangedSinceLastSync != 0)
+ {
+ //appQuadPositionY = moveYStateL.CurrentState;
+ Console.WriteLine("L Trigger " + moveYStateL.CurrentState);
+ }
+ if (moveYStateR.ChangedSinceLastSync != 0)
+ {
+ //appQuadPositionY = moveYStateR.CurrentState;
+ Console.WriteLine("R Trigger " + moveYStateR.CurrentState);
+ }
+
+ ActionStateVector2f moveJoystickStateL =
+ _oxrSession.GetActionStateVector2(_moveOnJoystickActionL);
+ if (moveJoystickStateL.ChangedSinceLastSync!=0)
+ {
+ //appCylPositionX = moveJoystickStateL.currentState.x;
+ //appCylPositionY = moveJoystickStateL.currentState.y;
+ //Console.WriteLine("L ThumpStick " + moveJoystickStateL.CurrentState.X + " , " + moveJoystickStateL.CurrentState.Y);
+ }
+
+ ActionStateVector2f moveJoystickStateR =
+ _oxrSession.GetActionStateVector2(_moveOnJoystickActionR);
+ if (moveJoystickStateR.ChangedSinceLastSync != 0)
+ {
+ //appCylPositionX = moveJoystickStateR.currentState.x;
+ //appCylPositionY = moveJoystickStateR.currentState.y;
+ //Console.WriteLine("R ThumpStick "+ moveJoystickStateR.CurrentState.X +" , "+ moveJoystickStateR.CurrentState.Y);
+ }
+ }
+ }
+
+ return 0;
+ }
+
+ public override HeadsetState GetHeadsetState()
+ {
+ return _headsetState;
+ }
+
+ public override IEnumerable GetEyes()
+ {
+ yield return XREye.Left;
+ yield return XREye.Right;
+ }
+
+ public override RenderTarget2D GetEyeRenderTarget(XREye eye)
+ {
+ int eyeIndex = (int)eye - 1;
+ RenderTarget2D rt = _Renderer.FrameBuffer[eyeIndex].RenderTarget;
+ return rt;
+ }
+
+ public override Matrix CreateProjection(XREye eye, float znear, float zfar)
+ {
+ int eyeIndex = (int)eye - 1;
+
+ GraphicsBackend graphicsBackend = _graphics.GraphicsDevice.Adapter.Backend;
+
+ Fovf fov = _projections[eyeIndex].Fov;
+ float tanLeft = (float)Math.Tan(fov.AngleLeft);
+ float tanRight = (float)Math.Tan(fov.AngleRight);
+ float tanDown = (float)Math.Tan(fov.AngleDown);
+ float tanUp = (float)Math.Tan(fov.AngleUp);
+
+ Matrix proj = XrMatrixf_CreateProjection(graphicsBackend, tanLeft, tanRight, tanUp, tanDown, znear, zfar);
+ return proj;
+ }
+
+ public override void CommitRenderTarget(XREye eye, RenderTarget2D rt)
+ {
+ int eyeIndex = (int)eye - 1;
+
+ GraphicsDevice device = this._graphics.GraphicsDevice;
+ device.SetRenderTarget(null);
+
+ ovrRenderer_RenderFrame_Acquire(ref _Renderer.FrameBuffer[eyeIndex]);
+
+ uint textureIndex = _Renderer.FrameBuffer[eyeIndex].TextureSwapChainIndex;
+
+ uint txSC = _Renderer.FrameBuffer[eyeIndex].ColorSwapChainImage[textureIndex].Image;
+
+ //ConcreteGraphicsAdapter adapter = ((IPlatformGraphicsAdapter)_graphics.GraphicsDevice.Adapter).Strategy.ToConcrete();
+ //var GL = adapter.Ogl;
+
+ // bind DrawFramebuffer
+ Android.Opengl.GLES30.GlBindFramebuffer(Android.Opengl.GLES30.GlDrawFramebuffer, _Renderer.FrameBuffer[eyeIndex].FrameBuffers[textureIndex]);
+ //GL.BindFramebuffer(OpenGL.FramebufferTarget.DrawFramebuffer, _Renderer.FrameBuffer[eyeIndex].FrameBuffers[textureIndex]);
+ //GL.CheckGLError();
+
+ // fix oversaturation
+ Android.Opengl.GLES30.GlDisable(FRAMEBUFFER_SRGB);
+
+ // copy rendertarget to texture
+ {
+ int[] sourceFramebuffer = new int[1];
+ Android.Opengl.GLES30.GlGenFramebuffers(sourceFramebuffer.Length, sourceFramebuffer, 0);
+ //int sourceFramebuffer = GL.GenFramebuffer();
+ //GL.CheckGLError();
+
+ Android.Opengl.GLES30.GlBindFramebuffer(Android.Opengl.GLES30.GlReadFramebuffer, sourceFramebuffer[0]);
+ //GL.BindFramebuffer(OpenGL.FramebufferTarget.ReadFramebuffer, sourceFramebuffer);
+ //GL.CheckGLError();
+
+ Android.Opengl.GLES30.GlFramebufferTexture2D(
+ Android.Opengl.GLES30.GlReadFramebuffer,
+ Android.Opengl.GLES30.GlColorAttachment0,
+ Android.Opengl.GLES30.GlTexture2d, (int)rt.GetSharedHandle(), 0);
+ //GL.FramebufferTexture2D(
+ // OpenGL.FramebufferTarget.ReadFramebuffer,
+ // OpenGL.FramebufferAttachment.ColorAttachment0,
+ // OpenGL.TextureTarget.Texture2D, (int)rt.GetSharedHandle(), 0
+ //);
+ //GL.CheckGLError();
+
+ // copy and y-flip
+ Android.Opengl.GLES30.GlBlitFramebuffer(0, 0, rt.Width, rt.Height,
+ 0, _Renderer.FrameBuffer[eyeIndex].Height - 1, _Renderer.FrameBuffer[eyeIndex].Width, 0,
+ Android.Opengl.GLES30.GlColorBufferBit, Android.Opengl.GLES30.GlNearest);
+ //GL.BlitFramebuffer(0, 0, rt.Width, rt.Height,
+ // 0, _Renderer.FrameBuffer[eyeIndex].Height - 1, _Renderer.FrameBuffer[eyeIndex].Width, 0,
+ // OpenGL.ClearBufferMask.ColorBufferBit, OpenGL.BlitFramebufferFilter.Nearest);
+ //GL.CheckGLError();
+
+ Android.Opengl.GLES30.GlBindFramebuffer(Android.Opengl.GLES30.GlReadFramebuffer, sourceFramebuffer[0]);
+ //GL.BindFramebuffer(OpenGL.FramebufferTarget.ReadFramebuffer, 0);
+ //GL.CheckGLError();
+
+ Android.Opengl.GLES30.GlDeleteFramebuffers(sourceFramebuffer.Length, sourceFramebuffer, 0);
+ //GL.DeleteFramebuffer(sourceFramebuffer);
+ //GL.CheckGLError();
+ }
+
+ ovrRenderer_RenderFrame_Release(ref _Renderer, eyeIndex);
+
+ // unbind DrawFramebuffer
+ Android.Opengl.GLES30.GlBindFramebuffer(Android.Opengl.GLES30.GlDrawFramebuffer, 0);
+ //GL.BindFramebuffer(OpenGL.FramebufferTarget.DrawFramebuffer, 0);
+ //GL.CheckGLError();
+ }
+
+ public unsafe override int EndFrame()
+ {
+ Result xrResult;
+
+ OxrSpace currentSpace = (_isTrackFloorLevelEnabled) ? _LocalFloorSpace : _LocalSpace;
+
+ // Set-up the compositor layers for this frame.
+ // NOTE: Multiple independent layers are allowed, but they need to be added
+ // in a depth consistent order.
+
+
+ uint LayerCount = 0;
+ _Layers = new ovrCompositorLayer_Union[ovrMaxLayerCount];
+
+ if (this.SessionMode == XRSessionMode.AR)
+ {
+ CompositionLayerPassthroughFB passthroughLayer = default;
+ passthroughLayer.Type = StructureType.CompositionLayerPassthroughFB;
+ passthroughLayer.LayerHandle = _passthroughLayerFB.PassthroughLayerFB;
+ passthroughLayer.Space = default;
+ passthroughLayer.Flags = CompositionLayerFlags.BlendTextureSourceAlphaBit;
+
+ _Layers[LayerCount++].PassthroughFB = passthroughLayer;
+ }
+
+ CompositionLayerProjectionView[] projection_layer_elements = new CompositionLayerProjectionView[]
+ {
+ new CompositionLayerProjectionView(StructureType.CompositionLayerProjectionView),
+ new CompositionLayerProjectionView(StructureType.CompositionLayerProjectionView),
+ };
+
+ {
+ fixed (CompositionLayerProjectionView* pprojection_layer_elements = projection_layer_elements)
+ {
+ CompositionLayerProjection projection_layer = new CompositionLayerProjection(StructureType.CompositionLayerProjection);
+ projection_layer.LayerFlags = CompositionLayerFlags.BlendTextureSourceAlphaBit;
+ projection_layer.LayerFlags |= CompositionLayerFlags.CorrectChromaticAberrationBit;
+ projection_layer.Space = currentSpace.Space;
+ projection_layer.ViewCount = ovrMaxNumEyes;
+ projection_layer.Views = pprojection_layer_elements;
+
+ for (int eye = 0; eye < ovrMaxNumEyes; eye++)
+ {
+ projection_layer_elements[eye] = new CompositionLayerProjectionView(StructureType.CompositionLayerProjectionView);
+
+ projection_layer_elements[eye].Pose = _viewTransform[eye].ToPosef();
+ projection_layer_elements[eye].Fov = _projections[eye].Fov;
+
+ projection_layer_elements[eye].SubImage = new SwapchainSubImage();
+ projection_layer_elements[eye].SubImage.Swapchain =
+ _Renderer.FrameBuffer[eye].ColorSwapChain.Swapchain;
+ projection_layer_elements[eye].SubImage.ImageRect.Offset.X = 0;
+ projection_layer_elements[eye].SubImage.ImageRect.Offset.Y = 0;
+ projection_layer_elements[eye].SubImage.ImageRect.Extent.Width =
+ (int)_Renderer.FrameBuffer[eye].ColorSwapChain.Width;
+ projection_layer_elements[eye].SubImage.ImageRect.Extent.Height =
+ (int)_Renderer.FrameBuffer[eye].ColorSwapChain.Height;
+ projection_layer_elements[eye].SubImage.ImageArrayIndex = 0;
+ }
+
+ _Layers[LayerCount++].Projection = projection_layer;
+ }
+ }
+
+ // Build the cylinder layer
+ if (false)
+ {
+ float appCylPositionX = 0.0f;
+ float appCylPositionY = 0.0f;
+
+ CompositionLayerCylinderKHR cylinder_layer = new CompositionLayerCylinderKHR(StructureType.CompositionLayerCylinderKhr);
+ cylinder_layer.LayerFlags = CompositionLayerFlags.BlendTextureSourceAlphaBit;
+ cylinder_layer.Space = _LocalSpace.Space;
+ cylinder_layer.EyeVisibility = EyeVisibility.Both;
+ cylinder_layer.SubImage = new SwapchainSubImage();
+ cylinder_layer.SubImage.Swapchain = _Scene.CylinderSwapChain.Swapchain;
+ cylinder_layer.SubImage.ImageRect.Offset.X = 0;
+ cylinder_layer.SubImage.ImageRect.Offset.Y = 0;
+ cylinder_layer.SubImage.ImageRect.Extent.Width = (int)_Scene.CylinderSwapChain.Width;
+ cylinder_layer.SubImage.ImageRect.Extent.Height = (int) _Scene.CylinderSwapChain.Height;
+ cylinder_layer.SubImage.ImageArrayIndex = 0;
+ Vector3f axis = new Vector3f(0.0f, 1.0f, 0.0f);
+ Vector3f pos = new Vector3f(appCylPositionX, appCylPositionY, 0.0f);
+ cylinder_layer.Pose.Orientation = XrQuaternionf_CreateFromAxisAngle(axis, -45.0f * (float)(Math.PI / 180.0f));
+ cylinder_layer.Pose.Position = pos;
+ cylinder_layer.Radius = 2.0f;
+
+ cylinder_layer.CentralAngle = (float)(Math.PI / 4.0);
+ cylinder_layer.AspectRatio = 2.0f;
+
+ _Layers[LayerCount++].Cylinder = cylinder_layer;
+ }
+
+ // Compose the layers for this frame.
+ CompositionLayerBaseHeader*[] layers = new CompositionLayerBaseHeader*[ovrMaxLayerCount];
+ fixed (ovrCompositorLayer_Union* p_Layers = _Layers)
+ for (int i = 0; i < LayerCount; i++)
+ {
+ layers[i] = &p_Layers[i].BaseHeader;
+ }
+
+ // end frame
+ fixed (CompositionLayerBaseHeader** players = layers)
+ {
+ FrameEndInfo endFrameInfo = new FrameEndInfo(StructureType.FrameEndInfo);
+ endFrameInfo.DisplayTime = _frameState.PredictedDisplayTime;
+ endFrameInfo.EnvironmentBlendMode = EnvironmentBlendMode.Opaque;
+ endFrameInfo.LayerCount = LayerCount;
+ endFrameInfo.Layers = (CompositionLayerBaseHeader**)players;
+
+ xrResult = _oxrSession.EndFrame(ref endFrameInfo);
+ Debug.Assert(xrResult == Result.Success, "EndFrame");
+ }
+
+ return 0;
+ }
+
+ public override HandsState GetHandsState()
+ {
+ return _handsState;
+ }
+
+ public override void EndSessionAsync()
+ {
+ throw new PlatformNotSupportedException();
+ }
+
+ public override void TrackFloorLevelAsync(bool enable)
+ {
+ if (enable == true)
+ {
+ if (!_isLocalFloorSupported)
+ throw new NotImplementedException();
+ }
+
+ _isTrackFloorLevelEnabled = enable;
+ }
+
+ private void GraphicsDeviceCreated(object sender, EventArgs e)
+ {
+ }
+ private void GraphicsDeviceResetting(object sender, EventArgs e)
+ {
+ }
+ private void GraphicsDeviceReset(object sender, EventArgs e)
+ {
+ }
+ private void GraphicsDeviceDisposing(object sender, EventArgs e)
+ {
+ this.Dispose();
+ }
+
+ private unsafe int Initialize(GraphicsDevice graphicsDevice)
+ {
+ // check supported feature level
+ switch (graphicsDevice.Adapter.Backend)
+ {
+ case GraphicsBackend.DirectX11:
+ case GraphicsBackend.DirectX12:
+ //if (graphicsDevice.GraphicsProfile < GraphicsProfile.FL10_0)
+ // throw new InvalidOperationException("GraphicsProfile must be FL10_0 or higher.");
+ break;
+ case GraphicsBackend.OpenGL:
+ case GraphicsBackend.GLES:
+ if (graphicsDevice.GraphicsProfile < GraphicsProfile.HiDef)
+ throw new InvalidOperationException("GraphicsProfile must be HiDef or higher.");
+ break;
+ }
+
+ Result xrResult;
+
+ // Check that the extensions required are present.
+ List requiredExtensionNames = new List();
+ requiredExtensionNames.Add(nameof(OxrExtensions.XR_EXT_local_floor));
+ requiredExtensionNames.Add(nameof(OxrExtensions.XR_KHR_opengl_es_enable));
+ requiredExtensionNames.Add(nameof(OxrExtensions.XR_EXT_performance_settings));
+ requiredExtensionNames.Add(nameof(OxrExtensions.XR_KHR_android_thread_settings));
+ requiredExtensionNames.Add(nameof(OxrExtensions.XR_KHR_composition_layer_cube));
+ requiredExtensionNames.Add(nameof(OxrExtensions.XR_KHR_composition_layer_cylinder));
+ requiredExtensionNames.Add(nameof(OxrExtensions.XR_KHR_composition_layer_equirect2));
+ requiredExtensionNames.Add(nameof(OxrExtensions.XR_FB_display_refresh_rate));
+ requiredExtensionNames.Add(nameof(OxrExtensions.XR_FB_color_space));
+ requiredExtensionNames.Add(nameof(OxrExtensions.XR_FB_swapchain_update_state));
+ requiredExtensionNames.Add(nameof(OxrExtensions.XR_FB_swapchain_update_state_opengl_es));
+ requiredExtensionNames.Add(nameof(OxrExtensions.XR_FB_foveation));
+ requiredExtensionNames.Add(nameof(OxrExtensions.XR_FB_foveation_configuration));
+
+ if (this.SessionMode == XRSessionMode.AR)
+ requiredExtensionNames.Add(nameof(OxrExtensions.XR_FB_passthrough));
+
+ // Check the list of required extensions against what is supported by the runtime.
+ uint numRequiredExtensions = (uint)requiredExtensionNames.Count;
+ for (int i = 0; i < numRequiredExtensions; i++)
+ {
+ if (_extensions.Contains(requiredExtensionNames[i]))
+ {
+ Console.WriteLine("Found required extension {0}", requiredExtensionNames[i]);
+ }
+ else
+ {
+ Console.WriteLine("Failed to find required extension {0}", requiredExtensionNames[i]);
+ //throw new Exception();
+ }
+ }
+
+
+ // Create the OpenXR instance.
+ xrResult = _xrAPI.CreateInstance(_applicationName, _engineName,
+ requiredExtensionNames, out _oxrInstance);
+ if (xrResult != Result.Success)
+ {
+ Console.WriteLine("Failed to create XR instance: {0}.", xrResult);
+ return (int)xrResult;
+ }
+ Console.WriteLine("OpenXR Instance initialized successfully. {0}.", _oxrInstance.Instance.Handle);
+
+ Console.WriteLine("Runtime: {0}.", _oxrInstance.RuntimeName);
+ Console.WriteLine("Version: {0}.", _oxrInstance.RuntimeVersion);
+
+ if (xrResult == Result.ErrorFormFactorUnavailable)
+ {
+ Console.WriteLine("Failed to get system; the specified form factor is not available. Is your headset connected?");
+ return (int)xrResult;
+ }
+ if (xrResult != Result.Success)
+ {
+ Console.WriteLine("xrGetSystem failed, error {0}", xrResult);
+ return (int)xrResult;
+ }
+ Console.WriteLine("systemId. {0}.", _oxrInstance.SystemId);
+
+ SystemColorSpacePropertiesFB colorSpacePropertiesFB = new SystemColorSpacePropertiesFB(StructureType.SystemColorSpacePropertiesFB);
+
+ xrResult = _oxrInstance.GetSystemProperties(&colorSpacePropertiesFB, out SystemProperties systemProperties);
+ Debug.Assert(xrResult == Result.Success, "GetSystemProperties");
+
+ string systemName = Encoding.ASCII.GetString(systemProperties.SystemName, (int)SilkXR.MaxSystemNameSize);
+ systemName = systemName.TrimEnd('\0');
+ Console.WriteLine("System Properties: Name={0} VendorId={1}",
+ systemName,
+ systemProperties.VendorId);
+ Console.WriteLine("System Graphics Properties: MaxWidth={0} MaxHeight={1} MaxLayers={2}",
+ systemProperties.GraphicsProperties.MaxSwapchainImageWidth,
+ systemProperties.GraphicsProperties.MaxSwapchainImageHeight,
+ systemProperties.GraphicsProperties.MaxLayerCount);
+ Console.WriteLine("System Tracking Properties: OrientationTracking={0} PositionTracking={1}",
+ systemProperties.TrackingProperties.OrientationTracking != 0 ? "True" : "False",
+ systemProperties.TrackingProperties.PositionTracking != 0 ? "True" : "False");
+
+ Console.WriteLine("System Color Space Properties: colorspace={0}", colorSpacePropertiesFB.ColorSpace);
+
+ Debug.Assert(ovrMaxLayerCount <= systemProperties.GraphicsProperties.MaxLayerCount);
+
+ // Get the graphics requirements.
+
+ xrResult = _oxrInstance.GetInstanceProcAddr("xrGetOpenGLESGraphicsRequirementsKHR",
+ out xrGetOpenGLESGraphicsRequirementsKHRDelegate Fun_xrGetOpenGLESGraphicsRequirementsKHR);
+ Debug.Assert(xrResult == Result.Success, "GetInstanceProcAddr");
+
+ GraphicsRequirementsOpenGLESKHR graphicsRequirements = new GraphicsRequirementsOpenGLESKHR(StructureType.GraphicsRequirementsOpenglESKhr);
+
+ xrResult = Fun_xrGetOpenGLESGraphicsRequirementsKHR(_oxrInstance.Instance, _oxrInstance.SystemId, &graphicsRequirements);
+ Debug.Assert(xrResult == Result.Success, "Fun_xrGetOpenGLESGraphicsRequirementsKHR");
+ OxrVersion minApiVersionSupported = new OxrVersion(graphicsRequirements.MinApiVersionSupported);
+ OxrVersion maxApiVersionSupported = new OxrVersion(graphicsRequirements.MaxApiVersionSupported);
+ Console.WriteLine("GLES MinApiVersionSupported {0}, MaxApiVersionSupported {1} ", minApiVersionSupported, maxApiVersionSupported);
+
+ /*
+ var adapter = ((IPlatformGraphicsAdapter)_graphics.GraphicsDevice.Adapter).Strategy.ToConcrete();
+ var GL = adapter.Ogl;
+
+ // Check the graphics requirements.
+ int eglMajor = 0;
+ int eglMinor = 0;
+ GL.GetInteger(OpenGL.GetPName.MajorVersion, out eglMajor);
+ GL.CheckGLError();
+ GL.GetInteger(OpenGL.GetPName.MinorVersion, out eglMinor);
+ GL.CheckGLError();
+ OxrVersion eglVersion = new OxrVersion((short)eglMajor, (short)eglMinor, 0);
+ if (eglVersion.Packed < minApiVersionSupported.Packed
+ || eglVersion.Packed > maxApiVersionSupported.Packed)
+ {
+ Console.WriteLine("GLES version {0} not supported", eglVersion);
+ return (int)xrResult;
+ }
+ */
+
+ _CpuLevel = CPU_LEVEL;
+ _GpuLevel = GPU_LEVEL;
+
+ Android.Opengl.EGLDisplay display = Android.Opengl.EGL14.EglGetCurrentDisplay();
+ Android.Opengl.EGLContext context = Android.Opengl.EGL14.EglGetCurrentContext();
+ int[] currentConfig = new int[1];
+ Android.Opengl.EGL14.EglQueryContext(display, context, Android.Opengl.EGL14.EglConfigId, currentConfig, 0);
+ int eglConfig = currentConfig[0];
+
+ // Create the OpenXR Session.
+ GraphicsBindingOpenGLESAndroidKHR graphicsBindingAndroidGLES = new GraphicsBindingOpenGLESAndroidKHR(StructureType.GraphicsBindingOpenglESAndroidKhr);
+ graphicsBindingAndroidGLES.Display = display.Handle;
+ graphicsBindingAndroidGLES.Context = context.Handle;
+ graphicsBindingAndroidGLES.Config = eglConfig;
+
+ //AndroidGameWindow gameWindow = AndroidGameWindow.FromHandle(_graphics.GraphicsDevice.PresentationParameters.DeviceWindowHandle);
+ //graphicsBindingAndroidGLES.Display = _graphics.GraphicsDevice.Adapter.MonitorHandle;
+ //graphicsBindingAndroidGLES.Context = gameWindow.EglContext.Handle;
+ //graphicsBindingAndroidGLES.Config = gameWindow.EglConfig.Handle;
+
+
+ xrResult = _oxrInstance.CreateSession(&graphicsBindingAndroidGLES, out _oxrSession);
+ if (xrResult != Result.Success)
+ {
+ Console.WriteLine("Failed to create XR session: {0}.", xrResult);
+ return (int)xrResult;
+ }
+ Console.WriteLine("XR session created successfully.");
+
+
+ // App only supports the primary stereo view config.
+ const ViewConfigurationType supportedViewConfigType = ViewConfigurationType.PrimaryStereo;
+
+ // Enumerate the viewport configurations.
+ xrResult = _oxrInstance.EnumerateViewConfiguration(out ViewConfigurationType[] viewportConfigurationTypes);
+ Console.WriteLine("Available Viewport Configuration Types: {0}", viewportConfigurationTypes.Length);
+
+ ViewConfigurationView[] ViewConfigurationView = new ViewConfigurationView[ovrMaxNumEyes];
+
+ for (uint i = 0; i < viewportConfigurationTypes.Length; i++)
+ {
+ ViewConfigurationType viewportConfigType = viewportConfigurationTypes[i];
+
+ Console.WriteLine(
+ "Viewport configuration type {0} : {1}",
+ viewportConfigType,
+ viewportConfigType == supportedViewConfigType ? "Selected" : "");
+
+ xrResult = _oxrInstance.GetViewConfigurationProperties(viewportConfigType, out _viewportConfig);
+ Debug.Assert(xrResult == Result.Success, "GetViewConfigurationProperties");
+
+ Console.WriteLine(
+ "FovMutable={0} ConfigurationType {0}",
+ _viewportConfig.FovMutable != 0 ? "true" : "false",
+ _viewportConfig.ViewConfigurationType);
+
+ xrResult = _oxrInstance.EnumerateViewConfigurationView(viewportConfigType, out ViewConfigurationView[] elements);
+
+ if (elements.Length > 0)
+ {
+ // Log the view config info for each view type for debugging purposes.
+ for (uint e = 0; e < elements.Length; e++)
+ {
+ ViewConfigurationView element = elements[e];
+
+ Console.WriteLine(
+ "Viewport [{0}]: Recommended Width={1} Height={2} SampleCount={3}",
+ e,
+ element.RecommendedImageRectWidth,
+ element.RecommendedImageRectHeight,
+ element.RecommendedSwapchainSampleCount);
+
+ Console.WriteLine(
+ "Viewport [{0}]: Max Width={1} Height={2} SampleCount={3}",
+ e,
+ element.MaxImageRectWidth,
+ element.MaxImageRectHeight,
+ element.MaxSwapchainSampleCount);
+ }
+
+ // Cache the view config properties for the selected config type.
+ if (viewportConfigType == supportedViewConfigType)
+ {
+ Debug.Assert(elements.Length == ovrMaxNumEyes);
+ for (uint e = 0; e < elements.Length; e++)
+ {
+ ViewConfigurationView[e] = elements[e];
+ }
+ }
+ }
+ else
+ {
+ Console.WriteLine("Empty viewport configuration type: {0}", elements.Length);
+ }
+ }
+
+ // Get the viewport configuration info for the chosen viewport configuration type.
+ xrResult = _oxrInstance.GetViewConfigurationProperties(supportedViewConfigType, out ViewConfigurationProperties ViewportConfig);
+ Debug.Assert(xrResult == Result.Success, "GetViewConfigurationProperties");
+
+ // Enumerate the supported color space options for the system.
+ {
+ xrResult = _oxrInstance.GetInstanceProcAddr("xrEnumerateColorSpacesFB",
+ out xrEnumerateColorSpacesFB Fun_xrEnumerateColorSpacesFB);
+ Debug.Assert(xrResult == Result.Success, "xrGetInstanceProcAddr");
+
+ uint colorSpaceCountOutput = 0;
+ xrResult = Fun_xrEnumerateColorSpacesFB(_oxrSession.Session, 0, &colorSpaceCountOutput, null);
+ Debug.Assert(xrResult == Result.Success, "Fun_xrEnumerateColorSpacesFB");
+
+ ColorSpaceFB[] colorSpaces = new ColorSpaceFB[colorSpaceCountOutput];
+
+ fixed (ColorSpaceFB* pcolorSpaces = colorSpaces)
+ xrResult = Fun_xrEnumerateColorSpacesFB(_oxrSession.Session, colorSpaceCountOutput, &colorSpaceCountOutput, pcolorSpaces);
+ Debug.Assert(xrResult == Result.Success, "Fun_xrEnumerateColorSpacesFB");
+
+ Console.WriteLine("Supported ColorSpaces:");
+ for (uint i = 0; i < colorSpaceCountOutput; i++)
+ {
+ Console.WriteLine("{0}:{1}", i, colorSpaces[i]);
+ }
+
+ ColorSpaceFB requestColorSpace = ColorSpaceFB.AdobeRgbFB;
+
+ xrResult = _oxrInstance.GetInstanceProcAddr("xrSetColorSpaceFB",
+ out xrSetColorSpaceFB Fun_xrSetColorSpaceFB);
+ Debug.Assert(xrResult == Result.Success, "xrGetInstanceProcAddr");
+
+ xrResult = Fun_xrSetColorSpaceFB(_oxrSession.Session, requestColorSpace);
+ Debug.Assert(xrResult == Result.Success, "Fun_xrEnumerateColorSpacesFB");
+ }
+
+ // Get the supported display refresh rates for the system.
+ {
+ _oxrInstance.GetInstanceProcAddr("xrEnumerateDisplayRefreshRatesFB",
+ out xrEnumerateDisplayRefreshRatesFB pfnxrEnumerateDisplayRefreshRatesFB);
+
+ _NumSupportedDisplayRefreshRates = 0;
+ xrResult = pfnxrEnumerateDisplayRefreshRatesFB(_oxrSession.Session, 0, ref _NumSupportedDisplayRefreshRates, null);
+ Debug.Assert(xrResult == Result.Success, "pfnxrEnumerateDisplayRefreshRatesFB");
+
+ _SupportedDisplayRefreshRates = new float[_NumSupportedDisplayRefreshRates];
+
+ fixed (float* pSupportedDisplayRefreshRates = _SupportedDisplayRefreshRates)
+ xrResult = pfnxrEnumerateDisplayRefreshRatesFB(_oxrSession.Session, _NumSupportedDisplayRefreshRates, ref _NumSupportedDisplayRefreshRates, pSupportedDisplayRefreshRates);
+ Debug.Assert(xrResult == Result.Success, "pfnxrEnumerateDisplayRefreshRatesFB");
+
+ Console.WriteLine("Supported Refresh Rates:");
+ for (uint i = 0; i < _NumSupportedDisplayRefreshRates; i++)
+ {
+ Console.WriteLine("{0}:{1}", i, _SupportedDisplayRefreshRates[i]);
+ }
+
+ _oxrInstance.GetInstanceProcAddr("xrGetDisplayRefreshRateFB",
+ out _pfnxrGetDisplayRefreshRateFB);
+
+ float currentDisplayRefreshRate = 0.0f;
+ xrResult = _pfnxrGetDisplayRefreshRateFB(_oxrSession.Session, ¤tDisplayRefreshRate);
+ Debug.Assert(xrResult == Result.Success, "pfnxrGetDisplayRefreshRateFB");
+ Console.WriteLine("Current System Display Refresh Rate: {0}", currentDisplayRefreshRate);
+
+ _oxrInstance.GetInstanceProcAddr("xrRequestDisplayRefreshRateFB",
+ out _pfnxrRequestDisplayRefreshRateFB);
+
+ // Test requesting the system default.
+ xrResult = _pfnxrRequestDisplayRefreshRateFB(_oxrSession.Session, 0.0f);
+ Debug.Assert(xrResult == Result.Success, "pfnRequestDisplayRefreshRate");
+ Console.WriteLine("Requesting system default display refresh rate");
+ }
+
+ xrResult = _oxrSession.EnumerateReferenceSpaces(out ReferenceSpaceType[] referenceSpaces);
+ Debug.Assert(xrResult == Result.Success, "EnumerateReferenceSpaces");
+
+ for (uint i = 0; i < referenceSpaces.Length; i++)
+ {
+ if (referenceSpaces[i] == ReferenceSpaceType.LocalFloor)
+ _isLocalFloorSupported = true;
+ if (referenceSpaces[i] == ReferenceSpaceType.Stage)
+ _isStageSupported = true;
+ }
+
+ // Create a space to the first path
+ xrResult = _oxrSession.CreateReferenceSpace(ReferenceSpaceType.View, XrPosef_CreateIdentity(), out _HeadSpace);
+ Debug.Assert(xrResult == Result.Success, "CreateReferenceSpace");
+
+ xrResult = _oxrSession.CreateReferenceSpace(ReferenceSpaceType.Local, XrPosef_CreateIdentity(), out _LocalSpace);
+ Debug.Assert(xrResult == Result.Success, "CreateReferenceSpace");
+
+ if (_isLocalFloorSupported)
+ {
+ xrResult = _oxrSession.CreateReferenceSpace(ReferenceSpaceType.LocalFloor, XrPosef_CreateIdentity(), out _LocalFloorSpace);
+ Debug.Assert(xrResult == Result.Success, "CreateReferenceSpace");
+ Console.WriteLine("Created local floor space");
+ }
+
+ // Create a default stage space to use if SPACE_TYPE_STAGE is not
+ // supported, or calls to xrGetReferenceSpaceBoundsRect fail.
+ {
+ Posef poseInReferenceSpace = XrPosef_CreateIdentity();
+ poseInReferenceSpace.Position.Y = -1.6750f;
+ xrResult = _oxrSession.CreateReferenceSpace(ReferenceSpaceType.Local, poseInReferenceSpace, out _FakeStageSpace);
+ Debug.Assert(xrResult == Result.Success, "CreateReferenceSpace");
+ Console.WriteLine("Created fake stage space from local space with offset");
+ _CurrentStageSpace = _FakeStageSpace;
+ }
+
+ if (_isStageSupported)
+ {
+ xrResult = _oxrSession.CreateReferenceSpace(ReferenceSpaceType.Stage, XrPosef_CreateIdentity(), out _StageSpace);
+ Debug.Assert(xrResult == Result.Success, "CreateReferenceSpace");
+ Console.WriteLine("Created stage space");
+ _CurrentStageSpace = _StageSpace;
+ }
+
+ for (int eye = 0; eye < ovrMaxNumEyes; eye++)
+ {
+ _projections[eye].Type = StructureType.View;
+ }
+
+ // setup passthrough
+ if (this.SessionMode == XRSessionMode.AR)
+ {
+ _passthroughFB = _oxrSession.CreatePassthroughFB(default(PassthroughFlagsFB), _oxrInstance);
+ _passthroughLayerFB = _oxrSession.CreatePassthroughLayerFB(_passthroughFB, PassthroughLayerPurposeFB.ReconstructionFB, default(PassthroughFlagsFB), _oxrInstance);
+
+ xrResult = _passthroughFB.Start();
+ xrResult = _passthroughLayerFB.Resume();
+ }
+
+ // Actions
+ _runningActionSet =
+ _oxrInstance.CreateActionSet(1, "running_action_set", "Action Set used on main loop");
+
+ _actionButtonX = _runningActionSet.CreateAction(ActionType.BooleanInput, "x_button", "Button X", 0, null);
+ _actionButtonY = _runningActionSet.CreateAction(ActionType.BooleanInput, "y_button", "Button Y", 0, null);
+ _actionButtonLeftThumbstick = _runningActionSet.CreateAction(ActionType.BooleanInput, "left_thumbstick_click", "Button LeftThumbstick", 0, null);
+
+ _actionButtonA = _runningActionSet.CreateAction(ActionType.BooleanInput, "a_button", "Button A", 0, null);
+ _actionButtonB = _runningActionSet.CreateAction(ActionType.BooleanInput, "b_button", "Button B", 0, null);
+ _actionButtonRightThumbstick = _runningActionSet.CreateAction(ActionType.BooleanInput, "right_thumbstick_click", "Button RightThumbstick", 0, null);
+
+ _actionButtonXtouch = _runningActionSet.CreateAction(ActionType.BooleanInput, "x_touch", "Touch X", 0, null);
+ _actionButtonYtouch = _runningActionSet.CreateAction(ActionType.BooleanInput, "y_touch", "Touch Y", 0, null);
+ _actionButtonLeftThumbsticktouch = _runningActionSet.CreateAction(ActionType.BooleanInput, "left_thumbstick_touch", "Touch LeftThumbstick", 0, null);
+
+ _actionButtonAtouch = _runningActionSet.CreateAction(ActionType.BooleanInput, "a_touch", "Touch A", 0, null);
+ _actionButtonBtouch = _runningActionSet.CreateAction(ActionType.BooleanInput, "b_touch", "Touch B", 0, null);
+ _actionButtonRightThumbsticktouch = _runningActionSet.CreateAction(ActionType.BooleanInput, "right_thumbstick_touch", "Touch RightThumbstick", 0, null);
+
+ /*
+ Right Hand: right_menu_click
+ left_menu_click
+
+ Select Button (Often associated with the A or X buttons, depending on the controller):
+ right_select_click or right_a_click
+ left_select_click or left_x_click
+
+ Trigger Button:
+ right_trigger_click
+ left_trigger_click
+ Grip Button (Squeeze):
+ right_grip_click or right_squeeze_click
+ left_grip_click or left_squeeze_click
+
+ */
+
+ _moveOnXActionL = _runningActionSet.CreateAction(
+ ActionType.FloatInput, "left_move_on_x", "Move on Left X", 0, null);
+ _moveOnXActionR = _runningActionSet.CreateAction(
+ ActionType.FloatInput, "right_move_on_x", "Move on Right X", 0, null);
+
+ _moveOnYActionL = _runningActionSet.CreateAction(
+ ActionType.FloatInput, "left_move_on_y", "Move on Left Y", 0, null);
+ _moveOnYActionR = _runningActionSet.CreateAction(
+ ActionType.FloatInput, "right_move_on_y", "Move on Right Y", 0, null);
+
+ _actionTriggerLeftTouch = _runningActionSet.CreateAction(
+ ActionType.FloatInput, "left_trigger_touch", "Left Trigger Touch", 0, null);
+ _actionTriggerRightTouch = _runningActionSet.CreateAction(
+ ActionType.FloatInput, "right_trigger_touch", "Right Trigger Touch", 0, null);
+
+ //_actionGripLeftTouch = _runningActionSet.CreateAction(
+ // ActionType.FloatInput, "left_squeeze_touch", "Left Squeeze Touch", 0, null);
+ //_actionGripRightTouch = _runningActionSet.CreateAction(
+ // ActionType.FloatInput, "right_squeeze_touch", "Right Squeeze Touch", 0, null);
+
+ _moveOnJoystickActionL = _runningActionSet.CreateAction(
+ ActionType.Vector2fInput, "left_move_on_joy", "Move on Left Joy", 0, null);
+ _moveOnJoystickActionR = _runningActionSet.CreateAction(
+ ActionType.Vector2fInput, "right_move_on_joy", "Move on Right Joy", 0, null);
+
+ _vibrateLeftFeedback = _runningActionSet.CreateAction(
+ ActionType.VibrationOutput,
+ "vibrate_left_feedback",
+ "Vibrate Left Controller Feedback",
+ 0,
+ null);
+ _vibrateRightFeedback = _runningActionSet.CreateAction(
+ ActionType.VibrationOutput,
+ "vibrate_right_feedback",
+ "Vibrate Right Controller Feedback",
+ 0,
+ null);
+
+ xrResult = _oxrInstance.StringToPath("/user/hand/left", out _leftHandPath);
+ Debug.Assert(xrResult == Result.Success, "StringToPath");
+ xrResult = _oxrInstance.StringToPath("/user/hand/right", out _rightHandPath);
+ Debug.Assert(xrResult == Result.Success, "StringToPath");
+ ActionPath[] handSubactionPaths = new ActionPath[] { _leftHandPath, _rightHandPath };
+
+ _aimPoseAction = _runningActionSet.CreateAction(
+ ActionType.PoseInput, "aim_pose", null, 2, handSubactionPaths);
+ _gripPoseAction = _runningActionSet.CreateAction(
+ ActionType.PoseInput, "grip_pose", null, 2, handSubactionPaths);
+
+ ActionPath interactionProfilePath = default;
+ ActionPath interactionProfilePathTouch = default;
+ ActionPath interactionProfilePathKHRSimple = default;
+
+ xrResult = _oxrInstance.StringToPath("/interaction_profiles/oculus/touch_controller", out interactionProfilePathTouch);
+ Debug.Assert(xrResult == Result.Success, "StringToPath");
+ xrResult = _oxrInstance.StringToPath("/interaction_profiles/khr/simple_controller", out interactionProfilePathKHRSimple);
+ Debug.Assert(xrResult == Result.Success, "StringToPath");
+
+ // Toggle this to force simple as a first choice, otherwise use it as a last resort
+ _useSimpleProfile = false; /// true;
+ if (_useSimpleProfile)
+ {
+ Console.WriteLine("xrSuggestInteractionProfileBindings found bindings for Khronos SIMPLE controller");
+ interactionProfilePath = interactionProfilePathKHRSimple;
+ }
+ else
+ {
+ // Query Set
+ OxrActionSet queryActionSet =
+ _oxrInstance.CreateActionSet(1, "query_action_set", "Action Set used to query device caps");
+ XrAction dummyAction = queryActionSet.CreateAction(
+ ActionType.BooleanInput, "dummy_action", "Dummy Action", 0, null);
+
+ // Map bindings
+ ActionSuggestedBinding[] bindings = new ActionSuggestedBinding[1];
+ bindings[0] = _oxrInstance.ActionSuggestedBinding(dummyAction, "/user/hand/right/input/system/click");
+
+ // Try all
+ Result suggestTouchResult =
+ _oxrInstance.SuggestInteractionProfileBinding(interactionProfilePathTouch, bindings);
+ Debug.Assert(suggestTouchResult == Result.Success, "StringToPath");
+
+ if (Result.Success == suggestTouchResult)
+ {
+ Console.WriteLine("xrSuggestInteractionProfileBindings found bindings for QUEST controller");
+ interactionProfilePath = interactionProfilePathTouch;
+ }
+
+ if (interactionProfilePath.Handle == 0)
+ {
+ // Simple as a fallback
+ bindings[0] = _oxrInstance.ActionSuggestedBinding(dummyAction, "/user/hand/right/input/select/click");
+
+ Result suggestKHRSimpleResult =
+ _oxrInstance.SuggestInteractionProfileBinding(interactionProfilePathKHRSimple, bindings);
+ Debug.Assert(suggestKHRSimpleResult == Result.Success, "xrSuggestInteractionProfileBindings");
+
+ if (Result.Success == suggestKHRSimpleResult)
+ {
+ Console.WriteLine(
+ "xrSuggestInteractionProfileBindings found bindings for Khronos SIMPLE controller");
+ interactionProfilePath = interactionProfilePathKHRSimple;
+ }
+ else
+ {
+ Console.WriteLine("xrSuggestInteractionProfileBindings did NOT find any bindings.");
+ Debug.Assert(false);
+ }
+ }
+ }
+
+ // Action creation
+ {
+ // Map bindings
+ ActionSuggestedBinding[] bindings = null;
+ if (interactionProfilePath == interactionProfilePathTouch)
+ {
+ bindings = new ActionSuggestedBinding[]
+ {
+ _oxrInstance.ActionSuggestedBinding(_actionButtonX, "/user/hand/left/input/x/click"),
+ _oxrInstance.ActionSuggestedBinding(_actionButtonY, "/user/hand/left/input/y/click"),
+ _oxrInstance.ActionSuggestedBinding(_actionButtonLeftThumbstick, "/user/hand/left/input/thumbstick/click"),
+
+ _oxrInstance.ActionSuggestedBinding(_actionButtonA, "/user/hand/right/input/a/click"),
+ _oxrInstance.ActionSuggestedBinding(_actionButtonB, "/user/hand/right/input/b/click"),
+ _oxrInstance.ActionSuggestedBinding(_actionButtonRightThumbstick, "/user/hand/right/input/thumbstick/click"),
+
+ _oxrInstance.ActionSuggestedBinding(_actionButtonXtouch, "/user/hand/left/input/x/touch"),
+ _oxrInstance.ActionSuggestedBinding(_actionButtonYtouch, "/user/hand/left/input/y/touch"),
+ _oxrInstance.ActionSuggestedBinding(_actionButtonLeftThumbsticktouch, "/user/hand/left/input/thumbstick/touch"),
+
+ _oxrInstance.ActionSuggestedBinding(_actionButtonAtouch, "/user/hand/right/input/a/touch"),
+ _oxrInstance.ActionSuggestedBinding(_actionButtonBtouch, "/user/hand/right/input/b/touch"),
+ _oxrInstance.ActionSuggestedBinding(_actionButtonRightThumbsticktouch, "/user/hand/right/input/thumbstick/touch"),
+
+ _oxrInstance.ActionSuggestedBinding(_moveOnYActionL, "/user/hand/left/input/trigger/value"),
+ _oxrInstance.ActionSuggestedBinding(_moveOnYActionR, "/user/hand/right/input/trigger/value"),
+ _oxrInstance.ActionSuggestedBinding(_moveOnXActionL, "/user/hand/left/input/squeeze/value"),
+ _oxrInstance.ActionSuggestedBinding(_moveOnXActionR, "/user/hand/right/input/squeeze/value"),
+
+ _oxrInstance.ActionSuggestedBinding(_actionTriggerLeftTouch, "/user/hand/left/input/trigger/touch"),
+ _oxrInstance.ActionSuggestedBinding(_actionTriggerRightTouch, "/user/hand/right/input/trigger/touch"),
+
+ // BUG: Grip touch doesn't work on native OpenXR.
+ // https://communityforums.atmeta.com/t5/OpenXR-Development/unable-to-get-grip-button-input-on-quest-1-getting-path/td-p/833021
+ //_oxrInstance.ActionSuggestedBinding(_actionGripLeftTouch, "/user/hand/left/input/squeeze/touch"),
+ //_oxrInstance.ActionSuggestedBinding(_actionGripRightTouch, "/user/hand/right/input/squeeze/touch"),
+
+ _oxrInstance.ActionSuggestedBinding(_moveOnJoystickActionL, "/user/hand/left/input/thumbstick"),
+ _oxrInstance.ActionSuggestedBinding(_moveOnJoystickActionR, "/user/hand/right/input/thumbstick"),
+
+ _oxrInstance.ActionSuggestedBinding(_vibrateLeftFeedback, "/user/hand/left/output/haptic"),
+ _oxrInstance.ActionSuggestedBinding(_vibrateRightFeedback, "/user/hand/right/output/haptic"),
+
+ _oxrInstance.ActionSuggestedBinding(_aimPoseAction, "/user/hand/left/input/aim/pose"),
+ _oxrInstance.ActionSuggestedBinding(_aimPoseAction, "/user/hand/right/input/aim/pose"),
+ _oxrInstance.ActionSuggestedBinding(_gripPoseAction, "/user/hand/left/input/grip/pose"),
+ _oxrInstance.ActionSuggestedBinding(_gripPoseAction, "/user/hand/right/input/grip/pose"),
+ };
+ }
+
+ if (interactionProfilePath == interactionProfilePathKHRSimple)
+ {
+ bindings = new ActionSuggestedBinding[]
+ {
+ _oxrInstance.ActionSuggestedBinding(_vibrateLeftFeedback, "/user/hand/left/output/haptic"),
+ _oxrInstance.ActionSuggestedBinding(_vibrateRightFeedback, "/user/hand/right/output/haptic"),
+
+ _oxrInstance.ActionSuggestedBinding(_aimPoseAction, "/user/hand/left/input/aim/pose"),
+ _oxrInstance.ActionSuggestedBinding(_aimPoseAction, "/user/hand/right/input/aim/pose"),
+ _oxrInstance.ActionSuggestedBinding(_gripPoseAction, "/user/hand/left/input/grip/pose"),
+ _oxrInstance.ActionSuggestedBinding(_gripPoseAction, "/user/hand/right/input/grip/pose"),
+ };
+ }
+
+ xrResult = _oxrInstance.SuggestInteractionProfileBinding(interactionProfilePath, bindings);
+ Debug.Assert(xrResult == Result.Success, "SuggestInteractionProfileBinding");
+
+ // Attach to session
+ xrResult = _oxrSession.AttachSessionActionSets(_runningActionSet);
+ Debug.Assert(xrResult == Result.Success, "AttachSessionActionSets");
+
+
+ // Enumerate actions
+ XrAction[] actionsToEnumerate =
+ {
+ _actionButtonX,
+ _actionButtonY,
+ _actionButtonRightThumbstick,
+ _actionButtonA,
+ _actionButtonB,
+ _actionButtonLeftThumbstick,
+
+ _actionButtonXtouch,
+ _actionButtonYtouch,
+ _actionButtonRightThumbsticktouch,
+ _actionButtonAtouch,
+ _actionButtonBtouch,
+ _actionButtonLeftThumbsticktouch,
+
+ _moveOnXActionL,
+ _moveOnXActionR,
+ _moveOnYActionL,
+ _moveOnYActionR,
+
+ _actionTriggerLeftTouch,
+ _actionTriggerRightTouch,
+ //_actionGripLeftTouch,
+ //_actionGripRightTouch,
+
+ _moveOnJoystickActionL,
+ _moveOnJoystickActionR,
+
+ _vibrateLeftFeedback,
+ _vibrateRightFeedback,
+ _aimPoseAction,
+ _gripPoseAction,
+ };
+
+ for (int i = 0; i < actionsToEnumerate.Length; i++)
+ {
+ ulong[] actionPathsBuffer = _oxrSession.EnumerateBoundSourcesForAction(actionsToEnumerate[i]);
+
+ Console.WriteLine(
+ "xrEnumerateBoundSourcesForAction action={0} count={1}",
+ actionsToEnumerate[i].Handle,
+ actionPathsBuffer.Length);
+
+ for (uint a = 0; a < actionPathsBuffer.Length; a++)
+ {
+ InputSourceLocalizedNameGetInfo nameGetInfo = new InputSourceLocalizedNameGetInfo(StructureType.InputSourceLocalizedNameGetInfo);
+ nameGetInfo.SourcePath = actionPathsBuffer[a];
+ nameGetInfo.WhichComponents = InputSourceLocalizedNameFlags.UserPathBit
+ | InputSourceLocalizedNameFlags.InteractionProfileBit
+ | InputSourceLocalizedNameFlags.ComponentBit;
+
+ uint stringCount = 0;
+ xrResult = _oxrInstance.OXRAPI.Api.GetInputSourceLocalizedName(_oxrSession.Session, &nameGetInfo, 0, &stringCount, (byte*)null);
+ Debug.Assert(xrResult == Result.Success, "GetInputSourceLocalizedName");
+
+ char[] stringBuffer = new char[stringCount];
+ string localizedName;
+
+ fixed (void* pstringBuffer = stringBuffer)
+ {
+ xrResult = _oxrInstance.OXRAPI.Api.GetInputSourceLocalizedName(_oxrSession.Session, &nameGetInfo, stringCount, &stringCount, (byte*)pstringBuffer);
+ Debug.Assert(xrResult == Result.Success, "GetInputSourceLocalizedName");
+ localizedName = Encoding.ASCII.GetString((byte*)pstringBuffer, (int)stringCount);
+ Console.Write("Bound source: " + localizedName);
+ }
+
+
+ string strpathStr;
+ byte[] pathStr = new byte[stringCount];
+ fixed (byte* ppathStr = pathStr)
+ {
+ uint strLen = 0;
+ xrResult = _oxrInstance.OXRAPI.Api.PathToString(
+ _oxrInstance.Instance,
+ actionPathsBuffer[a],
+ stringCount,
+ &strLen,
+ ppathStr);
+
+ strpathStr = Encoding.ASCII.GetString((byte*)ppathStr, (int)strLen);
+ }
+
+ Console.Write(
+ " -> path = {0} `{1}` -> `{2}`",
+ actionPathsBuffer[a],
+ strpathStr,
+ localizedName //stringBuffer
+ );
+ }
+ }
+
+
+ // Create the frame buffers.
+ for (int eye = 0; eye < ovrMaxNumEyes; eye++)
+ {
+ ovrFramebuffer_Create(
+ ref _Renderer.FrameBuffer[eye],
+ SurfaceFormat.ColorSRgba, //SRGB8_ALPHA8
+ (int)ViewConfigurationView[0].RecommendedImageRectWidth,
+ (int)ViewConfigurationView[0].RecommendedImageRectHeight,
+ NUM_MULTI_SAMPLES
+ );
+ }
+
+ ovrRenderer_SetFoveation(
+ ref _Renderer,
+ FoveationLevelFB.HighFB,
+ 0,
+ FoveationDynamicFB.DisabledFB
+ );
+
+ //CreateDefaultLayer(graphicsDevice,
+ // SurfaceFormat.Color,
+ // DepthFormat.Depth24Stencil8,
+ // 4, 1,
+ // out _layer);
+
+ TouchController.DeviceHandle = new ConcreteTouchControllerStrategy(this);
+ this._deviceState = XRDeviceState.Enabled;
+
+ return 0;
+ }
+ }
+
+ private unsafe bool ovrFramebuffer_Create(
+ ref ovrFramebuffer frameBuffer,
+ SurfaceFormat colorFormat,
+ int width,
+ int height,
+ int multisamples)
+ {
+ Result xrResult;
+
+ frameBuffer.Width = width;
+ frameBuffer.Height = height;
+ frameBuffer.Multisamples = multisamples;
+
+ // Get the number of supported formats.
+ xrResult = _oxrSession.EnumerateSwapchainFormats(out long[] supportedFormats);
+ Debug.Assert(xrResult == Result.Success, "EnumerateSwapchainFormats");
+ Array.Sort(supportedFormats);
+
+ long requestedGLFormat = SRGB8_ALPHA8;
+
+ // Verify the requested format is supported.
+ long selectedFormat = 0;
+ for (uint i = 0; i < supportedFormats.Length; i++)
+ {
+ if (supportedFormats[i] == requestedGLFormat)
+ {
+ selectedFormat = supportedFormats[i];
+ break;
+ }
+ }
+ Debug.Assert(selectedFormat != 0, "requestedGLFormat not supported.");
+ if (selectedFormat == 0)
+ {
+ Console.WriteLine("Format not supported");
+ }
+
+ SwapchainCreateInfo swapChainCreateInfo = new SwapchainCreateInfo(StructureType.SwapchainCreateInfo);
+ swapChainCreateInfo.UsageFlags = SwapchainUsageFlags.SampledBit
+ | SwapchainUsageFlags.ColorAttachmentBit;
+ swapChainCreateInfo.Format = selectedFormat;
+ swapChainCreateInfo.SampleCount = 1;
+ swapChainCreateInfo.Width = (uint)width;
+ swapChainCreateInfo.Height = (uint)height;
+ swapChainCreateInfo.FaceCount = 1;
+ swapChainCreateInfo.ArraySize = 1;
+ swapChainCreateInfo.MipCount = 1;
+
+ // Enable Foveation on this swapchain
+ SwapchainCreateInfoFoveationFB swapChainFoveationCreateInfo = new SwapchainCreateInfoFoveationFB(StructureType.SwapchainCreateInfoFoveationFB);
+ swapChainCreateInfo.Next = &swapChainFoveationCreateInfo;
+
+ // Create the swapchain.
+ xrResult = _oxrSession.CreateSwapchain(ref swapChainCreateInfo, out frameBuffer.ColorSwapChain);
+ Debug.Assert(xrResult == Result.Success, "CreateSwapchain");
+ // Get the number of swapchain images.
+ frameBuffer.TextureSwapChainLength = frameBuffer.ColorSwapChain.GetImagesCount();
+
+ // Allocate the swapchain images array.
+ SwapchainImageOpenGLESKHR[] colorSwapChainImage = new SwapchainImageOpenGLESKHR[frameBuffer.TextureSwapChainLength];
+ frameBuffer.ColorSwapChainImage = colorSwapChainImage;
+
+ // Populate the swapchain image array.
+ for (uint i = 0; i < frameBuffer.TextureSwapChainLength; i++)
+ {
+ colorSwapChainImage[i].Type = StructureType.SwapchainImageOpenglESKhr;
+ colorSwapChainImage[i].Next = null;
+ }
+
+ fixed (SwapchainImageOpenGLESKHR* pcolorSwapChainImage = colorSwapChainImage)
+ {
+ xrResult = _oxrInstance.OXRAPI.Api.EnumerateSwapchainImages(
+ frameBuffer.ColorSwapChain.Swapchain,
+ frameBuffer.TextureSwapChainLength,
+ ref frameBuffer.TextureSwapChainLength,
+ (SwapchainImageBaseHeader*)pcolorSwapChainImage);
+ Debug.Assert(xrResult == Result.Success, "EnumerateSwapchainImages");
+ }
+
+ frameBuffer.RenderTarget = new RenderTarget2D(_graphics.GraphicsDevice,
+ width, height,
+ false, SurfaceFormat.Color,
+ DepthFormat.Depth24Stencil8,
+ 4,
+ RenderTargetUsage.DiscardContents,
+ true
+ );
+
+ frameBuffer.FrameBuffers = new int[frameBuffer.TextureSwapChainLength];
+
+ Android.Opengl.GLES30.GlGenFramebuffers((int)frameBuffer.TextureSwapChainLength, frameBuffer.FrameBuffers, 0);
+
+ //ConcreteGraphicsAdapter adapter = ((IPlatformGraphicsAdapter)_graphics.GraphicsDevice.Adapter).Strategy.ToConcrete();
+ //var GL = adapter.Ogl;
+
+ for (uint i = 0; i < frameBuffer.TextureSwapChainLength; i++)
+ {
+ // Create the color buffer texture.
+ uint colorTexture = frameBuffer.ColorSwapChainImage[i].Image;
+
+ // create DrawFramebuffer
+ //frameBuffer.FrameBuffers[i] = GL.GenFramebuffer();
+ //GL.CheckGLError();
+
+ Android.Opengl.GLES30.GlBindFramebuffer(Android.Opengl.GLES30.GlDrawFramebuffer, frameBuffer.FrameBuffers[i]);
+ //GL.BindFramebuffer(OpenGL.FramebufferTarget.DrawFramebuffer, frameBuffer.FrameBuffers[i]);
+ //GL.CheckGLError();
+
+ Android.Opengl.GLES30.GlFramebufferTexture2D(
+ Android.Opengl.GLES30.GlDrawFramebuffer,
+ Android.Opengl.GLES30.GlColorAttachment0,
+ Android.Opengl.GLES30.GlTexture2d, (int)colorTexture, 0);
+ //GL.FramebufferTexture2D(
+ // OpenGL.FramebufferTarget.DrawFramebuffer,
+ // OpenGL.FramebufferAttachment.ColorAttachment0,
+ // OpenGL.TextureTarget.Texture2D, (int)colorTexture, 0);
+ //GL.CheckGLError();
+ }
+
+ return true;
+ }
+
+
+ public unsafe delegate Result xrGetOpenGLESGraphicsRequirementsKHRDelegate(Instance xrInstance, ulong systemId, GraphicsRequirementsOpenGLESKHR* graphicsRequirements);
+ public unsafe delegate Result xrEnumerateColorSpacesFB(Session xrSession, uint count, uint* outcount, ColorSpaceFB* colorSpaces);
+ public unsafe delegate Result xrSetColorSpaceFB(Session xrSession, ColorSpaceFB colorSpace);
+
+ public unsafe delegate Result xrEnumerateDisplayRefreshRatesFB(Session xrSession, uint count, ref uint outcount, float* displayRefreshRates);
+ public unsafe delegate Result xrGetDisplayRefreshRateFB(Session xrSession, float* displayRefreshRate);
+ public unsafe delegate Result xrRequestDisplayRefreshRateFB(Session xrSession, float displayRefreshRate);
+
+ public unsafe delegate Result xrPerfSettingsSetPerformanceLevelEXT(Session xrSession, PerfSettingsDomainEXT perfDomain, PerfSettingsLevelEXT perfLevel);
+
+ const int ovrMaxLayerCount = 16;
+ const int ovrMaxNumEyes = 2;
+
+ const int CPU_LEVEL = 2;
+ const int GPU_LEVEL = 3;
+ const int NUM_MULTI_SAMPLES = 4;
+
+ public struct ovrRenderer
+ {
+ public ovrFramebuffer[] FrameBuffer = new ovrFramebuffer[ovrMaxNumEyes];
+
+ public ovrRenderer()
+ {
+ }
+ }
+
+ private long ToXrTime(double sec)
+ {
+ return (long)(sec * 1000 * 1000 * 1000); //nSec
+ }
+
+ private unsafe LocVel GetSpaceLocVel(OxrSpace space, long time)
+ {
+ Result xrResult;
+
+ OxrSpace currentSpace = (_isTrackFloorLevelEnabled) ? _LocalFloorSpace : _LocalSpace;
+
+ SpaceVelocity velocity = new SpaceVelocity(StructureType.SpaceVelocity);
+
+ xrResult = _oxrInstance.LocateSpace(space, currentSpace, time, &velocity,
+ out Posef pose, out SpaceLocationFlags locationFlags);
+ Debug.Assert(xrResult == Result.Success, "LocateSpace");
+
+ LocVel lv = new LocVel();
+ lv.LocationFlags = locationFlags;
+ lv.Pose = pose.ToPose3();
+
+ lv.VelocityFlags = velocity.VelocityFlags;
+ lv.LinearVelocity = velocity.LinearVelocity.ToVector3();
+ lv.AngularVelocity = velocity.AngularVelocity.ToVector3();
+
+ return lv;
+ }
+
+ public static void CreatePerspectiveFieldOfView(float leftTan, float rightTan, float bottomTan, float topTan, float nearPlaneDistance, float farPlaneDistance, out Matrix result)
+ {
+ if (nearPlaneDistance <= 0f)
+ {
+ throw new ArgumentException("nearPlaneDistance <= 0");
+ }
+ if (farPlaneDistance <= 0f)
+ {
+ throw new ArgumentException("farPlaneDistance <= 0");
+ }
+ if (nearPlaneDistance >= farPlaneDistance)
+ {
+ throw new ArgumentException("nearPlaneDistance >= farPlaneDistance");
+ }
+
+ result.M11 = (2f) / (leftTan + rightTan);
+ result.M12 = result.M13 = result.M14 = 0;
+
+ result.M22 = (2f) / (topTan + bottomTan);
+ result.M21 = result.M23 = result.M24 = 0;
+
+ result.M31 = (rightTan - leftTan) / (leftTan + rightTan);
+ result.M32 = (topTan - bottomTan) / (bottomTan + topTan);
+ result.M33 = farPlaneDistance / (nearPlaneDistance - farPlaneDistance);
+ result.M34 = -1;
+
+ result.M43 = (nearPlaneDistance * farPlaneDistance) / (nearPlaneDistance - farPlaneDistance);
+ result.M41 = result.M42 = result.M44 = 0;
+ }
+
+ public struct LocVel
+ {
+ public SpaceLocationFlags LocationFlags;
+ public Pose3 Pose;
+
+ public SpaceVelocityFlags VelocityFlags;
+ public Vector3 LinearVelocity;
+ public Vector3 AngularVelocity;
+
+ }
+
+ // https://github.com/KhronosGroup/OpenXR-SDK/blob/main/src/common/xr_linear.h
+
+ static unsafe Vector3f XrVector3f_Set(float value)
+ {
+ Vector3f result;
+ result.X = value;
+ result.Y = value;
+ result.Z = value;
+ return result;
+ }
+
+ static unsafe Vector3f XrVector3f_Add(Vector3f a, Vector3f b)
+ {
+ Vector3f result;
+ result.X = a.X + b.X;
+ result.Y = a.Y + b.Y;
+ result.Z = a.Z + b.Z;
+ return result;
+ }
+
+ static unsafe Vector3f XrVector3f_Scale(Vector3f a, float scaleFactor)
+ {
+ Vector3f result;
+ result.X = a.X * scaleFactor;
+ result.Y = a.Y * scaleFactor;
+ result.Z = a.Z * scaleFactor;
+ return result;
+ }
+
+ static unsafe float XrRcpSqrt(float x)
+ {
+ float SMALLEST_NON_DENORMAL = 1.1754943508222875e-038f; // ( 1U << 23 )
+ float rcp = (x >= SMALLEST_NON_DENORMAL) ? 1.0f / (float)Math.Sqrt(x) : 1.0f;
+ return rcp;
+ }
+
+ static unsafe Quaternionf XrQuaternionf_CreateIdentity()
+ {
+ Quaternionf result;
+ result.X = 0.0f;
+ result.Y = 0.0f;
+ result.Z = 0.0f;
+ result.W = 1.0f;
+ return result;
+ }
+
+ static unsafe Quaternionf XrQuaternionf_CreateFromAxisAngle(Vector3f axis, float angleInRadians)
+ {
+ Quaternionf result;
+ float s = (float)Math.Sin(angleInRadians / 2.0f);
+ float lengthRcp = XrRcpSqrt(axis.X * axis.X + axis.Y * axis.Y + axis.Z * axis.Z);
+ result.X = s * axis.X * lengthRcp;
+ result.Y = s * axis.Y * lengthRcp;
+ result.Z = s * axis.Z * lengthRcp;
+ result.W = (float)Math.Cos(angleInRadians / 2.0f);
+ return result;
+ }
+
+ static unsafe Quaternionf XrQuaternionf_Multiply(Quaternionf a, Quaternionf b)
+ {
+ Quaternionf result;
+ result.X = (b.W * a.X) + (b.X * a.W) + (b.Y * a.Z) - (b.Z * a.Y);
+ result.Y = (b.W * a.Y) - (b.X * a.Z) + (b.Y * a.W) + (b.Z * a.X);
+ result.Z = (b.W * a.Z) + (b.X * a.Y) - (b.Y * a.X) + (b.Z * a.W);
+ result.W = (b.W * a.W) - (b.X * a.X) - (b.Y * a.Y) - (b.Z * a.Z);
+ return result;
+ }
+
+ static unsafe Quaternionf XrQuaternionf_Invert(Quaternionf q)
+ {
+ Quaternionf result;
+ result.X = -q.X;
+ result.Y = -q.Y;
+ result.Z = -q.Z;
+ result.W = q.W;
+ return result;
+ }
+
+ static unsafe Vector3f XrQuaternionf_RotateVector3f(Quaternionf a, Vector3f v)
+ {
+ Quaternionf q = new Quaternionf(v.X, v.Y, v.Z, 0.0f);
+ Quaternionf aq = XrQuaternionf_Multiply(q, a);
+ Quaternionf aInv = XrQuaternionf_Invert(a);
+ Quaternionf aqaInv = XrQuaternionf_Multiply(aInv, aq);
+
+ Vector3f result;
+ result.X = aqaInv.X;
+ result.Y = aqaInv.Y;
+ result.Z = aqaInv.Z;
+ return result;
+ }
+
+ static unsafe Vector3f XrQuaternionf_RotateVector3f_Opt(Quaternionf q, Vector3f vec)
+ {
+ Vector4f qvec;
+ qvec.X = +(q.W * vec.X) -(q.Z * vec.Y) +(q.Y * vec.Z);
+ qvec.Y = +(q.Z * vec.X) +(q.W * vec.Y) -(q.X * vec.Z);
+ qvec.Z = -(q.Y * vec.X) +(q.X * vec.Y) +(q.W * vec.Z);
+ qvec.W = -(q.X * vec.X) -(q.Y * vec.Y) -(q.Z * vec.Z);
+
+ Vector3f result;
+ result.X = +(qvec.X * q.W) -(qvec.Y * q.Z) +(qvec.Z * q.Y) -(qvec.W * q.X);
+ result.Y = +(qvec.X * q.Z) +(qvec.Y * q.W) -(qvec.Z * q.X) -(qvec.W * q.Y);
+ result.Z = -(qvec.X * q.Y) +(qvec.Y * q.X) +(qvec.Z * q.W) -(qvec.W * q.Z);
+
+ return result;
+ }
+
+ static unsafe Posef XrPosef_CreateIdentity()
+ {
+ Posef result;
+ result.Orientation = XrQuaternionf_CreateIdentity();
+ result.Position = XrVector3f_Set(0);
+ return result;
+ }
+
+ static unsafe Vector3f XrPosef_TransformVector3f(Posef a, Vector3f v)
+ {
+ Vector3f result;
+ Vector3f r0 = XrQuaternionf_RotateVector3f_Opt(a.Orientation, v);
+ result = XrVector3f_Add(r0, a.Position);
+ return result;
+ }
+
+ static unsafe Posef XrPosef_Multiply(Posef a, Posef b)
+ {
+ Posef result;
+ result.Orientation = XrQuaternionf_Multiply(b.Orientation, a.Orientation);
+ result.Position = XrPosef_TransformVector3f(a, b.Position);
+ return result;
+ }
+
+ static unsafe Posef XrPosef_Invert(Posef a)
+ {
+ Posef result;
+ result.Orientation = XrQuaternionf_Invert(a.Orientation);
+ Vector3f aPosNeg = XrVector3f_Scale(a.Position, -1.0f);
+ result.Position = XrQuaternionf_RotateVector3f_Opt(result.Orientation, aPosNeg);
+ return result;
+ }
+
+ // Creates a projection matrix based on the specified FOV.
+ static unsafe Matrix XrMatrixf_CreateProjectionFov(GraphicsBackend graphicsApi, Fovf fov,
+ float nearZ, float farZ)
+ {
+ float tanLeft = (float)Math.Tan(fov.AngleLeft);
+ float tanRight = (float)Math.Tan(fov.AngleRight);
+ float tanDown = (float)Math.Tan(fov.AngleDown);
+ float tanUp = (float)Math.Tan(fov.AngleUp);
+
+
+ Matrix result;
+ result = XrMatrixf_CreateProjection(graphicsApi, tanLeft, tanRight, tanUp, tanDown, nearZ, farZ);
+ return result;
+ }
+
+ // Creates a projection matrix based on the specified dimensions.
+ // The projection matrix transforms -Z=forward, +Y=up, +X=right to the appropriate clip space for the graphics API.
+ // The far plane is placed at infinity if farZ <= nearZ.
+ // An infinite projection matrix is preferred for rasterization because, except for
+ // things *right* up against the near plane, it always provides better precision:
+ // "Tightening the Precision of Perspective Rendering"
+ // Paul Upchurch, Mathieu Desbrun
+ // Journal of Graphics Tools, Volume 16, Issue 1, 2012
+ static unsafe Matrix XrMatrixf_CreateProjection(GraphicsBackend graphicsApi, float tanAngleLeft,
+ float tanAngleRight, float tanAngleUp, float tanAngleDown,
+ float nearZ, float farZ)
+ {
+ float tanAngleWidth = tanAngleRight - tanAngleLeft;
+
+ // Set to tanAngleDown - tanAngleUp for a clip space with positive Y down (Vulkan).
+ // Set to tanAngleUp - tanAngleDown for a clip space with positive Y up (OpenGL / D3D / Metal).
+ float tanAngleHeight = graphicsApi == GraphicsBackend.Vulkan ? (tanAngleDown - tanAngleUp) : (tanAngleUp - tanAngleDown);
+
+ // Set to nearZ for a [-1,1] Z clip space (OpenGL / OpenGL ES).
+ // Set to zero for a [0,1] Z clip space (Vulkan / D3D / Metal).
+ float offsetZ = (graphicsApi == GraphicsBackend.OpenGL || graphicsApi == GraphicsBackend.GLES) ? nearZ : 0;
+
+ if (farZ <= nearZ)
+ {
+ Matrix result;
+
+ // place the far plane at infinity
+ result.M11 = 2.0f / tanAngleWidth;
+ result.M12 = 0.0f;
+ result.M13 = 0.0f;
+ result.M14 = 0.0f;
+
+ result.M21 = 0.0f;
+ result.M22 = 2.0f / tanAngleHeight;
+ result.M23 = 0.0f;
+ result.M24 = 0.0f;
+
+ result.M31 = (tanAngleRight + tanAngleLeft) / tanAngleWidth;
+ result.M32 = (tanAngleUp + tanAngleDown) / tanAngleHeight;
+ result.M33 = -1.0f;
+ result.M34 = -1.0f;
+
+ result.M41 = 0.0f;
+ result.M42 = 0.0f;
+ result.M43 = -(nearZ + offsetZ);
+ result.M44 = 0.0f;
+
+ return result;
+ }
+ else
+ {
+ Matrix result;
+
+ // normal projection
+ result.M11 = (2f) / tanAngleWidth;
+ result.M12 = result.M13 = result.M14 = 0;
+
+ result.M22 = (2f) / tanAngleHeight;
+ result.M21 = result.M23 = result.M24 = 0;
+
+ result.M31 = (tanAngleRight + tanAngleLeft) / tanAngleWidth;
+ result.M32 = (tanAngleUp + tanAngleDown) / tanAngleHeight;
+ result.M33 = (farZ + offsetZ) / (nearZ-farZ);
+ result.M34 = -1;
+
+ result.M43 = (farZ * (nearZ + offsetZ)) / (nearZ-farZ);
+ result.M41 = result.M42 = result.M44 = 0;
+
+ return result;
+ }
+ }
+
+
+ private unsafe void ovrRenderer_RenderFrame_Acquire(ref ovrFramebuffer frameBuffer)
+ {
+ Result xrResult;
+
+ // Acquire the swapchain image
+ xrResult = frameBuffer.ColorSwapChain.AcquireSwapchainImage(ref frameBuffer.TextureSwapChainIndex);
+ Debug.Assert(xrResult == Result.Success, "AcquireSwapchainImage");
+
+ SwapchainImageWaitInfo waitInfo = new SwapchainImageWaitInfo(StructureType.SwapchainImageWaitInfo);
+ waitInfo.Timeout = 1000000000; /* timeout in nanoseconds */
+ xrResult = frameBuffer.ColorSwapChain.WaitSwapchainImage(ref waitInfo);
+
+ int i = 0;
+ while (xrResult == Result.TimeoutExpired)
+ {
+ xrResult = frameBuffer.ColorSwapChain.WaitSwapchainImage(ref waitInfo);
+ i++;
+ Console.WriteLine(
+ " Retry xrWaitSwapchainImage {0} times due to XR_TIMEOUT_EXPIRED (duration {0} seconds)",
+ i,
+ waitInfo.Timeout * (1E-9));
+ }
+ }
+
+ private unsafe void ovrRenderer_RenderFrame_Release(ref ovrRenderer renderer, int eye)
+ {
+ fixed (ovrFramebuffer* frameBuffer = &renderer.FrameBuffer[eye])
+ {
+ Result xrResult;
+
+ xrResult = frameBuffer->ColorSwapChain.ReleaseSwapchainImage();
+ }
+ }
+
+ private void UpdateStageBounds()
+ {
+ Extent2Df stageBounds = default;
+
+ Result xrResult;
+ xrResult = _oxrSession.GetReferenceSpaceBoundsRect(ReferenceSpaceType.Stage, out stageBounds);
+ if (xrResult != Result.Success)
+ {
+ Console.WriteLine("Stage bounds query failed: using small defaults");
+ stageBounds.Width = 1.0f;
+ stageBounds.Height = 1.0f;
+
+ _CurrentStageSpace = _FakeStageSpace;
+ }
+
+ Console.WriteLine("Stage bounds: width = {0}, depth {1}", stageBounds.Width, stageBounds.Height);
+
+ float halfWidth = stageBounds.Width * 0.5f;
+ float halfDepth = stageBounds.Height * 0.5f;
+
+ //ovrGeometry_Destroy(&pappState->Scene.GroundPlane);
+ //ovrGeometry_DestroyVAO(&pappState->Scene.GroundPlane);
+ //ovrGeometry_CreateStagePlane(
+ // &pappState->Scene.GroundPlane, -halfWidth, -halfDepth, halfWidth, halfDepth);
+ //ovrGeometry_CreateVAO(&pappState->Scene.GroundPlane);
+ }
+
+
+ public struct ovrTrackedController
+ {
+ public bool Active;
+ public Pose3 Pose;
+ internal Vector3 LinearVelocity;
+
+ internal void Clear()
+ {
+ this.Active = false;
+ this.Pose = Pose3.Identity;
+ this.LinearVelocity = Vector3.Zero;
+ }
+ }
+
+ public struct ovrScene
+ {
+ public bool CreatedScene;
+ public ovrTrackedController[] TrackedController = new ovrTrackedController[4]; // left aim, left grip, right aim, right grip
+
+ public OxrSwapChain CylinderSwapChain;
+ public SwapchainImageOpenGLESKHR[] CylinderSwapChainImage;
+ //internal RenderTargetSwapChain CylinderSwapRenderTarget;
+
+ public ovrScene()
+ {
+ }
+ }
+
+ internal abstract class OxrSwapChainDataBase
+ {
+ //internal abstract OvrTextureSwapChain SwapChain { get; }
+
+ internal abstract RenderTarget2D GetRenderTarget(int eye);
+ internal abstract int SubmitRenderTarget(GraphicsDevice graphicsDevice, RenderTarget2D rt);
+ }
+
+
+ ovrScene _Scene = new ovrScene();
+
+ private unsafe bool ovrScene_IsCreated(ref ovrScene scene)
+ {
+ return scene.CreatedScene;
+ }
+
+ private unsafe void ovrScene_Create(ref ovrScene scene)
+ {
+ // Simple ground plane and box geometry.
+ {
+ }
+
+
+ // Simple cubemap loaded from ktx file on the sdcard. NOTE: Currently only
+ // handles texture2d or cubemap types.
+ // CubeMapSwapChain
+ {
+ }
+
+ // Simple checkerboard pattern.
+ // EquirectSwapChain
+ {
+ }
+
+
+ // Simple checkerboard pattern.
+ // CylinderSwapChain
+ {
+ }
+
+ // Simple checkerboard pattern.
+ // QuadSwapChain
+ if (false)
+ {
+ Result xrResult;
+
+ int CYLINDER_WIDTH = 512;
+ int CYLINDER_HEIGHT = 128;
+
+ SwapchainCreateInfo swapChainCreateInfo = new SwapchainCreateInfo(StructureType.SwapchainCreateInfo);
+ swapChainCreateInfo.CreateFlags = SwapchainCreateFlags.StaticImageBit;
+ swapChainCreateInfo.UsageFlags =
+ SwapchainUsageFlags.SampledBit | SwapchainUsageFlags.ColorAttachmentBit;
+ swapChainCreateInfo.Format = SRGB8_ALPHA8;
+ swapChainCreateInfo.SampleCount = 1;
+ swapChainCreateInfo.Width = (uint)CYLINDER_WIDTH;
+ swapChainCreateInfo.Height = (uint)CYLINDER_HEIGHT;
+ swapChainCreateInfo.FaceCount = 1;
+ swapChainCreateInfo.ArraySize = 1;
+ swapChainCreateInfo.MipCount = 1;
+
+ // Create the swapchain.
+ xrResult = _oxrSession.CreateSwapchain(ref swapChainCreateInfo, out scene.CylinderSwapChain);
+ Debug.Assert(xrResult == Result.Success, "CreateSwapchain");
+ // Get the number of swapchain images.
+ uint length;
+ xrResult = _oxrInstance.OXRAPI.Api.EnumerateSwapchainImages(scene.CylinderSwapChain.Swapchain, 0, &length, null);
+ Debug.Assert(xrResult == Result.Success, "EnumerateSwapchainImages");
+
+ SwapchainImageOpenGLESKHR[] cylinderSwapChainImage = new SwapchainImageOpenGLESKHR[length];
+ scene.CylinderSwapChainImage = cylinderSwapChainImage;
+ for (uint i = 0; i < length; i++)
+ {
+ scene.CylinderSwapChainImage[i].Type = StructureType.SwapchainImageOpenglESKhr;
+ scene.CylinderSwapChainImage[i].Next = null;
+ }
+ fixed (SwapchainImageOpenGLESKHR* pcylinderSwapChainImage = cylinderSwapChainImage)
+ {
+ xrResult = _oxrInstance.OXRAPI.Api.EnumerateSwapchainImages(
+ scene.CylinderSwapChain.Swapchain,
+ length,
+ &length,
+ (SwapchainImageBaseHeader*)pcylinderSwapChainImage);
+ Debug.Assert(xrResult == Result.Success, "EnumerateSwapchainImages");
+ }
+
+ uint[] data = new uint[CYLINDER_WIDTH * CYLINDER_HEIGHT];
+ fixed (uint* texData = data)
+ {
+ for (int y = 0; y < CYLINDER_HEIGHT; y++)
+ {
+ for (int x = 0; x < CYLINDER_WIDTH; x++)
+ {
+ texData[y * CYLINDER_WIDTH + x] = ((x ^ y) & 64)!=0 ? 0xFF6464F0 : 0xFFF06464;
+ }
+ }
+ for (int y = 0; y < CYLINDER_HEIGHT; y++)
+ {
+ int g = (int)(255.0f * (y / (CYLINDER_HEIGHT - 1.0f)));
+ texData[y * CYLINDER_WIDTH] = (uint)(0xff000000 | (g << 8));
+ }
+ for (int x = 0; x < CYLINDER_WIDTH; x++)
+ {
+ int r = (int)(255.0f * (x / (CYLINDER_WIDTH - 1.0f)));
+ texData[x] = (uint)(0xff000000 | r);
+ }
+
+ int texId = (int)scene.CylinderSwapChainImage[0].Image;
+
+ //scene.CylinderSwapRenderTarget = new RenderTargetSwapChain(_graphics.GraphicsDevice,
+ // new IntPtr(texId),
+ // CYLINDER_WIDTH,
+ // CYLINDER_HEIGHT,
+ // false, SurfaceFormat.Color, DepthFormat.None, 0, RenderTargetUsage.DiscardContents,
+ // PresentInterval.Default
+ // );
+ //scene.CylinderSwapRenderTarget.SetData(0,
+ // new Rectangle(0,0, CYLINDER_WIDTH, CYLINDER_HEIGHT),
+ // data, 0, data.Length);
+
+
+ //var adapter = ((IPlatformGraphicsAdapter)_graphics.GraphicsDevice.Adapter).Strategy.ToConcrete();
+ //var GL = adapter.Ogl;
+
+ //GL.BindTexture(OpenGL.TextureTarget.Texture2D, texId);
+ //GL.CheckGLError();
+ //GL.TexSubImage2D(
+ // OpenGL.TextureTarget.Texture2D,
+ // 0,
+ // 0,
+ // 0,
+ // CYLINDER_WIDTH,
+ // CYLINDER_HEIGHT,
+ // OpenGL.PixelFormat.Rgba,
+ // OpenGL.PixelType.UnsignedByte,
+ // new IntPtr(texData));
+ //GL.CheckGLError();
+
+ //GL.TexParameteri(OpenGL.TextureTarget.Texture2D, OpenGL.TextureParameterName.TextureWrapS, (int)OpenGL.TextureWrapMode.ClampToBorder);
+ //GL.CheckGLError();
+ //GL.TexParameteri(OpenGL.TextureTarget.Texture2D, OpenGL.TextureParameterName.TextureWrapT, (int)OpenGL.TextureWrapMode.ClampToBorder);
+ //GL.CheckGLError();
+ //float[] borderColor = { 0.0f, 0.0f, 0.0f, 0.0f };
+ //fixed(float* pborderColor = borderColor)
+ //GL.TexParameterfv(OpenGL.TextureTarget.Texture2D, OpenGL.TextureParameterName.TextureBorderColor, pborderColor);
+ //GL.CheckGLError();
+
+ //GL.BindTexture(OpenGL.TextureTarget.Texture2D, 0);
+ //GL.CheckGLError();
+
+ } // free(texData);
+
+ uint index = 0;
+ xrResult = scene.CylinderSwapChain.AcquireSwapchainImage(ref index);
+ Debug.Assert(xrResult == Result.Success, "AcquireSwapchainImage");
+
+ SwapchainImageWaitInfo waitInfo = new SwapchainImageWaitInfo(StructureType.SwapchainImageWaitInfo);
+ xrResult = scene.CylinderSwapChain.WaitSwapchainImage(ref waitInfo);
+ Debug.Assert(xrResult == Result.Success, "WaitSwapchainImage");
+
+ xrResult = scene.CylinderSwapChain.ReleaseSwapchainImage();
+ Debug.Assert(xrResult == Result.Success, "ReleaseSwapchainImage");
+ }
+
+ scene.CreatedScene = true;
+ return;
+ }
+
+ private unsafe void ovrApp_HandleXrEvents()
+ {
+ Result xrResult;
+
+ EventDataBuffer eventDataBuffer = new EventDataBuffer(StructureType.EventDataBuffer);
+
+ // Poll for events
+ for (; ; )
+ {
+ EventDataBaseHeader* baseEventHeader = (EventDataBaseHeader*)(&eventDataBuffer);
+ baseEventHeader->Type = StructureType.EventDataBuffer;
+ baseEventHeader->Next = null;
+
+ Result r;
+ r = _oxrInstance.PollEvent(ref eventDataBuffer);
+ if (r != Result.Success)
+ break;
+
+ switch (baseEventHeader->Type)
+ {
+ case StructureType.EventDataEventsLost:
+ Console.WriteLine("xrPollEvent: received XR_TYPE_EVENT_DATA_EVENTS_LOST event");
+ break;
+ case StructureType.EventDataInstanceLossPending:
+ {
+ EventDataInstanceLossPending* instance_loss_pending_event =
+ (EventDataInstanceLossPending*)(baseEventHeader);
+ Console.WriteLine(
+ "xrPollEvent: received XR_TYPE_EVENT_DATA_INSTANCE_LOSS_PENDING event: time {0}",
+ instance_loss_pending_event->LossTime // FromXrTime(instance_loss_pending_event->LossTime)
+ );
+ }
+ break;
+ case StructureType.EventDataInteractionProfileChanged:
+ Console.WriteLine("xrPollEvent: received XR_TYPE_EVENT_DATA_INTERACTION_PROFILE_CHANGED event");
+ break;
+ case StructureType.EventDataPerfSettingsExt:
+ {
+ EventDataPerfSettingsEXT* perf_settings_event =
+ (EventDataPerfSettingsEXT*)(baseEventHeader);
+ Console.WriteLine(
+ "xrPollEvent: received XR_TYPE_EVENT_DATA_PERF_SETTINGS_EXT event: type {0} subdomain {1} : level {2} -> level {3}",
+ perf_settings_event->Type,
+ perf_settings_event->SubDomain,
+ perf_settings_event->FromLevel,
+ perf_settings_event->ToLevel);
+ }
+ break;
+ case StructureType.EventDataDisplayRefreshRateChangedFB:
+ {
+ EventDataDisplayRefreshRateChangedFB* refresh_rate_changed_event =
+ (EventDataDisplayRefreshRateChangedFB*)(baseEventHeader);
+ Console.WriteLine(
+ "xrPollEvent: received XR_TYPE_EVENT_DATA_DISPLAY_REFRESH_RATE_CHANGED_FB event: fromRate {0} -> toRate {1}",
+ refresh_rate_changed_event->FromDisplayRefreshRate,
+ refresh_rate_changed_event->ToDisplayRefreshRate);
+ }
+ break;
+ case StructureType.EventDataReferenceSpaceChangePending:
+ {
+ EventDataReferenceSpaceChangePending* ref_space_change_event =
+ (EventDataReferenceSpaceChangePending*)(baseEventHeader);
+ Console.WriteLine(
+ "xrPollEvent: received XR_TYPE_EVENT_DATA_REFERENCE_SPACE_CHANGE_PENDING event: changed space: {0} for session {1} at time {2}",
+ ref_space_change_event->ReferenceSpaceType,
+ ref_space_change_event->Session.Handle,
+ ref_space_change_event->ChangeTime //FromXrTime(ref_space_change_event->ChangeTime)
+ );
+ }
+ break;
+
+ case StructureType.EventDataSessionStateChanged:
+ {
+ EventDataSessionStateChanged* session_state_changed_event =
+ (EventDataSessionStateChanged*)(baseEventHeader);
+ Console.WriteLine(
+ "xrPollEvent: received XR_TYPE_EVENT_DATA_SESSION_STATE_CHANGED: {0} for session {1} at time {2}",
+ session_state_changed_event->State,
+ session_state_changed_event->Session.Handle,
+ session_state_changed_event->Time //FromXrTime(session_state_changed_event->Time)
+ );
+
+ this._sessionState = session_state_changed_event->State;
+
+ switch (session_state_changed_event->State)
+ {
+ case SessionState.Idle:
+ break;
+ case SessionState.Focused:
+ _Focused = true;
+ break;
+ case SessionState.Visible:
+ _Focused = false;
+ break;
+ case SessionState.Ready:
+ ovrApp_HandleSessionStateChangesReady(session_state_changed_event->State);
+ break;
+ case SessionState.Stopping:
+ ovrApp_HandleSessionStateChangesStopping(session_state_changed_event->State);
+ break;
+
+ case SessionState.Synchronized:
+ break;
+ case SessionState.LossPending:
+ break;
+ case SessionState.Exiting:
+ break;
+
+ default:
+ Console.WriteLine("Unknown State: {0}.", session_state_changed_event->State);
+ break;
+ }
+ }
+ break;
+
+ default:
+ Console.WriteLine("xrPollEvent: Unknown event {0}." , baseEventHeader->Type);
+ break;
+ }
+ }
+
+ }
+
+ ViewConfigurationProperties _viewportConfig;
+ private bool _Resumed;
+ private bool _Focused;
+ private bool _SessionActive;
+ SessionState _sessionState;
+
+ SessionState IOculusDeviceStrategy.SessionState { get { return _sessionState; } }
+
+ private unsafe void ovrApp_HandleSessionStateChangesReady(SessionState state)
+ {
+ //Debug.Assert(_Resumed);
+ Debug.Assert(_SessionActive == false);
+
+ Result xrResult;
+ xrResult = _oxrSession.BeginSession(_viewportConfig.ViewConfigurationType);
+ Debug.Assert(xrResult == Result.Success, "BeginSession");
+
+ _SessionActive = (xrResult == Result.Success);
+
+ // Set session state once we have entered VR mode and have a valid session object.
+ if (_SessionActive)
+ {
+ PerfSettingsLevelEXT cpuPerfLevel = PerfSettingsLevelEXT.SustainedHighExt;
+ switch (_CpuLevel)
+ {
+ case 0:
+ cpuPerfLevel = PerfSettingsLevelEXT.PowerSavingsExt;
+ break;
+ case 1:
+ cpuPerfLevel = PerfSettingsLevelEXT.SustainedLowExt;
+ break;
+ case 2:
+ cpuPerfLevel = PerfSettingsLevelEXT.SustainedHighExt;
+ break;
+ case 3:
+ cpuPerfLevel = PerfSettingsLevelEXT.BoostExt;
+ break;
+ default:
+ Console.WriteLine("Invalid CPU level {0}", _CpuLevel);
+ break;
+ }
+
+ PerfSettingsLevelEXT gpuPerfLevel = PerfSettingsLevelEXT.SustainedHighExt;
+ switch (_GpuLevel)
+ {
+ case 0:
+ gpuPerfLevel = PerfSettingsLevelEXT.PowerSavingsExt;
+ break;
+ case 1:
+ gpuPerfLevel = PerfSettingsLevelEXT.SustainedLowExt;
+ break;
+ case 2:
+ gpuPerfLevel = PerfSettingsLevelEXT.SustainedHighExt;
+ break;
+ case 3:
+ gpuPerfLevel = PerfSettingsLevelEXT.BoostExt;
+ break;
+ default:
+ Console.WriteLine("Invalid GPU level {0}", _GpuLevel);
+ break;
+ }
+
+ xrResult = _oxrInstance.GetInstanceProcAddr(
+ "xrPerfSettingsSetPerformanceLevelEXT",
+ out xrPerfSettingsSetPerformanceLevelEXT pfnPerfSettingsSetPerformanceLevelEXT);
+ Debug.Assert(xrResult == Result.Success, "xrPerfSettingsSetPerformanceLevelEXT");
+
+ xrResult = pfnPerfSettingsSetPerformanceLevelEXT(
+ _oxrSession.Session, PerfSettingsDomainEXT.CpuExt, cpuPerfLevel);
+ Debug.Assert(xrResult == Result.Success, "pfnPerfSettingsSetPerformanceLevelEXT");
+ xrResult = pfnPerfSettingsSetPerformanceLevelEXT(
+ _oxrSession.Session, PerfSettingsDomainEXT.GpuExt, gpuPerfLevel);
+ Debug.Assert(xrResult == Result.Success, "pfnPerfSettingsSetPerformanceLevelEXT");
+ }
+ }
+
+ private void ovrApp_HandleSessionStateChangesStopping(SessionState state)
+ {
+ Debug.Assert(_Resumed == false);
+ Debug.Assert(_SessionActive);
+
+ Result xrResult;
+
+ xrResult = _oxrSession.EndSession();
+ Debug.Assert(xrResult == Result.Success, "EndSession");
+ _SessionActive = false;
+ }
+
+ private unsafe void OnDisplayLost()
+ {
+ //OvrGraphicsLuid prevGraphicsLuid = _ovrSession.GraphicsLuid;
+
+ // destroy session
+ //_ovrSession.Dispose();
+ //_ovrSession = null;
+ //_ovrClient.Dispose();
+ //_ovrClient = null;
+
+ _deviceState = XRDeviceState.Disabled;
+ }
+
+ unsafe public struct ovrFramebuffer
+ {
+ public int Width;
+ public int Height;
+ public int Multisamples;
+
+ public uint TextureSwapChainLength;
+ public uint TextureSwapChainIndex;
+ public OxrSwapChain ColorSwapChain;
+
+ public SwapchainImageOpenGLESKHR[] ColorSwapChainImage;
+ public RenderTarget2D RenderTarget;
+ internal int[] FrameBuffers;
+ }
+
+ [StructLayout(LayoutKind.Explicit)]
+ public struct ovrCompositorLayer_Union
+ {
+ [FieldOffset(0)]
+ public CompositionLayerBaseHeader BaseHeader;
+
+ [FieldOffset(0)]
+ public CompositionLayerProjection Projection;
+ [FieldOffset(0)]
+ public CompositionLayerQuad Quad;
+ [FieldOffset(0)]
+ public CompositionLayerCylinderKHR Cylinder;
+ [FieldOffset(0)]
+ public CompositionLayerCubeKHR Cube;
+ [FieldOffset(0)]
+ public CompositionLayerEquirect2KHR Equirect2;
+
+ [FieldOffset(0)]
+ public CompositionLayerPassthroughFB PassthroughFB;
+ }
+
+ public unsafe delegate Result xrCreateFoveationProfileFBDelegate(Session session, FoveationProfileCreateInfoFB* profileCreateInfo, FoveationProfileFB* foveationProfile);
+ public unsafe delegate Result xrDestroyFoveationProfileFBDelegate(FoveationProfileFB* foveationProfile);
+ public unsafe delegate Result xrUpdateSwapchainFBDelegate(Swapchain ColorSwapChainHandle, SwapchainStateBaseHeaderFB* foveationUpdateState);
+
+ private unsafe bool ovrRenderer_SetFoveation(
+ ref ovrRenderer renderer,
+ FoveationLevelFB level,
+ float verticalOffset,
+ FoveationDynamicFB dynamic
+ )
+ {
+ Result xrResult;
+
+ xrResult = _oxrInstance.GetInstanceProcAddr("xrCreateFoveationProfileFB",
+ out xrCreateFoveationProfileFBDelegate Fun_pfnCreateFoveationProfileFB);
+ Debug.Assert(xrResult == Result.Success, "GetInstanceProcAddr");
+
+ xrResult = _oxrInstance.GetInstanceProcAddr("xrDestroyFoveationProfileFB",
+ out xrDestroyFoveationProfileFBDelegate Fun_pfnDestroyFoveationProfileFB);
+ Debug.Assert(xrResult == Result.Success, "GetInstanceProcAddr");
+
+ PfnVoidFunction pfnUpdateSwapchainFB;
+ xrResult = _oxrInstance.GetInstanceProcAddr("xrUpdateSwapchainFB",
+ out xrUpdateSwapchainFBDelegate Fun_pfnUpdateSwapchainFB);
+ Debug.Assert(xrResult == Result.Success, "GetInstanceProcAddr");
+
+ for (int eye = 0; eye < ovrMaxNumEyes; eye++)
+ {
+ FoveationLevelProfileCreateInfoFB levelProfileCreateInfo = new FoveationLevelProfileCreateInfoFB(StructureType.FoveationLevelProfileCreateInfoFB);
+ levelProfileCreateInfo.Level = level;
+ levelProfileCreateInfo.VerticalOffset = verticalOffset;
+ levelProfileCreateInfo.Dynamic = dynamic;
+
+ FoveationProfileCreateInfoFB profileCreateInfo = new FoveationProfileCreateInfoFB(StructureType.FoveationProfileCreateInfoFB);
+ profileCreateInfo.Next = &levelProfileCreateInfo;
+
+ FoveationProfileFB foveationProfile = default;
+ xrResult = Fun_pfnCreateFoveationProfileFB(_oxrSession.Session, &profileCreateInfo, &foveationProfile);
+ Debug.Assert(xrResult == Result.Success, "Fun_pfnCreateFoveationProfileFB");
+
+
+ SwapchainStateFoveationFB foveationUpdateState = new SwapchainStateFoveationFB(StructureType.SwapchainStateFoveationFB);
+ foveationUpdateState.Profile = foveationProfile;
+
+ xrResult = Fun_pfnUpdateSwapchainFB(
+ renderer.FrameBuffer[eye].ColorSwapChain.Swapchain,
+ (SwapchainStateBaseHeaderFB*)&foveationUpdateState);
+ Debug.Assert(xrResult == Result.Success, "Fun_pfnUpdateSwapchainFB");
+
+
+ //xrResult = Fun_pfnDestroyFoveationProfileFB(&foveationProfile);
+ //Debug.Assert(xrResult == Result.Success, "Fun_pfnDestroyFoveationProfileFB");
+ }
+
+ return false;
+ }
+
+ private int CreateDefaultLayer(GraphicsDevice graphicsDevice,
+ SurfaceFormat preferredFormat, DepthFormat preferredDepthFormat, int preferredMultiSampleCount,
+ int pixelsPerDisplayPixel
+ //,
+ //out OvrLayerEyeFov layer
+ )
+ {
+ int ovrResult = 0;
+
+ //OvrHmdDesc HmdDesc = _ovrSession.GetHmdDesc();
+
+ // create layer
+ //layer = default(OvrLayerEyeFov);
+ //layer.Header.Type = OvrLayerType.EyeFov;
+ //layer.Header.Flags = 0;
+
+ for (int eye = 0; eye < 2; eye++)
+ {
+ //OvrFovPort fov = HmdDesc.DefaultEyeFov[eye];
+ //OvrSizei texRes = _ovrSession.GetFovTextureSize((OvrEyeType)eye, fov, pixelsPerDisplayPixel);
+ //layer.Viewport[eye] = new OvrRecti(0, 0, texRes.W, texRes.H);
+
+ //ovrResult = ConcreteOvrSwapChainData.CreateSwapChain(
+ // graphicsDevice, _ovrSession,
+ // texRes.W, texRes.H,
+ // preferredFormat, preferredDepthFormat, preferredMultiSampleCount,
+ // out _swapChainData[eye]);
+ //layer.ColorTexture[eye] = _swapChainData[eye].SwapChain.NativePtr;
+
+ //OvrEyeRenderDesc renderDesc = _ovrSession.GetRenderDesc((OvrEyeType)eye, fov);
+ //layer.Fov[eye] = renderDesc.Fov;
+ //_hmdToEyePose[eye] = renderDesc.HmdToEyePose;
+ }
+
+ return 0;
+ }
+
+
+
+
+
+
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ if (_passthroughFB != null)
+ _passthroughFB.Dispose();
+ if (_passthroughLayerFB != null)
+ _passthroughLayerFB.Dispose();
+
+ if (_HeadSpace != null)
+ _HeadSpace.Dispose();
+ if (_LocalSpace != null)
+ _LocalSpace.Dispose();
+ if (_LocalFloorSpace != null)
+ _LocalFloorSpace.Dispose();
+ if (_FakeStageSpace != null)
+ _FakeStageSpace.Dispose();
+ if (_StageSpace != null)
+ _StageSpace.Dispose();
+
+ if (_leftControllerAimSpace != null)
+ _leftControllerAimSpace.Dispose();
+ if (_rightControllerAimSpace != null)
+ _rightControllerAimSpace.Dispose();
+ if (_leftControllerGripSpace != null)
+ _leftControllerGripSpace.Dispose();
+ if (_rightControllerGripSpace != null)
+ _rightControllerGripSpace.Dispose();
+ }
+
+ _passthroughFB = null;
+ _passthroughLayerFB = null;
+
+ _HeadSpace = null;
+ _LocalSpace = null;
+ _LocalFloorSpace = null;
+ _FakeStageSpace = null;
+ _StageSpace = null;
+ _CurrentStageSpace = null;
+
+ _leftControllerAimSpace = null;
+ _rightControllerAimSpace = null;
+ _leftControllerGripSpace = null;
+ _rightControllerGripSpace = null;
+
+ }
+
+ }
+
+
+ internal static class OpenXRExtensions
+ {
+ public static Vector3 ToVector3(this Vector3f value)
+ {
+ return new Vector3(value.X, value.Y, value.Z);
+ }
+
+ public static Quaternion ToQuaternion(this Quaternionf value)
+ {
+ return new Quaternion(value.X, value.Y, value.Z, value.W);
+ }
+
+ public static Pose3 ToPose3(this Posef value)
+ {
+ return new Pose3(
+ value.Orientation.ToQuaternion(),
+ value.Position.ToVector3()
+ );
+ }
+
+
+ public static Vector3f ToVector3f(this Vector3 value)
+ {
+ return new Vector3f(value.X, value.Y, value.Z);
+ }
+
+ public static Quaternionf ToQuaternionf(this Quaternion value)
+ {
+ return new Quaternionf(value.X, value.Y, value.Z, value.W);
+ }
+
+ public static Posef ToPosef(this Pose3 value)
+ {
+ return new Posef(
+ value.Orientation.ToQuaternionf(),
+ value.Translation.ToVector3f()
+ );
+ }
+
+ }
+}
\ No newline at end of file
diff --git a/Platforms/XR/.Oculus/ConcreteXRFactory.cs b/Platforms/XR/.Oculus/ConcreteXRFactory.cs
new file mode 100644
index 00000000000..8267013f25e
--- /dev/null
+++ b/Platforms/XR/.Oculus/ConcreteXRFactory.cs
@@ -0,0 +1,21 @@
+// Copyright (C)2024 Nick Kastellanos
+
+using System;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.XR;
+
+namespace Microsoft.Xna.Platform.XR
+{
+ public sealed class ConcreteXRFactory : XRFactory
+ {
+ public override XRDeviceStrategy CreateXRDeviceStrategy(string applicationName, IServiceProvider services)
+ {
+ return new ConcreteXRDevice(applicationName, services);
+ }
+
+ public override XRDeviceStrategy CreateXRDeviceStrategy(string applicationName, Game game)
+ {
+ return new ConcreteXRDevice(applicationName, game);
+ }
+ }
+}
diff --git a/Platforms/XR/.Oculus/lib/arm64-v8a/libopenxr_loader.so b/Platforms/XR/.Oculus/lib/arm64-v8a/libopenxr_loader.so
new file mode 100644
index 00000000000..dc2942cf1dd
Binary files /dev/null and b/Platforms/XR/.Oculus/lib/arm64-v8a/libopenxr_loader.so differ
diff --git a/README.md b/README.md
index d8fe14a9828..aed3968a134 100644
--- a/README.md
+++ b/README.md
@@ -20,7 +20,8 @@ We support a growing list of platforms across the desktop, mobile, and console s
* Windows 8.1 and up (OpenGL & DirectX)
* Windows Store Apps (UWP)
- * Oculus VR (OvrPC/DirectX)
+ * Oculus VR (OvrPC/DirectX/meta link)
+ * Oculus VR/AR (native/OpenGL)
* Linux (OpenGL)
* macOS 10.15 and up (OpenGL)
* Android 6.0 and up (OpenGL)
diff --git a/Templates/VisualStudio2022/ProjectTemplates/Oculus.NetCore/Activity.cs b/Templates/VisualStudio2022/ProjectTemplates/Oculus.NetCore/Activity.cs
new file mode 100644
index 00000000000..292f2c8cb3a
--- /dev/null
+++ b/Templates/VisualStudio2022/ProjectTemplates/Oculus.NetCore/Activity.cs
@@ -0,0 +1,39 @@
+using Android.App;
+using Android.Content.PM;
+using Android.OS;
+using Android.Views;
+
+namespace $safeprojectname$
+{
+ [Activity(Label = "$projectname$"
+ , MainLauncher = true
+ , Icon = "@drawable/icon"
+ , Theme = "@style/Theme.Splash"
+ , AlwaysRetainTaskState = true
+ , LaunchMode = LaunchMode.SingleInstance
+ , ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.Keyboard | ConfigChanges.KeyboardHidden | ConfigChanges.ScreenSize | ConfigChanges.ScreenLayout | ConfigChanges.UiMode | ConfigChanges.SmallestScreenSize
+ | ConfigChanges.Density | ConfigChanges.LayoutDirection | ConfigChanges.FontScale
+ , ScreenOrientation = ScreenOrientation.Landscape
+ , ExcludeFromRecents = true
+ , Exported = true
+ )]
+ [IntentFilter(new[] { Android.Content.Intent.ActionMain },
+ Categories = new[]
+ {
+ "org.khronos.openxr.intent.category.IMMERSIVE_HMD",
+ "com.oculus.intent.category.VR",
+ "android.intent.category.LAUNCHER"
+ }
+ )]
+ public class $safeprojectname$Activity : Microsoft.Xna.Framework.AndroidGameActivity
+ {
+ protected override void OnCreate(Bundle bundle)
+ {
+ base.OnCreate(bundle);
+ var game = new $safeprojectname$Game();
+ SetContentView((View)game.Services.GetService(typeof(View)));
+ game.Run();
+ }
+ }
+}
+
diff --git a/Templates/VisualStudio2022/ProjectTemplates/Oculus.NetCore/AndroidManifest.xml b/Templates/VisualStudio2022/ProjectTemplates/Oculus.NetCore/AndroidManifest.xml
new file mode 100644
index 00000000000..e524edb8fa0
--- /dev/null
+++ b/Templates/VisualStudio2022/ProjectTemplates/Oculus.NetCore/AndroidManifest.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Templates/VisualStudio2022/ProjectTemplates/Oculus.NetCore/Application.csproj b/Templates/VisualStudio2022/ProjectTemplates/Oculus.NetCore/Application.csproj
new file mode 100644
index 00000000000..414595f4c63
--- /dev/null
+++ b/Templates/VisualStudio2022/ProjectTemplates/Oculus.NetCore/Application.csproj
@@ -0,0 +1,60 @@
+
+
+
+ false
+ net8.0-android
+ $guid1$
+ Exe
+ True
+ partial
+ $safeprojectname$
+ $safeprojectname$
+ bin\$(Platform)\$(Configuration)\
+ $(DefineConstants);ANDROID
+ Android
+ com.companyname.$ext_safeprojectname$
+ 1
+ 1.0
+ .m4a
+
+
+
+ True
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Templates/VisualStudio2022/ProjectTemplates/Oculus.NetCore/Assets/AboutAssets.txt b/Templates/VisualStudio2022/ProjectTemplates/Oculus.NetCore/Assets/AboutAssets.txt
new file mode 100644
index 00000000000..ee398862952
--- /dev/null
+++ b/Templates/VisualStudio2022/ProjectTemplates/Oculus.NetCore/Assets/AboutAssets.txt
@@ -0,0 +1,19 @@
+Any raw assets you want to be deployed with your application can be placed in
+this directory (and child directories) and given a Build Action of "AndroidAsset".
+
+These files will be deployed with you package and will be accessible using Android's
+AssetManager, like this:
+
+public class ReadAsset : Activity
+{
+ protected override void OnCreate (Bundle bundle)
+ {
+ base.OnCreate (bundle);
+
+ InputStream input = Assets.Open ("my_asset.txt");
+ }
+}
+
+Additionally, some Android functions will automatically load asset files:
+
+Typeface tf = Typeface.CreateFromAsset (Context.Assets, "fonts/samplefont.ttf");
\ No newline at end of file
diff --git a/Templates/VisualStudio2022/ProjectTemplates/Oculus.NetCore/Content/Content.mgcb b/Templates/VisualStudio2022/ProjectTemplates/Oculus.NetCore/Content/Content.mgcb
new file mode 100644
index 00000000000..895694b660a
--- /dev/null
+++ b/Templates/VisualStudio2022/ProjectTemplates/Oculus.NetCore/Content/Content.mgcb
@@ -0,0 +1,15 @@
+
+#----------------------------- Global Properties ----------------------------#
+
+/outputDir:bin/$(Platform)
+/intermediateDir:obj/$(Platform)
+/platform:Android
+/config:
+/profile:Reach
+/compress:False
+
+#-------------------------------- References --------------------------------#
+
+
+#---------------------------------- Content ---------------------------------#
+
diff --git a/Templates/VisualStudio2022/ProjectTemplates/Oculus.NetCore/Directory.Build.props b/Templates/VisualStudio2022/ProjectTemplates/Oculus.NetCore/Directory.Build.props
new file mode 100644
index 00000000000..4d9b5eb6808
--- /dev/null
+++ b/Templates/VisualStudio2022/ProjectTemplates/Oculus.NetCore/Directory.Build.props
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/Templates/VisualStudio2022/ProjectTemplates/Oculus.NetCore/Game.cs b/Templates/VisualStudio2022/ProjectTemplates/Oculus.NetCore/Game.cs
new file mode 100644
index 00000000000..ff00af76358
--- /dev/null
+++ b/Templates/VisualStudio2022/ProjectTemplates/Oculus.NetCore/Game.cs
@@ -0,0 +1,169 @@
+using System;
+using System.Collections.Generic;
+using Microsoft.Xna.Framework;
+using Microsoft.Xna.Framework.Audio;
+using Microsoft.Xna.Framework.Media;
+using Microsoft.Xna.Framework.Graphics;
+using Microsoft.Xna.Framework.Input;
+using Microsoft.Xna.Framework.Input.Touch;
+using Microsoft.Xna.Framework.Input.XR;
+using Microsoft.Xna.Framework.XR;
+
+namespace $safeprojectname$
+{
+ ///
+ /// This is the main type for your game.
+ ///
+ public class $safeprojectname$Game : Game
+ {
+ GraphicsDeviceManager graphics;
+ XRDevice xrDevice;
+ SpriteBatch spriteBatch;
+
+ public $safeprojectname$Game()
+ {
+ graphics = new GraphicsDeviceManager(this);
+ Content.RootDirectory = "Content";
+
+ graphics.IsFullScreen = true;
+ graphics.SupportedOrientations = DisplayOrientation.LandscapeLeft | DisplayOrientation.LandscapeRight;
+
+ // OXR requires at least feature level 9.3.
+ graphics.GraphicsProfile = GraphicsProfile.HiDef;
+
+ // Syncronize to the headset refresh rate.
+ graphics.SynchronizeWithVerticalRetrace = true;
+ IsFixedTimeStep = false;
+ // 72Hz Frame rate for oculus.
+ TargetElapsedTime = TimeSpan.FromTicks(138888);
+
+ // We don't care is the main window is Focuses or not
+ // because we render on the XR rendertargets.
+ InactiveSleepTime = TimeSpan.FromSeconds(0);
+
+ xrDevice = new XRDevice("$safeprojectname$", this.Services);
+ }
+
+ ///
+ /// Allows the game to perform any initialization it needs to before starting to run.
+ /// This is where it can query for any required services and load any non-graphic
+ /// related content. Calling base.Initialize will enumerate through any components
+ /// and initialize them as well.
+ ///
+ protected override void Initialize()
+ {
+ // TODO: Add your initialization logic here
+
+ base.Initialize();
+
+ xrDevice.BeginSessionAsync(XRSessionMode.VR);
+ xrDevice.TrackFloorLevelAsync(true);
+ }
+
+ ///
+ /// LoadContent will be called once per game and is the place to load
+ /// all of your content.
+ ///
+ protected override void LoadContent()
+ {
+ // Create a new SpriteBatch, which can be used to draw textures.
+ spriteBatch = new SpriteBatch(GraphicsDevice);
+
+ // TODO: Use this.Content to load your game content here
+ }
+
+ ///
+ /// UnloadContent will be called once per game and is the place to unload
+ /// game-specific content.
+ ///
+ protected override void UnloadContent()
+ {
+ // TODO: Unload any non ContentManager content here
+ }
+
+ ///
+ /// Allows the game to run logic such as updating the world,
+ /// checking for collisions, gathering input, and playing audio.
+ ///
+ /// Provides a snapshot of timing values.
+ protected override void Update(GameTime gameTime)
+ {
+ MouseState mouseState = Mouse.GetState();
+ KeyboardState keyboardState = Keyboard.GetState();
+ GamePadState gamePadState = GamePad.GetState(PlayerIndex.One);
+ GamePadState touchControllerState = TouchController.GetState(TouchControllerType.Touch);
+
+ if (keyboardState.IsKeyDown(Keys.Escape) ||
+ keyboardState.IsKeyDown(Keys.Back) ||
+ gamePadState.Buttons.Back == ButtonState.Pressed)
+ {
+ try { Exit(); }
+ catch (PlatformNotSupportedException) { /* ignore */ }
+ }
+
+ // TODO: Add your update logic here
+
+ base.Update(gameTime);
+ }
+
+ ///
+ /// This is called when the game should draw itself.
+ ///
+ /// Provides a snapshot of timing values.
+ protected override void Draw(GameTime gameTime)
+ {
+ float aspect = GraphicsDevice.Viewport.AspectRatio;
+ Matrix view = Matrix.CreateLookAt(Vector3.Zero, Vector3.Forward, Vector3.Up);
+ Matrix projection = Matrix.CreatePerspectiveFieldOfView(1, aspect, 0.05f, 1000);
+
+ if (xrDevice.DeviceState == XRDeviceState.Enabled)
+ {
+
+ // Draw on XR headset.
+ xrDevice.BeginFrame();
+ try
+ {
+ HeadsetState headsetState = xrDevice.GetHeadsetState();
+
+ // Draw each eye on a rendertarget.
+ foreach (XREye eye in xrDevice.GetEyes())
+ {
+ RenderTarget2D rt = xrDevice.GetEyeRenderTarget(eye);
+ GraphicsDevice.SetRenderTarget(rt);
+
+ // Get XR view and projection.
+ view = headsetState.GetEyeView(eye);
+ projection = xrDevice.CreateProjection(eye, 0.05f, 1000);
+
+ // TODO: Add your drawing code here
+ if (xrDevice.SessionMode == XRSessionMode.AR)
+ GraphicsDevice.Clear(Color.Transparent);
+ else
+ GraphicsDevice.Clear(Color.CornflowerBlue);
+
+ base.Draw(gameTime);
+
+ // Resolve eye rendertarget.
+ GraphicsDevice.SetRenderTarget(null);
+ // Submit eye rendertarget.
+ xrDevice.CommitRenderTarget(eye, rt);
+ }
+ }
+ finally
+ {
+ // Submit XR frame.
+ int result = xrDevice.EndFrame();
+ }
+ return;
+ }
+
+ // Draw on the backbuffer.
+ GraphicsDevice.SetRenderTarget(null);
+
+ // TODO: Add your drawing code here
+ GraphicsDevice.Clear(Color.CornflowerBlue);
+
+ base.Draw(gameTime);
+ }
+ }
+}
diff --git a/Templates/VisualStudio2022/ProjectTemplates/Oculus.NetCore/Game.vstemplate b/Templates/VisualStudio2022/ProjectTemplates/Oculus.NetCore/Game.vstemplate
new file mode 100644
index 00000000000..085d9b25346
--- /dev/null
+++ b/Templates/VisualStudio2022/ProjectTemplates/Oculus.NetCore/Game.vstemplate
@@ -0,0 +1,54 @@
+
+
+ KNI Oculus Project (.net8)
+ A KNI game project for Oculus.
+ CSharp
+ KNI
+ csharp
+ android
+ games
+ KNI
+ XR
+ 1
+ 43201
+ true
+ Game
+ true
+ Enabled
+ true
+ TemplateIcon.png
+ true
+
+
+
+
+ Directory.Build.props
+ AndroidManifest.xml
+ Activity.cs
+ Game.cs
+
+
+ Content.mgcb
+
+
+
+ AboutAssets.txt
+
+
+
+ AboutResources.txt
+
+ Icon.png
+ Splash.png
+
+
+ Resource.Designer.cs
+
+ Strings.xml
+ Styles.xml
+
+
+
+
+
+
diff --git a/Templates/VisualStudio2022/ProjectTemplates/Oculus.NetCore/Resources/AboutResources.txt b/Templates/VisualStudio2022/ProjectTemplates/Oculus.NetCore/Resources/AboutResources.txt
new file mode 100644
index 00000000000..b0fc999bdb7
--- /dev/null
+++ b/Templates/VisualStudio2022/ProjectTemplates/Oculus.NetCore/Resources/AboutResources.txt
@@ -0,0 +1,44 @@
+Images, layout descriptions, binary blobs and string dictionaries can be included
+in your application as resource files. Various Android APIs are designed to
+operate on the resource IDs instead of dealing with images, strings or binary blobs
+directly.
+
+For example, a sample Android app that contains a user interface layout (Main.xml),
+an internationalization string table (Strings.xml) and some icons (drawable/Icon.png)
+would keep its resources in the "Resources" directory of the application:
+
+Resources/
+ Drawable/
+ Icon.png
+
+ Layout/
+ Main.axml
+
+ Values/
+ Strings.xml
+
+In order to get the build system to recognize Android resources, the build action should be set
+to "AndroidResource". The native Android APIs do not operate directly with filenames, but
+instead operate on resource IDs. When you compile an Android application that uses resources,
+the build system will package the resources for distribution and generate a class called
+"Resource" that contains the tokens for each one of the resources included. For example,
+for the above Resources layout, this is what the Resource class would expose:
+
+public class Resource {
+ public class Drawable {
+ public const int Icon = 0x123;
+ }
+
+ public class Layout {
+ public const int Main = 0x456;
+ }
+
+ public class String {
+ public const int FirstString = 0xabc;
+ public const int SecondString = 0xbcd;
+ }
+}
+
+You would then use Resource.Drawable.Icon to reference the Drawable/Icon.png file, or
+Resource.Layout.Main to reference the Layout/Main.axml file, or Resource.String.FirstString
+to reference the first string in the dictionary file Values/Strings.xml.
\ No newline at end of file
diff --git a/Templates/VisualStudio2022/ProjectTemplates/Oculus.NetCore/Resources/Drawable/Icon.png b/Templates/VisualStudio2022/ProjectTemplates/Oculus.NetCore/Resources/Drawable/Icon.png
new file mode 100644
index 00000000000..51ed3b63ba8
Binary files /dev/null and b/Templates/VisualStudio2022/ProjectTemplates/Oculus.NetCore/Resources/Drawable/Icon.png differ
diff --git a/Templates/VisualStudio2022/ProjectTemplates/Oculus.NetCore/Resources/Drawable/Splash.png b/Templates/VisualStudio2022/ProjectTemplates/Oculus.NetCore/Resources/Drawable/Splash.png
new file mode 100644
index 00000000000..fd05e395689
Binary files /dev/null and b/Templates/VisualStudio2022/ProjectTemplates/Oculus.NetCore/Resources/Drawable/Splash.png differ
diff --git a/Templates/VisualStudio2022/ProjectTemplates/Oculus.NetCore/Resources/Resource.Designer.cs b/Templates/VisualStudio2022/ProjectTemplates/Oculus.NetCore/Resources/Resource.Designer.cs
new file mode 100644
index 00000000000..492318024b9
--- /dev/null
+++ b/Templates/VisualStudio2022/ProjectTemplates/Oculus.NetCore/Resources/Resource.Designer.cs
@@ -0,0 +1,99 @@
+#pragma warning disable 1591
+//------------------------------------------------------------------------------
+//
+// This code was generated by a tool.
+// Runtime Version:4.0.30319.42000
+//
+// Changes to this file may cause incorrect behavior and will be lost if
+// the code is regenerated.
+//
+//------------------------------------------------------------------------------
+
+[assembly: global::Android.Runtime.ResourceDesignerAttribute("$safeprojectname$.Resource", IsApplication=true)]
+
+namespace $safeprojectname$
+{
+
+
+ [System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "1.0.0.0")]
+ public partial class Resource
+ {
+
+ static Resource()
+ {
+ global::Android.Runtime.ResourceIdManager.UpdateIdValues();
+ }
+
+ public static void UpdateIdValues()
+ {
+ }
+
+ public partial class Attribute
+ {
+
+ static Attribute()
+ {
+ global::Android.Runtime.ResourceIdManager.UpdateIdValues();
+ }
+
+ private Attribute()
+ {
+ }
+ }
+
+ public partial class Drawable
+ {
+
+ // aapt resource value: 0x7f020000
+ public const int Icon = 2130837504;
+
+ // aapt resource value: 0x7f020001
+ public const int Splash = 2130837505;
+
+ static Drawable()
+ {
+ global::Android.Runtime.ResourceIdManager.UpdateIdValues();
+ }
+
+ private Drawable()
+ {
+ }
+ }
+
+ public partial class String
+ {
+
+ // aapt resource value: 0x7f030001
+ public const int ApplicationName = 2130903041;
+
+ // aapt resource value: 0x7f030000
+ public const int Hello = 2130903040;
+
+ static String()
+ {
+ global::Android.Runtime.ResourceIdManager.UpdateIdValues();
+ }
+
+ private String()
+ {
+ }
+ }
+
+ public partial class Style
+ {
+
+ // aapt resource value: 0x7f040000
+ public const int Theme_Splash = 2130968576;
+
+ static Style()
+ {
+ global::Android.Runtime.ResourceIdManager.UpdateIdValues();
+ }
+
+ private Style()
+ {
+ }
+ }
+ }
+}
+#pragma warning restore 1591
diff --git a/Templates/VisualStudio2022/ProjectTemplates/Oculus.NetCore/Resources/Values/Strings.xml b/Templates/VisualStudio2022/ProjectTemplates/Oculus.NetCore/Resources/Values/Strings.xml
new file mode 100644
index 00000000000..7bfcdd3f89e
--- /dev/null
+++ b/Templates/VisualStudio2022/ProjectTemplates/Oculus.NetCore/Resources/Values/Strings.xml
@@ -0,0 +1,4 @@
+
+
+ $safeprojectname$
+
diff --git a/Templates/VisualStudio2022/ProjectTemplates/Oculus.NetCore/Resources/Values/Styles.xml b/Templates/VisualStudio2022/ProjectTemplates/Oculus.NetCore/Resources/Values/Styles.xml
new file mode 100644
index 00000000000..51021340133
--- /dev/null
+++ b/Templates/VisualStudio2022/ProjectTemplates/Oculus.NetCore/Resources/Values/Styles.xml
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/Templates/VisualStudio2022/ProjectTemplates/Oculus.NetCore/TemplateIcon.png b/Templates/VisualStudio2022/ProjectTemplates/Oculus.NetCore/TemplateIcon.png
new file mode 100644
index 00000000000..23893d2cafe
Binary files /dev/null and b/Templates/VisualStudio2022/ProjectTemplates/Oculus.NetCore/TemplateIcon.png differ
diff --git a/default.build b/default.build
index 4fdf3ee6cfc..4d1b95b0459 100644
--- a/default.build
+++ b/default.build
@@ -40,6 +40,7 @@
+
@@ -58,6 +59,7 @@
+
@@ -82,6 +84,7 @@
+
@@ -169,6 +172,11 @@
+
+
+
+
+