diff --git a/documentation/proposals/(Superseded) Proposal - Enhanced Input Events.md b/documentation/proposals/(Superseded) Proposal - Enhanced Input Events.md new file mode 100644 index 0000000000..94c27e66c7 --- /dev/null +++ b/documentation/proposals/(Superseded) Proposal - Enhanced Input Events.md @@ -0,0 +1,168 @@ +# Summary +Proposal for adding important missing functionality to input, as well as other enhancements. + +# Contributors +- ThomasMiz + +# Current Status +- [x] Proposed +- [x] Discussed with API Review Board (ARB) +- [ ] Approved +- [ ] Implemented + +# Design Decisions +- Event parameters will be turned into readonly structures to prevent long parameter lists in some cases and allow more parameters (such as deltas) to be added in the future without breaking. + +# Proposed API +The only API changes will be to the events presented by IMouse, IKeyboard and possibly other device interfaces. + +## Enums + +#### KeyModifiers +Based on [the modifier keys flag from GLFW](https://www.glfw.org/docs/latest/group__mods.html). +```cs +[Flags] +public enum KeyModifiers +{ + Shift = 1 << 0, + Control = 1 << 1, + Alt = 1 << 2, + Super = 1 << 3, + CapsLock = 1 << 4, + NumLock = 1 << 5 +} +``` + +## Structs + +#### KeyDownEvent +```cs +public readonly struct KeyDownEvent +{ + public IKeyboard Keyboard { get; } + public Key Key { get; } + public int KeyCode { get; } + public KeyModifiers Modifiers { get; } + public bool IsRepeat { get; } + + public KeyDownEvent(IKeyboard keyboard, Key key, int keyCode, KeyModifiers modifiers, bool isRepeat); +} +``` + +#### KeyUpEvent +```cs +public readonly struct KeyUpEvent +{ + public IKeyboard Keyboard { get; } + public Key Key { get; } + public int KeyCode { get; } + public KeyModifiers Modifiers { get; } + + public KeyUpEvent(IKeyboard keyboard, Key key, int keyCode, KeyModifiers modifiers); +} +``` + +#### KeyCharEvent +```cs +public readonly struct KeyCharEvent +{ + public IKeyboard Keyboard { get; } + public char Character { get; } + public int KeyCode { get; } + + public KeyCharEvent(IKeyboard keyboard, char character, int keyCode); +} +``` + +#### MouseMoveEvent +```cs +public readonly struct MouseMoveEvent +{ + public IMouse Mouse { get; } + public Vector2 Position { get; } + public Vector2 Delta { get; } + + public MouseMoveEvent(IMouse mouse, Vector2 position, Vector2 delta); +} +``` + +#### MouseButtonEvent +```cs +public readonly struct MouseButtonEvent +{ + public IMouse Mouse { get; } + public Vector2 Position { get; } + public MouseButton Button { get; } + public KeyModifiers Modifiers { get; } + + public MouseButtonEvent(IMouse mouse, Vector2 position, MouseButton button, KeyModifiers modifiers); +} +``` + +#### MouseScrollEvent +```cs +public readonly struct MouseScrollEvent +{ + public IMouse Mouse { get; } + public Vector2 Position { get; } + public Vector2 WheelPosition { get; } + public Vector2 Delta { get; } + + public MouseScrollEvent(IMouse mouse, Vector2 position, Vector2 wheelPosition, Vector2 delta); +} +``` + +#### MouseClickEvent +```cs +public readonly struct MouseClickEvent +{ + public IMouse Mouse { get; } + public Vector2 Position { get; } + public MouseButton Button { get; } + public KeyModifiers Modifiers { get; } + + public MouseClickEvent(IMouse mouse, Vector2 position, MouseButton button, KeyModifiers modifiers) +} +``` + +## Interface changes + +#### IKeyboard +```cs +public interface IKeyboard : IInputDevice +{ + // The old events get removed: + // event Action KeyDown; + // event Action KeyUp; + // event Action KeyChar; + + // KeyDown reports key down and key repeats + event Action KeyDown; + event Action KeyUp; + + event Action KeyChar; +} +``` + +#### IMouse +```cs +public interface IMouse : IInputDevice +{ + // The old events get removed: + // event Action MouseMove; + // event Action MouseDown; + // event Action MouseUp; + // event Action Scroll; + // event Action Click; + // event Action DoubleClick; + + event Action MouseMove; + event Action MouseDown; + event Action MouseUp; + event Action Scroll; + event Action Click; + event Action DoubleClick; +} +``` + +These changes can also be applied to other IDevices to keep consistency across our API. diff --git a/documentation/proposals/Proposal - 3.0 & 3.X Software Development Plan.md b/documentation/proposals/Proposal - 3.0 & 3.X Software Development Plan.md new file mode 100644 index 0000000000..d2bd7d7cfd --- /dev/null +++ b/documentation/proposals/Proposal - 3.0 & 3.X Software Development Plan.md @@ -0,0 +1,201 @@ +# Summary + +3.0 software development plan & ongoing monthly update, breaking change, and support policy for 3.X. + +# Contributors +- Dylan P (@Perksey) + +# Current Status +- [x] Proposed +- [x] Discussed with Working Group +- [x] Approved +- [ ] Implemented + +# Silk.NET 3.0 + +## Goals of 3.0 + +The key tenets of 3.0 are **portability**, **maintainability**, **usability**, and **performance**. To this end, the following objectives have been identified: +- Use .NET 6 - the first version of modern .NET to run on the majority of our desired target platforms + - (tenet: portability) +- Allow Silk.NET's rich abstractions to be integrated into other frameworks rather than being completely standalone. + - WPF, WinForms, MAUI, Avalonia (tenet: usability) +- Rewrite windowing to be more portable and facilitate true write-once-run-everywhere. + - For more information, see [the Windowing 3.0 proposal](Proposal%20-%20Windowing%203.0.md). (tenet: portability) +- Remove the bulk of our bindings generation code in favour of more mature alternatives + - For more information, see [the SilkTouch 3.0 proposal](Proposal%20-%20Generation%20of%20Library%20Sources%20and%20PInvoke%20Mechanisms.md). (tenet: maintainability) +- Accelerate our maths library using SIMD hardware intrinsics + - For more information, see [the Vectorization SIMD proposal](Proposal%20-%20Vectorization%20-%20SIMD.md). (tenet: performance) +- Redesign our input library to work in multiple scenarios and environments, as well as be less prone to breaking changes. + - For more information, see [the Multi-Backend Input proposal](Proposal%20-%20Multi-Backend%20Input.md). (tenet: usability) + +Silk.NET 3.0 presents us with an opportunity to rethink the entire library taking into account everything we've learnt over the past 2 years of the project's development. + +## Development Roadmap + +Note that this development roadmap does not take into account unit tests, only functional tests such as experiments. The team should of course strive to add as many tests as possible where possible. + +### 3.0 Preview 1 + +Before we can do anything, we need to get our brand new generators up and running. In this version: +- The Scraper works as a minimum viable product. It has minimal support for adding extra attributes for invoking overloaders. + - The Khronos APIs in particular will likely be incomplete compared to 2.X in this version. +- The Emitter works completely as intended. +- The Overloader works as a minimum viable product. It doesn't necessarily implement all overloads specified yet. +- Windowing and Input are implemented for desktop platforms, and have received initial testing. +- No development on Maths for this preview. +- Exclusive support for .NET 6 + +3.0 Preview 1 is not a production-ready preview and is very experimental. + +### 3.0 Preview 2 + +Now that we've got an initial preview out to show what our aims are, we can start refining everything. In this version: +- Bugfixes from 3.0 Preview 1 +- The Scraper has near-complete support for adding extra attributes for invoking overloaders. +- The Overloader has more overloads implemented. All generic overloads should be implemented by now, but some API-specific overloads may not be implemented. +- Android support has been restored for Windowing and Input, and have received initial testing on this platform. +- No development on Maths for this preview. + +3.0 Preview 2 is not a production-ready preview and is very experimental. + +### 3.0 Preview 3 + +By this preview, the groundwork has been established for 3.0 and we should ensure that all of our goals have ample progress towards the end product. In this version: +- Bugfixes from 3.0 Preview 2 +- The Scraper is complete. +- The Overloader is complete. +- iOS support has been added for Windowing and Input, and have received initial testing on this platform. +- If time permits, a start has been made on the SIMD APIs in Maths. No work has been done on integrating it into the other Maths types. + +3.0 Preview 3 is not a production-ready preview and is very experimental. + +### 3.0 Preview 4 + +This is the first "production-ready" preview and we want users to start integrating into their workloads, so we need to make sure good progress has been made to all goals for the 3.0 update and as many forseeable breaking changes as possible done. In this version: +- Bugfixes from 3.0 Preview 3 +- Windowing integrations for WPF and WinForms have been developed and have at least basic OpenGL support. The support may not be the most high performance possible at this time. +- SIMD APIs in Maths have been complete, and work has started to integrate them into the other Maths types in the most common cases. +- Ample work has been done to migrate 2.0 code to 3.0 code to evaluate differences in public API, fixing them where we deem necessary. + +3.0 Preview 4 is a production-ready preview and users are encouraged to start integrating this preview into their code. + +### 3.0 Preview 5 + +This is the last preview and is primarily a bugfix release. All breaking changes should've been done in previous previews, but if this is not the case all forseeable breaking must be 100% done in this preview. In this version: +- Bugfixes from 3.0 Preview 4 +- A windowing integration for MAUI has been developed and has at least basic OpenGL(ES) support in a state that is as high-performance and as smoothed-out as possible. +- If time permits, a windowing integration for Avalonia has been developed and has at least basic OpenGL(ES) support. If there is not enough time, this can be pushed to 3.X. +- SIMD APIs should be integrated into Maths in as many common cases as possible. Ongoing performance improvements may be done in 3.X. + +## Problems identified in past development + +- We have severely lacking documentation + - The intention is that all developers of large amounts of code write implementation documentation and/or "orientation guides" for their codebases informing readers of all major things there is to know in their code. + - We should also write documentation containing examples on using as many features of the surface APIs as possible \[for high level utilities\] + - We will enforce XML documentation in all manually-written utilities and as much as possible in bindings. + - If time permits, we should productionize our website powered by Statiq + our custom API reference generator. +- There's not a lot of planning + - We have solved this in the form of the proposal you are reading and all linked proposals: getting all the design done now and documented now, to prevent design debates later down the line. This should reduce friction when actually working on the library. + - We have been keeping the working group and key stakeholders in the loop with the 3.0 kickoff (again, see this proposal you are reading) + - The team are trying to communicate with eachother and figure out how to distribute work among themselves depending on individual circumstances and free time + - Codeowners have been established +- Barrier to entry for external contributors is very high + - Documentation should help with this. + - We should at least consider introducing something like stylecop to ensure code is readable and easy to navigate. + - We should look to make a general repo "orientation guide" teaching prospective contributors where they can find to expect what codebases. + - Hopefully we can pick up some external contributors along the way so _they_ can tell _us_ how to improve? +- Our level of correctness is inconsistent + - We should use .NET 5 enhanced warnings to help combat this. + - Our adoption of C# 8 nullability should be at a much greater extent than it is today, and not using nullability should require great justification. + +## Documentation Regime + +Documentation on how to use the surface API we expose for our High Level Utilities should be plentiful, and include examples for all of the common usecases of our libraries, if not more. The `documentation` folder will be structured as follows: + +``` +documentation + assets + branding + deprecation-notices + for-contributors + generators + input + maths + proposals + 1.0 + 1.x + 2.0 + 2.x + 3.0 + 3.x + rejected + windowing + generators + input + maths + windowing +``` + +The `documentation/for-contributors` folder will be used to document the implementation specifics, such as structure and implementation design philosophy, to help prospective contributors understand the library internals. + +The `documentation/assets` folder just contains images and other assets for the front page README.md. This folder was renamed from `documentation/readme`. + +The `documentation/branding` folder is a new folder containing all branding images for Silk.NET. + +The `documentation/deprecation-notices` is as it is today. + +The `documentation/for-contributors/proposals` folder, once the 3.0 proposals have been reviewed and signed-off by the Working Group, is as the `documentation/proposals` folder is today but slightly refactored to better organise the proposals and make it more clear which proposals concern which versions. + +All other folders will contain documentation targeted at users for using specific areas of the library. This can include surface API explanations, minimal code examples, and more: basically anything to make the usage of our library clearer to our users. + +# Silk.NET 3.X + +## Monthly Updates + +Silk.NET has been proven to excel at binding to OpenGL with games and applications such as [Project Hedra](https://projecthedra.com), a game made by @maxilevi; and [a clone of The Settlers](https://github.com/Pyrdacor/Freeserf.Net) made by @Pyrdacor. + +One thing we want to place an emphasis on is our commitment to actually keeping Silk.NET up-to-date. The schedule will be that on the **first Friday of the month** the bindings will be regenerated and a patch released containing all the changes since the last patch. + +We have a lot of bindings by now and the libraries we bind to change all the time. As such, monthly updates are critical to ensure our bindings are regenerated and are as up-to-date as possible. Bugfixes found over the month will be swept up in these monthly updates. + +### Emergency Patches + +If a bug is determined (agreed upon by the majority of maintainers) to be causing massive disruption to the point where the library is borderline unusable in some or all use cases of the library with a considerable proportion of the userbase affected, an out-of-cycle "emergency patch" may be issued on any other Friday between updates. + +### Versioning + +Any post-3.0, pre-4.0 release will be versioned as follows: +- The major version will always be 3 +- The minor version will be the number of the monthly update cycle i.e. the first monthly update will be versioned 3.1, the second 3.2, etc... +- The patch version will always be 0, unless it is an emergency patch in which case it'll be the number of the emergency patch i.e. if an emergency patch is required after the first monthly update the version will be 3.1.1, if another one is required (heaven forbid) in this same cycle it'll be 3.1.2 etc... +- The revision version will always be 0. + +Users are expected to keep all of the versions of all Silk.NET packages they are using in-sync. We could write a Roslyn analyser or MSBuild target to help push users to this. + +### Breaking Changes + +If an API is determined (agreed upon by the majority of maintainers) to be causing massive disruption or widespread confusion among a considerable proportion of the userbase, the Silk.NET team may reserve the right to make a breaking change in a post-3.0, pre-4.0 update as part of a monthly update cycle. This class of breaking changes shouldn't be done in an emergency patch unless the API issue in question was introduced in the then-current monthly update cycle (e.g. we need to quickly remove an API because it's super problematic for lots of people) + +Breaking changes in generated sources caused by changes in a third-party/external source the generated sources are generated from are allowed. + +ABI breaks may be allowed, but should be deferred unless absolutely necessary, so long as they are not source breaking - benign given all versions are in-sync, the only scenarios which could be affect is reflection. + +Additive changes which introduce a break are forbidden in a post-3.0, pre-4.0 update. + +### Support + +There are currently no plans to officially support anything but the latest monthly update i.e. the end-of-life date of a particular update is as soon as the next monthly update is released. Users are expected to be aware or made aware of the monthly update schedule and plan their work and/or support needs accordingly. + +Individual developers on the team may diverge from this, but they will be responsible for any support they give outside of this notice. If this changes and the Silk.NET team opt to introduce another support option, this proposal (or a future proposal which supersedes this one) will be updated accordingly and discussed with the Working Group. + +# Meeting Notes + +## 25/02/2022 + +[Video](https://youtu.be/dac3t0oh3VU?t=529) + +- Approved. +- Support Eto.Forms? + - Not really used or requested compared to the others, maybe as a community thing. +- There were some questions about the bindings libraries and how the generator differences are going to be consolidated. \ No newline at end of file diff --git a/documentation/proposals/Proposal - Generation of Library Sources and PInvoke Mechanisms.md b/documentation/proposals/Proposal - Generation of Library Sources and PInvoke Mechanisms.md new file mode 100644 index 0000000000..5393371f67 --- /dev/null +++ b/documentation/proposals/Proposal - Generation of Library Sources and PInvoke Mechanisms.md @@ -0,0 +1,342 @@ +# Summary +Proposal design for a platform invoke (P/Invoke) mechanism for Silk.NET 3.0. + +# Contributors +- Dylan Perks (@Perksey) +- Kai Jellinghaus (@HurricanKai) + +# Current Status +- [x] Proposed +- [x] Discussed with Working Group (WG) +- [x] Approved +- [ ] Implemented + +# Design Decisions +- This proposal builds on the foundations laid out by Silk.NET's move to source generators in 2.0, and the introduction of the SilkTouch source generator as a result of this. +- This proposal assumes no knowledge of Silk.NET 2.0's SilkTouch. +- This takes the knowledge and insight gained during development of SilkTouch, and uses it to create a new set of generators which incorporate lessons learnt. +- This proposal breaks down the generator process into three distinct stages: + +## SilkTouch Scraper + +The Scraper is responsible for creating partial classes from some input source. It is a drop-in replacement for what BuildTools does today. Instead of doing all the parsing and interpretation of the input source ourselves, the proposed Scraper will instead use only C headers and have a "preburner" for gathering minimal metadata to feed into the generation process. + +The generation process of the proposed Scraper will be entirely different. Silk.NET will no longer do any parsing and interpretation of C headers or XML of C headers, instead we will delegate this to the ClangSharp P/Invoke Generator in the form of a "subagent" (a separate process spun up by the Scraper), adding appropriate modifications to ClangSharp P/Invoke Generator as necessary. This means that we will no longer be using the Khronos XML registries for generating bindings. Instead, we'll use the preburner stage to use the XML registry only to gather minimal metadata instructing the ClangSharp subagent to add metadata attributes to certain functions, parameters, or types; which will then be picked up on by the later stages of SilkTouch. An example of such metadata would be parsing the flow and len XML attributes to add appropriate C# attributes to influence overload generation. + +This also naturally makes us entirely dependent on an external dependency, but I propose we work with Microsoft as much as possible to add the functionality we need in the least breaking way possible, and in a way that satisfies both us and Microsoft. All designs for such modifications will be formalized in the ClangSharp repo. Should we fail to do this, we'll maintain a fork so we can still benefit from improvements made upstream, while giving ourselves the freedom to add the functionality we need. + +Microsoft have already stated that they're happy to work with us to get Silk.NET using ClangSharp, one maintainer even saying they're happy to add a CI test stage into the ClangSharp repo to ensure no incoming changes break Silk.NET's generation process. + +There is no required behaviour for the Scraper (due to a lot of unknowns at the moment) other than it **MUST** invoke ClangSharp to generate C# Emitter-compatible classes, and it **MUST** add appropriate attributes to invoke the Overloader stage according to any metadata available from Khronos XML if applicable. + +## SilkTouch Emitter +The Emitter, one of the two final stages whose resources **MUST** be entirely independent of eachother, is responsible for generating the actual indirect calls for performing the P/Invoke. + +The Emitter operates on partial methods, the behaviour of the implementations of which depending on the context in which it's used. + +All attributes **MUST** be name matched only, to allow the user to define these themselves and not create a hard dependency on a specific Silk.NET library such as the Silk.NET Core. + +Candidate methods for implementation by the Emitter **MUST** be partial and not have an implementation part yet. Their containing types **MUST** also be partial. + +The Emitter **MUST** be able to be invoked via the SilkTouch CLI and **MAY** be able to be invoked via an incremental Roslyn source generator. + +### Call Styles + +The Emitter's primary purpose is to load and use function pointers in a native library sourced from an operating system's kernel, though this doesn't necessarily have to be the case. This logic will be emitted by the Emitter itself, and will not require an external dependency like the Silk.NET Core. However, this logic will no longer be implicit. + +#### Built-in: Dynamic-Link Library Call Style + +Consider the following example: +```cs +[UseDynamicLibrary("glfw3.dll")] +public partial class Glfw +{ + public partial void glfwInit(); +} +``` + +The `UseDynamicLibrary` attribute instructs the Emitter that it **MUST** use [`DllImport`](https://docs.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.dllimportattribute?view=net-5.0) to access native functions. [`NativeLibrary`](https://docs.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.nativelibrary?view=net-5.0) can be used to modify the libsuperrary loading process per other requirements defined below. For the entry point, the function name **MUST** be used unless a `NativeApi` attribute is provided, in which case the `EntryPoint` indicated by that attribute **MUST** be used. + +Consider the following example: +```cs +[UseDynamicLibrary("glfw3.dll", "libglfw3.dylib")] +public partial class Glfw { /* ... */ } +``` + +`UseDynamicLibrary` **MUST** be able to be specified on either a function or type. + +The Emitter **MUST** allow multiple candidate library names and cycle through each candidate until one loads successfully. + +#### Built-in: Static-Link Library Call Style + +Consider the following example: +```cs +[UseStaticLibrary] +public partial class Glfw +{ + public partial void glfwInit(); +} +``` + +The `UseStaticLibrary` attribute instructs the Emitter that it **MUST** use [`DllImport`](https://docs.microsoft.com/en-us/dotnet/api/system.runtime.interopservices.dllimportattribute?view=net-5.0) to access native functions. `__Internal` **MUST** be used for the library name. For the entry point, the function name **MUST** be used unless a `NativeApi` attribute is provided, in which case the `EntryPoint` indicated by that attribute **MUST** be used. + +`UseStaticLibrary` **MUST** be able to be specified on either a function or type. + +Consider the following example: +```cs +#if __IOS__ +[UseStaticLibrary] +#endif +[UseDynamicLibrary("glfw3.dll")] +public partial class Glfw +{ + public partial void glfwInit(); +} +``` + +The Emitter **MUST** only generate code if all preprocessor directives guarding the `UseStaticLibrary` attribute evaluate to true. If the attribute is defined on both the function and the containing type, the preprocessor directives surrounding the function's attribute **MUST** be used instead of the preprocessor directives surrounding the type. + +Note to the reader: Given preprocessor directives are processed at parse time in Roslyn, both of those last requirements are basically benign. + +#### Custom Call Style: Procedure Address Expressions + +Custom code may be used as a call style by providing a pointer to SilkTouch using a Procedure Address Expression. This is useful in scenarios such as COM interop. + +Procedure Address Expressions are C# expressions that **MUST** evaluate to a `void*`, `nint`, or `IntPtr`. This is the actual address in memory of the function being invoked. + +Consider the following example: +```cs +public partial struct IUnknown +{ + public void** LpVtbl; + [UseExpression("LpVtbl[1]")] + public partial uint AddRef(); +} +``` + +`GetProcAddress` indicates that the C# code given **MUST** be used as the Procedure Address Expression to retrieve the function address to call. Any arbritrary code can be inserted into this attribute, so long as the result of the code once evaluated meets the Procedure Address Expression definition. For example, this is valid: +```cs +public partial struct IUnknownNullableContainer +{ + public IUnknownPtr? Value; + [UseExpression("Value.GetValueOrDefault().InnerValue->LpVtbl[1]")] + public partial uint AddRef(); +} + +public struct IUnknownPtr +{ + public IUnknown* InnerValue; +} + +public struct IUnknown +{ + public void** LpVtbl; +} +``` + +The Emitter **SHOULD** implicitly parenthesise the expression given in the attribute. + +Unless another call style is applicable, the Emitter **MUST** mandate that every function has a `UseExpression` (Procedure Address Expression) specified. + +The Emitter **MUST** call the function pointer returned by the Procedure Address Expression as part of this call style. + +#### Custom Call Style: Procedure Address Methods + +A level more abstracted than Procedure Address Expressions, which allows any custom code to retrieve a function pointer; Procedure Address Methods work similarly to the native library call style from an API perspective, but function similarly to the Procedure Address Expressions call style. + +The aim of this call style is to provide flexibility without comprimising code readability. Consider the following example: + +```cs +[UseMethod(nameof(GetProcAddressShim))] +public partial class Glfw +{ + [UseDynamicLibrary("glfw3.dll")] + public partial nint glfwGetProcAddress(byte* str); + + // shim to convert the string, which SilkTouch needs to use, to a byte pointer - THIS IS NOT A MODEL EXAMPLE! + private nint GetProcAddressShim(string str) => glfwGetProcAddress((byte*) Marshal.StringToHGlobalAnsi(str)); + + public partial void glBegin(uint mode); +} +``` + +Procedure Address Methods are method groups (or an otherwise callable expression) within the scope of the method that **MUST** return a `void*`, `nint`, or `IntPtr` when invoked. This is the actual address in memory of the function being invoked. + +Procedure Address Methods **MUST** take one parameter of type `string`. + +For the parameter passed into the callable specified in the attribute, the function name **MUST** be used unless the `EntryPoint` property in the `NativeApi` attribute is provided, in which case the `EntryPoint` indicated by the attribute **MUST** be used. + +The Emitter **MUST** call the function pointer returned by the Procedure Address Method as part of this call style. + +#### Call Style Priority + +Consider the following example: + +```cs +[UseDynamicLibrary("glfw3.dll")] +public partial class Glfw +{ + public partial nint glfwGetProcAddress(byte* str); + [UseExpression("glfwGetProcAddress((byte*)Unsafe.AsPointer(ref Unsafe.AsRef(0x006e696765426c67)))")] + public partial void glBegin(uint mode); +} +``` + +Here, a class using the Dynamic Library call style has a method which does not follow the call style defined at the class level, and is overridden using a `UseExpression` attribute. + +If multiple call styles are applicable, the following order of preference **MUST** be respected: +- Procedure Address Expressions +- Procedure Address Methods +- Static-Link Library +- Dynamic-Link Library + +Function-level attributes **MUST** be preferred over type-level ones, and follow the same order of preference. + +### Native Calls +For the most part, the resultant native signature used by the Emitter is matched 1:1 with the method signature. However there are certain modifications you can apply. Namely, the `NativeApi` attribute will allow specification of specific calling conventions. For example: + +```cs +[NativeApi(Conventions = new[]{typeof(CallConvMemberFunction), typeof(CallConvSuppressGCTransition)}] +public partial D3D12_HEAP_PROPERTIES GetCustomHeapProperties(uint nodeMask, D3D12_HEAP_TYPE heapType); +``` + + +`Conventions` will be used as the primary mechanism for customizing the behaviour of generation, just as `NativeApi` will be used as the primary attribute for this as well. The behaviour of each bit will be described in documentation comments in the Proposed API section. + +The Emitter does not do any marshalling. As such, the Emitter **MUST** mandate that every parameter and return type of every function fits the `unmanaged` constraint. For the readers benefit, this can be done using a property on `ITypeSymbol` in Roslyn. + +## SilkTouch Overloader +The Overloader, one of the two final stages whose resources **MUST** be entirely independent of eachother, creates overloads of functions that expose a more user-friendly interface than the function it overloads, and do appropriate marshalling to lower the parameter types used down to the original function's types. + +The Overloader **MUST** be able to be used on any function and not be tied to any of the Emitter's constraints. + +The Overloader **MUST** be able to be invoked via the SilkTouch CLI and **MAY** be able to be invoked via an incremental Roslyn source generator. + +The Overloader does not care about existing methods. If the Overloader generates an overload that also happens to exist manually, it is the user's repsonsibility to disable the relevant overloads for these cases. + +However, if the Overloader thinks that the overload it's generating may conflict with another overload or the original function, it **SHOULD** output the overload as an extension method rather than a method within the containing type, unless the original method is static in which case it **MUST** discard the overload and generate a warning. It **SHOULD** also do this if the containing type is not partial. + +The Silk.NET team does not wish to specify the functionality of the overloader at this time, and wishes to instead define this by experimenting with the overloader's functionality during development; with the understanding that the Silk.NET team must formalize a proposal with the working group before a "go live" release ships. + +# Proposed API +- Here you do some code blocks, this is the heart and soul of the proposal. DON'T DO ANY IMPLEMENTATIONS! Just declarations. + +## `UseDynamicLibrary` +```cs +namespace Silk.NET.Core +{ + [AttributeUsage(AttributeTargets.Function | AttributeTargets.Class)] + public class UseDynamicLibraryAttribute : Attribute + { + public UseDynamicLibrary(string libraryName, params string[] alternativeNames); + public string LibraryName { get; } + public string[] AlternativeNames { get; } + } +} +``` + +## `UseStaticLibrary` +```cs +namespace Silk.NET.Core +{ + [AttributeUsage(AttributeTargets.Function | AttributeTargets.Class)] + public class UseStaticLibraryAttribute : Attribute + { + } +} +``` + +## `UseExpression` +```cs +namespace Silk.NET.Core +{ + [AttributeUsage(AttributeTargets.Function | AttributeTargets.Class)] + public class UseExpressionAttribute : Attribute + { + public UseExpressionAttribute(string expr); + public string Expression { get; } + } +} +``` + +## `UseMethod` +```cs +namespace Silk.NET.Core +{ + [AttributeUsage(AttributeTargets.Function | AttributeTargets.Class)] + public class UseMethodAttribute : Attribute + { + public UseMethodAttribute(string expr); + public string Expression { get; } + } +} +``` + +## `NativeApi` +```cs +namespace Silk.NET.Core +{ + public class NativeApiAttribute : Attribute + { + public string EntryPoint { get; set; } + public Type[] Conventions { get; set; } + } +} +``` + +# Meeting Notes + +## 05/08/2021 + +- SilkTouch for 2.0 is very hard to use +- A lot of code +- Will explode the repo a lot, but will also improve compile times because everything's already there and no need to generate at compile time +- ClangSharp is used by win32metadata (official c#, rust bindings) and generally accurate for parsing header files + - very correct, battle tested, more reliable than BuildTools 2.0 +- Just use ReadOnlySpan (implicit conversion from string) + - does our userbase know this? +- Too many overloads could cause confusion/lack of visibility + - promote "best practice" + - include exposed native api +- Only overload what we determine as "best practice", discourage per-parameter overloading + - One permutation per "overload style"? i.e. one function with all spans + - Special "intermediary" types don't really make sense as it loses compile-time safety and has other compiler-level issues +- Establish a baseline of overloads + - Scrap ArrayOverloads + - Scrap RefOverloads +- Group? i.e. only create overloads based on a particular style +- **Overloader needs more review/work, postpone to another meeting** + - number of overloads is a big concern right now + +## 25/02/2022 + +[Video](https://youtu.be/dac3t0oh3VU?t=7616) + +- Approved (call conv modifier discussion notwithstanding), but we must come back to the overloader - it's a bit too early to decide on something solid as there's no perfect rule that we know of to generate them at this time - we can only get this through experimentation! +- Why use an enum and custom attribute rather than reusing UnmanagedCallConv and the typeof(CallConv*) types that C#/.NET have standardized on for moving forward? + - We don't really have control over those types. + - For \[contrived\] example, what if we want a JavaScript calling convention? + - We can't just hack up a "calling convention" the runtime doesn't support, `MemberFunction` for example was just something that _happened_ to work on the Windows ABI + - We could use CallModifiers to, for example, influence codegen to call into IJsInProcessRuntime and call JavaScript code - this isn't necessarily _just_ an ABI-based concept. It could support other scenarios. + - It makes more sense to separate these, as these are sort of associated with DllImport and that side of the calling process, and a "JavaScript" convention concept (as discussed before). + - It's difficult to represent right now, because we have the native API attribute + - **We could/should change this to use the CallConv types instead** + - Direct advantages + - As the runtime versions in the future, it will continue to add CallConv types. These types are the official way moving forward to represent any calling convention information for the rutnime going forward. + - SilkTouch has to go out of this way to map this and understand this anyway, unless we just have the types then we can specify them as-is - SilkTouch doesn't even need to understand these. + - "\[DllImport\] is effectively magic" - @tannergooding + - **Just change Modifiers to a CallConv type array** +- The overload problem does need to be solved in some way. Some functions have absurd amounts of overloads (particularly in assimp) + - We want to scrap a bunch of overloads as well. A lot of this is only generating a bunch of "important" overloads. + - Was there consideration for a source generator approach to opt-in to the friendliest variant that they want? + - Yes, kind of. We don't have a formal proposal as we only just thought of this today. + - We need to bake the most basic overloads into the assembly itself. + - We'd like to have a source generator. + - We want SilkTouch to be productized, and find a way to remap types per their liking and use overloads etc. + - We should experiment with this and report back in a future community meeting. + +**ACTIONS** +- [x] Change `Modifiers` to a CallConv\* `Type` array + +**FUTURE** +- [ ] Report back to the Community our findings in experimenting with overloads diff --git a/documentation/proposals/Proposal - Multi-Backend Input.md b/documentation/proposals/Proposal - Multi-Backend Input.md new file mode 100644 index 0000000000..aa13242989 --- /dev/null +++ b/documentation/proposals/Proposal - Multi-Backend Input.md @@ -0,0 +1,901 @@ +# Summary +Proposal API for backend-agnostic, refactored Input via keyboards, mice, and controllers. + +# Contributors +- Dylan Perks (@Perksey) + +# Current Status +- [x] Proposed +- [x] Discussed with Working Group +- [x] Approved +- [ ] Implemented + +# Design Decisions +- This proposal, while aiming to keep the same look and feel as the 2.0 API, is a complete redesign of the Input API with the following goals: + - Input backends can be added and removed at will to a central input context object. + - All input devices, regardless of what backends they come from, can be accessed from a central input context object. + - Should the Silk.NET team design more APIs for more devices (for example VR hands using OpenXR) in the future, this can be added in a non-breaking way. + - All input devices instead of exposing properties only expose a single `State` property, with additional properties and/or methods for writable state (i.e. vibration motors, mouse position). This also addresses comments from the Working Group on previous iterations of this proposal, where the Working Group expressed interest in the state being immutable and trivially capturable. + - Ensuring the input API is accessible and easy to use. + - Addressing APIs that the userbase frequently had issues/distaste with. For example, events are no longer on each individual device, rather they are aggregated in a list and accessed from a single entry-point. +- This proposal aims to keep the look and feel of the Input APIs intended for general consumption similar to that of 2.0. +- This proposal incorporates/supersedes the Enhanced Input Events proposal. +- This proposal assumes knowledge of the Windowing 3.0 proposal. +- This proposal assumes knowledge of Silk.NET.Core 2.0. + +Please note that text marked **INFORMATIVE TEXT** does not form part of this proposal's requirements or proposed API, and is merely text to help the reader understand our intentions. Informative text is not well-defined, and may not reflect the actual implementation. + +Note that the parties in this proposal are: +- **developer**: The developer using Silk.NET. +- **end user**: The user using an application using Silk.NET, and by extension the user inputting data into the application via Silk.NET. + +Cases where the **user** word is used without the **end** prefix can be assumed to be referring to the **developer** rather than the **end user**. + +# Usage Examples +```cs +IWindowHandlesSource someWindow = null!; +var inputContext = someWindow.CreateInput(); +inputContext.Update(); +inputContext.Gamepads.ThumbstickMove += @event => +{ + Console.WriteLine($"Thumbstick {@event.Index} moved from {@event.Value - @event.Delta} to {@event.Value}"); +}; +var isButtonDown = inputContext.Gamepads.Any(gamepadState => gamepadState.Buttons[JoystickButton.A]); +``` +```cs +IWindowHandlesSource someWindow = null!; +var inputContext = new InputContext(); +inputContext.Update(); +inputContext.Backends.Add(someWindow.CreateInputBackend()); +// in future: +// inputContext.Backends.Add(new OpenXRInputBackend(...)); +``` +```cs +class MyThing +{ + [SilkEntryPoint] + public static void Run(ISurface surface) + { + var inputContext = surface.CreateInput(); + surface.Update += _ => inputContext.Update(); + inputContext.Gamepads.ThumbstickMove += @event => + { + Console.WriteLine($"Thumbstick {@event.Index} moved from {@event.OldValue} to {@event.NewValue}"); + }; + surface.Run(); + } +} +``` + +# Reference Implementation + +Similar to Windowing 3.0, a reference implementation will be included in the main `Silk.NET.Input` package which uses the same API or family of APIs as Windowing's reference implementation. This will be exposed by the `InputWindowExtensions` class. + +```cs +public static class InputWindowExtensions +{ + public static IInputBackend CreateInputBackend(this WindowHandles window); + public static IInputBackend CreateInputBackend(this IWindowHandlesSource window); + public static InputContext CreateInput(this WindowHandles window); + public static InputContext CreateInput(this IWindowHandlesSource window); +} +``` + +The `CreateInputBackend` will create an instance of the reference implementation for the given `WindowHandles`. The `IWindowHandlesSource` overloads just forward to the `WindowHandles` overload. This is because `ISurface` will implement `IWindowHandlesSource`, so the extension methods will be usable on an `ISurface` without having a hard reference between Windowing and Input. + +The `CreateInput` methods simply return an `InputContext` preconfigured with the backend created by `CreateInputBackend` for ease of use. + +Please see the Windowing 3.0 proposal for `IWindowHandlesSource` and `WindowHandles`. + +# Devices + +Input devices all inherit from a root interface. + +```cs +public interface IInputDevice +{ + nint Id { get; } + string Name { get; } +} +``` + +`Id` is an globally-unique integral identifier for this device. + +`Name` is a rough description of the input device. Its value is not intrinsically meaningful. + +All devices originate from a backend. + +# Backends + +```cs +public interface IInputBackend +{ + string Name { get; } + nint Id { get; } + IReadOnlyList Devices { get; } + void Update(IInputHandler? handler = null); +} +``` + +`Name` is a rough description of the input backend. Its value is not intrinsically meaningful. + +`Id` is a globally-unique integral identifier for this backend. + +`Devices` enumerates all of the **connected** devices available from this input backend. When a device is disconnected, its `IInputDevice` object should be discarded by all that consumed it, as it can not be relied upon for being reused by the input backend. An implementation is welcome to reuse old objects, but this is strictly implementation-defined. A device not being present in the `Devices` list is sufficient evidence that a device has been disconnected. + +`Update` will update the state of all devices contained within this input backend. The value of the `State` properties on each device must not change until this method is called. This is a departure from 1.0's and 2.0's model of updating state as soon as new information is available, which has resulted in lots of inconsistencies in the past. + +The onus is on the user to coordinate using this type across threads, as the input context is not thread safe. In addition, certain backends may have (unavoidable) restrictions on what thread `IInputBackend.Update` can be called on - the user is responsible for respecting these threading rules as well. + +Threading rules for the reference implementation (if any) will be explicitly documented, and guidance for using this type safely against any first-party implementations will be included in the XML and user documentation. + +**INFORMATIVE TEXT:** For example, it is illegal for GLFW functions to be called anywhere except the thread `glfwInit` was called on, and it is illegal on some operating systems (such as macOS) for `glfwInit` to be called anywhere except the thread that called `main`. + +## Input Handlers + +Some users will want to receive events in the order that they happen. Observing state does not allow us to do this if, for instance, a button was pressed and released between update calls - the state would have a net-zero change and thus we wouldn't be able to detect such events. + +This is solved by the handler model, which is implemented by the custom list types - the handler methods invoking the matching events. + +**INFORMATIVE TEXT:** A possible question is "why don't we just expose the events directly on the devices? Why do we need this handler model?". The answer to that is having events on the devices has considerations for the lifetime of the device objects, which the Silk.NET team wishes to leave unrestricted. For instance, when a device connects the input context will need to bind the events on each device itself and when a device disconnects the behaviour thereafter would be undefined because we have not explicitly allowed or disallowed the reuse of device objects, and we do not wish to. If a device object were to be reused when the device reconnects, those events would still be there. It's better to encapsulate these events in their own object using this handler model. + +```cs +public interface IInputHandler +{ + void HandleDeviceConnectionChanged(ConnectionEvent @event); +} +``` + +The base `IInputHandler` is simple, handling only device connections and device disconnections. + +`HandleDeviceConnectionChanged` must be called with a device and a value of `true` if the `ConnectionEvent.Device` has just been added to the `IInputBackend.Devices` list. + +`HandleDeviceConnectionChanged` must be called with a device and a value of `false` if the `ConnectionEvent.Device` has just been removed from the `IInputBackend.Devices` list. + +All handler methods are called in the order that the state changes happened in the underlying backend. For example, a "double click" would be mouse down, mouse up, mouse down, and mouse up again. Even if those events happened entirely between two update calls (thus resulting in a net-zero change of state, given the button was released at the last update and is still released at this update), the input backend is expected to queue up the events so they may be delivered to the handler in the order in which they occurred when `Update` is called **where possible**. The events being in order is preferred and strongly recommended for backend implementations, and if a backend is able to order events in this way it is required to do so and required to do so consistently. If the backend is unable to do so, then the events being in order is not required. + +**INFORMATIVE TEXT:** Some backends, such as OpenXR, may only operate on a state query basis, and obviously we only query the state when `Update` is called. This is the reason why it's only a "where possible" requirement, because if we can't actually get the events in the order they happened from the underlying backend (like we can for GLFW, for example) then it's impossible for us to convey this up to the developer. However, regardless of backend-specifics, a `State` update must always be paired with an handler method call. + +The `IInputHandler` passed into `Update` may implement multiple other handler interfaces (as defined below), and if the actor implements an extra interface (such as `IMouseInputHandler` defined below) that would allow the backend to forward more events to the handler, the backend must do so via type checking. That is, if `handler` is an instance of `IMouseInputHandler`, any mouse events are delivered to that actor. But if `handler` does not implement `IMouseInputHandler`, no mouse events will be delivered. All events, including those that were not delivered due to the actor not implementing a necessary interface, must be discarded at the end of the `Update` call. + +Note that during the `Update` call, a backend must only update the device's state in the order that the events are delivered. For example when `IInputBackend.Update` is called: +1. The backend has a queued "mouse down" event. +2. The backend updates the `State` of the relevant `IMouse` for that button press. +3. The backend calls `HandleButtonDown` on the `IMouseInputHandler` (if applicable). +4. The backend has a queued "mouse up" event. +5. The backend updates the `State` of the relevant `IMouse` for that button release. +6. The backend calls `HandleButtonUp` on the `IMouseInputHandler` (if applicable). + +This allows the actor to work with the whole device state with the device state being representative of the time that the original event occurred. + +More actors will be defined later in the proposal. + +All of the `Devices` and `Update`s are aggregated and coordinated by a central input context object. + +# Contexts + +```cs +public partial class InputContext +{ + public Mice Mice { get; } + public Keyboards Keyboards { get; } + public Gamepads Gamepads { get; } + public Joysticks Joysticks { get; } + public IReadOnlyList Devices { get; } + public IList Backends { get; } + public event Action? ConnectionChanged; + public void Update(); +} +``` + +The central input object acts as the main entry point into the Input API, and is responsible for comparing the state reported by the devices for differences between `Update` calls (raising events as necessary). + +`Mice`, `Keyboards`, `Gamepads`, and `Joysticks` are all custom `IReadOnlyList` types for enumerating the devices. However, these custom types also contain the events. This is so we can "scope" the events, rather than putting them at the top-level and having to call the events `MouseButtonDown`, `JoystickButtonDown`, etc. + +By virtue of the `State` properties not updating until `IInputBackend.Update` is called, the states of the devices enumerated by the lists will not change until `Update` is called. + +`Update` will call `IInputBackend.Update` on each of the `Backends`, passing in a handler which implements `IInputHandler`, `IMouseInputHandler`, `IKeyboardInputHandler`, `IGamepadInputHandler`, and `IJoystickInputHandler` with each of the methods invoking a matching event defined in "Custom List Types" or on the input context itself (such as `ConnectionChanged`). + +`Backends` is a mutable list of input backends. Until `Update` is called again, no device lists, state, etc on the context will be updated. The `ConnectionChanged` rules above will still be respected e.g. when you remove a backend, all of its devices will have a disconnected event raised for them. + +`Devices` contains all devices reported by all backends, including devices that do not necessarily fit into one of our more specialized wrapper lists. This means that if a backend has a device type we do not recognise, it will be accessible via this list. + +## Custom List Types + +These are relatively simple list wrappers with the events fired when state changes. + +```cs +public partial class Mice : IReadOnlyList +{ + public MouseClickConfiguration ClickConfiguration { get; set; } + public event Action? ButtonDown; + public event Action? ButtonUp; + public event Action? Click; + public event Action? DoubleClick; + public event Action? CursorMove; + public event Action? Scroll; +} + +public partial class Keyboards : IReadOnlyList +{ + public event Action? KeyDown; + public event Action? KeyUp; + public event Action? KeyChar; +} + +public partial class Gamepads : IReadOnlyList +{ + public event Action? ButtonDown; + public event Action? ButtonUp; + public event Action? ThumbstickMove; + public event Action? TriggerMove; +} + +public partial class Joysticks : IReadOnlyList +{ + public event Action? ButtonDown; + public event Action? ButtonUp; + public event Action? AxisMove; + public event Action? HatMove; +} +``` + +All events will be raised when their matching handler methods are called, with the exception of `Click` and `DoubleClick` which are implemented on top of `ButtonDown` and `ButtonUp` respectively (as in 2.X). + +`DoubleClick` will be raised if `Mice.ButtonDown` is raised two consecutive times within `MouseClickConfiguration.DoubleClickTime` milliseconds, and the `MouseState.Position`'s `X` or `Y` did not change more than `MouseClickConfiguration.DoubleClickRange` between the two events. If these conditions are not met, `Click` is raised instead. For the avoidance of doubt, the behaviour of the click implementation here is exactly as it is in 2.X. + +**INFORMATIVE TEXT:** The click implementation may also even be exactly the same implementation as it is 2.X copied and pasted into 3.0, given a lot of research and effort went into this by the community contributor that implemented it. + +`MouseClickConfiguration` is defined as follows: + +```cs +public record struct MouseClickConfiguration(int DoubleClickTime, float DoubleClickRange); +``` + +This will be configurable on `Mice` (i.e. via `InputContext.Mice.ClickConfiguration`). The Silk.NET team wishes to reserve the right to define the initial values set on `Mice.ClickConfiguration`, but these will most likely be the same as in 2.X. + +Unlike 1.0 and 2.0, this proposal uses `readonly record struct`s as their only argument for the event action. This allows us to provide more information to the event handlers without breaking in the future. These types are farily simple: + +```cs +public readonly record struct ConnectionEvent(IInputDevice Device, bool IsConnected); +public readonly record struct KeyDownEvent(IKeyboard Keyboard, Key Key, bool IsRepeat); +public readonly record struct KeyUpEvent(IKeyboard Keyboard, Key Key); +public readonly record struct KeyCharEvent(IKeyboard Keyboard, char Character); +public readonly record struct MouseDownEvent(IMouse Mouse, Vector2 Position, MouseButton Button); +public readonly record struct MouseUpEvent(IMouse Mouse, Vector2 Position, MouseButton Button); +public readonly record struct MouseMoveEvent(IMouse Mouse, Vector2 Position, Vector2 Delta); +public readonly record struct MouseScrollEvent(IMouse Mouse, Vector2 Position, Vector2 WheelPosition, Vector2 Delta); +public readonly record struct MouseClickEvent(IMouse Mouse, Vector2 Position, MouseButton Button); +public readonly record struct JoystickDownEvent(IJoystick Joystick, JoystickButton Button); +public readonly record struct JoystickUpEvent(IJoystick Joystick, JoystickButton Button); +public readonly record struct JoystickHatMoveEvent(IJoystick, Vector2 Value, Vector2 Delta); +public readonly record struct JoystickAxisMoveEvent(IJoystick Joystick, int Axis, float Value, float Delta); +public readonly record struct GamepadDownEvent(IGamepad Gamepad, JoystickButton Button); +public readonly record struct GamepadUpEvent(IGamepad Gamepad, JoystickButton Button); +public readonly record struct GamepadThumbstickMoveEvent(IJoystick, Vector2 Value, Vector2 Delta); +public readonly record struct GamepadTriggerMoveEvent(IJoystick Joystick, int Axis, float Value, float Delta); +``` + +This is the part of this proposal that incorporates the ideas in Enhanced Input Events, and is why this proposal supersedes that one. + +One final point to note is that throughout the rest of the proposal the following type will be used: + +```cs +public struct InputReadOnlyList : IReadOnlyList +{ + public InputReadOnlyList(IReadOnlyList other); +} +``` + +The Silk.NET team wishes to reserve the right to add more constructors to this type as it sees fit. + +This exists so that, should the Silk.NET choose to, we can optimize the lookup of elements while ensuring things like indexers are inlined and don't result in a virtual call if our implementation allows us to do so. + +**INFORMATIVE TEXT:** For example, for joystick and mouse buttons we could use a fixed-sized bit buffer where each bit represents an individual button: 1 for pressed, 0 for unpressed. But for something like keyboard input where there are a large amount of keys, we can't do that and will likely use `Memory` instead. + +# Mouse Input + +As discussed earlier, the interface will be very simple. + +```cs +public interface IMouse : IInputDevice +{ + ref readonly MouseState State { get; } + ICursorConfiguration Cursor { get; } + void SetPosition(Vector2 pos); +} +``` + +`State` is the device state as defined earlier. + +`Cursor` contains the cursor configuration. This isn't actually state that the end user can change, and has been made an interface rather than a state struct accordingly. + +`SetPosition` allows moving the mouse cursor without the end user physically moving their mouse. Please note that this does not immediately update `State` with the new value - the changes will be reflected next time `IInputBackend.Update` is called. + +The device state returned by `State` fills out the following structure: + +```cs +public readonly record struct MouseState +( + MouseButtonState Buttons, + Vector2 Position, + Vector2 WheelPosition +); +``` + +`MouseButtonState` is defined as: +```cs +public readonly record struct MouseButtonState +( + InputReadOnlyList Down +) +{ + public bool this[MouseButton btn] { get; } +} +``` + +The indexer returns `true` if a particular button is pressed, false otherwise. If the developer wishes to enumerate the button state, they must explicitly enumerate through the `Down` buttons. + +**INFORMATIVE TEXT:** This struct only exists so we can implement an indexer that accepts a `MouseButton`, given that `Down` is effectively just a list and only takes an `int` index as a result. + +The indexer will be implemented in terms of `Down`, which is the only property that a backend will need to set. + +Changes to `MouseState` also have matching handler methods which are subject to the handler method rules i.e. the backend should call them in the order in which the backend received the events where possible etc (read the Input Handlers section). + +```cs +public interface IMouseInputHandler : IInputHandler +{ + void HandleButtonDown(MouseDownEvent @event); + void HandleButtonUp(MouseUpEvent @event); + void HandleCursorMove(MouseCursorEvent @event); + void HandleScroll(MouseScrollEvent @event); +} +``` + +`HandleButtonDown` must be called when a button is added to `MouseState.Buttons.Down`. + +`HandleButtonUp` must be called when a button is removed from `MouseState.Buttons.Down`. + +`HandleCursorMove` must be called when `MouseState.Position` changes. + +`HandleScroll` must be called when `MouseState.WheelPosition` changes. + +Note that the click events, just as in 2.X, are not implemented by the backend and instead implemented by the input context because it is not a requirement that backends can record clicks. **INFORMATIVE TEXT:** The original reason for this requirement in 2.X is because GLFW doesn't actually send click and double click events. + +## Enums + +```cs +public enum MouseButton +{ + Unknown, + LeftButton, + RightButton, + MiddleButton, + Button4, + Button5, + Button6, + Button7, + Button8, + Button9, + Button10, + Button11, + Button12, + Button13, + Button14, + Button15, + Button16, + Button17, + Button18, + Button19, + Button20, + Button21, + Button22, + Button23, + Button24, + Button25, + Button26, + Button27, + Button28, + Button29, + Button30, + Button31 +} +``` + +## Cursor Configuration + +`ICursorConfiguration` is defined as: + +```cs +public interface ICursorConfiguration +{ + CursorModes SupportedModes { get; } + CursorModes Mode { get; set; } + CursorStyles SupportedStyles { get; } + CursorStyles Style { get; set; } + CursorFlags SupportedFlags { get; } + CursorFlags Flags { get; set; } + RawImage? Image { get; set; } +} +``` + +Please note that the `Hotspot` properties present in 1.X and 2.0 have been removed given that they didn't do anything, only providing a place for the developer to store that information and use it if they want to. If the developer wants this, they should use their own variables instead. + +`SupportedModes` is a bitmask containing all of the cursor modes that are supported by this backend. This must be queried before setting `Mode` - the currently active cursor mode. An exception should be thrown if an attempt is made to set `Mode` to an unsupported mode or multiple modes (i.e. multiple bits set). + +`SupportedStyles` is a bitmask containing all of the cursor styles that are supported by this backend. This must be queried before setting `Style` - the currently active cursor style. An exception should be thrown if an attempt is made to set `Style` to an unsupported style or multiple styles (i.e. multiple bits set). + +`Image` uses `RawImage` as-is from Silk.NET.Core, and when set to a non-null value implicitly sets `Style` to custom. As such, you must query `SupportedStyles` before using this property as well. Setting `Image` to `null` will set `Style` back to a standard cursor style, defined by the implementation. It is therefore recommended you set `Style` explicitly when disabling a custom cursor. Note that setting `Style` to a non-`Custom` value will also implicitly set this property to `null`. Setting `Mode` **to** `Custom` explicitly is undefined behaviour, as `Image` won't be set at the time of setting `Mode`. + +`SupportedFlags` is a bitmask containing other supported options for the cursor which can be mixed and matched if supported. This must be queried before setting `Flags` - the currently active options. An exception should be thrown if an attempt is made to set an unsupported flag on `Flags`. Unlike the other properties, `Flags` can have multiple bits set. + +`Flags` replaces `IsConfined`, which was undefined behaviour on platforms where it was not supported in 2.X - now with `SupportedFlags` you can query its support before setting it. + +### Enums + +```cs +[Flags] +public enum CursorModes +{ + Normal, + Hidden = 1 << 0, + Disabled = 1 << 1, + Raw = 1 << 2 +} +``` +```cs +[Flags] +public enum CursorStyles +{ + Default, + Arrow = 1 << 0, + IBeam = 1 << 1, + Crosshair = 1 << 2, + Hand = 1 << 3, + HResize = 1 << 4, + VResize = 1 << 5, + Custom = 1 << 6, +} +``` +```cs +[Flags] +public enum CursorFlags +{ + None, + Confined = 1 << 0 +} +``` + +# Keyboard Input + +Once again, the interface is very simple. + +```cs +public interface IKeyboard : IInputDevice +{ + ref readonly KeyboardState State { get; } + string? ClipboardText { get; set; } + void BeginInput(); + void EndInput(); +} +``` + +`State` is the device state as defined earlier. + +`BeginInput` starts recording textual input, bringing up the on-screen keyboard on platforms where this is needed (i.e. mobile). The `State.Text` property will not be populated until after this is called. + +`ClipboardText` allows getting and setting the text on the user's clipboard so they can paste information to/from your application in others. + +`KeyboardState` is defined as follows: + +```cs +public readonly record struct KeyboardState +( + InputReadOnlyList? Text, + KeyState Keys +); +``` + +`Text` contains the characters typed on the keyboard since `IKeyboard.BeginInput`, and accounts for backspaces. This is cleared (set to `null`) when `IKeyboard.EndInput` is called, and will not be non-`null` again until another `IKeyboard.BeginInput` call. Given that `KeyChar` events are raised one character at a time, this property will update one character at a time to keep the state consistent with the event. + +**INFORMATIVE TEXT:** This is something we can optimize in `InputList` to not be allocatey, rest assured it is not acceptable to the Silk.NET team to allocate a new list for every character. + +```cs +public readonly record struct KeyState +( + InputReadOnlyList Down +) +{ + public bool this[KeyName btn] { get; } + public bool this[int scancode] { get; } +} +``` + +The indexer returns `true` if a particular key is pressed, false otherwise. If the developer wishes to enumerate the key state, they must explicitly enumerate through the `Down` buttons. + +**INFORMATIVE TEXT:** This struct only exists so we can implement an indexer that accepts a `KeyName` or scancode, given that `Down` is effectively just a list and only takes an `int` index as a result. + +The indexer will be implemented in terms of `Down`, which is the only property that a backend will need to set. + +Note because not all keys are named, and because some developers may prefer to use scancodes instead, a `Key` struct is used instead of just having the list be a list of key names. + +```cs +public readonly record struct Key(KeyName Name, int Scancode); +``` + +`KeyName` will be `Unknown` for scancode-only, unnamed keys. + +Changes to `KeyboardState` also have matching handler methods which are subject to the handler method rules i.e. the backend should call them in the order in which the backend received the events where possible etc (read the Input Handlers section). + +```cs +public interface IKeyboardInputHandler : IInputHandler +{ + void HandleKeyDown(KeyDownEvent @event); + void HandleKeyUp(KeyUpEvent @event); + void HandleKeyChar(KeyCharEvent @event); +} +``` + +`HandleKeyDown` must be called when a `Key` is added to the `KeyState.Down` list. + +`HandleKeyUp` must be called when a `Key` is removed from the `KeyState.Down` list. + +`HandleKeyChar` must be called when a character is added to `KeyboardState.Text`. + + +## Enums + +```cs +public enum KeyName +{ + Unknown = 0, + Space, + Apostrophe /* ' */, + Comma /* , */, + Minus /* - */, + Period /* . */, + Slash /* / */, + Number0, + Number1, + Number2, + Number3, + Number4, + Number5, + Number6, + Number7, + Number8, + Number9, + Semicolon /* ; */, + Equal /* = */, + A, + B, + C, + D, + E, + F, + G, + H, + I, + J, + K, + L, + M, + N, + O, + P, + Q, + R, + S, + T, + U, + V, + W, + X, + Y, + Z, + LeftBracket /* [ */, + BackSlash /* \ */, + RightBracket /* ] */, + GraveAccent /* ` */, + Escape, + Enter, + Tab, + Backspace, + Insert, + Delete, + Right, + Left, + Down, + Up, + PageUp, + PageDown, + Home, + End, + CapsLock, + ScrollLock, + NumLock, + PrintScreen, + Pause, + F1, + F2, + F3, + F4, + F5, + F6, + F7, + F8, + F9, + F10, + F11, + F12, + F13, + F14, + F15, + F16, + F17, + F18, + F19, + F20, + F21, + F22, + F23, + F24, + F25, + Keypad0, + Keypad1, + Keypad2, + Keypad3, + Keypad4, + Keypad5, + Keypad6, + Keypad7, + Keypad8, + Keypad9, + KeypadDecimal, + KeypadDivide, + KeypadMultiply, + KeypadSubtract, + KeypadAdd, + KeypadEnter, + KeypadEqual, + ShiftLeft, + ControlLeft, + AltLeft, + SuperLeft, + ShiftRight, + ControlRight, + AltRight, + SuperRight, + Menu +} +``` + +The `KeyName` enum is exactly the same as the `Key` enum in 2.X. The integral values of each enumerant, not included here, must match the en-US scancode for that key. A backend must match a scancode to a `KeyName` as if it were an en-US scancode, as this is the keyboard layout from which these key names were derived. + +The Silk.NET team wishes to reserve the right to remove any key names which do not have a matching en-US scancode. This is because the above enum is just copied and pasted from 2.X, and has not been cross-referenced with the keyboard layout at this time. + +# Gamepad Input + +```cs +public interface IGamepad : IInputDevice +{ + ref readonly GamepadState State { get; } + IReadOnlyList VibrationMotors { get; } +} +``` + +`State` is the device state as defined earlier. + +`VibrationMotors` enumerates the vibration motors on the device. The values within `IMotor` do not change according to end user input, as such they are not encapsulated in the `State`, following the same principles as `ICursorConfiguration`. + +`IMotor` is defined as follows: +```cs +public interface IMotor +{ + float Speed { get; set; } +} +``` + +This is exactly as in 2.X. + +**INFORMATIVE TEXT:** It is possible that, should integration with DualSense controllers be implemented, that more properties are added to `IMotor` than just `Speed`. + +`GamepadState` is defined as follows: +```cs +public readonly record struct GamepadState +( + JoystickButtonState Buttons, + DualReadOnlyList Thumbsticks, + DualReadOnlyList Triggers, +); +``` + +`GamepadState` reuses a lot of the joystick API types, which are defined later in this proposal. + +`Thumbsticks` contain the two thumbsticks on this gamepad. The X and Y values within this list range from -1 to 1: -1 being leftmost, and 1 being rightmost. + +`Triggers` contains the two triggers on thsi gamepad. The values within this list range from 0 to 1: 0 being unpressed, and 1 being fully pressed. + +Note the use of the `DualReadOnlyList` type. This is basically just: +```cs +public readonly struct DualReadOnlyList : IReadOnlyList +{ + public readonly T Left; + public readonly T Right; +} +``` + +This is used where the list will only ever have exactly two elements, mainly because the "gamepad" form factor is standard and it doesn't make sense to have multiple thumbsticks or triggers given a human only has two thumbs or index fingers. More exotic devices should be exposed using the joystick API. + +Changes to `GamepadState` also have matching handler methods which are subject to the handler method rules i.e. the backend should call them in the order in which the backend received the events where possible etc (read the Input Handlers section). + +```cs +public interface IGamepadInputHandler : IInputHandler +{ + void HandleButtonDown(GamepadDownEvent @event); + void HandleButtonUp(GamepadUpEvent @event); + void HandleThumbstickMove(GamepadThumbstickMoveEvent @event); + void HandleTriggerMove(GamepadTriggerMoveEvent @event); +} +``` + +`HandleButtonDown` must be called when a button is added to `GamepadState.Buttons.Down`. + +`HandleButtonUp` must be called when a button is removed from `GamepadState.Buttons.Down`. + +`HandleThumbstickMove` must be called when any value of `GamepadState.Thumbsticks` changes. + +`HandleTriggerMove` must be called when any value of `GamepadState.Triggers` changes. + +# Joystick Input + +This is the polyglot interface for any other human input device that roughly meets the description of being "joystick". + +```cs +public interface IJoystick : IInputDevice +{ + ref readonly JoystickState State { get; } +} +``` +```cs +public readonly record struct JoystickState +{ + InputReadOnlyList Axes, + JoystickButtonState Buttons, + InputReadOnlyList Hats +} +``` + +This is pretty closely modeled as in 2.X: `Axes` containing the individual axes that can be represented by this joystick, `Buttons` containing the buttons which can be pressed, and `Hats` containing the hats. + +**INFORMATIVE TEXT:** The only difference is `Hats` is now a `Vector2` instead of a `Position2D`. It is still intended that the X and Y values are only ever `0` or `1`, but this is not a requirement for more exotic backends. + +`JoystickButtonState` is defined as follows: +```cs +public readonly record struct JoystickButtonState +( + InputReadOnlyList Down +) +{ + public bool this[JoystickButton btn] { get; } +} +``` + +The indexer returns `true` if a particular button is pressed, false otherwise. If the developer wishes to enumerate the button state, they must explicitly enumerate through the `Down` buttons. + +**INFORMATIVE TEXT:** This struct only exists so we can implement an indexer that accepts a `JoystickButton`, given that `Down` is effectively just a list and only takes an `int` index as a result. + +The indexer will be implemented in terms of `Down`, which is the only property that a backend will need to set. + +`JoystickButton` is defined as follows: +```cs +public enum JoystickButton +{ + Unknown, + ButtonDown, + A = ButtonDown, + ButtonRight, + B = ButtonRight, + ButtonLeft, + X = ButtonLeft, + ButtonUp, + Y = ButtonUp, + LeftBumper, + RightBumper, + Back, + Start, + Home, + LeftStick, + RightStick, + DPadUp, + DPadRight, + DPadDown, + DPadLeft +} +``` + +Changes to `JoystickState` also have matching handler methods which are subject to the handler method rules i.e. the backend should call them in the order in which the backend received the events where possible etc (read the Input Handlers section). + +```cs +public interface IJoystickInputHandler : IInputHandler +{ + void HandleButtonDown(JoystickDownEvent @event); + void HandleButtonUp(JoystickUpEvent @event); + void HandleAxisMove(JoystickAxisMoveEvent @event); + void HandleHatMove(JoystickHatMoveEvent @event); +} +``` + +`HandleButtonDown` must be called when a button is added to `JoystickState.Buttons.Down`. + +`HandleButtonUp` must be called when a button is removed from `JoystickState.Buttons.Down`. + +`HandleAxisMove` must be called when any value of `JoystickState.Axes` changes. + +`HandleHatMove` must be called when any value of `JoystickState.Hats` changes. + +# Meeting Notes + +## 05/08/2021 + +This also includes notes for [Enhanced Input Events]((Superseded)%20Proposal%20-%20Enhanced%20Input%20Events) as well as the previous version of this proposal. + +- Document the change in the enhanced input events (migration guide?) +- Record struct ASAP when available to us, unanimous agreement + - no need for meeting clarification +- Make IsConnected less meaningful +- Return `IReadOnlyList` +- Make input states structs (immutable) + - have a `State` readonly property + - have methods for all settable things (where necessary) + - @HurricanKai To rework Multi Backend Input the proposal slightly + +## 25/02/2022 + +[Video](https://youtu.be/dac3t0oh3VU?t=3722) + +- Approved, but do make the joystick buttons direction instead of Xbox-opinionated ABXY. +- Structs are good for inputs. Help with versioning and more performant if you pass more than ~4-5 args. +- Both event-driven input and state-based input available. + - Call Update with an IInputHandler to get events in the order they were received, pass null to just use the state-based API + - State will always be updated regardless of null +- Some examples on using gamepads, keyboards, and mice in the same game would be appreciated. + - (Silk is fine with you using all simultaneously, you don't need to tell it to switch) +- e.g. what if you want to assert that spacebar was hit exactly 2 times, or one before a frame and one after a frame? + - If spacebar was pressed between two frames, you will get up and down events for each press. + - Examples would be appreciated. +- Engines are moving towards an "input action" model, where you have something like "Jump" and then have buttons mapped to that. + - If a user wants to jump, is A/X/Space pressed? It's an inverse mapping to what we have today. + - Possible extension point/on top of this API. +- Enums are potentially opinionated for naming. + - Perhaps we could have something like the Key struct for joysticks as well? + - GLFW doesn't support anything other that the buttons we've exposed today. + - Not sure that having an integer helps if we haven't named it at all. + - Introducing multiple names for a single enum should be fine. + - Most trivial example is Nintendo controllers vs the Xbox controllers, A/B and X/Y are swapped (could be confusing!). Either going to assume that A = Nintendo's A (middle right) or A = Xbox A (bottom button) + - **Rename to be positional rather than opinionated.** + - More nuanced buttons can be via the joystick API, and gamepad kept a standard controller. We can't accomodate for everything, so we need to make concessions and be opinionated for gamepad, and leave the more exotic stuff for joystick. + - We could just have ISteamControllerGamepad on top of IGamepad for controllers with touch pads, for example. + - Take keyboards: you have integer-identified buttons but no keyboard layout is the same. An API is built on top of that abstract concept and query "what does that button represent" - on the high-level side we could have a nicer abstraction. + - Comes back to that concept of button codes. + - Nice and easy API for the common case, need for esoteric features like touchpads then you can actually go and add that support, you just need to do some more additional work. + - Difficult to say where this starts and ends: we might want to expose all of the buttons, but we also want to expose low-level features then we might want to expose all of the motors, then all the lights, etc etc: where does it end?! + - Difficult to tell what we can and can't support, and comes back to what the backends can actually do. + - Basically every OS exposes a "here's the raw PCI device ID, and here's a byte array of the data and what HID input it came from" - low-level API is very primitive + - Could always add another input backend. + - "Easier to get a smaller thing done now than have a larger thing in the works forever" - @Redhacker + - What we have today is a lot of what most people will want to use. A low-level thing could be built _as a parallel_. + - **Think about low-level input in the future in addition to this** + - Could we expose at least the vendor ID/device ID to the developer (in raw form or common vendor enums) to mitigate this for now. + - Notably, the same vendor/device information by Vulkan/DirectX and any audio device - it's a very common concept. + - **Think about exposing vendor and device IDs** + - Potentially modifying GLFW to get us that + +**ACTIONS** +- [x] Rename ABXY to be less opinionated, and alias ABXY to that. + +**FUTURE** +- [ ] Think about an "input action" model +- [ ] Add examples on using gamepads, keyboards, and mice in the same game. +- [ ] Think about low-level input in the future in addition to this +- [ ] Think about exposing vendor and device IDs diff --git a/documentation/proposals/Proposal - Windowing 3.0.md b/documentation/proposals/Proposal - Windowing 3.0.md new file mode 100644 index 0000000000..eba6e6d895 --- /dev/null +++ b/documentation/proposals/Proposal - Windowing 3.0.md @@ -0,0 +1,1013 @@ +# Summary + +Cross-platform windowing for Silk.NET rebuilt from the ground-up. + +# Contributors +- Dylan Perks (@Perksey) +- Kai Jellinghaus (@HurricanKai) + +# Current Status +- [x] Proposed +- [x] Discussed with Community +- [x] Approved +- [ ] Implemented + +# Design Decisions +- This proposal assumes no knowledge of any previous iterations of Silk.NET Windowing. +- This is a complete rethink of Silk.NET Windowing built from the ground up to account for our goal of "write once run everywhere" for .NET 6 and Silk.NET 3.0. +- In this proposal, there are three parties: + - **User**: the person using the Silk.NET Windowing library and by extension a platform implementation. + - **Platform Implementor**: the party providing an implementation of the Silk.NET Windowing abstractions (abstractions such as the "Surface" as defined later in this proposal). This implementation will be referred to herein as the **Platform Implementation** (or the **Platform** for short) + - **Library Implementor**: the party providing the plubming around the surface abstraction (such as the platform selection mechanisms and accompanying source generators). This will be referred to herein as the **Library Implementation** (or the **Library** for short) + + +# Platforms + +There will be a number of "reference" implementations for the APIs laid out in this proposal. These are: +- For `net6.0` and `net6.0-windows`: GLFW. +- For `net6.0-ios`, `net6.0-tvos` and `net6.0-maccatalyst`: UIKit. (NB: The latter two will be a target for Silk.NET 3.X) +- For `net6.0-macos`: AppKit (NB: This will be a target for Silk.NET 3.X, it is recommended developers targeting macOS use the regular `net6.0` TFM) +- For `net6.0-android`: EGL and ANativeWindow. +- For `net6.0-tizen`: TZSH. (NB: This will be a target for Silk.NET 3.X) + +The decision has been made to drop SDL due to complications and the fact that all non-desktop targets are distinctly unique in their own right. We believe that in creating platform-specific code for each of these will result in significantly more robust support for each of these platforms. + +- **QUESTION:** Should we drop our SDL bindings too? +- **MAINTAINERS' ANSWER:** Yes, it's no longer used by us and a library such as SDL is a large amount of maintenance weight to carry. Akin to us dropping EGL in the 1.X-2.0 transition, we will be dropping our SDL answer. + +All of the above is informative text, however, and no reference implementations are required or guaranteed to use these APIs under-the-hood. + +Unlike previous iterations, one reference implementation **MUST** be bundled with the main Silk.NET.Windowing assembly. Given that we can easily use platform-specific TFMs there's no reason to keep the fragmentation of different implentations across different assemblies. + +The reference implementation will be accessed through the static `Surface` class. +- `IsPlatformSupported` **MAY** return false if there is no reference implementation available for the given environment. +- `GetOrCreate` **MUST NOT** return `null`. It **MUST** always return a valid `ISurface` if `IsPlatformSupported` is true and no exceptions or errors ocurred during surface creation. This method **MUST** always return the same `ISurface` instance. +- `CreateNew` **MUST NOT** return the same `ISurface` object, and **MUST NOT** return `null`. It **MUST** always return a valid `ISurface` if `IsPlatformSupported` is true and no exceptions or errors ocurred during surface creation. +- `ClearCurrentContexts` **MUST** call `ClearCurrent` on all `IGLSurface` or `IGlesSurface` instances this reference implementation has created. + +For the reader's benefit, this `Surface` class isn't really intended for general consumption though it must be public for those applications don't fit into the model defined in the "Windowing Entry Points" section. + +# Surface + +In this proposal, a plane upon which graphics can be rendered on is represented by an `ISurface`. `ISurface` defines a minimal subset of basic APIs which **MUST** all be present on an **Platform Implementation**. The idea is `ISurface` just provides the bare necessities for rendering a game or application without knowing too much about the form factor, better encouraging cross-platform/"write once run everywhere" code. For a description of what this entials, see the defined API and the documentation comments therein. + +`ISurface` **Platform Implementations** **SHOULD** also implement any extension interfaces that it can support for a given platform. Through these extension interfaces, if user code needs to access APIs which are more specific to certain form factors or platforms, they should use casts to get a more specific surface. + +The "core `ISurface` API" is as minimalistic as possible to allow easy use in integrations into UI frameworks or other environments. For example, at some point in the future we'd like to make `ISurface` implementations atop WPF, MAUI, Avalonia, and more; as well as atop Blazor WASM (and by extension HTML5 and WebGL) + +# Windowing Entry Points + +Even with the platform selection mechanism, there is a lot of plumbing required to get to the stage of acquiring a surface on the various platforms. In Silk.NET 3.0, the **Library** **MAY** decide to expose a Roslyn source generator (the "**Gluer**" as used herein) to assist with this. If it doesn't, this section and its requirements can be discarded. + +The **Gluer** **MUST** be distributed in package/namespace `Silk.NET.Windowing.Roslyn`. + +The Gluer operates on **User** methods that: +- **MUST** be static +- **MUST** return `void` +- **MUST** have a single parameter of type `ISurface` or a type that inherits from `ISurface`. If the latter, the Glue **MUST** assert that the `ISurface` created is assignable to the parameter type or throw an exception if this is not the case. This allows applications to use `IDesktopSurface` only if that's their jam. +- **MUST** have the attribute `SilkEntryPoint` + +The idea is the Gluer will generate `Main` methods on .NET, `Activity`s on .NET for Android, etc... The **Gluer**, if generating **MUST** generate the necessary APIs to ensure that the method in question is called on application start-up (the "Glue"). If any of the above User requirements aren't met, the **Gluer** **MUST NOT** generate Glue. If a method is found with the `SilkEntryPoint` attribute but fails to meet one of the other requirements, the **Gluer** **MUST** generate a compiler error; otherwise it **MUST NOT** impact the compilation whatsoever. + +If multiple methods are found with `SilkEntryPoint`, the **Gluer** **MUST** generate a compiler error. All attributes **MUST** only be name matched by the **Gluer**. + +The Gluer **MUST NOT** modify the original type (i.e. all Glue must be defined in its own self-contained type) + +The **User** **MUST NOT** define their own application entry point (such as a static `Main` method in the case of regular .NET) if they are using the Gluer, though the Gluer does not have to enforce this. + +The method **MAY** be called multiple times throughout the lifetime of an application. This is because of operating system restrictions - the only way to definitively know whether this method is being called for the last time is the `IsTerminating` property or `Terminating` event on `ISurface`. + +The Glue is purposely left undefined as this is operating system & platform specific, and would not reflect any future platforms we decide to add. + +A model example of what this looks like: +```cs +public class MyGame +{ + [SilkEntryPoint] + public static void RunGame(ISurface surface) + { + // do things with your surface + surface.Run(); + } +} +``` + +# Optional Features +## Desktop Surface + +A more rich set of APIs intended for use on desktop-style window management systems. For a description of what this entials, see the defined API and the documentation comments therein. + +## OpenGL Surface + +Enables OpenGL context creation atop a native surface. For a description of what this entials, see the defined API and the documentation comments therein. + +## OpenGLES Surface + +Enables OpenGLES context creation atop a native surface. For a description of what this entials, see the defined API and the documentation comments therein. + +## Vulkan Surface + +Enables Vulkan surface creation atop a native surface. For a description of what this entials, see the defined API and the documentation comments therein. + +## OpenGL Surface with Framebuffer Transparency + +Framebuffer transparency is an optional feature, and therefore has its own interface. This allows the a window to be created where the content area is transparent. For a description of what this entials, see the defined API and the documentation comments therein. + +NB: We've been discussing `IWebGLSurface` a lot recently, but this is left out of this proposal as this is a target for Silk.NET 3.X. + +# Proposed API +- Here you do some code blocks, this is the heart and soul of the proposal. DON'T DO ANY IMPLEMENTATIONS! Just declarations. + +## Delegates + +NB: instead of generic delegates like we've used in previous iterations, we use named delegates instead so the parameter names are auto-filled out by IDEs with indicative names, instead of `obj` or `argN`. + +```cs +public delegate void Vector2DAction(Vector2D newValue); +public delegate void DeltaAction(double deltaTime); +public delegate void WindowStateAction(WindowState newState); +public delegate void FilePathsAction(string[] filePaths); +public delegate void ToggleAction(bool newValue); +``` + +## `ISurface` +```cs +namespace Silk.NET.Windowing +{ + public interface ISurface : IWindowHandlesSource, IDisposable + { + /// + /// Determines whether the surface is being destroyed by the platform. + /// + bool IsTerminating { get; } + + /// + /// Determines whether the surface is being paused by the platform. + /// + bool IsPausing { get; } + + /// + /// Elapsed time in seconds since the Run method last started. + /// + double Time { get; } + + /// + /// The size of the surface's inner framebuffer. May differ from the surface size. + /// + // NB: This is not OpenGL specific and is valid in any case where there's a high DPI monitor. + Vector2D FramebufferSize { get; } + + /// + /// The size of the surface. + /// + Vector2D Size { get; } + + /// + /// The number of rendering operations to run every second. + /// + double FramesPerSecond { get; set; } + + /// + /// The number of update operations to run every second. + /// + double UpdatesPerSecond { get; set; } + + /// + /// Raised when the surface is resized. + /// + event Vector2DAction? Resize; + + /// + /// Raised when the surface's framebuffer is resized. + /// + event Vector2DAction? FramebufferResize; + + /// + /// Raised when the surface is being terminated. + /// + event Action? Terminating; + + /// + /// Raised when the surface is running low on memory. + /// + event Action? LowMemory; + + /// + /// Raised when the surface is about to pause. This is a good indicator that the Run method is about to exit, though this may not necessarily be the case, but the surface isn't terminating yet. + /// + event Action? Pausing; + + /// + /// Raised when the surface is about to resume. This is a good indicator to expect the entry point to be called again, though this may not necessarily be the case. + /// + event Action? Resuming; + + /// + /// Raised when the surface is initialized for the first time. + /// + event Action? Created; + + /// + /// Raised just before the Update event is raised. + /// + event Action? PreUpdate; + + /// + /// Raised when an update should be run. + /// + event DeltaAction? Update; + + /// + /// Raised when a frame should be rendered. + /// + event DeltaAction? Render; + + /// + /// Creates the surface on the underlying platform. + /// + void Initialize(); + + /// + /// Calls the Render event. + /// + void DoRender(); + + /// + /// Calls the Update event. + /// + void DoUpdate(); + + /// + /// Polls the underlying platform for events. + /// + void DoEvents(); + + /// + /// Unloads the surface on the underlying platform. + /// + void Reset(); + + /// + /// Terminates this surface. + /// + void Terminate(); + + /// + /// Converts this point to framebuffer coordinates. + /// + /// The point to transform. + /// The transformed point. + /// Expects client coordinates as input. + Vector2D PointToFramebuffer(Vector2D point); + + /// + /// Initiates a render loop in which the given callback is called as fast as the underlying platform can manage. + /// + /// The callback to run each frame. + void Run(Action onFrame); + } +} +``` + +## `IDesktopSurface` + +```cs +namespace Silk.NET.Windowing +{ + /// + /// A surface which wraps a Desktop Window. + /// + public interface IDesktopSurface : ISurface + { + /// + /// Whether or not the window is visible. + /// + bool IsVisible { get; set; } + + /// + /// The position of the window. If set to -1, use the backend default. + /// + Vector2D Position { get; set; } + + /// + /// The size of the window in pixels. + /// + new Vector2D Size { get; set; } + + /// + /// The window title. + /// + string Title { get; set; } + + /// + /// The window state. + /// + WindowState WindowState { get; set; } + + /// + /// The window border. + /// + WindowBorder WindowBorder { get; set; } + + /// + /// The video mode. + /// + VideoMode VideoMode { get; set; } + + /// + /// Gets the screen on which this window is active. + /// + IScreen? CurrentScreen { get; set; } + + /// + /// Gets the available screens for this surface. + /// + IEnumerable? AvailableScreens { get; } + + /// + /// Gets or sets whether the window waits for an event to be posted before existing . + /// + bool IsEventDriven { get; set; } + + /// + /// Gets or sets whether the window has been requested to close. + /// + bool IsCloseRequested { get; set; } + + /// + /// Gets whether the window is focused or not. + /// + bool IsFocused { get; } + + /// + /// Gets the distances in screen coordinates from the edges of the content area to the corresponding edges of + /// the full window. + /// + /// + /// Because these are distances and not coordinates, they are always zero or positive. + /// + /// + Rectangle BorderSize { get; } + + /// + /// Raised when the window has been requested to close. + /// + event Action CloseRequested; + + /// + /// Raised when the window is moved. + /// + event Vector2DAction? Move; + + /// + /// Raised when the window state is changed. + /// + event WindowStateAction? StateChanged; + + /// + /// Raised when the user drops files onto the window. + /// + event FilePathsAction? FileDrop; + + /// + /// Raised when the window focus changes. + /// + event ToggleAction? FocusChanged; + + /// + /// Sets the window icons. + /// + /// Either a collection of window icons, or null to set to the default icon. + void SetWindowIcon(ReadOnlySpan icons); + + /// + /// When using = true, wakes the main thread from + /// its blocking wait on incoming events. Can be called from any thread. + /// + void ContinueEvents(); + + /// + /// Converts this point to client coordinates. + /// + /// The point to transform. + /// The transformed point. + /// Expects screen coordinates as input. + Vector2D PointToClient(Vector2D point); + + /// + /// Converts this point to screen coordinates. + /// + /// The point to transform. + /// The transformed point. + /// Expects client coordinates as input. + Vector2D PointToScreen(Vector2D point); + } +} +``` + +## `INativeGLSurface` + +```cs +namespace Silk.NET.Windowing +{ + public interface INativeGLSurface : ISurface + { + nint Handle { get; } + bool IsContextCurrent { get; set; } + bool ShouldSwapAutomatically { get; set; } + + /// + /// Sets the number of vertical blanks to wait between calling and presenting the image, + /// a.k.a vertical synchronization (V-Sync). Set to 1 to enable V-Sync. + /// + /// + /// Due to platform restrictions, this value can only be set and not retrieved. + /// + int SwapInterval { set; } + + /// + /// Preferred depth buffer bits of the window's framebuffer. + /// + /// + /// Pass null or -1 to use the system default. + /// + int? PreferredDepthBufferBits { get; set; } + + /// + /// Preferred stencil buffer bits of the window's framebuffer. + /// + /// + /// Pass null or -1 to use the system default. + /// + int? PreferredStencilBufferBits { get; set; } + + /// + /// Preferred red, green, blue, and alpha bits of the window's framebuffer. + /// + /// + /// Pass null or -1 for any of the channels to use the system default. + /// + Vector4D? PreferredBitDepth { get; set; } + + /// + /// The API version to use. + /// + Version32? ApiVersion { get; set; } + + nint? GetProcAddress(string proc); + void SwapBuffers(); + } +} +``` + +## `IGLSurface` + +```cs +namespace Silk.NET.Windowing +{ + public interface IGLSurface : INativeGLSurface + { + ContextFlags ContextFlags { get; set; } + ContextProfile ContextProfile { get; set; } + IGLSurface? SharedContext { get; set; } + + /// + /// Enables OpenGL support for this surface. This will create a surface upon initialization. + /// + bool TryEnableOpenGL(); + } +} +``` + +## `IGlesSurface` + +```cs +namespace Silk.NET.Windowing +{ + public interface IGlesSurface : INativeGLSurface + { + IGlesSurface? SharedContext { get; set; } + /// + /// Enables OpenGLES support for this surface. This will create a surface upon initialization. + /// + bool TryEnableOpenGLES(); + } +} +``` + +## `IVkSurface` + +```cs +namespace Silk.NET.Windowing +{ + public interface IVkSurface : ISurface + { + /// Enables Vulkan support for this surface. + bool TryEnableVulkan(); + + /// + /// Create a Vulkan surface. + /// + /// The Vulkan instance to create a surface for. + /// A custom Vulkan allocator. Can be omitted by passing null. + /// A handle to the Vulkan surface created + unsafe ulong Create(nint instance, void* allocator); + + /// + /// Get the extensions required for Vulkan to work on this platform. + /// + /// The number of extensions in the returned array + /// An array of strings, containing names for all required extensions + unsafe byte** GetRequiredExtensions(out uint count); + } +} +``` + +## `IGLDesktopSurface` + +```cs +namespace Silk.NET.Windowing +{ + public interface IGLDesktopSurface : IDesktopSurface, IGLSurface { } +} +``` + + +## `IGlesDesktopSurface` + +```cs +namespace Silk.NET.Windowing +{ + public interface IGlesDesktopSurface : IDesktopSurface, IGlesSurface { } +} +``` + +## `IVkDesktopSurface` + +```cs +namespace Silk.NET.Windowing +{ + public interface IVkDesktopSurface : IDesktopSurface, IVkSurface { } +} +``` + +## `IGLTransparentFramebufferSurface` + +```cs +namespace Silk.NET.Windowing +{ + public interface IGLTransparentFramebuffer : INativeGLSurface + { + bool TransparentFramebuffer { get; set; } + } +} +``` + +## `ContextFlags` + +```cs +namespace Silk.NET.Windowing +{ + /// + /// Represents flags related to the OpenGL context. + /// + [Flags] + public enum ContextFlags + { + /// + /// No flags enabled. + /// + Default = 0, + + /// + /// Enables debug context; debug contexts provide more debugging info, but can run slower. + /// + Debug = 1, + + /// + /// Enables forward compatability; this context won't support anything marked as deprecated in the current + /// version. + /// + /// On OpenGL contexts older than 3.0, this flag does nothing. + ForwardCompatible = 2 + } +} +``` + +## `ContextProfile` + +```cs +namespace Silk.NET.Windowing +{ + /// + /// Represents the context profile OpenGL should use. + /// + public enum ContextProfile + { + /// + /// Uses a core OpenGL context, which removes some deprecated functionality. + /// + Core = 0, + + /// + /// Uses a compatability OpenGL context, allowing for some deprecated functionality. This should only ever be + /// used for maintaining legacy code; no newly-written software should use this. + /// + Compatability + } +} +``` + +## `WindowBorder` + +```cs +namespace Silk.NET.Windowing +{ + /// + /// Represents the window border. + /// + public enum WindowBorder + { + /// + /// The window can be resized by clicking and dragging its border. + /// + Resizable = 0, + + /// + /// The window border is visible, but cannot be resized. All window-resizings must happen solely in the code. + /// + Fixed, + + /// + /// The window border is hidden. + /// + Hidden + } +} +``` + +## `WindowState` + +```cs +namespace Silk.NET.Windowing +{ + /// + /// Represents the current state of the window. + /// + public enum WindowState + { + /// + /// The window is in its regular configuration. + /// + Normal = 0, + + /// + /// The window has been minimized to the task bar. + /// + Minimized, + + /// + /// The window has been maximized, covering the entire desktop, but not the taskbar. + /// + Maximized, + + /// + /// The window has been fullscreened, covering the entire surface of the monitor. + /// + Fullscreen + } +} +``` + +## `IScreen` + +```cs +namespace Silk.NET.Windowing +{ + /// + /// An interface representing a screen. + /// + public interface IScreen + { + /// + /// The name of this screen. + /// + string Name { get; } + + /// + /// The index of this screen. + /// + int Index { get; } + + /// + /// The workarea of this screen. + /// + Rectangle WorkArea { get; } + + /// + /// The current video mode of this monitor. + /// + VideoMode VideoMode { get; } + + /// + /// This screen's gamma correction. + /// + float Gamma { get; set; } + + /// + /// Get all video modes that this screen supports. + /// + /// An array of all video modes. + IEnumerable GetAllVideoModes(); + } +} +``` + +## `VideoMode` + +```cs +namespace Silk.NET.Windowing +{ + public readonly struct VideoMode + { + public VideoMode(Vector2D? resolution = null, int? refreshRate = null); + public VideoMode(int? refreshRate); + + /// + /// Resolution of the full screen window. + /// + public Vector2D? Resolution { get; init; } + + /// + /// Refresh rate of the full screen window in Hz. + /// + public int? RefreshRate { get; init; } + + /// + /// The default video mode. This uses the window size for resolution and doesn't care about other values. + /// + public static VideoMode Default { get; } + } +} +``` + +## `Surface` + +```cs +namespace Silk.NET.Windowing +{ + public static class Surface + { + public static bool IsPlatformSupported { get; } + public static ISurface GetOrCreate(); + public static ISurface CreateNew(); + public static void ClearCurrentContexts(); + } +} +``` + +## `SurfaceExtensions` + +```cs +namespace Silk.NET.Windowing +{ + /// + /// Extensions for ISurface + /// + public static class SurfaceExtensions + { + /// + /// Start the default event loop on this surface. + /// + /// The surface to begin the loop on. + public static void Run(this ISurface surface); + + /// + /// Gets the full size of the given window including its borders. + /// + /// The window to get size information from. + /// The full size of the window (including both content area and borders) + public static Vector2D GetFullSize(this IDesktopSurface window); + + /// + /// Centers this window to the given monitor or, if null, the current monitor the window's on. + /// + /// The window to center. + /// The specific screen to center the window to, if any. + public static void Center(this IDesktopSurface window, IScreen? screen = null); + + /// + /// Sets the window icon to default on the given window. + /// + /// The window. + public static void SetDefaultIcon(this IDesktopWindow window); + + /// + /// Sets a single window icon on the given window. + /// + /// The window. + /// The icon to set. + public static void SetWindowIcon(this IDesktopSurface window, ref RawImage icon); + } +} +``` + +## `Version32` + +Exactly as is from 2.X. + +```cs +namespace Silk.NET.Core +{ + /// + /// A 32-bit version structure. + /// + public readonly struct Version32 + { + /// + /// The underlying Vulkan-compatible 32-bit version integer. + /// + public uint Value { get; } + + /// + /// Creates a Vulkan version structure from the given major, minor, and patch values. + /// + /// The major value. + /// The minor value. + /// The patch value. + public Version32(uint major, uint minor, uint patch); + + /// + /// Creates a Vulkan version structure from the given Vulkan-compatible value. + /// + /// The value. + private Version32(uint value); + + /// + /// Gets the major component of this version structure. + /// + public uint Major { get; } + + /// + /// Gets the minor component of this version structure. + /// + public uint Minor { get; } + + /// + /// Gets the patch component of this version structure. + /// + public uint Patch { get; } + + /// + /// Creates a 32-bit version structure from the given 32-bit unsigned integer. + /// + /// The uint value. + /// The 32-bit version structure. + public static explicit operator Version32(uint val); + + /// + /// Creates a 32-bit version structure from the given managed version class. + /// + /// The version instance. + /// The 32-bit version structure. + public static implicit operator Version32(Version version); + + /// + /// Gets the 32-bit unsigned integer representation for this 32-bit version structure. + /// + /// The 32-bit version structure. + /// The 32-bit unsigned integer. + public static implicit operator uint(Version32 version); + + /// + /// Converts this 32-bit version structure to a managed version class. + /// + /// The 32-bit version structure. + /// The managed representation. + public static implicit operator Version(Version32 version); + } +} +``` + +## `RawImage` + +Exactly as is from 2.X. + +```cs +namespace Silk.NET.Core +{ + /// + /// Represents loaded, uncompressed, processed image data. + /// + public readonly struct RawImage : IEquatable + { + /// + /// Creates a given pixel data and pixel dimensions. + /// + /// The width of the image. + /// The height of the image. + /// The image daqta. + public RawImage(int width, int height, Memory rgbaPixels); + + /// + /// The width of the image in pixels + /// + public int Width { get; } + + /// + /// The height of the image in pixels. + /// + public int Height { get; } + + /// + /// The image data. + /// + public Memory Pixels { get; } + + /// + /// Checks whether the two given s are equal. + /// + /// The first raw image. + /// The second raw image to compare the first against. + /// True if they are equal, false otherwise. + /// + /// This does not check whether the byte arrays are equal, only whether their references are the same. + /// + public static bool operator ==(RawImage left, RawImage right); + + /// + /// Checks whether the two given s are not equal. + /// + /// The first raw image. + /// The second raw image to compare the first against. + /// True if they are not equal, false otherwise. + /// + /// This does not check whether the byte arrays are equal, only whether their references are the same. + /// + public static bool operator !=(RawImage left, RawImage right); + + /// + /// Checks whether the given is equal to this one. + /// + /// The raw image to compare this raw image against. + /// True if they are equal, false otherwise. + /// + /// This does not check whether the byte arrays have equal, only whether their references are the same. + /// + public bool Equals(RawImage other); + + /// + public override bool Equals(object obj); + + /// + public override int GetHashCode(); + } +} +``` + +## `IWindowHandlesSource` + +Pretty much as is from 2.X's `INativeWindowSource` except using the new struct. + +```cs +namespace Silk.NET.Core +{ + public interface IWindowHandlesSource + { + WindowHandles Native { get; } + } +} +``` + +## `WindowHandles` + +Replaces 2.X's `INativeWindow` + +```cs +namespace Silk.NET.Core +{ + [StructLayout(LayoutKind.Auto)] + public struct WindowHandles + { + // ... + } +} +``` + +The Silk.NET team wishes to reserve the right to add any relevant window handles that arise when implementing the reference implementation as nullable fields in this struct. + +The goal is to keep this type lightweight as it is in Silk.NET.Core (to ensure smooth interoperability between Silk.NET packages without creating hard references), hence why interfaces or type-system-driven approached were not used. + +# Meeting Notes + +## 25/02/2022 + +[Video](https://youtu.be/dac3t0oh3VU?t=1984) + +- Approved. +- Questions around windows handles + - What Win32 handles should be available? + - Mostly what we have for 2.X today, but as a struct + - StructLayout = Auto for WindowHandles so that people can't depend on the memory layout (so we can add more handles!) +- Is glue mandatory? + - Nope, you can use GetOrCreate but SilkEntryPoint will be recommended. \ No newline at end of file