Skip to content

Commit b21cefd

Browse files
committed
Add helper for making XrGraphicsBindingOpenGL* instances, start XR demo
1 parent b848fc8 commit b21cefd

File tree

14 files changed

+1240
-51
lines changed

14 files changed

+1240
-51
lines changed

Silk.NET.sln

Lines changed: 228 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<OutputType>Exe</OutputType>
5+
<TargetFramework>net5.0</TargetFramework>
6+
<RootNamespace>OpenGL_VR_Demo</RootNamespace>
7+
<Nullable>enable</Nullable>
8+
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
9+
</PropertyGroup>
10+
11+
<ItemGroup>
12+
<ProjectReference Include="..\..\..\..\src\OpenGL\Silk.NET.OpenGL\Silk.NET.OpenGL.csproj" />
13+
<ProjectReference Include="..\..\..\..\src\OpenXR\Extensions\Silk.NET.OpenXR.Extensions.EXT\Silk.NET.OpenXR.Extensions.EXT.csproj" />
14+
<ProjectReference Include="..\..\..\..\src\OpenXR\Extensions\Silk.NET.OpenXR.Extensions.KHR\Silk.NET.OpenXR.Extensions.KHR.csproj" />
15+
<ProjectReference Include="..\..\..\..\src\OpenXR\Silk.NET.OpenXR\Silk.NET.OpenXR.csproj" />
16+
<ProjectReference Include="..\..\..\..\src\Windowing\Silk.NET.Windowing\Silk.NET.Windowing.csproj" />
17+
</ItemGroup>
18+
19+
</Project>
Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Collections.Generic;
6+
using System.Diagnostics;
7+
using System.Runtime.InteropServices;
8+
using Silk.NET.Core;
9+
using Silk.NET.Core.Native;
10+
using Silk.NET.Maths;
11+
using Silk.NET.OpenGL;
12+
using Silk.NET.OpenXR;
13+
using Silk.NET.OpenXR.Extensions.EXT;
14+
using Silk.NET.OpenXR.Extensions.KHR;
15+
using Silk.NET.Windowing;
16+
17+
namespace OpenGL_VR_Demo.OpenXR
18+
{
19+
public abstract class OpenGLXRGame : IDisposable
20+
{
21+
// API Objects for accessing OpenXR and OpenGL
22+
public XR Xr;
23+
public GL Gl;
24+
25+
// ExtDebugUtils is a handy OpenXR debugging extension which we'll enable if available unless told otherwise.
26+
public bool? IsDebugUtilsSupported;
27+
public ExtDebugUtils? ExtDebugUtils;
28+
29+
// Hooking OpenXR up to graphics APIs requires specialized extensions. OpenGL and OpenGLES have separate ones,
30+
// but we'll make variables for both so we can support both.
31+
public KhrOpenglEnable? GlEnable;
32+
public KhrOpenglEsEnable? GlesEnable;
33+
public bool IsGles;
34+
public bool UseMinimumVersion;
35+
36+
// Maintain a list of extensions we're using. Both for sanity and so we can tell OpenXR about them when creating
37+
// the instance.
38+
protected List<string> Extensions = new();
39+
40+
// Our windowing objects! We're using IView so we can potentially expand into mobile later.
41+
public IView View;
42+
public string Name;
43+
44+
// OpenXR handles
45+
public Instance Instance;
46+
public DebugUtilsMessengerEXT MessengerExt;
47+
public System System;
48+
49+
protected OpenGLXRGame(string name, bool forceNoDebug = false, bool useMinimumVersion = false)
50+
{
51+
Name = name;
52+
if (forceNoDebug)
53+
{
54+
IsDebugUtilsSupported = false;
55+
}
56+
57+
UseMinimumVersion = useMinimumVersion;
58+
}
59+
60+
public void Run()
61+
{
62+
}
63+
64+
/// <summary>
65+
/// A simple function which throws an exception if the given OpenXR result indicates an error has been raised.
66+
/// </summary>
67+
/// <param name="result">The OpenXR result in question.</param>
68+
/// <returns>
69+
/// The same result passed in, just in case it's meaningful and we just want to use this to filter out errors.
70+
/// </returns>
71+
/// <exception cref="Exception">An exception for the given result if it indicates an error.</exception>
72+
internal static Result CheckResult(Result result)
73+
{
74+
if ((int) result < 0)
75+
{
76+
throw new($"OpenXR raised an error! Code: {result} ({result:X})");
77+
}
78+
79+
return result;
80+
}
81+
82+
/// <summary>
83+
/// A function which checks if the extension with the given name is available, and adds it to the list of
84+
/// requested <see cref="Extensions"/> if so, returning true; or just returns false otherwise.
85+
/// </summary>
86+
/// <param name="name">The extension name to check for and request.</param>
87+
/// <returns>Whether the extension was present or not.</returns>
88+
private bool TryRequestExtension(string name)
89+
{
90+
// Check if OpenXR supports the extension.
91+
if (Xr.IsInstanceExtensionPresent(null, name))
92+
{
93+
// It does! Add it to our list of requested extensions, for use when creating the instance later...
94+
Extensions.Add(name);
95+
return true;
96+
}
97+
98+
// Oh dear! Not supported.
99+
return false;
100+
}
101+
102+
private unsafe void PrepareOpenXR()
103+
{
104+
// Create our API object for OpenXR.
105+
Xr = XR.GetApi();
106+
107+
// If forceNoDebug is specified in the constructor, IsDebugUtilsSupported will already be false so we won't
108+
// request the extension whatsoever. Otherwise, request the extension!
109+
IsDebugUtilsSupported ??= TryRequestExtension(ExtDebugUtils.ExtensionName);
110+
111+
// Let's request desktop GL first.
112+
if (!TryRequestExtension(KhrOpenglEnable.ExtensionName))
113+
{
114+
// No desktop GL? What about OpenGLES?
115+
if (!TryRequestExtension(KhrOpenglEsEnable.ExtensionName))
116+
{
117+
throw new PlatformNotSupportedException
118+
(
119+
"Neither OpenGL nor OpenGLES is supported by OpenXR on this platform!"
120+
);
121+
}
122+
123+
// I guess we're using OpenGLES from now on!
124+
IsGles = true;
125+
}
126+
127+
// Before we do anything, OpenXR needs to know a little about our application.
128+
var appInfo = new ApplicationInfo
129+
{
130+
ApiVersion = new Version64(1, 0, 9) // this is the OpenXR version number this demo is written against
131+
};
132+
133+
// We've got to marshal our strings and put them into global, immovable memory. To do that, we use
134+
// SilkMarshal.
135+
SilkMarshal.StringIntoSpan(Name, MemoryMarshal.CreateSpan(ref appInfo.ApplicationName[0], 128));
136+
SilkMarshal.StringIntoSpan("Silk.NET Examples", MemoryMarshal.CreateSpan(ref appInfo.EngineName[0], 128));
137+
var requestedExtensions = SilkMarshal.StringArrayToPtr(Extensions);
138+
var instanceCreateInfo = new InstanceCreateInfo
139+
(
140+
applicationInfo: appInfo,
141+
enabledExtensionCount: (uint) Extensions.Count,
142+
enabledExtensionNames: (byte**) requestedExtensions
143+
);
144+
145+
// Now we're ready to make our instance!
146+
CheckResult(Xr.CreateInstance(in instanceCreateInfo, ref Instance));
147+
148+
// If debug utils is supported, enable it!
149+
if (IsDebugUtilsSupported.Value && Xr.TryGetInstanceExtension(null, Instance, out ExtDebugUtils))
150+
{
151+
// This local function is called by OpenXR. There are a lot of advanced things you can do with the data
152+
// you get in DebugUtilsMessengerCallbackDataEXT, such as inspecting objects, but for now we're just
153+
// going to use the debug messenger as a simple OpenXR logger.
154+
static uint OnDebug
155+
(
156+
DebugUtilsMessageSeverityFlagsEXT severity,
157+
DebugUtilsMessageTypeFlagsEXT type,
158+
DebugUtilsMessengerCallbackDataEXT* data,
159+
void* userData
160+
)
161+
{
162+
var severityString = severity
163+
.ToString()["DebugUtilsMessageSeverity".Length..^"BitExt".Length]
164+
.ToUpper();
165+
166+
var typeString = type.ToString()["DebugUtilsMessageType".Length..^"BitExt".Length];
167+
168+
// Marshal OpenXR's byte* back to C# strings
169+
var msgString = SilkMarshal.PtrToString((nint) data->Message);
170+
var idString = SilkMarshal.PtrToString((nint) data->MessageId);
171+
172+
Console.WriteLine($"[{severityString}] {typeString}: {msgString} ({idString})");
173+
return 0;
174+
}
175+
176+
// Now that we've defined the callback, let's tell OpenXR about it and create our messenger!
177+
var debugUtilsCreateInfo = new DebugUtilsMessengerCreateInfoEXT
178+
(
179+
messageSeverities: DebugUtilsMessageSeverityFlagsEXT.DebugUtilsMessageSeverityErrorBitExt |
180+
DebugUtilsMessageSeverityFlagsEXT.DebugUtilsMessageSeverityWarningBitExt |
181+
DebugUtilsMessageSeverityFlagsEXT.DebugUtilsMessageSeverityInfoBitExt,
182+
messageTypes: DebugUtilsMessageTypeFlagsEXT.DebugUtilsMessageTypeGeneralBitExt |
183+
DebugUtilsMessageTypeFlagsEXT.DebugUtilsMessageTypeConformanceBitExt |
184+
DebugUtilsMessageTypeFlagsEXT.DebugUtilsMessageTypePerformanceBitExt |
185+
DebugUtilsMessageTypeFlagsEXT.DebugUtilsMessageTypeValidationBitExt,
186+
userCallback: (DebugUtilsMessengerCallbackFunctionEXT) OnDebug
187+
);
188+
189+
if (ExtDebugUtils!.CreateDebugUtilsMessenger(Instance, in debugUtilsCreateInfo, ref MessengerExt)
190+
!= Result.Success)
191+
{
192+
Console.WriteLine("[WARNING] Application: Failed to create OpenXR debug messenger!");
193+
}
194+
}
195+
196+
// For our benefit, let's log some information about the instance we've just created.
197+
InstanceProperties properties = new();
198+
CheckResult(Xr.GetInstanceProperties(Instance, ref properties));
199+
200+
var runtimeName = SilkMarshal.PtrToString((nint) properties.RuntimeName);
201+
var runtimeVersion = ((Version) (Version64) properties.RuntimeVersion).ToString(3);
202+
203+
Console.WriteLine($"[INFO] Application: Using OpenXR Runtime \"{runtimeName}\" v{runtimeVersion}");
204+
205+
// We're creating a head-mounted-display (HMD, i.e. a VR headset) example, so we ask for a runtime which
206+
// supports that form factor. The response we get is a ulong that is the System ID.
207+
var systemId = 0UL;
208+
var getInfo = new SystemGetInfo(formFactor: FormFactor.HeadMountedDisplay);
209+
CheckResult(Xr.GetSystem(Instance, in getInfo, ref systemId));
210+
211+
// Let our System abstraction take it from here!
212+
System = new(this, systemId);
213+
}
214+
215+
private void PrepareView()
216+
{
217+
}
218+
}
219+
}
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
using System;
5+
using System.Diagnostics;
6+
using Silk.NET.Core;
7+
using Silk.NET.Maths;
8+
using Silk.NET.OpenGL;
9+
using Silk.NET.OpenXR;
10+
using Silk.NET.Windowing;
11+
using static OpenGL_VR_Demo.OpenXR.OpenGLXRGame;
12+
using Action = System.Action;
13+
14+
namespace OpenGL_VR_Demo.OpenXR
15+
{
16+
public class Renderer : IDisposable
17+
{
18+
private IView _view;
19+
private OpenGLXRGame _game;
20+
21+
// OpenXR handles
22+
public Session Session;
23+
public Space ReferenceSpace;
24+
25+
public GL Gl { get; private set; }
26+
public XR Xr { get; init; }
27+
public bool IsStage { get; private set; }
28+
29+
public Renderer(OpenGLXRGame game)
30+
{
31+
_game = game;
32+
33+
Xr = game.Xr;
34+
35+
var opts = ViewOptions.Default;
36+
opts.VSync = false;
37+
opts.FramesPerSecond = 0;
38+
opts.PreferredBitDepth = new(8, 8, 8, 8);
39+
opts.PreferredDepthBufferBits = 24;
40+
if (game.IsGles)
41+
{
42+
GraphicsRequirementsOpenGLESKHR esReqs = new();
43+
CheckResult
44+
(game.GlesEnable!.GetOpenGlesgraphicsRequirements(game.Instance, game.System.Id, ref esReqs));
45+
var version = (Version64) (game.UseMinimumVersion
46+
? esReqs.MinApiVersionSupported
47+
: esReqs.MaxApiVersionSupported);
48+
opts.API = new
49+
(
50+
ContextAPI.OpenGLES, ContextProfile.Core,
51+
Debugger.IsAttached ? ContextFlags.Debug : ContextFlags.Default,
52+
new((int) version.Major, (int) version.Minor)
53+
);
54+
}
55+
else
56+
{
57+
GraphicsRequirementsOpenGLKHR reqs = new();
58+
CheckResult(game.GlEnable!.GetOpenGlgraphicsRequirements(game.Instance, game.System.Id, ref reqs));
59+
var version = (Version64) (game.UseMinimumVersion
60+
? reqs.MinApiVersionSupported
61+
: reqs.MaxApiVersionSupported);
62+
opts.API = new
63+
(
64+
ContextAPI.OpenGL, ContextProfile.Core,
65+
Debugger.IsAttached ? ContextFlags.Debug : ContextFlags.Default,
66+
new((int) version.Major, (int) version.Minor)
67+
);
68+
}
69+
70+
if (Window.IsViewOnly)
71+
{
72+
_view = Window.GetView(opts);
73+
}
74+
else
75+
{
76+
var wOpts = new WindowOptions(opts)
77+
{
78+
Position = -Vector2D<int>.One,
79+
Size = new
80+
((int) (game.System.RenderTargetSize.X / ((float) game.System.RenderTargetSize.Y / 720)), 720)
81+
};
82+
83+
_view = Window.Create(wOpts);
84+
}
85+
86+
_view.Load += ViewOnLoad;
87+
}
88+
89+
public event Action<Renderer>? Load;
90+
public event Action<double>? Render;
91+
public event Action? Unload;
92+
93+
private void ViewOnLoad()
94+
{
95+
Gl = _view.CreateOpenGL();
96+
Load?.Invoke(this);
97+
}
98+
99+
private unsafe void PrepareSession()
100+
{
101+
var binding = _game.IsGles ? _view.CreateOpenGLESBinding() : _view.CreateOpenGLBinding();
102+
var sessionCreateInfo = new SessionCreateInfo(systemId: _game.System.Id, next: &binding);
103+
CheckResult(Xr.CreateSession(_game.Instance, in sessionCreateInfo, ref Session));
104+
105+
// Now, let's create the reference space that describes the real-world environment where the VR game is
106+
// being played.
107+
108+
// First we need to check what kinds of spaces we have. OpenXR (OOTB) has:
109+
// - view, where the content doesn't take into account eye orientation.
110+
// - local, where the content knows about the user's eye orientation but not about their physical location.
111+
// i'll refer to this as "seated-scale", as you're unlikely to move in a seat and this is what the spec
112+
// calls it.
113+
// - stage, where the content knows about both eye orientation and physical location. this can be used for
114+
// "room-scale" and "standing-scale" content, where the user's physical movements affect the content.
115+
var numRefSpaces = 0u;
116+
CheckResult(Xr.EnumerateReferenceSpaces(Session, numRefSpaces, ref numRefSpaces, null));
117+
Span<ReferenceSpaceType> refSpaces = stackalloc ReferenceSpaceType[(int) numRefSpaces];
118+
CheckResult(Xr.EnumerateReferenceSpaces(Session, ref numRefSpaces, refSpaces));
119+
120+
var refSpaceCreateInfo = new ReferenceSpaceCreateInfo();
121+
foreach (var referenceSpaceType in refSpaces)
122+
{
123+
if (referenceSpaceType == ReferenceSpaceType.Stage)
124+
{
125+
refSpaceCreateInfo.ReferenceSpaceType = ReferenceSpaceType.Stage;
126+
IsStage = true;
127+
}
128+
}
129+
130+
if (!IsStage)
131+
{
132+
// use seated-scale instead. all OpenXR runtimes MUST support seated-scale content.
133+
refSpaceCreateInfo.ReferenceSpaceType = ReferenceSpaceType.Local;
134+
}
135+
136+
CheckResult(Xr.CreateReferenceSpace(Session, in refSpaceCreateInfo, ref ReferenceSpace));
137+
138+
// Next up, the swapchain.
139+
}
140+
141+
public void Run() => _view.Run();
142+
143+
}
144+
}

0 commit comments

Comments
 (0)