|
| 1 | +# Static vs Input Bindings |
| 2 | + |
| 3 | +## Overview |
| 4 | + |
| 5 | +Silk.NET has multiple ways to access the underlying APIs, either through a static method (e.g. `GL.GenBuffers`) or |
| 6 | +through an "API object" (e.g. created with `GL.Create` and then accessed as `gl.GenBuffers`). Not all APIs are the |
| 7 | +same, and some are better accessed through one method or the other. Note that Silk.NET does make both available however, |
| 8 | +so if you're prefer consistency feel free to pick one method and stick to it, though you may result in some minor |
| 9 | +inefficiencies. |
| 10 | + |
| 11 | +All native APIs are accessed using a "function pointer" - a location in memory at which the native code resides. |
| 12 | +This is typically fetched using `DllImport`, but some APIs require custom mechanisms. An example in OpenGL, where you |
| 13 | +must use a "context API" (e.g. WGL, GLX, EGL, etc) to create a context and, after setting up that context state, use a |
| 14 | +function provided by that context API to get the function pointers for OpenGL (e.g. `wglGetProcAddress`). We refer to |
| 15 | +these as "stateful APIs" in this document. Note that OpenGL is not the only stateful API, this will be elaborated later. |
| 16 | + |
| 17 | +Stateless APIs are those where the function pointers aren't contingent on any other state, and they're effectively |
| 18 | +accessed as if they were static functions. For these, the function pointers are typically retrieved through `DllImport`, |
| 19 | +but this needn't preclude other APIs from having bespoke mechanisms to statelessly retrieve function pointers (though, |
| 20 | +there are no examples of this today). |
| 21 | + |
| 22 | +When using a stateful API, you should use API objects if possible i.e. `API.Create`, and dispose of that object when you |
| 23 | +are done using that API. When using a stateless API, you should use the static functions exposed directly on the API |
| 24 | +class. |
| 25 | + |
| 26 | +> [!NOTE] |
| 27 | +> Future releases of Silk.NET are intended to contain analysers to indicate the correct access method. |
| 28 | +
|
| 29 | +Below is a description of the stateful APIs. All other APIs not listed here are, or can be treated as, stateless. |
| 30 | + |
| 31 | +## OpenGL |
| 32 | + |
| 33 | +OpenGL is a stateful API because it requires a context to be created and "made current" on that thread before function |
| 34 | +pointers can be retrieved. Typically, this context is created using Silk.NET.Windowing, and the functions would be |
| 35 | +retrieved using `surface.OpenGL.GetProcAddress` in that example. |
| 36 | + |
| 37 | +To create an API object, our OpenGL bindings provide a utility function `CreateOpenGL`: |
| 38 | +```csharp |
| 39 | +IGL gl = null!; |
| 40 | +surface.Created += _ => |
| 41 | +{ |
| 42 | + gl = surface.CreateOpenGL(); |
| 43 | + // Use gl functions here... |
| 44 | + gl.Flush(); |
| 45 | +} |
| 46 | +surface.Render += _ => |
| 47 | +{ |
| 48 | + // Use gl functions here... |
| 49 | + gl.Clear(ClearBufferMask.ColorBufferBit); |
| 50 | +} |
| 51 | +``` |
| 52 | + |
| 53 | +If you'd prefer to use static methods despite OpenGL being stateful, the static functions on `GL` will forward to |
| 54 | +`GL.ThisThread`, which essentially forwards to a thread-specific `IGL` instance. You can change the `IGL` instance used |
| 55 | +by a thread using `GL.ThisThread.MakeCurrent`, which will also implicitly make the `IGLContext` you pass it current |
| 56 | +(if applicable). Note that Silk.NET.Windowing will implicitly call this, so you can use the static OpenGL functions in |
| 57 | +the obvious way, albeit with the implied indirection through `GL.ThisThread`: |
| 58 | +```csharp |
| 59 | +surface.Created += _ => |
| 60 | +{ |
| 61 | + GL.Flush(); |
| 62 | +} |
| 63 | +surface.Render += _ => |
| 64 | +{ |
| 65 | + GL.Clear(ClearBufferMask.ColorBufferBit); |
| 66 | +} |
| 67 | +``` |
| 68 | + |
| 69 | +> [!CAUTION] |
| 70 | +> TODO: Silk.NET.Windowing does not currently do this! `surface.MakeCurrent()` must be used in `surface.Created` to make |
| 71 | +> this happen. |
| 72 | + |
| 73 | +## Vulkan |
| 74 | + |
| 75 | +Vulkan is a stateful API because its function pointers are dependent on the `InstanceHandle` and `DeviceHandle` being |
| 76 | +used. Our Vulkan bindings intercept calls to `Vk.CreateInstance` and `Vk.CreateDevice`, and set `CurrentInstance` and |
| 77 | +`CurrentDevice` respectively on the Vulkan API object for later use with `Vk.GetDeviceProcAddr` and |
| 78 | +`Vk.GetInstanceProcAddr`. |
| 79 | + |
| 80 | +The Vulkan API object will first try `vkGetDeviceProcAddr` to load a function pointer (where the value for `device` is |
| 81 | +as in `CurrentDevice`), followed by `vkGetInstanceProcAddr` (where the value for `instance` is as in `CurrentInstance`). |
| 82 | +For `vkGetInstanceProcAddr` itself, `DllImport` is used. |
| 83 | + |
| 84 | +`CurrentInstance` is set upon a successful call to `vkCreateInstance`, and `CurrentDevice` is set upon a successful call |
| 85 | +to `vkCreateDevice`. Note that it is illegal to change these values on an API object if they're already set, if you have |
| 86 | +scenarios requiring multiple instance-device combinations you must create multiple API objects. |
| 87 | + |
| 88 | +> [!TIP] |
| 89 | +> In cases where you have one instance from which multiple devices are created, simply clone the `IVk` object using |
| 90 | +> `IVk.Clone()` prior to **any device** being created. This will reuse the function pointers already loaded for that |
| 91 | +> instance. |
| 92 | + |
| 93 | +If you'd prefer to use static methods despite Vulkan being stateful, the static functions on `Vk` will forward to |
| 94 | +`Vk.ThisThread`, which essentially forwards to a thread-specific `IVk` instance. You can change the `IVk` instance used |
| 95 | +by a thread using `Vk.ThisThread.MakeCurrent`. The static functions, much like using a single `IVk` instance, will throw |
| 96 | +if multiple instance-device combinations are used on the same thread without changing the `IVk` object being used. |
| 97 | + |
| 98 | +## OpenXR |
| 99 | + |
| 100 | +OpenXR has the same caveats as Vulkan but with `CurrentInstance` only. |
| 101 | + |
| 102 | +## OpenAL |
| 103 | + |
| 104 | +OpenAL has the same caveats as OpenGL, with the exception that `alGetProcAddress` is made available to retrieve the |
| 105 | +function pointers. This still has the requirement of a thread-specific context, however, which can be made current using |
| 106 | +`AL.ThisThread.MakeCurrent`. |
| 107 | + |
| 108 | +Unlike OpenGL, OpenAL has an official context API: OpenAL Context (ALC). `alcMakeContextCurrent` will implicitly call |
| 109 | +`AL.ThisThread.MakeCurrent` meaning that the static OpenAL functions are made available in the obvious way: |
| 110 | + |
| 111 | +```csharp |
| 112 | +// NOTE: We are making use of ALContext's static functions here as well. |
| 113 | +// The same applies as if `ALContext.Create` were used. |
| 114 | +DeviceHandle device = ALContext.OpenDevice(""); |
| 115 | +if (device == nullptr) throw new("failed to create device"); |
| 116 | + |
| 117 | +ContextHandle context = ALContext.CreateContext(device, nullptr); |
| 118 | +if (context == nullptr) throw new("failed to create context"); |
| 119 | + |
| 120 | +// Now make the context current. This implicitly calls AL.ThisThread.MakeCurrent |
| 121 | +ALContext.MakeContextCurrent(context); |
| 122 | + |
| 123 | +// Static functions now just work. |
| 124 | +var source = AL.GenSource(); |
| 125 | +``` |
| 126 | + |
| 127 | +## OpenAL Context (ALContext/ALC) |
| 128 | + |
| 129 | +ALC has the same caveats as Vulkan, given that the ALC function pointers are tied to a specific device. As a result, |
| 130 | +`alcOpenDevice` is intercepted to set the value of `CurrentDevice` to then be fed into `alcGetProcAddress` (or |
| 131 | +`alcGetProcAddress2` if available). |
0 commit comments