diff --git a/Examples/Complete/Integrations/Wpf/View/MainWindow.xaml.cs b/Examples/Complete/Integrations/Wpf/View/MainWindow.xaml.cs
index ae3e94500..5df66952d 100644
--- a/Examples/Complete/Integrations/Wpf/View/MainWindow.xaml.cs
+++ b/Examples/Complete/Integrations/Wpf/View/MainWindow.xaml.cs
@@ -53,7 +53,7 @@ private void Position_PropertyChanged(object sender, System.ComponentModel.Prope
private void OpenFusee()
{
- Task.Run(() =>
+ Task.Run((System.Action)(() =>
{
IO.IOImp = new Fusee.Base.Imp.Desktop.IOImp();
@@ -100,8 +100,8 @@ private void OpenFusee()
// Inject Fusee.Engine InjectMe dependencies (hard coded)
fuseeApp.CanvasImplementor = new Fusee.Engine.Imp.Graphics.Desktop.RenderCanvasImp();
fuseeApp.ContextImplementor = new Fusee.Engine.Imp.Graphics.Desktop.RenderContextImp(fuseeApp.CanvasImplementor);
- Input.AddDriverImp(new Fusee.Engine.Imp.Graphics.Desktop.RenderCanvasInputDriverImp(fuseeApp.CanvasImplementor));
- Input.AddDriverImp(new Fusee.Engine.Imp.Graphics.Desktop.WindowsTouchInputDriverImp(fuseeApp.CanvasImplementor));
+ Input.AddDriverImp((Engine.Common.IInputDriverImp)new Fusee.Engine.Imp.Graphics.Desktop.RenderCanvasInputDriverImp(fuseeApp.CanvasImplementor));
+ Input.AddDriverImp((Engine.Common.IInputDriverImp)new Fusee.Engine.Imp.Graphics.Desktop.WindowsTouchInputDriverImp(fuseeApp.CanvasImplementor));
// app.InputImplementor = new Fusee.Engine.Imp.Graphics.Desktop.InputImp(app.CanvasImplementor);
// app.InputDriverImplementor = new Fusee.Engine.Imp.Input.Desktop.InputDriverImp();
// app.VideoManagerImplementor = ImpFactory.CreateIVideoManagerImp();
@@ -110,7 +110,7 @@ private void OpenFusee()
// Start the app
fuseeApp.Run();
- });
+ }));
}
private void FusToWpfEvents(object sender, Core.FusEvent e)
diff --git a/Examples/Complete/Simple/Silk/Fusee.Examples.Simple.Silk.csproj b/Examples/Complete/Simple/Silk/Fusee.Examples.Simple.Silk.csproj
new file mode 100644
index 000000000..a9fa613e0
--- /dev/null
+++ b/Examples/Complete/Simple/Silk/Fusee.Examples.Simple.Silk.csproj
@@ -0,0 +1,16 @@
+
+
+
+ net6.0
+ $(BaseOutputPath)\Examples\Simple\Desktop\
+
+ Exe
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Examples/Complete/Simple/Silk/FuseeLogo.ico b/Examples/Complete/Simple/Silk/FuseeLogo.ico
new file mode 100644
index 000000000..dbdc5bc33
Binary files /dev/null and b/Examples/Complete/Simple/Silk/FuseeLogo.ico differ
diff --git a/Examples/Complete/Simple/Silk/Main.cs b/Examples/Complete/Simple/Silk/Main.cs
new file mode 100644
index 000000000..1bf5e7452
--- /dev/null
+++ b/Examples/Complete/Simple/Silk/Main.cs
@@ -0,0 +1,79 @@
+using Fusee.Base.Common;
+using Fusee.Base.Core;
+using Fusee.Base.Imp.Desktop;
+using Fusee.Engine.Core;
+using Fusee.Engine.Core.Scene;
+using Fusee.Serialization;
+using System.IO;
+using System.Threading.Tasks;
+
+namespace Fusee.Examples.Simple.Silk
+{
+ public class Simple
+ {
+ public static void Main()
+ {
+ // Inject Fusee.Engine.Base InjectMe dependencies
+ IO.IOImp = new IOImp();
+
+ var fap = new FileAssetProvider("Assets");
+ fap.RegisterTypeHandler(
+ new AssetHandler
+ {
+ ReturnedType = typeof(Font),
+ DecoderAsync = async (string id, object storage) =>
+ {
+ if (!Path.GetExtension(id).Contains("ttf", System.StringComparison.OrdinalIgnoreCase)) return null;
+ return await Task.FromResult(new Font { _fontImp = new FontImp((Stream)storage) });
+ },
+ Decoder = (string id, object storage) =>
+ {
+ if (!Path.GetExtension(id).Contains("ttf", System.StringComparison.OrdinalIgnoreCase)) return null;
+ return new Font { _fontImp = new FontImp((Stream)storage) };
+ },
+ Checker = id => Path.GetExtension(id).Contains("ttf", System.StringComparison.OrdinalIgnoreCase)
+ });
+ fap.RegisterTypeHandler(
+ new AssetHandler
+ {
+ ReturnedType = typeof(SceneContainer),
+ DecoderAsync = async (string id, object storage) =>
+ {
+ if (!Path.GetExtension(id).Contains("fus", System.StringComparison.OrdinalIgnoreCase)) return null;
+ return await FusSceneConverter.ConvertFromAsync(ProtoBuf.Serializer.Deserialize((Stream)storage), id);
+ },
+ Decoder = (string id, object storage) =>
+ {
+ if (!Path.GetExtension(id).Contains("fus", System.StringComparison.OrdinalIgnoreCase)) return null;
+ return FusSceneConverter.ConvertFrom(ProtoBuf.Serializer.Deserialize((Stream)storage), id);
+ },
+ Checker = id => Path.GetExtension(id).Contains("fus", System.StringComparison.OrdinalIgnoreCase)
+ });
+
+ AssetStorage.RegisterProvider(fap);
+
+ var app = new Core.Simple();
+
+ // Inject Fusee.Engine InjectMe dependencies (hard coded)
+ var icon = AssetStorage.Get("FuseeIconTop32.png");
+ app.CanvasImplementor = new Fusee.Engine.Imp.Graphics.SilkDesktop.RenderCanvasImp(icon);
+ app.ContextImplementor = new Fusee.Engine.Imp.Graphics.SilkDesktop.RenderContextImp(app.CanvasImplementor);
+ //Input.AddDriverImp(new Fusee.Engine.Imp.Graphics.SilkDesktop.RenderCanvasInputDriverImp(app.CanvasImplementor));
+ //Input.AddDriverImp(new Fusee.Engine.Imp.Graphics.SilkDesktop.WindowsTouchInputDriverImp(app.CanvasImplementor));
+ // app.InputImplementor = new Fusee.Engine.Imp.Graphics.Desktop.InputImp(app.CanvasImplementor);
+ // app.InputDriverImplementor = new Fusee.Engine.Imp.Input.Desktop.InputDriverImp();
+ // app.VideoManagerImplementor = ImpFactory.CreateIVideoManagerImp();
+
+ app.CanvasImplementor.Init += (s, e) =>
+ {
+
+ app.InitApp();
+ };
+
+
+
+ // Start the app
+ app.Run();
+ }
+ }
+}
\ No newline at end of file
diff --git a/Fusee.sln b/Fusee.sln
index e68dba608..04b90b3c9 100644
--- a/Fusee.sln
+++ b/Fusee.sln
@@ -296,14 +296,11 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Fusee.Examples.RenderContex
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Fusee.Examples.PickingRayCast.Blazor", "Examples\Complete\PickingRayCast\Blazor\Fusee.Examples.PickingRayCast.Blazor.csproj", "{3AFEF2ED-6325-4C11-9B87-A32514FD5AE1}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Fusee.Engine.Imp.Graphics.Silk", "src\Engine\Imp\Graphics\Silk\Fusee.Engine.Imp.Graphics.Silk.csproj", "{29B228C2-2168-46A0-BAB4-BDA0D7ABDDB1}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Fusee.Examples.Simple.Silk", "Examples\Complete\Simple\Silk\Fusee.Examples.Simple.Silk.csproj", "{DEE03C44-A421-43FE-8267-0A9A5BC4E7C3}"
+EndProject
Global
- GlobalSection(SharedMSBuildProjectFiles) = preSolution
- src\Engine\Imp\Graphics\Shared\Fusee.Engine.Imp.Graphics.Shared.projitems*{253263c9-9c67-44a5-94d3-51c586bbdaec}*SharedItemsImports = 13
- src\Engine\Imp\Graphics\Shared\Fusee.Engine.Imp.Graphics.Shared.projitems*{b3ce4f39-fcc4-4388-8130-9d0b9d65d034}*SharedItemsImports = 5
- src\PointCloud\Las\Shared\Fusee.PointCloud.Las.Shared.projitems*{dcc7da71-3e2e-476c-8391-1f9651637503}*SharedItemsImports = 13
- src\PointCloud\Las\Shared\Fusee.PointCloud.Las.Shared.projitems*{f28634e7-52b8-4935-b19e-cb8a6844e6f1}*SharedItemsImports = 5
- src\Engine\Imp\Graphics\Shared\Fusee.Engine.Imp.Graphics.Shared.projitems*{f7ad2bb5-d2b0-4697-bddb-4cc26152a6b6}*SharedItemsImports = 5
- EndGlobalSection
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug-Android|Any CPU = Debug-Android|Any CPU
@@ -1714,6 +1711,42 @@ Global
{3AFEF2ED-6325-4C11-9B87-A32514FD5AE1}.Release-Blazor|Any CPU.Build.0 = Release|Any CPU
{3AFEF2ED-6325-4C11-9B87-A32514FD5AE1}.Release-Desktop|Any CPU.ActiveCfg = Release|Any CPU
{3AFEF2ED-6325-4C11-9B87-A32514FD5AE1}.Release-NuGet|Any CPU.ActiveCfg = Release|Any CPU
+ {29B228C2-2168-46A0-BAB4-BDA0D7ABDDB1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {29B228C2-2168-46A0-BAB4-BDA0D7ABDDB1}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {29B228C2-2168-46A0-BAB4-BDA0D7ABDDB1}.Debug-Android|Any CPU.ActiveCfg = Debug|Any CPU
+ {29B228C2-2168-46A0-BAB4-BDA0D7ABDDB1}.Debug-Android|Any CPU.Build.0 = Debug|Any CPU
+ {29B228C2-2168-46A0-BAB4-BDA0D7ABDDB1}.Debug-Blazor|Any CPU.ActiveCfg = Debug|Any CPU
+ {29B228C2-2168-46A0-BAB4-BDA0D7ABDDB1}.Debug-Blazor|Any CPU.Build.0 = Debug|Any CPU
+ {29B228C2-2168-46A0-BAB4-BDA0D7ABDDB1}.Debug-Desktop|Any CPU.ActiveCfg = Debug|Any CPU
+ {29B228C2-2168-46A0-BAB4-BDA0D7ABDDB1}.Debug-Desktop|Any CPU.Build.0 = Debug|Any CPU
+ {29B228C2-2168-46A0-BAB4-BDA0D7ABDDB1}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {29B228C2-2168-46A0-BAB4-BDA0D7ABDDB1}.Release|Any CPU.Build.0 = Release|Any CPU
+ {29B228C2-2168-46A0-BAB4-BDA0D7ABDDB1}.Release-Android|Any CPU.ActiveCfg = Release|Any CPU
+ {29B228C2-2168-46A0-BAB4-BDA0D7ABDDB1}.Release-Android|Any CPU.Build.0 = Release|Any CPU
+ {29B228C2-2168-46A0-BAB4-BDA0D7ABDDB1}.Release-Blazor|Any CPU.ActiveCfg = Release|Any CPU
+ {29B228C2-2168-46A0-BAB4-BDA0D7ABDDB1}.Release-Blazor|Any CPU.Build.0 = Release|Any CPU
+ {29B228C2-2168-46A0-BAB4-BDA0D7ABDDB1}.Release-Desktop|Any CPU.ActiveCfg = Release|Any CPU
+ {29B228C2-2168-46A0-BAB4-BDA0D7ABDDB1}.Release-Desktop|Any CPU.Build.0 = Release|Any CPU
+ {29B228C2-2168-46A0-BAB4-BDA0D7ABDDB1}.Release-NuGet|Any CPU.ActiveCfg = Release|Any CPU
+ {29B228C2-2168-46A0-BAB4-BDA0D7ABDDB1}.Release-NuGet|Any CPU.Build.0 = Release|Any CPU
+ {DEE03C44-A421-43FE-8267-0A9A5BC4E7C3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {DEE03C44-A421-43FE-8267-0A9A5BC4E7C3}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {DEE03C44-A421-43FE-8267-0A9A5BC4E7C3}.Debug-Android|Any CPU.ActiveCfg = Debug|Any CPU
+ {DEE03C44-A421-43FE-8267-0A9A5BC4E7C3}.Debug-Android|Any CPU.Build.0 = Debug|Any CPU
+ {DEE03C44-A421-43FE-8267-0A9A5BC4E7C3}.Debug-Blazor|Any CPU.ActiveCfg = Debug|Any CPU
+ {DEE03C44-A421-43FE-8267-0A9A5BC4E7C3}.Debug-Blazor|Any CPU.Build.0 = Debug|Any CPU
+ {DEE03C44-A421-43FE-8267-0A9A5BC4E7C3}.Debug-Desktop|Any CPU.ActiveCfg = Debug|Any CPU
+ {DEE03C44-A421-43FE-8267-0A9A5BC4E7C3}.Debug-Desktop|Any CPU.Build.0 = Debug|Any CPU
+ {DEE03C44-A421-43FE-8267-0A9A5BC4E7C3}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {DEE03C44-A421-43FE-8267-0A9A5BC4E7C3}.Release|Any CPU.Build.0 = Release|Any CPU
+ {DEE03C44-A421-43FE-8267-0A9A5BC4E7C3}.Release-Android|Any CPU.ActiveCfg = Release|Any CPU
+ {DEE03C44-A421-43FE-8267-0A9A5BC4E7C3}.Release-Android|Any CPU.Build.0 = Release|Any CPU
+ {DEE03C44-A421-43FE-8267-0A9A5BC4E7C3}.Release-Blazor|Any CPU.ActiveCfg = Release|Any CPU
+ {DEE03C44-A421-43FE-8267-0A9A5BC4E7C3}.Release-Blazor|Any CPU.Build.0 = Release|Any CPU
+ {DEE03C44-A421-43FE-8267-0A9A5BC4E7C3}.Release-Desktop|Any CPU.ActiveCfg = Release|Any CPU
+ {DEE03C44-A421-43FE-8267-0A9A5BC4E7C3}.Release-Desktop|Any CPU.Build.0 = Release|Any CPU
+ {DEE03C44-A421-43FE-8267-0A9A5BC4E7C3}.Release-NuGet|Any CPU.ActiveCfg = Release|Any CPU
+ {DEE03C44-A421-43FE-8267-0A9A5BC4E7C3}.Release-NuGet|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -1845,8 +1878,18 @@ Global
{FF0DEE54-C91E-480F-B103-593FB00E92FF} = {E6E73351-7DED-4B97-B254-A0E903D769C1}
{ED0AC25F-615E-4AE6-A491-ADD41ABEB2D2} = {E6E73351-7DED-4B97-B254-A0E903D769C1}
{3AFEF2ED-6325-4C11-9B87-A32514FD5AE1} = {425DDCA8-659F-44FB-832E-117C7DED152E}
+ {29B228C2-2168-46A0-BAB4-BDA0D7ABDDB1} = {6295AB06-EBE4-4887-A811-20737F6F8645}
+ {DEE03C44-A421-43FE-8267-0A9A5BC4E7C3} = {F799DB2B-D7BA-4B88-A16F-F8E2317137D3}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {CC1775C2-579F-4897-8770-592966D00E3D}
EndGlobalSection
+ GlobalSection(SharedMSBuildProjectFiles) = preSolution
+ src\Engine\Imp\Graphics\Shared\Fusee.Engine.Imp.Graphics.Shared.projitems*{253263c9-9c67-44a5-94d3-51c586bbdaec}*SharedItemsImports = 13
+ src\Engine\Imp\Graphics\Shared\Fusee.Engine.Imp.Graphics.Shared.projitems*{29b228c2-2168-46a0-bab4-bda0d7abddb1}*SharedItemsImports = 5
+ src\Engine\Imp\Graphics\Shared\Fusee.Engine.Imp.Graphics.Shared.projitems*{b3ce4f39-fcc4-4388-8130-9d0b9d65d034}*SharedItemsImports = 5
+ src\PointCloud\Las\Shared\Fusee.PointCloud.Las.Shared.projitems*{dcc7da71-3e2e-476c-8391-1f9651637503}*SharedItemsImports = 13
+ src\PointCloud\Las\Shared\Fusee.PointCloud.Las.Shared.projitems*{f28634e7-52b8-4935-b19e-cb8a6844e6f1}*SharedItemsImports = 5
+ src\Engine\Imp\Graphics\Shared\Fusee.Engine.Imp.Graphics.Shared.projitems*{f7ad2bb5-d2b0-4697-bddb-4cc26152a6b6}*SharedItemsImports = 5
+ EndGlobalSection
EndGlobal
diff --git a/src/Engine/Core/RenderCanvas.cs b/src/Engine/Core/RenderCanvas.cs
index 2c971e4c8..e7f25d066 100644
--- a/src/Engine/Core/RenderCanvas.cs
+++ b/src/Engine/Core/RenderCanvas.cs
@@ -148,6 +148,11 @@ protected int GetWindowHeight()
///
public EventHandler LoadingCompleted;
+ ///
+ /// This event is usually triggered when loading is completed (after init() method)
+ ///
+ public EventHandler InitComplete;
+
///
/// Called after can be used to await async tasks (e.g. loading methods)
///
@@ -309,8 +314,8 @@ public void OpenLink(string link)
/// necessary initialization, call the Init method and enter the application main loop.
///
public void Run()
- {
- CanvasImplementor.Run();
+ {
+ CanvasImplementor.Run();
}
///
diff --git a/src/Engine/Imp/Graphics/Shared/BufferHandle.cs b/src/Engine/Imp/Graphics/Shared/BufferHandle.cs
index 9e54e63ed..501324329 100644
--- a/src/Engine/Imp/Graphics/Shared/BufferHandle.cs
+++ b/src/Engine/Imp/Graphics/Shared/BufferHandle.cs
@@ -4,6 +4,8 @@
namespace Fusee.Engine.Imp.Graphics.Desktop
#elif PLATFORM_ANDROID
namespace Fusee.Engine.Imp.Graphics.Android
+#elif PLATFORM_SILK
+namespace Fusee.Engine.Imp.Graphics.SilkDesktop
#endif
{
///
diff --git a/src/Engine/Imp/Graphics/Shared/Font.cs b/src/Engine/Imp/Graphics/Shared/Font.cs
index bd211bc7f..1bdd8c2a5 100644
--- a/src/Engine/Imp/Graphics/Shared/Font.cs
+++ b/src/Engine/Imp/Graphics/Shared/Font.cs
@@ -5,6 +5,8 @@
namespace Fusee.Engine.Imp.Graphics.Desktop
#elif PLATFORM_ANDROID
namespace Fusee.Engine.Imp.Graphics.Android
+#elif PLATFORM_SILK
+namespace Fusee.Engine.Imp.Graphics.SilkDesktop
#endif
{
internal class Font : IFont
diff --git a/src/Engine/Imp/Graphics/Shared/MeshImp.cs b/src/Engine/Imp/Graphics/Shared/MeshImp.cs
index e92ad0f56..fedcaae16 100644
--- a/src/Engine/Imp/Graphics/Shared/MeshImp.cs
+++ b/src/Engine/Imp/Graphics/Shared/MeshImp.cs
@@ -4,8 +4,9 @@
namespace Fusee.Engine.Imp.Graphics.Desktop
#elif PLATFORM_ANDROID
-
namespace Fusee.Engine.Imp.Graphics.Android
+#elif PLATFORM_SILK
+namespace Fusee.Engine.Imp.Graphics.SilkDesktop
#endif
{
///
diff --git a/src/Engine/Imp/Graphics/Shared/ShaderHandleImp.cs b/src/Engine/Imp/Graphics/Shared/ShaderHandleImp.cs
index da2a64344..789e17215 100644
--- a/src/Engine/Imp/Graphics/Shared/ShaderHandleImp.cs
+++ b/src/Engine/Imp/Graphics/Shared/ShaderHandleImp.cs
@@ -4,6 +4,8 @@
namespace Fusee.Engine.Imp.Graphics.Desktop
#elif PLATFORM_ANDROID
namespace Fusee.Engine.Imp.Graphics.Android
+#elif PLATFORM_SILK
+namespace Fusee.Engine.Imp.Graphics.SilkDesktop
#endif
{
///
diff --git a/src/Engine/Imp/Graphics/Shared/ShaderParam.cs b/src/Engine/Imp/Graphics/Shared/ShaderParam.cs
index a51ceb731..1b6815bf5 100644
--- a/src/Engine/Imp/Graphics/Shared/ShaderParam.cs
+++ b/src/Engine/Imp/Graphics/Shared/ShaderParam.cs
@@ -4,6 +4,8 @@
namespace Fusee.Engine.Imp.Graphics.Desktop
#elif PLATFORM_ANDROID
namespace Fusee.Engine.Imp.Graphics.Android
+#elif PLATFORM_SILK
+namespace Fusee.Engine.Imp.Graphics.SilkDesktop
#endif
{
///
diff --git a/src/Engine/Imp/Graphics/Shared/TextureHandle.cs b/src/Engine/Imp/Graphics/Shared/TextureHandle.cs
index 0c29976f0..5b4005948 100644
--- a/src/Engine/Imp/Graphics/Shared/TextureHandle.cs
+++ b/src/Engine/Imp/Graphics/Shared/TextureHandle.cs
@@ -4,6 +4,8 @@
namespace Fusee.Engine.Imp.Graphics.Desktop
#elif PLATFORM_ANDROID
namespace Fusee.Engine.Imp.Graphics.Android
+#elif PLATFORM_SILK
+namespace Fusee.Engine.Imp.Graphics.SilkDesktop
#endif
{
///
diff --git a/src/Engine/Imp/Graphics/Silk/Fusee.Engine.Imp.Graphics.Silk.csproj b/src/Engine/Imp/Graphics/Silk/Fusee.Engine.Imp.Graphics.Silk.csproj
new file mode 100644
index 000000000..43dba3e7d
--- /dev/null
+++ b/src/Engine/Imp/Graphics/Silk/Fusee.Engine.Imp.Graphics.Silk.csproj
@@ -0,0 +1,43 @@
+
+
+
+ net6.0
+ $(DefineConstants);PLATFORM_SILK
+ $(OutputPath)\$(RootNamespace).xml
+
+ true
+ Fusee Engine Imp Graphics Silk
+
+ true
+
+
+
+
+ analyzers
+
+
+ analyzers
+
+
+ analyzers
+
+
+ analyzers
+
+
+ analyzers
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/Engine/Imp/Graphics/Silk/InputImp.cs b/src/Engine/Imp/Graphics/Silk/InputImp.cs
new file mode 100644
index 000000000..56c3df68f
--- /dev/null
+++ b/src/Engine/Imp/Graphics/Silk/InputImp.cs
@@ -0,0 +1,1012 @@
+using Fusee.Engine.Common;
+using Silk.NET.Input;
+using Silk.NET.Maths;
+using Silk.NET.Windowing;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using MouseButton = Silk.NET.Input.MouseButton;
+
+namespace Fusee.Engine.Imp.Graphics.SilkDesktop
+{
+ ///
+ /// Input driver implementation for keyboard and mouse input on Desktop and Android.
+ ///
+ public class RenderCanvasInputDriverImp : IInputDriverImp
+ {
+ ///
+ /// Constructor. Use this in platform specific application projects.
+ ///
+ /// The render canvas to provide mouse and keyboard input for.
+ public RenderCanvasInputDriverImp(IRenderCanvasImp renderCanvas)
+ {
+ if (renderCanvas == null)
+ throw new ArgumentNullException(nameof(renderCanvas));
+
+ if (!(renderCanvas is RenderCanvasImp))
+ throw new ArgumentException("renderCanvas must be of type RenderCanvasImp", nameof(renderCanvas));
+
+ _gameWindow = ((RenderCanvasImp)renderCanvas)._gameWindow.window;
+ if (_gameWindow == null)
+ throw new ArgumentNullException(nameof(_gameWindow));
+
+ _keyboard = new KeyboardDeviceImp(_gameWindow);
+ _mouse = new MouseDeviceImp(_gameWindow);
+ //_gamePad0 = new GamePadDeviceImp(_gameWindow, 0);
+ //_gamePad1 = new GamePadDeviceImp(_gameWindow, 1);
+ //_gamePad2 = new GamePadDeviceImp(_gameWindow, 2);
+ //_gamePad3 = new GamePadDeviceImp(_gameWindow, 3);
+ }
+
+ private readonly IWindow _gameWindow;
+ private readonly KeyboardDeviceImp _keyboard;
+ private readonly MouseDeviceImp _mouse;
+ //private readonly GamePadDeviceImp _gamePad0;
+ //private readonly GamePadDeviceImp _gamePad1;
+ //private readonly GamePadDeviceImp _gamePad2;
+ //private readonly GamePadDeviceImp _gamePad3;
+
+
+ ///
+ /// Devices supported by this driver: One mouse, one keyboard and up to four gamepads.
+ ///
+ public IEnumerable Devices
+ {
+ get
+ {
+ yield return _mouse;
+ yield return _keyboard;
+ //yield return _gamePad0;
+ //yield return _gamePad1;
+ //yield return _gamePad2;
+ //yield return _gamePad3;
+
+ }
+ }
+
+ ///
+ /// Returns a human readable description of this driver.
+ ///
+ public string DriverDesc
+ {
+ get
+ {
+ const string pf = "Desktop";
+ return "OpenTK GameWindow Mouse and Keyboard input driver for " + pf;
+ }
+ }
+
+ ///
+ /// Returns a (hopefully) unique ID for this driver. Uniqueness is granted by using the
+ /// full class name (including namespace).
+ ///
+ public string DriverId
+ {
+ get { return GetType().FullName; }
+ }
+
+#pragma warning disable 0067
+ ///
+ /// Not supported on this driver. Mouse and keyboard are considered to be connected all the time.
+ /// You can register handlers but they will never get called.
+ ///
+ public event EventHandler DeviceDisconnected;
+
+ ///
+ /// Not supported on this driver. Mouse and keyboard are considered to be connected all the time.
+ /// You can register handlers but they will never get called.
+ ///
+ public event EventHandler NewDeviceConnected;
+#pragma warning restore 0067
+
+ #region IDisposable Support
+ private bool disposedValue = false; // To detect redundant calls
+
+ ///
+ /// Part of the Dispose pattern.
+ ///
+ ///
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!disposedValue)
+ {
+ if (disposing)
+ {
+ // TODO: dispose managed state (managed objects).
+ }
+
+ // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
+ // TODO: set large fields to null.
+
+ disposedValue = true;
+ }
+ }
+
+ // TODO: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources.
+ // ~RenderCanvasInputDriverImp() {
+ // // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
+ // Dispose(false);
+ // }
+
+ // This code added to correctly implement the disposable pattern.
+ ///
+ /// Part of the dispose pattern.
+ ///
+ public void Dispose()
+ {
+ // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
+ Dispose(true);
+ // TODO: uncomment the following line if the finalizer is overridden above.
+ // GC.SuppressFinalize(this);
+ }
+ #endregion
+ }
+
+
+ ///
+ /// Implements a gamepad control option only works for XBox Gamepads
+ ///
+ /// ///
+ /// The current implementation does not fire the and
+ /// events. This driver will always report one connected GamePad no matter how many physical devices are connected
+ /// to the machine. If no physical GamePad is present all of its axes and buttons will return 0 or false.
+ ///
+ // public class GamePadDeviceImp : IInputDeviceImp
+ // {
+ // private readonly IWindow _gameWindow;
+ // private readonly int DeviceID;
+ // private ButtonImpDescription _btnADesc, _btnXDesc, _btnYDesc, _btnBDesc, _btnStartDesc, _btnSelectDesc, _dpadUpDesc, _dpadDownDesc, _dpadLeftDesc, _dpadRightDesc, _btnLeftDesc, _btnRightDesc, _btnL3Desc, _btnR3Desc;
+
+ // internal GamePadDeviceImp(IWindow window, int deviceID = 0)
+ // {
+ // _gameWindow = window;
+ // DeviceID = deviceID;
+
+ // _btnADesc = new ButtonImpDescription
+ // {
+ // ButtonDesc = new ButtonDescription
+ // {
+ // Name = "GP A",
+ // Id = 0
+ // },
+ // PollButton = true
+ // };
+ // _btnXDesc = new ButtonImpDescription
+ // {
+ // ButtonDesc = new ButtonDescription
+ // {
+ // Name = "GP X",
+ // Id = 1
+ // },
+ // PollButton = true
+ // };
+ // _btnYDesc = new ButtonImpDescription
+ // {
+ // ButtonDesc = new ButtonDescription
+ // {
+ // Name = "GP Y",
+ // Id = 2
+ // },
+ // PollButton = true
+ // };
+ // _btnBDesc = new ButtonImpDescription
+ // {
+ // ButtonDesc = new ButtonDescription
+ // {
+ // Name = "GP B",
+ // Id = 3
+ // },
+ // PollButton = true
+ // };
+ // _btnStartDesc = new ButtonImpDescription
+ // {
+ // ButtonDesc = new ButtonDescription
+ // {
+ // Name = "GP Start",
+ // Id = 4
+ // },
+ // PollButton = true
+ // };
+ // _btnSelectDesc = new ButtonImpDescription
+ // {
+ // ButtonDesc = new ButtonDescription
+ // {
+ // Name = "GP Back",
+ // Id = 5
+ // },
+ // PollButton = true
+ // };
+ // _btnLeftDesc = new ButtonImpDescription
+ // {
+ // ButtonDesc = new ButtonDescription
+ // {
+ // Name = "GP left button",
+ // Id = 6
+ // },
+ // PollButton = true
+ // };
+ // _btnRightDesc = new ButtonImpDescription
+ // {
+ // ButtonDesc = new ButtonDescription
+ // {
+ // Name = "GP right button",
+ // Id = 7
+ // },
+ // PollButton = true
+ // };
+ // _btnL3Desc = new ButtonImpDescription
+ // {
+ // ButtonDesc = new ButtonDescription
+ // {
+ // Name = "GP L3 button",
+ // Id = 8
+ // },
+ // PollButton = true
+ // };
+ // _btnR3Desc = new ButtonImpDescription
+ // {
+ // ButtonDesc = new ButtonDescription
+ // {
+ // Name = "GP R3 button",
+ // Id = 9
+ // },
+ // PollButton = true
+ // };
+ // _dpadUpDesc = new ButtonImpDescription
+ // {
+ // ButtonDesc = new ButtonDescription
+ // {
+ // Name = "GP Dpad up",
+ // Id = 10
+ // },
+ // PollButton = true
+ // };
+ // _dpadDownDesc = new ButtonImpDescription
+ // {
+ // ButtonDesc = new ButtonDescription
+ // {
+ // Name = "GP Dpad Down",
+ // Id = 11
+ // },
+ // PollButton = true
+ // };
+ // _dpadLeftDesc = new ButtonImpDescription
+ // {
+ // ButtonDesc = new ButtonDescription
+ // {
+ // Name = "GP Dpad Left",
+ // Id = 12
+ // },
+ // PollButton = true
+ // };
+ // _dpadRightDesc = new ButtonImpDescription
+ // {
+ // ButtonDesc = new ButtonDescription
+ // {
+ // Name = "GP Dpad Right",
+ // Id = 13
+ // },
+ // PollButton = true
+ // };
+ // }
+
+ // ///
+ // /// Returns Name of Device.
+ // ///
+ // public string Id
+ // {
+
+ // get
+ // {
+ // try
+ // {
+ // return GLFW.GetGamepadName(DeviceID);
+ // }
+ // catch
+ // {
+ // return "No gamepad connected";
+ // }
+ // }
+ // }
+ // ///
+ // /// Description.
+ // ///
+ // public string Desc
+ // {
+ // get
+ // {
+ // return "Standard XBox-Gamepad implementation.";
+ // }
+ // }
+
+ // ///
+ // /// Returns Type of input device.
+ // ///
+ // public DeviceCategory Category
+ // {
+ // get
+ // {
+ // return DeviceCategory.GameController;
+ // }
+ // }
+ // ///
+ // /// Returns Number of Axes.
+ // ///
+ // public int AxesCount => 6;
+
+ // ///
+ // /// Returns description information for all axes.
+ // ///
+ // public IEnumerable AxisImpDesc
+ // {
+ // get
+ // {
+ // yield return new AxisImpDescription
+ // {
+ // AxisDesc = new AxisDescription
+ // {
+ // Name = "Left Stick X",
+ // Id = 0,
+ // Direction = AxisDirection.X,
+ // Nature = AxisNature.Position,
+ // Bounded = AxisBoundedType.Constant,
+ // MinValueOrAxis = -1,
+ // MaxValueOrAxis = 1
+ // },
+ // PollAxis = true
+ // };
+ // yield return new AxisImpDescription
+ // {
+ // AxisDesc = new AxisDescription
+ // {
+ // Name = "Left Stick Y",
+ // Id = 1,
+ // Direction = AxisDirection.Y,
+ // Nature = AxisNature.Position,
+ // Bounded = AxisBoundedType.Constant,
+ // MinValueOrAxis = -1,
+ // MaxValueOrAxis = 1
+ // },
+ // PollAxis = true
+ // };
+ // yield return new AxisImpDescription
+ // {
+ // AxisDesc = new AxisDescription
+ // {
+ // Name = "Right Stick X",
+ // Id = 2,
+ // Direction = AxisDirection.X,
+ // Nature = AxisNature.Position,
+ // Bounded = AxisBoundedType.Constant,
+ // MinValueOrAxis = -1,
+ // MaxValueOrAxis = 1
+ // },
+ // PollAxis = true
+ // };
+ // yield return new AxisImpDescription
+ // {
+ // AxisDesc = new AxisDescription
+ // {
+ // Name = "Right Stick Y",
+ // Id = 3,
+ // Direction = AxisDirection.Y,
+ // Nature = AxisNature.Position,
+ // Bounded = AxisBoundedType.Constant,
+ // MinValueOrAxis = -1,
+ // MaxValueOrAxis = 1
+ // },
+ // PollAxis = true
+ // };
+ // yield return new AxisImpDescription
+ // {
+ // AxisDesc = new AxisDescription
+ // {
+ // Name = "Left Trigger",
+ // Id = 4,
+ // Direction = AxisDirection.Y,
+ // Nature = AxisNature.Position,
+ // Bounded = AxisBoundedType.Constant,
+ // MinValueOrAxis = 0,
+ // MaxValueOrAxis = 1
+ // },
+ // PollAxis = true
+ // };
+ // yield return new AxisImpDescription
+ // {
+ // AxisDesc = new AxisDescription
+ // {
+ // Name = "Right Trigger",
+ // Id = 5,
+ // Direction = AxisDirection.Y,
+ // Nature = AxisNature.Position,
+ // Bounded = AxisBoundedType.Constant,
+ // MinValueOrAxis = 0,
+ // MaxValueOrAxis = 1
+ // },
+ // PollAxis = true
+ // };
+ // }
+ // }
+ // ///
+ // /// Returns Number of Buttons.
+ // ///
+ // public int ButtonCount
+ // {
+ // get
+ // {
+ // return 14;
+ // }
+
+ // }
+
+ // ///
+ // /// A gamepad exposes 14 buttons.
+ // ///
+ // public IEnumerable ButtonImpDesc
+ // {
+ // get
+ // {
+ // yield return _btnADesc;
+ // yield return _btnXDesc;
+ // yield return _btnYDesc;
+ // yield return _btnBDesc;
+ // yield return _btnStartDesc;
+ // yield return _btnSelectDesc;
+ // yield return _btnRightDesc;
+ // yield return _btnLeftDesc;
+ // yield return _btnR3Desc;
+ // yield return _btnL3Desc;
+ // yield return _dpadUpDesc;
+ // yield return _dpadDownDesc;
+ // yield return _dpadLeftDesc;
+ // yield return _dpadRightDesc;
+
+ // }
+ // }
+ // ///
+ // /// All axis are Poll based see GetAxis
+ // ///
+ //#pragma warning disable 0067
+ // public event EventHandler AxisValueChanged;
+
+ // ///
+ // /// no Buttons implemented
+ // ///
+ // public event EventHandler ButtonValueChanged;
+ //#pragma warning restore 0067
+
+ // ///
+ // /// Retrieves values for the X, Y and Trigger axes. No other axes are supported by this device.
+ // ///
+ // /// The axis to retrieve information for.
+ // /// The value at the given axis.
+ // public float GetAxis(int iAxisId)
+ // {
+ // JoystickState state = _gameWindow.JoystickStates[DeviceID];
+ // if (state != null)
+ // {
+ // try
+ // {
+ // return state.GetAxis(iAxisId);
+ // }
+ // catch
+ // {
+ // return 0;
+ // }
+ // }
+ // return 0;
+ // }
+
+ // ///
+ // /// Returns a Boolean Value for Controller Input.
+ // ///
+ // public bool GetButton(int iButtonId)
+ // {
+ // JoystickState state = _gameWindow.JoystickStates[DeviceID];
+ // if (state != null)
+ // {
+ // try
+ // {
+ // return state.IsButtonDown(iButtonId);
+ // }
+ // catch
+ // {
+ // return false;
+ // }
+ // }
+ // return false;
+ // }
+ // }
+
+ ///
+ /// Keyboard input device implementation for Desktop an Android platforms.
+ ///
+ public class KeyboardDeviceImp : IInputDeviceImp
+ {
+ private readonly IWindow _gameWindow;
+ private readonly Keymapper _keymapper;
+
+ ///
+ /// Should be called by the driver only.
+ ///
+ ///
+ internal KeyboardDeviceImp(IWindow gameWindow)
+ {
+ _gameWindow = gameWindow;
+ _keymapper = new Keymapper();
+ var input = _gameWindow.CreateInput();
+ for (int i = 0; i < input.Keyboards.Count; i++)
+ {
+ input.Keyboards[i].KeyDown += OnGameWinKeyDown;
+ input.Keyboards[i].KeyUp += OnGameWinKeyUp;
+ }
+ }
+
+ ///
+ /// Returns the number of Axes (==0, keyboard does not support any axes).
+ ///
+ public int AxesCount
+ {
+ get
+ {
+ return 0;
+ }
+ }
+
+ ///
+ /// Empty enumeration for keyboard, since is 0.
+ ///
+ public IEnumerable AxisImpDesc
+ {
+ get
+ {
+ yield break;
+ }
+ }
+
+ ///
+ /// Returns the number of enum values of
+ ///
+ public int ButtonCount
+ {
+ get
+ {
+ return Enum.GetNames(typeof(KeyCodes)).Length;
+ }
+ }
+
+ ///
+ /// Returns a description for each keyboard button.
+ ///
+ public IEnumerable ButtonImpDesc
+ {
+ get
+ {
+ return from k in _keymapper orderby k.Value.Id select new ButtonImpDescription { ButtonDesc = k.Value, PollButton = false };
+ }
+ }
+
+ ///
+ /// This is a keyboard device, so this property returns .
+ ///
+ public DeviceCategory Category
+ {
+ get
+ {
+ return DeviceCategory.Keyboard;
+ }
+ }
+
+ ///
+ /// Human readable description of this device (to be used in dialogs).
+ ///
+ public string Desc
+ {
+ get
+ {
+ return "Standard Keyboard implementation.";
+ }
+ }
+
+ ///
+ /// Returns a (hopefully) unique ID for this driver. Uniqueness is granted by using the
+ /// full class name (including namespace).
+ ///
+ public string Id
+ {
+ get
+ {
+ return GetType().FullName;
+ }
+ }
+
+
+#pragma warning disable 0067
+ ///
+ /// No axes exist on this device, so listeners registered to this event will never get called.
+ ///
+ public event EventHandler AxisValueChanged;
+
+ ///
+ /// All buttons exhibited by this device are event-driven buttons, so this is the point to hook to in order
+ /// to get information from this device.
+ ///
+ public event EventHandler ButtonValueChanged;
+#pragma warning restore 0067
+
+ ///
+ /// Called when keyboard button is pressed down.
+ ///
+ /// The instance containing the event data.
+ protected void OnGameWinKeyDown(IKeyboard keyboard, Key key, int value)
+ {
+ if (ButtonValueChanged != null && _keymapper.TryGetValue(key, out ButtonDescription btnDesc))
+ {
+ ButtonValueChanged(this, new ButtonValueChangedArgs
+ {
+ Pressed = true,
+ Button = btnDesc
+ });
+ }
+ }
+
+ ///
+ /// Called when keyboard button is released.
+ ///
+ /// The instance containing the event data.
+ protected void OnGameWinKeyUp(IKeyboard keyboard, Key key, int arg3)
+ {
+ if (ButtonValueChanged != null && _keymapper.TryGetValue(key, out ButtonDescription btnDesc))
+ {
+ ButtonValueChanged(this, new ButtonValueChangedArgs
+ {
+ Pressed = false,
+ Button = btnDesc
+ });
+ }
+ }
+
+ ///
+ /// This device does not support any axes at all. Always throws.
+ ///
+ /// No matter what you specify here, you'll evoke an exception.
+ /// No return, always throws.
+ public float GetAxis(int iAxisId)
+ {
+ throw new InvalidOperationException($"Unsupported axis {iAxisId}. This device does not support any axis at all.");
+ }
+
+ ///
+ /// This device does not support to-be-polled-buttons. All keyboard buttons are event-driven. Listen to the
+ /// event to receive keyboard notifications from this device.
+ ///
+ /// No matter what you specify here, you'll evoke an exception.
+ /// No return, always throws.
+ public bool GetButton(int iButtonId)
+ {
+ throw new InvalidOperationException($"Button {iButtonId} does not exist or is no pollable. Listen to the ButtonValueChanged event to receive keyboard notifications from this device.");
+ }
+ }
+
+ ///
+ /// Mouse input device implementation for Desktop an Android platforms.
+ ///
+ public class MouseDeviceImp : IInputDeviceImp
+ {
+ private readonly IWindow _gameWindow;
+ private ButtonImpDescription _btnLeftDesc, _btnRightDesc, _btnMiddleDesc;
+
+ ///
+ /// Creates a new mouse input device instance using an existing .
+ ///
+ /// The game window providing mouse input.
+ public MouseDeviceImp(IWindow gameWindow)
+ {
+ _gameWindow = gameWindow;
+ var input = _gameWindow.CreateInput();
+
+ for (var i = 0; i < input.Mice.Count; i++)
+ {
+ input.Mice[i].MouseMove += OnMouseMove;
+ input.Mice[i].MouseDown += OnGameWinMouseDown;
+ input.Mice[i].MouseUp += OnGameWinMouseUp;
+ }
+
+
+ _btnLeftDesc = new ButtonImpDescription
+ {
+ ButtonDesc = new ButtonDescription
+ {
+ Name = "Left",
+ Id = (int)MouseButtons.Left
+ },
+ PollButton = false
+ };
+ _btnMiddleDesc = new ButtonImpDescription
+ {
+ ButtonDesc = new ButtonDescription
+ {
+ Name = "Middle",
+ Id = (int)MouseButtons.Middle
+ },
+ PollButton = false
+ };
+ _btnRightDesc = new ButtonImpDescription
+ {
+ ButtonDesc = new ButtonDescription
+ {
+ Name = "Right",
+ Id = (int)MouseButtons.Right
+ },
+ PollButton = false
+ };
+ }
+
+ ///
+ /// Number of axes. Here seven: "X", "Y" and "Wheel" as well as MinX, MaxX, MinY and MaxY
+ ///
+ public int AxesCount => 7;
+
+ ///
+ /// Returns description information for all axes.
+ ///
+ public IEnumerable AxisImpDesc
+ {
+ get
+ {
+ yield return new AxisImpDescription
+ {
+ AxisDesc = new AxisDescription
+ {
+ Name = "X",
+ Id = (int)MouseAxes.X,
+ Direction = AxisDirection.X,
+ Nature = AxisNature.Position,
+ Bounded = AxisBoundedType.OtherAxis,
+ MinValueOrAxis = (int)MouseAxes.MinX,
+ MaxValueOrAxis = (int)MouseAxes.MaxX
+ },
+ PollAxis = false
+ };
+ yield return new AxisImpDescription
+ {
+ AxisDesc = new AxisDescription
+ {
+ Name = "Y",
+ Id = (int)MouseAxes.Y,
+ Direction = AxisDirection.Y,
+ Nature = AxisNature.Position,
+ Bounded = AxisBoundedType.OtherAxis,
+ MinValueOrAxis = (int)MouseAxes.MinY,
+ MaxValueOrAxis = (int)MouseAxes.MaxY
+ },
+ PollAxis = false
+ };
+ yield return new AxisImpDescription
+ {
+ AxisDesc = new AxisDescription
+ {
+ Name = "Wheel",
+ Id = (int)MouseAxes.Wheel,
+ Direction = AxisDirection.Z,
+ Nature = AxisNature.Position,
+ Bounded = AxisBoundedType.Unbound,
+ MinValueOrAxis = float.NaN,
+ MaxValueOrAxis = float.NaN
+ },
+ PollAxis = true
+ };
+ yield return new AxisImpDescription
+ {
+ AxisDesc = new AxisDescription
+ {
+ Name = "MinX",
+ Id = (int)MouseAxes.MinX,
+ Direction = AxisDirection.X,
+ Nature = AxisNature.Position,
+ Bounded = AxisBoundedType.Unbound,
+ MinValueOrAxis = float.NaN,
+ MaxValueOrAxis = float.NaN
+ },
+ PollAxis = true
+ };
+ yield return new AxisImpDescription
+ {
+ AxisDesc = new AxisDescription
+ {
+ Name = "MaxX",
+ Id = (int)MouseAxes.MaxX,
+ Direction = AxisDirection.X,
+ Nature = AxisNature.Position,
+ Bounded = AxisBoundedType.Unbound,
+ MinValueOrAxis = float.NaN,
+ MaxValueOrAxis = float.NaN
+ },
+ PollAxis = true
+ };
+ yield return new AxisImpDescription
+ {
+ AxisDesc = new AxisDescription
+ {
+ Name = "MinY",
+ Id = (int)MouseAxes.MinY,
+ Direction = AxisDirection.Y,
+ Nature = AxisNature.Position,
+ Bounded = AxisBoundedType.Unbound,
+ MinValueOrAxis = float.NaN,
+ MaxValueOrAxis = float.NaN
+ },
+ PollAxis = true
+ };
+ yield return new AxisImpDescription
+ {
+ AxisDesc = new AxisDescription
+ {
+ Name = "MaxY",
+ Id = (int)MouseAxes.MaxY,
+ Direction = AxisDirection.Y,
+ Nature = AxisNature.Position,
+ Bounded = AxisBoundedType.Unbound,
+ MinValueOrAxis = float.NaN,
+ MaxValueOrAxis = float.NaN
+ },
+ PollAxis = true
+ };
+ }
+ }
+
+ ///
+ /// Number of buttons exposed by this device. Here three: Left, Middle and Right mouse buttons.
+ ///
+ public int ButtonCount => 3;
+
+ ///
+ /// A mouse exposes three buttons: left, middle and right.
+ ///
+ public IEnumerable ButtonImpDesc
+ {
+ get
+ {
+ yield return _btnLeftDesc;
+ yield return _btnMiddleDesc;
+ yield return _btnRightDesc;
+ }
+ }
+
+ ///
+ /// Returns , just because it's a mouse.
+ ///
+ public DeviceCategory Category => DeviceCategory.Mouse;
+
+ ///
+ /// Short description string for this device to be used in dialogs.
+ ///
+ public string Desc => "Standard Mouse implementation.";
+
+ ///
+ /// Returns a (hopefully) unique ID for this driver. Uniqueness is granted by using the
+ /// full class name (including namespace).
+ ///
+ public string Id => GetType().FullName;
+
+ ///
+ /// Mouse movement is event-based. Listen to this event to get information about mouse movement.
+ ///
+ public event EventHandler AxisValueChanged;
+
+ ///
+ /// All three mouse buttons are event-based. Listen to this event to get information about mouse button state changes.
+ ///
+ public event EventHandler ButtonValueChanged;
+
+ ///
+ /// Retrieves values for the X, Y and Wheel axes. No other axes are supported by this device.
+ ///
+ /// The axis to retrieve information for.
+ /// The value at the given axis.
+ public float GetAxis(int iAxisId)
+ {
+ var input = _gameWindow.CreateInput();
+
+
+ return iAxisId switch
+ {
+ (int)MouseAxes.Wheel => input.Mice[0].ScrollWheels[0].Y, // TODO: refactor
+ (int)MouseAxes.MinX => 0,
+ (int)MouseAxes.MaxX => _gameWindow.Size.X,
+ (int)MouseAxes.MinY => 0,
+ (int)MouseAxes.MaxY => _gameWindow.Size.Y,
+ _ => throw new InvalidOperationException($"Unknown axis {iAxisId}. Cannot get value for unknown axis."),
+ };
+ }
+
+ ///
+ /// Called when the game window's mouse is moved.
+ ///
+ /// The instance containing the event data.
+ protected void OnMouseMove(IMouse mouse, System.Numerics.Vector2 pos)
+ {
+ if (AxisValueChanged != null)
+ {
+ AxisValueChanged(this, new AxisValueChangedArgs { Axis = AxisImpDesc.First(x => x.AxisDesc.Id == (int)MouseAxes.X).AxisDesc, Value = pos.X });
+ AxisValueChanged(this, new AxisValueChangedArgs { Axis = AxisImpDesc.First(y => y.AxisDesc.Id == (int)MouseAxes.Y).AxisDesc, Value = pos.Y });
+ }
+ }
+
+ ///
+ /// This device does not support to-be-polled-buttons. All mouse buttons are event-driven. Listen to the
+ /// event to revive keyboard notifications from this device.
+ ///
+ /// No matter what you specify here, you'll evoke an exception.
+ /// No return, always throws.
+ public bool GetButton(int iButtonId)
+ {
+ throw new InvalidOperationException(
+ $"Unsupported axis {iButtonId}. This device does not support any to-be polled axes at all.");
+ }
+
+ ///
+ /// Called when the game window's mouse is pressed down.
+ ///
+ /// The instance containing the event data.
+ protected void OnGameWinMouseDown(IMouse mouse, MouseButton btn)
+ {
+ if (ButtonValueChanged != null)
+ {
+ ButtonDescription btnDesc;
+ switch (btn)
+ {
+ case MouseButton.Left:
+ btnDesc = _btnLeftDesc.ButtonDesc;
+ break;
+ case MouseButton.Middle:
+ btnDesc = _btnMiddleDesc.ButtonDesc;
+ break;
+ case MouseButton.Right:
+ btnDesc = _btnRightDesc.ButtonDesc;
+ break;
+ default:
+ return;
+ }
+
+ ButtonValueChanged(this, new ButtonValueChangedArgs
+ {
+ Pressed = true,
+ Button = btnDesc
+ });
+ }
+ }
+
+ ///
+ /// Called when the game window's mouse is released.
+ ///
+ /// The instance containing the event data.
+ protected void OnGameWinMouseUp(IMouse mouse, MouseButton btn)
+ {
+ if (ButtonValueChanged != null)
+ {
+ ButtonDescription btnDesc;
+ switch (btn)
+ {
+ case MouseButton.Left:
+ btnDesc = _btnLeftDesc.ButtonDesc;
+ break;
+ case MouseButton.Middle:
+ btnDesc = _btnMiddleDesc.ButtonDesc;
+ break;
+ case MouseButton.Right:
+ btnDesc = _btnRightDesc.ButtonDesc;
+ break;
+ default:
+ return;
+ }
+
+ ButtonValueChanged(this, new ButtonValueChangedArgs
+ {
+ Pressed = false,
+ Button = btnDesc
+ });
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Engine/Imp/Graphics/Silk/Keymapper.cs b/src/Engine/Imp/Graphics/Silk/Keymapper.cs
new file mode 100644
index 000000000..18bfe2d9c
--- /dev/null
+++ b/src/Engine/Imp/Graphics/Silk/Keymapper.cs
@@ -0,0 +1,106 @@
+using Fusee.Engine.Common;
+using Silk.NET.Input;
+using System.Collections.Generic;
+
+namespace Fusee.Engine.Imp.Graphics.SilkDesktop
+{
+ internal class Keymapper : Dictionary
+ {
+ #region Constructors
+ ///
+ /// Initializes the map between KeyCodes and OpenTK.Key
+ ///
+ internal Keymapper()
+ {
+ this.Add(Key.Escape, new ButtonDescription { Name = KeyCodes.Escape.ToString(), Id = (int)KeyCodes.Escape });
+
+ // Function keys
+ for (int i = 0; i < 24; i++)
+ {
+ this.Add(Key.F1 + i, new ButtonDescription { Name = $"F{i}", Id = (int)KeyCodes.F1 + i });
+ }
+
+ // Number keys (0-9)
+ for (int i = 0; i <= 9; i++)
+ {
+ this.Add(Key.D0 + i, new ButtonDescription { Name = $"D{i}", Id = (int)0x30 + i });
+ }
+
+ // Letters (A-Z)
+ for (int i = 0; i < 26; i++)
+ {
+ this.Add(Key.A + i, new ButtonDescription { Name = ((KeyCodes)(0x41 + i)).ToString(), Id = (int)(0x41 + i) });
+ }
+
+ this.Add(Key.Tab, new ButtonDescription { Name = KeyCodes.Tab.ToString(), Id = (int)KeyCodes.Tab });
+ this.Add(Key.CapsLock, new ButtonDescription { Name = KeyCodes.Capital.ToString(), Id = (int)KeyCodes.Capital });
+ this.Add(Key.ControlLeft, new ButtonDescription { Name = KeyCodes.LControl.ToString(), Id = (int)KeyCodes.LControl });
+ this.Add(Key.ShiftLeft, new ButtonDescription { Name = KeyCodes.LShift.ToString(), Id = (int)KeyCodes.LShift });
+ this.Add(Key.SuperLeft, new ButtonDescription { Name = KeyCodes.LWin.ToString(), Id = (int)KeyCodes.LWin });
+ this.Add(Key.AltLeft, new ButtonDescription { Name = KeyCodes.LMenu.ToString(), Id = (int)KeyCodes.LMenu });
+ this.Add(Key.Space, new ButtonDescription { Name = KeyCodes.Space.ToString(), Id = (int)KeyCodes.Space });
+ this.Add(Key.AltRight, new ButtonDescription { Name = KeyCodes.RMenu.ToString(), Id = (int)KeyCodes.RMenu });
+ this.Add(Key.SuperRight, new ButtonDescription { Name = KeyCodes.RWin.ToString(), Id = (int)KeyCodes.RWin });
+ this.Add(Key.Menu, new ButtonDescription { Name = KeyCodes.Apps.ToString(), Id = (int)KeyCodes.Apps });
+ this.Add(Key.ControlRight, new ButtonDescription { Name = KeyCodes.RControl.ToString(), Id = (int)KeyCodes.RControl });
+ this.Add(Key.ShiftRight, new ButtonDescription { Name = KeyCodes.RShift.ToString(), Id = (int)KeyCodes.RShift });
+ this.Add(Key.Enter, new ButtonDescription { Name = KeyCodes.Return.ToString(), Id = (int)KeyCodes.Return });
+ this.Add(Key.Backspace, new ButtonDescription { Name = KeyCodes.Back.ToString(), Id = (int)KeyCodes.Back });
+
+ this.Add(Key.Semicolon, new ButtonDescription { Name = KeyCodes.Oem1.ToString(), Id = (int)KeyCodes.Oem1 });
+ this.Add(Key.Slash, new ButtonDescription { Name = KeyCodes.Oem2.ToString(), Id = (int)KeyCodes.Oem2 });
+ //this.Add(Key.Tilde, new ButtonDescription { Name = KeyCodes.Oem3.ToString(), Id = (int)KeyCodes.Oem3 });
+ this.Add(Key.LeftBracket, new ButtonDescription { Name = KeyCodes.Oem4.ToString(), Id = (int)KeyCodes.Oem4 });
+ this.Add(Key.BackSlash, new ButtonDescription { Name = KeyCodes.Oem5.ToString(), Id = (int)KeyCodes.Oem5 });
+ this.Add(Key.RightBracket, new ButtonDescription { Name = KeyCodes.Oem6.ToString(), Id = (int)KeyCodes.Oem6 });
+ //this.Add(Key.Quote, new ButtonDescription { Name = KeyCodes.Oem7.ToString(), Id = (int)KeyCodes.Oem7 });
+ //this.Add(Key.Plus, new ButtonDescription { Name = KeyCodes.OemPlus.ToString(), Id = (int)KeyCodes.OemPlus });
+ this.Add(Key.Comma, new ButtonDescription { Name = KeyCodes.OemComma.ToString(), Id = (int)KeyCodes.OemComma });
+ this.Add(Key.Minus, new ButtonDescription { Name = KeyCodes.OemMinus.ToString(), Id = (int)KeyCodes.OemMinus });
+ this.Add(Key.Period, new ButtonDescription { Name = KeyCodes.OemPeriod.ToString(), Id = (int)KeyCodes.OemPeriod });
+
+ this.Add(Key.Home, new ButtonDescription { Name = KeyCodes.Home.ToString(), Id = (int)KeyCodes.Home });
+ this.Add(Key.End, new ButtonDescription { Name = KeyCodes.End.ToString(), Id = (int)KeyCodes.End });
+ this.Add(Key.Delete, new ButtonDescription { Name = KeyCodes.Delete.ToString(), Id = (int)KeyCodes.Delete });
+ this.Add(Key.PageUp, new ButtonDescription { Name = KeyCodes.Prior.ToString(), Id = (int)KeyCodes.Prior });
+ this.Add(Key.PageDown, new ButtonDescription { Name = KeyCodes.Next.ToString(), Id = (int)KeyCodes.Next });
+ this.Add(Key.PrintScreen, new ButtonDescription { Name = KeyCodes.Print.ToString(), Id = (int)KeyCodes.Print });
+ this.Add(Key.Pause, new ButtonDescription { Name = KeyCodes.Pause.ToString(), Id = (int)KeyCodes.Pause });
+ this.Add(Key.NumLock, new ButtonDescription { Name = KeyCodes.NumLock.ToString(), Id = (int)KeyCodes.NumLock });
+
+ this.Add(Key.ScrollLock, new ButtonDescription { Name = KeyCodes.Scroll.ToString(), Id = (int)KeyCodes.Scroll });
+ // Do we need to do something here?? this.Add(Key.PrintScreen, new ButtonDescription {Name = KeyCodes.Snapshot.ToString(), Id = (int)KeyCodes.Snapshot});
+ //this.Add(Key.Clear, new ButtonDescription { Name = KeyCodes.Clear.ToString(), Id = (int)KeyCodes.Clear });
+ this.Add(Key.Insert, new ButtonDescription { Name = KeyCodes.Insert.ToString(), Id = (int)KeyCodes.Insert });
+
+ //this.Add(Key.Sleep, new ButtonDescription { Name = KeyCodes.Sleep.ToString(), Id = (int)KeyCodes.Sleep });
+
+ // KeyPad
+ for (int i = 0; i <= 9; i++)
+ {
+ this.Add(Key.Keypad0 + i, new ButtonDescription { Name = $"Numpad{i}", Id = (int)KeyCodes.NumPad0 + i });
+ }
+
+ this.Add(Key.KeypadDecimal, new ButtonDescription { Name = KeyCodes.Decimal.ToString(), Id = (int)KeyCodes.Decimal });
+ this.Add(Key.KeypadAdd, new ButtonDescription { Name = KeyCodes.Add.ToString(), Id = (int)KeyCodes.Add });
+ this.Add(Key.KeypadSubtract, new ButtonDescription { Name = KeyCodes.Subtract.ToString(), Id = (int)KeyCodes.Subtract });
+ this.Add(Key.KeypadDivide, new ButtonDescription { Name = KeyCodes.Divide.ToString(), Id = (int)KeyCodes.Divide });
+ this.Add(Key.KeypadMultiply, new ButtonDescription { Name = KeyCodes.Multiply.ToString(), Id = (int)KeyCodes.Multiply });
+
+ // Navigation
+ this.Add(Key.Up, new ButtonDescription { Name = KeyCodes.Up.ToString(), Id = (int)KeyCodes.Up });
+ this.Add(Key.Down, new ButtonDescription { Name = KeyCodes.Down.ToString(), Id = (int)KeyCodes.Down });
+ this.Add(Key.Left, new ButtonDescription { Name = KeyCodes.Left.ToString(), Id = (int)KeyCodes.Left });
+ this.Add(Key.Right, new ButtonDescription { Name = KeyCodes.Right.ToString(), Id = (int)KeyCodes.Right });
+ /*
+ catch (ArgumentException e)
+ {
+ //Debug.Print("Exception while creating keymap: '{0}'.", e.ToString());
+ System.Windows.Forms.MessageBox.Show(
+ String.Format("Exception while creating keymap: '{0}'.", e.ToString()));
+ }
+ */
+ }
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/src/Engine/Imp/Graphics/Silk/RenderCanvasImp.cs b/src/Engine/Imp/Graphics/Silk/RenderCanvasImp.cs
new file mode 100644
index 000000000..5e6c5dcff
--- /dev/null
+++ b/src/Engine/Imp/Graphics/Silk/RenderCanvasImp.cs
@@ -0,0 +1,734 @@
+using Fusee.Base.Core;
+using Fusee.Engine.Common;
+using SixLabors.ImageSharp.PixelFormats;
+using SixLabors.ImageSharp.Processing;
+using System;
+using System.Diagnostics;
+using System.Runtime.InteropServices;
+using Silk.NET.Windowing;
+using Silk.NET.Input;
+using Silk.NET.OpenGL;
+using Silk.NET.Maths;
+
+namespace Fusee.Engine.Imp.Graphics.SilkDesktop
+{
+ ///
+ /// This is a default render canvas implementation creating its own rendering window.
+ ///
+ public class RenderCanvasImp : RenderCanvasImpBase, IRenderCanvasImp, IDisposable
+ {
+ #region Fields
+
+ //Some tryptichon related variables.
+ private bool _videoWallMode = false;
+ private int _videoWallMonitorsHor;
+ private int _videoWallMonitorsVert;
+ private bool _windowBorderHidden = false;
+
+ ///
+ /// Window handle for the window the engine renders to.
+ ///
+ public IWindowHandle WindowHandle { get; }
+
+ ///
+ /// Implementation Tasks: Gets and sets the width(pixel units) of the Canvas.
+ ///
+ ///
+ /// The width.
+ ///
+ public int Width
+ {
+ get => BaseWidth;
+ set
+ {
+ _gameWindow.window.Size = new Vector2D(value, _gameWindow.window.Size.Y);
+ BaseWidth = value;
+ ResizeWindow();
+ }
+ }
+
+ ///
+ /// Gets and sets the height in pixel units.
+ ///
+ ///
+ /// The height.
+ ///
+ public int Height
+ {
+ get => BaseHeight;
+ set
+ {
+ _gameWindow.window.Size = new Vector2D(_gameWindow.window.Size.X, value);
+ BaseHeight = value;
+ ResizeWindow();
+ }
+ }
+
+ ///
+ /// Gets and sets the caption(title of the window).
+ ///
+ ///
+ /// The caption.
+ ///
+ public string Caption
+ {
+ get => (_gameWindow == null) ? "" : _gameWindow.window.Title;
+ set { if (_gameWindow != null) _gameWindow.window.Title = value; }
+ }
+
+ ///
+ /// Gets the delta time.
+ /// The delta time is the time that was required to render the last frame in milliseconds.
+ /// This value can be used to determine the frames per second of the application.
+ ///
+ ///
+ /// The delta time in milliseconds.
+ ///
+ public float DeltaTime
+ {
+ get
+ {
+ if (_gameWindow != null)
+ return _gameWindow.DeltaTime;
+ return 0.01f;
+ }
+ }
+
+ ///
+ /// Gets the delta time.
+ /// The delta time is the time that was required to update the last frame in milliseconds.
+ ///
+ ///
+ /// The delta time in milliseconds.
+ ///
+ public float DeltaTimeUpdate
+ {
+ get
+ {
+ if (_gameWindow != null)
+ return _gameWindow.DeltaTimeUpdate;
+ return 0.01f;
+ }
+ }
+
+ ///
+ /// Gets and sets a value indicating whether [vertical synchronize].
+ /// This option is used to reduce "Glitches" during rendering.
+ ///
+ ///
+ /// true if [vertical synchronize]; otherwise, false.
+ ///
+ public bool VerticalSync
+ {
+ get => (_gameWindow != null) && _gameWindow.window.VSync == true;
+ set { if (_gameWindow != null) _gameWindow.window.VSync = value; }
+ }
+
+ ///
+ /// Gets and sets a value indicating whether [enable blending].
+ /// Blending is used to render transparent objects.
+ ///
+ ///
+ /// true if [enable blending]; otherwise, false.
+ ///
+ public bool EnableBlending
+ {
+ get => _gameWindow.Blending;
+ set => _gameWindow.Blending = value;
+ }
+
+ ///
+ /// Gets and sets a value indicating whether [fullscreen] is enabled.
+ ///
+ ///
+ /// true if [fullscreen]; otherwise, false.
+ ///
+ public bool Fullscreen
+ {
+ get => (_gameWindow.window.WindowState == WindowState.Fullscreen);
+ set => _gameWindow.window.WindowState = (value) ? WindowState.Fullscreen : WindowState.Normal;
+ }
+
+ ///
+ /// Gets a value indicating whether [focused].
+ /// This property is used to identify if this application is the active window of the user.
+ ///
+ ///
+ /// true if [focused]; otherwise, false.
+ ///
+ public bool Focused => _gameWindow.window.IsVisible;
+
+ // Some tryptichon related Fields.
+
+ ///
+ /// Activates (true) or deactivates (false) the video wall feature.
+ ///
+ public bool VideoWallMode
+ {
+ get => _videoWallMode;
+ set => _videoWallMode = value;
+ }
+
+ ///
+ /// This represents the number of the monitors in a vertical column.
+ ///
+ public int TryptMonitorSetupVertical
+ {
+ get => _videoWallMonitorsVert;
+ set => _videoWallMonitorsVert = value;
+ }
+
+ ///
+ /// This represents the number of the monitors in a horizontal row.
+ ///
+ public int TryptMonitorSetupHorizontal
+ {
+ get => _videoWallMonitorsHor;
+ set => _videoWallMonitorsHor = value;
+ }
+
+ internal RenderCanvasGameWindow _gameWindow;
+
+ #endregion
+
+ #region Constructors
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public RenderCanvasImp() : this(null, false)
+ {
+
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// If true OpenTk will call run() in a new Thread. The default value is false.
+ /// The window icon to use
+ public RenderCanvasImp(ImageData icon = null, bool isMultithreaded = false)
+ {
+ //TODO: Select correct monitor
+ //MonitorInfo mon = Monitors.GetMonitors()[0];
+
+ int width = 1280;
+ int height = 720;
+
+ //if (mon != null)
+ //{
+ // width = System.Math.Min(mon.HorizontalResolution - 100, width);
+ // height = System.Math.Min(mon.VerticalResolution - 100, height);
+ //}
+
+ try
+ {
+ _gameWindow = new RenderCanvasGameWindow(this, width, height, false, isMultithreaded);
+ }
+ catch
+ {
+ _gameWindow = new RenderCanvasGameWindow(this, width, height, false, isMultithreaded);
+ }
+
+ WindowHandle = new WindowHandle()
+ {
+ Handle = _gameWindow.window.Handle
+ };
+
+
+ //if (_gameWindow.IsMultiThreaded)
+ // _gameWindow.Context.MakeNoneCurrent();
+
+ // convert icon to OpenTKImage
+ if (icon != null)
+ {
+ // convert Bgra to Rgba for OpenTK.WindowIcon
+
+ var res = new Span(new Rgba32[width * height]);
+ var pxData = SixLabors.ImageSharp.Image.LoadPixelData(icon.PixelData, icon.Width, icon.Height);
+ pxData.Mutate(x => x.AutoOrient());
+ pxData.Mutate(x => x.RotateFlip(RotateMode.None, FlipMode.Vertical));
+
+ pxData.CopyPixelDataTo(res);
+
+ var resBytes = MemoryMarshal.AsBytes(res.ToArray());
+ //_gameWindow.window.Icon = new WindowIcon(new Image[] { new Image(icon.Width, icon.Height, resBytes.ToArray()) });
+ }
+ }
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The width of the render window.
+ /// The height of the render window.
+ /// If true OpenTk will call run() in a new Thread. The default value is false.
+ /// The window created by this constructor is not visible. Should only be used for internal testing.
+ public RenderCanvasImp(int width, int height, bool isMultithreaded = false)
+ {
+ try
+ {
+ _gameWindow = new RenderCanvasGameWindow(this, width, height, true, isMultithreaded);
+ }
+ catch
+ {
+ _gameWindow = new RenderCanvasGameWindow(this, width, height, false, isMultithreaded);
+ }
+
+ WindowHandle = new WindowHandle()
+ {
+ Handle = _gameWindow.window.Handle
+ };
+
+ //_gameWindow.IsVisible = false;
+ //if (_gameWindow.IsMultiThreaded)
+ // _gameWindow.Context.MakeNoneCurrent();
+ }
+
+ ///
+ /// Implementation of the Dispose pattern. Disposes of the OpenTK game window.
+ ///
+ public void Dispose()
+ {
+ _gameWindow.Dispose();
+ }
+
+ #endregion
+
+ #region Members
+
+ private void ResizeWindow()
+ {
+ if (!_videoWallMode)
+ {
+ //_gameWindow.window.WindowBorder = _windowBorderHidden ? WindowBorder.Hidden : OpenTK.Windowing.Common.WindowBorder.Resizable;
+ //_gameWindow.window.Bounds = new OpenTK.Mathematics.Box2i(BaseLeft, BaseTop, BaseWidth, BaseHeight);
+ }
+ else
+ {
+ //TODO: Select correct monitor
+ //MonitorInfo mon = Monitors.GetMonitors()[0];
+
+ //var oneScreenWidth = mon.HorizontalResolution;
+ //var oneScreenHeight = mon.VerticalResolution;
+
+ //var width = oneScreenWidth * _videoWallMonitorsHor;
+ //var height = oneScreenHeight * _videoWallMonitorsVert;
+
+ //_gameWindow.Bounds = new OpenTK.Mathematics.Box2i(0, 0, width, height);
+
+ //if (_windowBorderHidden)
+ // _gameWindow.WindowBorder = OpenTK.Windowing.Common.WindowBorder.Hidden;
+ }
+ }
+
+ ///
+ /// Changes the window of the application to video wall mode.
+ ///
+ /// Number of monitors on horizontal axis.
+ /// Number of monitors on vertical axis.
+ /// Start the window in activated state-
+ /// Start the window with a hidden windows border.
+ public void VideoWall(int monitorsHor = 1, int monitorsVert = 1, bool activate = true, bool borderHidden = false)
+ {
+ VideoWallMode = activate;
+ _videoWallMonitorsHor = monitorsHor > 0 ? monitorsHor : 1;
+ _videoWallMonitorsVert = monitorsVert > 0 ? monitorsVert : 1;
+ _windowBorderHidden = borderHidden;
+
+ ResizeWindow();
+ }
+
+ ///
+ /// Sets the size of the output window for desktop development.
+ ///
+ /// The width of the window.
+ /// The height of the window.
+ /// The x position of the window.
+ /// The y position of the window.
+ /// Show the window border or not.
+ public void SetWindowSize(int width, int height, int posx = -1, int posy = -1, bool borderHidden = false)
+ {
+ //MonitorInfo mon = Monitors.GetMonitors()[0];
+
+ BaseWidth = width;
+ BaseHeight = height;
+
+ //BaseLeft = (posx == -1) ? mon.HorizontalResolution / 2 - width / 2 : posx;
+ //BaseTop = (posy == -1) ? mon.VerticalResolution / 2 - height / 2 : posy;
+
+ _windowBorderHidden = borderHidden;
+
+ // Disable video wall mode for this because it would not make sense.
+ _videoWallMode = false;
+
+ ResizeWindow();
+ }
+
+ ///
+ /// Closes the GameWindow with a call to OpenTk.
+ ///
+ public void CloseGameWindow()
+ {
+ if (_gameWindow != null)
+ {
+ //_gameWindow.Dispose();
+ //_gameWindow.ProcessEvents();
+ _gameWindow.Dispose();
+ }
+ }
+
+ ///
+ /// Presents this application instance. Call this function after rendering to show the final image.
+ /// After Present is called the render buffers get flushed.
+ ///
+ public void Present()
+ {
+ if (!_gameWindow.window.IsClosing)
+ _gameWindow.window.SwapBuffers();
+ }
+
+ ///
+ /// Set the cursor (the mouse pointer image) to one of the predefined types
+ ///
+ /// The type of the cursor to set.
+ public void SetCursor(Common.CursorType cursorType)
+ {
+ // Currently not supported by OpenTK... Too bad.
+ }
+
+ ///
+ /// Opens the given URL in the user's standard web browser. The link MUST start with "http://".
+ ///
+ /// The URL to open
+ public void OpenLink(string link)
+ {
+ if (link.StartsWith("http://"))
+ {
+ //UseShellExecute needs to be set to true in .net 3.0. See:https://github.com/dotnet/corefx/issues/33714
+ ProcessStartInfo psi = new()
+ {
+ FileName = link,
+ UseShellExecute = true
+ };
+ Process.Start(psi);
+ }
+ }
+
+ ///
+ /// Implementation Tasks: Runs this application instance. This function should not be called more than once as its only for initialization purposes.
+ ///
+ public void Run()
+ {
+ _gameWindow.window.Run();
+
+ if (_gameWindow.window != null)
+ {
+ _gameWindow.window.FramesPerSecond = 0;
+ _gameWindow.window.UpdatesPerSecond = 60;
+ _gameWindow.window.Center();
+ }
+ }
+
+ ///
+ /// Creates a bitmap image from the current frame of the application.
+ ///
+ /// The width of the window, and therefore image to render.
+ /// The height of the window, and therefore image to render.
+ ///
+ public SixLabors.ImageSharp.Image ShootCurrentFrame(int width, int height)
+ {
+ DoInit();
+ DoRender();
+ DoResize(width, height);
+
+ var mem = new byte[width * height * 4];
+ _gameWindow.GL.PixelStore(PixelStoreParameter.PackRowLength, 1);
+ _gameWindow.GL.ReadPixels(0, 0, (uint)Width, (uint)Height, GLEnum.Bgra, GLEnum.UnsignedByte, new Span(mem));
+
+ var img = SixLabors.ImageSharp.Image.LoadPixelData(mem, Width, Height);
+
+ img.Mutate(x => x.AutoOrient());
+ img.Mutate(x => x.RotateFlip(RotateMode.None, FlipMode.Vertical));
+
+ return img;
+ }
+
+ #endregion
+ }
+
+ ///
+ /// OpenTK implementation of RenderCanvas for the window output.
+ ///
+ public class RenderCanvasImpBase
+ {
+ #region Fields
+
+ ///
+ /// The Width
+ ///
+ protected internal int BaseWidth;
+
+ ///
+ /// The Height
+ ///
+ protected internal int BaseHeight;
+
+ ///
+ /// The Top Position
+ ///
+ protected internal int BaseTop;
+
+ ///
+ /// The Left Position
+ ///
+ protected internal int BaseLeft;
+
+ #endregion
+
+ #region Events
+ ///
+ /// Occurs when [initialize].
+ ///
+ public event EventHandler Init;
+ ///
+ /// Occurs when [unload].
+ ///
+ public event EventHandler UnLoad;
+ ///
+ /// Occurs when [update].
+ ///
+ public event EventHandler Update;
+ ///
+ /// Occurs when [render].
+ ///
+ public event EventHandler Render;
+ ///
+ /// Occurs when [resize].
+ ///
+ public event EventHandler Resize;
+
+ #endregion
+
+ #region Internal Members
+
+ ///
+ /// Does the initialize of this instance.
+ ///
+ protected internal void DoInit()
+ {
+ Init?.Invoke(this, new InitEventArgs());
+ }
+
+ ///
+ /// Does the unload of this instance.
+ ///
+ protected internal void DoUnLoad()
+ {
+ UnLoad?.Invoke(this, new InitEventArgs());
+ }
+
+ ///
+ /// Does the update of this instance.
+ ///
+ protected internal void DoUpdate()
+ {
+ Update?.Invoke(this, new RenderEventArgs());
+ }
+
+ ///
+ /// Does the render of this instance.
+ ///
+ protected internal void DoRender()
+ {
+ Render?.Invoke(this, new RenderEventArgs());
+ }
+
+ ///
+ /// Does the resize on this instance.
+ ///
+ protected internal void DoResize(int width, int height)
+ {
+ Resize?.Invoke(this, new ResizeEventArgs(width, height));
+ }
+
+ #endregion
+ }
+
+ public class RenderCanvasGameWindow : IDisposable
+ {
+ #region Fields
+
+ private readonly RenderCanvasImp _renderCanvasImp;
+ internal GL GL;
+ internal IWindow window;
+ private bool disposedValue;
+
+ public Action Ready;
+
+ ///
+ /// Gets the delta time.
+ /// The delta time is the time that was required to render the last frame in milliseconds.
+ /// This value can be used to determine the frames per second of the application.
+ ///
+ ///
+ /// The delta time in milliseconds.
+ ///
+ public float DeltaTime { get; private set; }
+
+ ///
+ /// Gets the delta time.
+ /// The delta time is the time that was required to update the last frame in milliseconds.
+ ///
+ ///
+ /// The delta time in milliseconds.
+ ///
+ public float DeltaTimeUpdate { get; private set; }
+
+ ///
+ /// Gets and sets a value indicating whether [blending].
+ /// Blending is used to render transparent objects.
+ ///
+ ///
+ /// true if [blending]; otherwise, false.
+ ///
+ public bool Blending
+ {
+ get => GL.IsEnabled(EnableCap.Blend);
+ set
+ {
+ if (value)
+ {
+ GL.Enable(EnableCap.Blend);
+ GL.BlendFunc(BlendingFactor.SrcAlpha, BlendingFactor.OneMinusSrcAlpha);
+ }
+ else
+ {
+ GL.Disable(EnableCap.Blend);
+ }
+ }
+ }
+
+ #endregion
+
+ #region Constructors
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The render canvas implementation.
+ /// The width.
+ /// The height.
+ /// if set to true [anti aliasing] is on.
+ /// If true OpenTk will call run() in a new Thread. The default value is false.
+ internal RenderCanvasGameWindow(RenderCanvasImp renderCanvasImp, int width, int height, bool antiAliasing, bool isMultithreaded = false)
+ {
+ _renderCanvasImp = renderCanvasImp;
+ _renderCanvasImp.BaseWidth = width;
+ _renderCanvasImp.BaseHeight = height;
+
+ window = Window.Create(WindowOptions.Default);
+
+ window.Load += OnLoad;
+
+ window.FramebufferResize += OnResize;
+ window.Update += OnUpdateFrame;
+ window.Render += OnRenderFrame;
+ window.Closing += OnUnload;
+ }
+
+ #endregion
+
+ #region Overrides
+
+ protected void OnLoad()
+ {
+ GL = window.CreateOpenGL();
+
+ // Check for necessary capabilities
+ //string version = GL.GetString(StringName.Version);
+ //
+ //int major = version[0];
+ //// int minor = (int)version[2];
+ //
+ //if (major < 2)
+ //{
+ // throw new InvalidOperationException("You need at least OpenGL 2.0 to run this example. GLSL not supported.");
+ //}
+
+ GL.ClearColor(25, 25, 112, byte.MaxValue);
+
+ GL.Enable(EnableCap.DepthTest);
+ GL.Enable(EnableCap.CullFace);
+
+ // Use VSync!
+ //VSync = OpenTK.Windowing.Common.VSyncMode.On;
+
+ _renderCanvasImp.DoInit();
+ }
+
+ protected void OnUnload()
+ {
+ _renderCanvasImp.DoUnLoad();
+ _renderCanvasImp.Dispose();
+ }
+
+ protected void OnResize(Vector2D v)
+ {
+
+ if (_renderCanvasImp != null)
+ {
+ _renderCanvasImp.BaseWidth = v.X;
+ _renderCanvasImp.BaseHeight = v.Y;
+ _renderCanvasImp.DoResize(v.X, v.Y);
+ }
+ }
+
+ protected void OnUpdateFrame(double updateTime)
+ {
+
+ DeltaTimeUpdate = (float)updateTime;
+
+ //if (KeyboardState.IsKeyPressed(OpenTK.Windowing.GraphicsLibraryFramework.Keys.F11))
+ // WindowState = (WindowState != OpenTK.Windowing.Common.WindowState.Fullscreen) ? OpenTK.Windowing.Common.WindowState.Fullscreen : OpenTK.Windowing.Common.WindowState.Normal;
+
+ _renderCanvasImp?.DoUpdate();
+ }
+
+ protected void OnRenderFrame(double delta)
+ {
+
+ DeltaTime = (float)delta;
+
+ _renderCanvasImp?.DoRender();
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!disposedValue)
+ {
+ if (disposing)
+ {
+ window.Dispose();
+ GL.Dispose();
+ }
+
+ // TODO: free unmanaged resources (unmanaged objects) and override finalizer
+ // TODO: set large fields to null
+ disposedValue = true;
+ }
+ }
+
+ // // TODO: override finalizer only if 'Dispose(bool disposing)' has code to free unmanaged resources
+ // ~RenderCanvasGameWindow()
+ // {
+ // // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
+ // Dispose(disposing: false);
+ // }
+
+ public void Dispose()
+ {
+ // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
+ Dispose(disposing: true);
+ GC.SuppressFinalize(this);
+ }
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/src/Engine/Imp/Graphics/Silk/RenderCanvasImpSyncContext.cs b/src/Engine/Imp/Graphics/Silk/RenderCanvasImpSyncContext.cs
new file mode 100644
index 000000000..ff237a3a6
--- /dev/null
+++ b/src/Engine/Imp/Graphics/Silk/RenderCanvasImpSyncContext.cs
@@ -0,0 +1,42 @@
+using System;
+using System.Collections.Concurrent;
+using System.Runtime.ExceptionServices;
+using System.Threading;
+
+namespace Fusee.Engine.Imp.Graphics.SilkDesktop
+{
+ internal class RenderCanvasImpSyncContext : SynchronizationContext
+ {
+ private readonly ConcurrentQueue> _allCallbacks = new();
+
+ public override void Post(SendOrPostCallback d, object? state)
+ {
+ _allCallbacks.Enqueue(Tuple.Create(d, state));
+ }
+
+ internal void ExecutePendingPostAwaits()
+ {
+ while (!_allCallbacks.IsEmpty)
+ {
+ _ = _allCallbacks.TryDequeue(out var callback);
+
+ if (callback != null)
+ {
+ try
+ {
+ var d = callback.Item1;
+ var state = callback.Item2;
+ d(state);
+ }
+ catch (Exception exception)
+ {
+ ExceptionDispatchInfo.Capture(exception).Throw();
+ }
+ }
+ }
+
+ // should always be empty at this point, but nevertheless
+ _allCallbacks.Clear();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Engine/Imp/Graphics/Silk/RenderContextImp.cs b/src/Engine/Imp/Graphics/Silk/RenderContextImp.cs
new file mode 100644
index 000000000..3c3def721
--- /dev/null
+++ b/src/Engine/Imp/Graphics/Silk/RenderContextImp.cs
@@ -0,0 +1,2658 @@
+using Fusee.Base.Common;
+using Fusee.Base.Core;
+using Fusee.Engine.Common;
+using Fusee.Engine.Core.ShaderShards;
+using Fusee.Engine.Imp.Shared;
+using Fusee.Math.Core;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Silk.NET.OpenGL;
+using Silk.NET.Core;
+using Silk.NET.OpenGL.Extensions.ARB;
+
+namespace Fusee.Engine.Imp.Graphics.SilkDesktop
+{
+ ///
+ /// Implementation of the interface for usage with OpenTK framework.
+ ///
+ public class RenderContextImp : IRenderContextImp
+ {
+ ///
+ /// Constant id that describes the renderer. This can be used in shaders to do platform dependent things.
+ ///
+ public FuseePlatformId FuseePlatformId { get; } = FuseePlatformId.Desktop;
+
+ private int _textureCountPerShader;
+ private readonly Dictionary _shaderParam2TexUnit;
+
+ private GLEnum _blendEquationAlpha;
+ private GLEnum _blendEquationRgb;
+ private GLEnum _blendSrcRgb;
+ private GLEnum _blendDstRgb;
+ private GLEnum _blendSrcAlpha;
+ private GLEnum _blendDstAlpha;
+
+ private bool _isCullEnabled;
+ private bool _isPtRenderingEnabled;
+ private bool _isLineSmoothEnabled;
+
+#if DEBUG
+ private static DebugProc _openGlDebugDelegate;
+#endif
+
+ private static GL GL;
+
+ private IRenderCanvasImp canvas;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The render canvas interface.
+ public RenderContextImp(IRenderCanvasImp renderCanvas)
+ {
+ _textureCountPerShader = 0;
+ _shaderParam2TexUnit = new Dictionary();
+ canvas = renderCanvas;
+
+ ((RenderCanvasImp)canvas).Init += (s, e) =>
+ {
+ GL = ((RenderCanvasImp)canvas)._gameWindow.GL;
+ Init();
+ };
+ }
+
+ public void Init()
+ {
+
+
+#if DEBUG
+ GL.Enable(EnableCap.DebugOutput);
+ GL.Enable(EnableCap.DebugOutputSynchronous);
+
+ _openGlDebugDelegate = new DebugProc(OpenGLDebugCallback);
+
+ GL.DebugMessageCallback(_openGlDebugDelegate, IntPtr.Zero);
+ GL.DebugMessageControl(GLEnum.DontCare, GLEnum.DontCare, GLEnum.DebugSeverityNotification, 0, 0, false);
+#endif
+
+ // Due to the right-handed nature of OpenGL and the left-handed design of FUSEE
+ // the meaning of what's Front and Back of a face simply flips.
+ // TODO - implement this in render states!!!
+ GL.CullFace(CullFaceMode.Back);
+
+ //Needed for rendering more than one viewport.
+ GL.Enable(EnableCap.ScissorTest);
+
+ GL.GetInteger(GetPName.BlendSrcAlpha, out int blendSrcAlpha);
+ GL.GetInteger(GetPName.BlendDstAlpha, out int blendDstAlpha);
+ GL.GetInteger(GetPName.BlendDstRgb, out int blendDstRgb);
+ GL.GetInteger(GetPName.BlendSrcRgb, out int blendSrcRgb);
+ GL.GetInteger(GetPName.BlendEquationAlpha, out int blendEqA);
+ GL.GetInteger(GetPName.BlendEquationRgb, out int blendEqRgb);
+
+ _blendDstRgb = (GLEnum)blendDstRgb;
+ _blendSrcRgb = (GLEnum)blendSrcRgb;
+ _blendSrcAlpha = (GLEnum)blendSrcAlpha;
+ _blendDstAlpha = (GLEnum)blendDstAlpha;
+ _blendEquationAlpha = (GLEnum)blendEqA;
+ _blendEquationRgb = (GLEnum)blendEqRgb;
+
+ //Diagnostics.Debug(GL.GetString(StringName.Vendor) + " - " + GL.GetString(StringName.Renderer) + " - " + GL.GetString(StringName.Version));
+#if DEBUG
+ var numExtensions = GL.GetInteger(GLEnum.NumExtensions);
+ var extensions = new string[numExtensions];
+
+ for (int i = 0; i < numExtensions; i++)
+ {
+ //extensions[i] = GL.GetString(StringNameIndexed.Extensions, i);
+ }
+
+ Diagnostics.Verbose(string.Join(';', extensions));
+#endif
+ }
+
+#if DEBUG
+ private static void OpenGLDebugCallback(GLEnum source, GLEnum type, int id, GLEnum severity, int length, nint message, nint userParam)
+ {
+ Diagnostics.Debug($"{System.Runtime.InteropServices.Marshal.PtrToStringAnsi(message, length)}\n\tid:{id} severity:{severity} type:{type} source:{source}\n");
+ }
+#endif
+
+ #region Image data related Members
+
+ private Silk.NET.OpenGL.TextureCompareMode GetTexComapreMode(Common.TextureCompareMode compareMode)
+ {
+ return compareMode switch
+ {
+ Common.TextureCompareMode.None => Silk.NET.OpenGL.TextureCompareMode.None,
+ Common.TextureCompareMode.CompareRefToTexture => Silk.NET.OpenGL.TextureCompareMode.CompareRefToTexture,
+ _ => throw new ArgumentException("Invalid compare mode."),
+ };
+ }
+
+ private Tuple GetMinMagFilter(TextureFilterMode filterMode)
+ {
+ TextureMinFilter minFilter;
+ TextureMagFilter magFilter;
+
+ switch (filterMode)
+ {
+ case TextureFilterMode.Nearest:
+ minFilter = TextureMinFilter.Nearest;
+ magFilter = TextureMagFilter.Nearest;
+ break;
+ default:
+ case TextureFilterMode.Linear:
+ minFilter = TextureMinFilter.Linear;
+ magFilter = TextureMagFilter.Linear;
+ break;
+ case TextureFilterMode.NearestMipmapNearest:
+ minFilter = TextureMinFilter.NearestMipmapNearest;
+ magFilter = TextureMagFilter.Nearest;
+ break;
+ case TextureFilterMode.LinearMipmapNearest:
+ minFilter = TextureMinFilter.LinearMipmapNearest;
+ magFilter = TextureMagFilter.Linear;
+ break;
+ case TextureFilterMode.NearestMipmapLinear:
+ minFilter = TextureMinFilter.NearestMipmapLinear;
+ magFilter = TextureMagFilter.Nearest;
+ break;
+ case TextureFilterMode.LinearMipmapLinear:
+ minFilter = TextureMinFilter.LinearMipmapLinear;
+ magFilter = TextureMagFilter.Linear;
+ break;
+ }
+
+ return new Tuple(minFilter, magFilter);
+ }
+
+ private DepthFunction GetDepthCompareFunc(Compare compareFunc)
+ {
+ return compareFunc switch
+ {
+ Compare.Never => DepthFunction.Never,
+ Compare.Less => DepthFunction.Less,
+ Compare.Equal => DepthFunction.Equal,
+ Compare.LessEqual => DepthFunction.Lequal,
+ Compare.Greater => DepthFunction.Greater,
+ Compare.NotEqual => DepthFunction.Notequal,
+ Compare.GreaterEqual => DepthFunction.Gequal,
+ Compare.Always => DepthFunction.Always,
+ _ => throw new ArgumentOutOfRangeException("value"),
+ };
+ }
+
+ private Silk.NET.OpenGL.TextureWrapMode GetWrapMode(Common.TextureWrapMode wrapMode)
+ {
+ return wrapMode switch
+ {
+ Common.TextureWrapMode.MirroredRepeat => Silk.NET.OpenGL.TextureWrapMode.MirroredRepeat,
+ Common.TextureWrapMode.ClampToEdge => Silk.NET.OpenGL.TextureWrapMode.ClampToEdge,
+ Common.TextureWrapMode.ClampToBorder => Silk.NET.OpenGL.TextureWrapMode.ClampToBorder,
+ _ => Silk.NET.OpenGL.TextureWrapMode.Repeat,
+ };
+ }
+
+ private SizedInternalFormat GetSizedInteralFormat(ImagePixelFormat format)
+ {
+ return format.ColorFormat switch
+ {
+ ColorFormat.RGBA => SizedInternalFormat.Rgba8,
+ ColorFormat.fRGBA16 => SizedInternalFormat.Rgba16f,
+ ColorFormat.fRGBA32 => SizedInternalFormat.Rgba32f,
+ ColorFormat.iRGBA32 => SizedInternalFormat.Rgba32i,
+ _ => throw new ArgumentOutOfRangeException("SizedInternalFormat not supported. Try to use a format with r,g,b and a components."),
+ };
+ }
+
+ private TexturePixelInfo GetTexturePixelInfo(ImagePixelFormat pixelFormat)
+ {
+ GLEnum internalFormat;
+ PixelFormat format;
+ PixelType pxType;
+
+ //The wrong row alignment will lead to malformed textures.
+ //See https://www.khronos.org/opengl/wiki/Common_Mistakes#Texture_upload_and_pixel_reads
+ //and https://www.khronos.org/opengl/wiki/Pixel_Transfer#Pixel_layout
+ int rowAlignment = 4;
+
+ switch (pixelFormat.ColorFormat)
+ {
+ case ColorFormat.RGBA:
+ internalFormat = GLEnum.Rgba;
+ format = PixelFormat.Rgba;
+ pxType = PixelType.UnsignedByte;
+ break;
+
+ case ColorFormat.RGB:
+ internalFormat = GLEnum.Rgb;
+ format = PixelFormat.Rgb;
+ pxType = PixelType.UnsignedByte;
+ rowAlignment = 1;
+ break;
+
+ // TODO: Handle Alpha-only / Intensity-only and AlphaIntensity correctly.
+ case ColorFormat.Intensity:
+ internalFormat = GLEnum.R8;
+ format = PixelFormat.Red;
+ pxType = PixelType.UnsignedByte;
+ rowAlignment = 1;
+ break;
+
+ case ColorFormat.Depth24:
+ internalFormat = GLEnum.DepthComponent24;
+ format = PixelFormat.DepthComponent;
+ pxType = PixelType.Float;
+ break;
+
+ case ColorFormat.Depth16:
+ internalFormat = GLEnum.DepthComponent16;
+ format = PixelFormat.DepthComponent;
+ pxType = PixelType.Float;
+ break;
+
+ case ColorFormat.uiRgb8:
+ internalFormat = GLEnum.Rgba8ui;
+ format = PixelFormat.RgbaInteger;
+ pxType = PixelType.UnsignedByte;
+ rowAlignment = 1;
+ break;
+
+ case ColorFormat.fRGB32:
+ internalFormat = GLEnum.Rgb32f;
+ format = PixelFormat.Rgb;
+ pxType = PixelType.Float;
+ break;
+
+ case ColorFormat.fRGB16:
+ internalFormat = GLEnum.Rgb16f;
+ format = PixelFormat.Rgb;
+ pxType = PixelType.Float;
+ break;
+
+ case ColorFormat.fRGBA16:
+ internalFormat = GLEnum.Rgba16f;
+ format = PixelFormat.Rgba;
+ pxType = PixelType.Float;
+ break;
+
+ case ColorFormat.fRGBA32:
+ internalFormat = GLEnum.Rgba32f;
+ format = PixelFormat.Rgba;
+ pxType = PixelType.Float;
+ break;
+
+ case ColorFormat.iRGBA32:
+ internalFormat = GLEnum.Rgba32i;
+ format = PixelFormat.RgbaInteger;
+ pxType = PixelType.Int;
+ break;
+
+ default:
+ throw new ArgumentOutOfRangeException("CreateTexture: Image pixel format not supported");
+ }
+
+ return new TexturePixelInfo()
+ {
+ Format = format,
+ InternalFormat = internalFormat,
+ PxType = pxType,
+ RowAlignment = rowAlignment
+ };
+ }
+
+ ///
+ /// Creates a new Texture and binds it to the shader.
+ ///
+ /// A given ImageData object, containing all necessary information for the upload to the graphics card.
+ /// An ITextureHandle that can be used for texturing in the shader. In this implementation, the handle is an integer-value which is necessary for OpenTK.
+ public ITextureHandle CreateTexture(IWritableArrayTexture img)
+ {
+ uint id = GL.GenTexture();
+ GL.BindTexture(TextureTarget.Texture2DArray, id);
+
+ var glMinMagFilter = GetMinMagFilter(img.FilterMode);
+ var minFilter = glMinMagFilter.Item1;
+ var magFilter = glMinMagFilter.Item2;
+ var glWrapMode = GetWrapMode(img.WrapMode);
+ var pxInfo = GetTexturePixelInfo(img.PixelFormat);
+
+ GL.TexImage3D((GLEnum)TextureTarget.Texture2DArray, 0, (int)pxInfo.InternalFormat, (uint)img.Width, (uint)img.Height, (uint)img.Layers, 0, (GLEnum)pxInfo.Format, (GLEnum)pxInfo.PxType, 0);
+
+
+ if (img.DoGenerateMipMaps)
+ GL.GenerateMipmap(GLEnum.Texture2D);
+
+ GL.TexParameter(TextureTarget.Texture2DArray, TextureParameterName.TextureCompareMode, (int)GetTexComapreMode(img.CompareMode));
+ GL.TexParameter(TextureTarget.Texture2DArray, TextureParameterName.TextureCompareFunc, (int)GetDepthCompareFunc(img.CompareFunc));
+ GL.TexParameter(TextureTarget.Texture2DArray, TextureParameterName.TextureMinFilter, (int)minFilter);
+ GL.TexParameter(TextureTarget.Texture2DArray, TextureParameterName.TextureMagFilter, (int)magFilter);
+ GL.TexParameter(TextureTarget.Texture2DArray, TextureParameterName.TextureWrapS, (int)glWrapMode);
+ GL.TexParameter(TextureTarget.Texture2DArray, TextureParameterName.TextureWrapT, (int)glWrapMode);
+
+ ITextureHandle texID = new TextureHandle { TexHandle = (int)id };
+
+ return texID;
+ }
+
+ ///
+ /// Creates a new CubeMap and binds it to the shader.
+ ///
+ /// A given IWritableCubeMap object, containing all necessary information for the upload to the graphics card.
+ /// An ITextureHandle that can be used for texturing in the shader. In this implementation, the handle is an integer-value which is necessary for OpenTK.
+ public ITextureHandle CreateTexture(IWritableCubeMap img)
+ {
+ var id = GL.GenTexture();
+ GL.BindTexture(TextureTarget.TextureCubeMap, id);
+
+ var glMinMagFilter = GetMinMagFilter(img.FilterMode);
+ var minFilter = glMinMagFilter.Item1;
+ var magFilter = glMinMagFilter.Item2;
+
+ var glWrapMode = GetWrapMode(img.WrapMode);
+ var pxInfo = GetTexturePixelInfo(img.PixelFormat);
+
+ for (int i = 0; i < 6; i++)
+ GL.TexImage2D((GLEnum)TextureTarget.TextureCubeMapPositiveX + i, 0, (int)pxInfo.InternalFormat, (uint)img.Width, (uint)img.Height, 0, (GLEnum)pxInfo.Format, (GLEnum)pxInfo.PxType, 0);
+
+ GL.TexParameter(TextureTarget.TextureCubeMap, TextureParameterName.TextureCompareMode, (int)GetTexComapreMode(img.CompareMode));
+ GL.TexParameter(TextureTarget.TextureCubeMap, TextureParameterName.TextureCompareFunc, (int)GetDepthCompareFunc(img.CompareFunc));
+ GL.TexParameter(TextureTarget.TextureCubeMap, TextureParameterName.TextureMagFilter, (int)magFilter);
+ GL.TexParameter(TextureTarget.TextureCubeMap, TextureParameterName.TextureMinFilter, (int)minFilter);
+ GL.TexParameter(TextureTarget.TextureCubeMap, TextureParameterName.TextureWrapS, (int)glWrapMode);
+ GL.TexParameter(TextureTarget.TextureCubeMap, TextureParameterName.TextureWrapT, (int)glWrapMode);
+ GL.TexParameter(TextureTarget.TextureCubeMap, TextureParameterName.TextureWrapR, (int)glWrapMode);
+
+ ITextureHandle texID = new TextureHandle { TexHandle = (int)id };
+
+ return texID;
+ }
+
+ ///
+ /// Creates a new Texture and binds it to the shader.
+ ///
+ /// A given ITexture object, containing all necessary information for the upload to the graphics card.
+ /// An ITextureHandle that can be used for texturing in the shader. In this implementation, the handle is an integer-value which is necessary for OpenTK.
+ public ITextureHandle CreateTexture(ITexture img)
+ {
+ var id = GL.GenTexture();
+ GL.BindTexture(TextureTarget.Texture2D, id);
+
+ var glMinMagFilter = GetMinMagFilter(img.FilterMode);
+ var minFilter = glMinMagFilter.Item1;
+ var magFilter = glMinMagFilter.Item2;
+
+ var glWrapMode = GetWrapMode(img.WrapMode);
+
+ var pxInfo = GetTexturePixelInfo(img.ImageData.PixelFormat);
+
+ GL.PixelStore(PixelStoreParameter.UnpackAlignment, pxInfo.RowAlignment);
+ GL.TexImage2D((GLEnum)TextureTarget.Texture2D, 0, (int)pxInfo.InternalFormat, (uint)img.ImageData.Width, (uint)img.ImageData.Height, 0, (GLEnum)pxInfo.Format, (GLEnum)pxInfo.PxType, img.ImageData.PixelData);
+
+ if (img.DoGenerateMipMaps)
+ GL.GenerateMipmap(GLEnum.Texture2D);
+
+ GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)minFilter);
+ GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)magFilter);
+ GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)glWrapMode);
+ GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)glWrapMode);
+ GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapR, (int)glWrapMode);
+
+ ITextureHandle texID = new TextureHandle { TexHandle = (int)id };
+
+ return texID;
+ }
+
+ ///
+ /// Creates a new Texture and binds it to the shader.
+ ///
+ /// A given IWritableTexture object, containing all necessary information for the upload to the graphics card.
+ /// An ITextureHandle that can be used for texturing in the shader. In this implementation, the handle is an integer-value which is necessary for OpenTK.
+ public ITextureHandle CreateTexture(IWritableTexture tex)
+ {
+ var id = GL.GenTexture();
+ GL.BindTexture(TextureTarget.Texture2D, id);
+
+ var glMinMagFilter = GetMinMagFilter(tex.FilterMode);
+ var minFilter = glMinMagFilter.Item1;
+ var magFilter = glMinMagFilter.Item2;
+ var glWrapMode = GetWrapMode(tex.WrapMode);
+ var pxInfo = GetTexturePixelInfo(tex.PixelFormat);
+
+ GL.TexImage2D((GLEnum)TextureTarget.Texture2D, 0, (int)pxInfo.InternalFormat, (uint)tex.Width, (uint)tex.Height, 0, (GLEnum)pxInfo.Format, (GLEnum)pxInfo.PxType, 0);
+
+ if (tex.DoGenerateMipMaps)
+ GL.GenerateMipmap(GLEnum.Texture2D);
+
+ GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureCompareMode, (int)GetTexComapreMode(tex.CompareMode));
+ GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureCompareFunc, (int)GetDepthCompareFunc(tex.CompareFunc));
+ GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)minFilter);
+ GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)magFilter);
+ GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)glWrapMode);
+ GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)glWrapMode);
+
+ ITextureHandle texID = new TextureHandle { TexHandle = (int)id };
+
+ return texID;
+ }
+
+ ///
+ /// Updates a specific rectangle of a texture.
+ ///
+ /// The texture to which the ImageData is bound to.
+ /// The ImageData struct containing information about the image.
+ /// The x-value of the upper left corner of th rectangle.
+ /// The y-value of the upper left corner of th rectangle.
+ /// The width of the rectangle.
+ /// The height of the rectangle.
+ /// /// Look at the VideoTextureExample for further information.
+ public void UpdateTextureRegion(ITextureHandle tex, ITexture img, int startX, int startY, int width, int height)
+ {
+ var pxInfo = GetTexturePixelInfo(img.ImageData.PixelFormat);
+ PixelFormat format = pxInfo.Format;
+
+ // copy the bytes from img to GPU texture
+ int bytesTotal = width * height * img.ImageData.PixelFormat.BytesPerPixel;
+ var scanlines = img.ImageData.ScanLines(startX, startY, width, height);
+ byte[] bytes = new byte[bytesTotal];
+ int offset = 0;
+ do
+ {
+ if (scanlines.Current != null)
+ {
+ var lineBytes = scanlines.Current.GetScanLineBytes();
+ System.Buffer.BlockCopy(lineBytes, 0, bytes, offset, lineBytes.Length);
+ offset += lineBytes.Length;
+ }
+
+ } while (scanlines.MoveNext());
+
+ GL.PixelStore(PixelStoreParameter.PackAlignment, pxInfo.RowAlignment);
+ GL.BindTexture((GLEnum)TextureTarget.Texture2D, (uint)((TextureHandle)tex).TexHandle);
+ GL.TexSubImage2D((GLEnum)TextureTarget.Texture2D, 0, startX, startY, (uint)width, (uint)height, (GLEnum)format, GLEnum.UnsignedByte, bytes);
+
+ //GL.GenerateMipmap(GenerateMipmapTarget.Texture2D);
+
+ GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Nearest);
+ GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Nearest);
+ }
+
+ ///
+ /// Sets the textures filter mode ( at runtime.
+ ///
+ /// The handle of the texture.
+ /// The new filter mode.
+ public void SetTextureFilterMode(ITextureHandle tex, TextureFilterMode filterMode)
+ {
+ GL.BindTexture(TextureTarget.Texture2D, (uint)((TextureHandle)tex).TexHandle);
+ var glMinMagFilter = GetMinMagFilter(filterMode);
+ GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)glMinMagFilter.Item1);
+ GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)glMinMagFilter.Item2);
+ }
+
+ ///
+ /// Sets the textures filter mode ( at runtime.
+ ///
+ /// The handle of the texture.
+ ///The new wrap mode.
+ public void SetTextureWrapMode(ITextureHandle tex, Common.TextureWrapMode wrapMode)
+ {
+ GL.BindTexture(TextureTarget.Texture2D, (uint)((TextureHandle)tex).TexHandle);
+ var glWrapMode = GetWrapMode(wrapMode);
+ GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)glWrapMode);
+ GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)glWrapMode);
+ GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapR, (int)glWrapMode);
+ }
+
+ ///
+ /// Free all allocated gpu memory that belong to a frame-buffer object.
+ ///
+ /// The platform dependent abstraction of the gpu buffer handle.
+ public void DeleteFrameBuffer(IBufferHandle bh)
+ {
+ GL.DeleteFramebuffer((uint)((FrameBufferHandle)bh).Handle);
+ }
+
+ ///
+ /// Free all allocated gpu memory that belong to a render-buffer object.
+ ///
+ /// The platform dependent abstraction of the gpu buffer handle.
+ public void DeleteRenderBuffer(IBufferHandle bh)
+ {
+ GL.DeleteFramebuffer((uint)((RenderBufferHandle)bh).Handle);
+ }
+
+ ///
+ /// Free all allocated gpu memory that belong to the given .
+ ///
+ /// The which gpu allocated memory will be freed.
+ public void RemoveTextureHandle(ITextureHandle textureHandle)
+ {
+ TextureHandle texHandle = (TextureHandle)textureHandle;
+
+ if (texHandle.FrameBufferHandle != -1)
+ {
+ GL.DeleteFramebuffer((uint)texHandle.FrameBufferHandle);
+ }
+
+ if (texHandle.DepthRenderBufferHandle != -1)
+ {
+ GL.DeleteRenderbuffer((uint)texHandle.DepthRenderBufferHandle);
+ }
+
+ if (texHandle.TexHandle != -1)
+ {
+ GL.DeleteTexture((uint)texHandle.TexHandle);
+ _textureCountPerShader--;
+ }
+ }
+ #endregion
+
+ #region Shader related Members
+
+ ///
+ /// Creates a shader object from compute shader source code.
+ ///
+ /// A string containing the compute shader source.
+ ///
+ public IShaderHandle CreateShaderProgramCompute(string cs)
+ {
+ string info = string.Empty;
+ int statusCode = -1;
+
+ // Compile compute shader
+ uint computeObject = 0;
+ if (!string.IsNullOrEmpty(cs))
+ {
+ computeObject = GL.CreateShader(ShaderType.ComputeShader);
+
+ GL.ShaderSource(computeObject, cs);
+ GL.CompileShader(computeObject);
+ GL.GetShaderInfoLog(computeObject, out info);
+ GL.GetShader(computeObject, GLEnum.CompileStatus, out statusCode);
+ }
+
+ if (statusCode != 1)
+ throw new ApplicationException(info);
+
+ uint program = GL.CreateProgram();
+
+ GL.AttachShader(program, computeObject);
+ GL.LinkProgram(program); //Must be called AFTER BindAttribLocation
+ GL.DetachShader(program, computeObject);
+ GL.DeleteShader(computeObject);
+
+ return new ShaderHandleImp { Handle = (int)program };
+ }
+
+ ///
+ /// Creates the shader program by using a valid GLSL vertex and fragment shader code. This code is compiled at runtime.
+ /// Do not use this function in frequent updates.
+ ///
+ /// The vertex shader code.
+ /// The geometry shader code.
+ /// The pixel(=fragment) shader code.
+ /// An instance of .
+ ///
+ ///
+ public IShaderHandle CreateShaderProgram(string vs, string ps, string gs = null)
+ {
+ uint vertexObject = GL.CreateShader(ShaderType.VertexShader);
+ uint fragmentObject = GL.CreateShader(ShaderType.FragmentShader);
+
+ // Compile vertex shader
+ GL.ShaderSource(vertexObject, vs);
+ GL.CompileShader(vertexObject);
+ GL.GetShaderInfoLog(vertexObject, out string info);
+ GL.GetShader(vertexObject, GLEnum.CompileStatus, out int statusCode);
+
+ if (statusCode != 1)
+ throw new ApplicationException(info);
+
+ // Compile geometry shader
+ uint geometryObject = 0;
+ if (!string.IsNullOrEmpty(gs))
+ {
+ geometryObject = GL.CreateShader(ShaderType.GeometryShader);
+
+ GL.ShaderSource(geometryObject, gs);
+ GL.CompileShader(geometryObject);
+ GL.GetShaderInfoLog(geometryObject, out info);
+ GL.GetShader(geometryObject, GLEnum.CompileStatus, out statusCode);
+ }
+
+ if (statusCode != 1)
+ throw new ApplicationException(info);
+
+ // Compile pixel shader
+ GL.ShaderSource(fragmentObject, ps);
+ GL.CompileShader(fragmentObject);
+ GL.GetShaderInfoLog(fragmentObject, out info);
+ GL.GetShader(fragmentObject, GLEnum.CompileStatus, out statusCode);
+
+ if (statusCode != 1)
+ throw new ApplicationException(info);
+
+ uint program = GL.CreateProgram();
+ GL.AttachShader(program, fragmentObject);
+
+ if (!string.IsNullOrEmpty(gs))
+ GL.AttachShader(program, geometryObject);
+
+ GL.AttachShader(program, vertexObject);
+
+ // enable GLSL (ES) shaders to use fuVertex, fuColor and fuNormal attributes
+ GL.BindAttribLocation(program, (uint)AttributeLocations.VertexAttribLocation, UniformNameDeclarations.Vertex);
+ GL.BindAttribLocation(program, (uint)AttributeLocations.ColorAttribLocation, UniformNameDeclarations.VertexColor);
+ GL.BindAttribLocation(program, (uint)AttributeLocations.Color1AttribLocation, UniformNameDeclarations.VertexColor1);
+ GL.BindAttribLocation(program, (uint)AttributeLocations.Color2AttribLocation, UniformNameDeclarations.VertexColor2);
+ GL.BindAttribLocation(program, (uint)AttributeLocations.UvAttribLocation, UniformNameDeclarations.TextureCoordinates);
+ GL.BindAttribLocation(program, (uint)AttributeLocations.NormalAttribLocation, UniformNameDeclarations.Normal);
+ GL.BindAttribLocation(program, (uint)AttributeLocations.TangentAttribLocation, UniformNameDeclarations.Tangent);
+ GL.BindAttribLocation(program, (uint)AttributeLocations.BoneIndexAttribLocation, UniformNameDeclarations.BoneIndex);
+ GL.BindAttribLocation(program, (uint)AttributeLocations.BoneWeightAttribLocation, UniformNameDeclarations.BoneWeight);
+ GL.BindAttribLocation(program, (uint)AttributeLocations.BitangentAttribLocation, UniformNameDeclarations.Bitangent);
+ GL.BindAttribLocation(program, (uint)AttributeLocations.FuseePlatformIdLocation, UniformNameDeclarations.FuseePlatformId);
+
+ GL.LinkProgram(program); //Must be called AFTER BindAttribLocation
+
+ GL.DetachShader(program, fragmentObject);
+ GL.DetachShader(program, vertexObject);
+ GL.DeleteShader(fragmentObject);
+ GL.DeleteShader(vertexObject);
+
+ return new ShaderHandleImp { Handle = (int)program };
+ }
+
+ ///
+ ///
+ /// Removes shader from the GPU
+ ///
+ ///
+ public void RemoveShader(IShaderHandle sp)
+ {
+ var program = ((ShaderHandleImp)sp).Handle;
+
+ // wait for all threads to be finished
+ GL.Finish();
+ GL.Flush();
+
+ GL.DeleteProgram((uint)program);
+ }
+
+
+ ///
+ /// Sets the shader program onto the GL Render context.
+ ///
+ /// The shader program.
+ public void SetShader(IShaderHandle program)
+ {
+ _textureCountPerShader = 0;
+ _shaderParam2TexUnit.Clear();
+
+ GL.UseProgram((uint)((ShaderHandleImp)program).Handle);
+ }
+
+ ///
+ /// Gets the shader parameter.
+ /// The Shader parameter is used to bind values inside of shader programs that run on the graphics card.
+ /// Do not use this function in frequent updates as it transfers information from graphics card to the cpu which takes time.
+ ///
+ /// The shader program.
+ /// Name of the parameter.
+ /// The Shader parameter is returned if the name is found, otherwise null.
+ public IShaderParam GetShaderUniformParam(IShaderHandle shaderProgram, string paramName)
+ {
+ int h = GL.GetUniformLocation((uint)((ShaderHandleImp)shaderProgram).Handle, paramName);
+ return (h == -1) ? null : new ShaderParam { handle = h };
+ }
+
+ ///
+ /// Gets the float parameter value inside a shader program by using a as search reference.
+ /// Do not use this function in frequent updates as it transfers information from graphics card to the cpu which takes time.
+ ///
+ /// The program.
+ /// The parameter.
+ /// A float number (default is 0).
+ public float GetParamValue(IShaderHandle program, IShaderParam param)
+ {
+ GL.GetUniform((uint)((ShaderHandleImp)program).Handle, ((ShaderParam)param).handle, out float f);
+ return f;
+ }
+
+ ///
+ /// Returns a List of type for all ShaderStorageBlocks
+ ///
+ /// The shader program to query.
+ public IList GetShaderStorageBufferList(IShaderHandle shaderProgram)
+ {
+ var paramList = new List();
+ var sProg = (ShaderHandleImp)shaderProgram;
+ GL.GetProgramInterface((uint)sProg.Handle, GLEnum.ShaderStorageBlock, GLEnum.MaxNameLength, out int ssboMaxLen);
+ GL.GetProgramInterface((uint)sProg.Handle, GLEnum.ShaderStorageBlock, GLEnum.ActiveResources, out int nParams);
+
+ for (var i = 0; i < nParams; i++)
+ {
+ var paramInfo = new ShaderParamInfo();
+ GL.GetProgramResourceName((uint)sProg.Handle, GLEnum.ShaderStorageBlock, (uint)i, (uint)ssboMaxLen, out _, out string name);
+ paramInfo.Name = name;
+
+ uint h = GL.GetProgramResourceIndex((uint)sProg.Handle, GLEnum.ShaderStorageBlock, name);
+ paramInfo.Handle = (h == -1) ? null : new ShaderParam { handle = (int)h };
+ paramList.Add(paramInfo);
+ }
+
+ return paramList;
+ }
+
+ ///
+ /// Gets the shader parameter list of a specific .
+ ///
+ /// The shader program.
+ /// All Shader parameters of a shader program are returned.
+ ///
+ public IList GetActiveUniformsList(IShaderHandle shaderProgram)
+ {
+ var sProg = (ShaderHandleImp)shaderProgram;
+ var paramList = new List();
+
+ GL.GetProgram((uint)sProg.Handle, GLEnum.ActiveUniforms, out int nParams);
+
+ for (var i = 0; i < nParams; i++)
+ {
+ var paramInfo = new ShaderParamInfo();
+ paramInfo.Name = GL.GetActiveUniform((uint)sProg.Handle, (uint)i, out paramInfo.Size, out UniformType uType);
+ paramInfo.Handle = GetShaderUniformParam(sProg, paramInfo.Name);
+
+ //Diagnostics.Log($"Active Uniforms: {paramInfo.Name}");
+
+ paramInfo.Type = uType switch
+ {
+ UniformType.Int => typeof(int),
+ UniformType.Bool => typeof(bool),
+ UniformType.Float => typeof(float),
+ UniformType.Double => typeof(double),
+ UniformType.IntVec2 => typeof(float2),
+ UniformType.FloatVec2 => typeof(float2),
+ UniformType.FloatVec3 => typeof(float3),
+ UniformType.FloatVec4 => typeof(float4),
+ UniformType.FloatMat4 => typeof(float4x4),
+ UniformType.Sampler2D or UniformType.UnsignedIntSampler2D or UniformType.IntSampler2D or UniformType.Sampler2DShadow /*or UniformType.Image2D*/ => typeof(ITextureBase),
+ UniformType.SamplerCube or UniformType.SamplerCubeShadow => typeof(IWritableCubeMap),
+ UniformType.Sampler2DArray or UniformType.Sampler2DArrayShadow => typeof(IWritableArrayTexture),
+ _ => throw new ArgumentOutOfRangeException($"ActiveUniformType {uType} unknown."),
+ };
+ paramList.Add(paramInfo);
+ }
+ return paramList;
+ }
+
+ ///
+ /// Specifies the rasterized width of both aliased and antialiased lines.
+ ///
+ /// The width in px.
+ public void SetLineWidth(float width)
+ {
+ GL.LineWidth(width);
+ }
+
+ ///
+ /// Sets a float shader parameter.
+ ///
+ /// The parameter.
+ /// The value.
+ public void SetShaderParam(IShaderParam param, float val)
+ {
+ GL.Uniform1(((ShaderParam)param).handle, val);
+ }
+
+ ///
+ /// Sets a double shader parameter.
+ ///
+ /// The parameter.
+ /// The value.
+ public void SetShaderParam(IShaderParam param, double val)
+ {
+ GL.Uniform1(((ShaderParam)param).handle, val);
+ }
+
+ ///
+ /// Sets a shader parameter.
+ ///
+ /// The parameter.
+ /// The value.
+ public void SetShaderParam(IShaderParam param, float2 val)
+ {
+ GL.Uniform2(((ShaderParam)param).handle, val.x, val.y);
+ }
+
+ ///
+ /// Sets a array shader parameter.
+ ///
+ /// The parameter.
+ /// The value.
+ public unsafe void SetShaderParam(IShaderParam param, float2[] val)
+ {
+ fixed (float2* pFlt = &val[0])
+ GL.Uniform2(((ShaderParam)param).handle, (uint)val.Length, (float*)pFlt);
+ }
+
+ ///
+ /// Sets a shader parameter.
+ ///
+ /// The parameter.
+ /// The value.
+ public void SetShaderParam(IShaderParam param, float3 val)
+ {
+ GL.Uniform3(((ShaderParam)param).handle, val.x, val.y, val.z);
+ }
+
+ ///
+ /// Sets a array shader parameter.
+ ///
+ /// The parameter.
+ /// The value.
+ public unsafe void SetShaderParam(IShaderParam param, float3[] val)
+ {
+ fixed (float3* pFlt = &val[0])
+ GL.Uniform3(((ShaderParam)param).handle, (uint)val.Length, (float*)pFlt);
+ }
+
+ ///
+ /// Sets a shader parameter.
+ ///
+ /// The parameter.
+ /// The value.
+ public void SetShaderParam(IShaderParam param, float4 val)
+ {
+ GL.Uniform4(((ShaderParam)param).handle, val.x, val.y, val.z, val.w);
+ }
+
+ ///
+ /// Sets a shader parameter.
+ ///
+ /// The parameter.
+ /// The value.
+ public void SetShaderParam(IShaderParam param, float4x4 val)
+ {
+ unsafe
+ {
+ var mF = (float*)(&val);
+ // Row order notation
+ // GL.UniformMatrix4(((ShaderParam) param).handle, 1, false, mF);
+
+ // Column order notation
+ GL.UniformMatrix4(((ShaderParam)param).handle, 1, true, mF);
+ }
+ }
+
+ ///
+ /// Sets a array shader parameter.
+ ///
+ /// The parameter.
+ /// The value.
+ public unsafe void SetShaderParam(IShaderParam param, float4[] val)
+ {
+ fixed (float4* pFlt = &val[0])
+ GL.Uniform4(((ShaderParam)param).handle, (uint)val.Length, (float*)pFlt);
+ }
+
+ ///
+ /// Sets a array shader parameter.
+ ///
+ /// The parameter.
+ /// The value.
+ public unsafe void SetShaderParam(IShaderParam param, float4x4[] val)
+ {
+ var tmpArray = new float4[val.Length * 4];
+
+ for (var i = 0; i < val.Length; i++)
+ {
+ tmpArray[i * 4] = val[i].Column1;
+ tmpArray[i * 4 + 1] = val[i].Column2;
+ tmpArray[i * 4 + 2] = val[i].Column3;
+ tmpArray[i * 4 + 3] = val[i].Column4;
+ }
+
+ fixed (float4* pMtx = &tmpArray[0])
+ GL.UniformMatrix4(((ShaderParam)param).handle, (uint)val.Length, false, (float*)pMtx);
+ }
+
+ ///
+ /// Sets a int shader parameter.
+ ///
+ /// The parameter.
+ /// The value.
+ public void SetShaderParam(IShaderParam param, int val)
+ {
+ GL.Uniform1(((ShaderParam)param).handle, val);
+ }
+
+ private void BindImage(TextureType texTarget, ITextureHandle texId, int texUint, GLEnum access, SizedInternalFormat format)
+ {
+ switch (texTarget)
+ {
+ case TextureType.Image2D:
+ GL.BindImageTexture((uint)texUint, (uint)((TextureHandle)texId).TexHandle, 0, false, 0, access, (GLEnum)format);
+ break;
+ default:
+ throw new ArgumentException($"Unknown texture target: {texTarget}.");
+ }
+ }
+
+ private void BindTextureByTarget(ITextureHandle texId, TextureType texTarget)
+ {
+ switch (texTarget)
+ {
+ case TextureType.Texture1D:
+ GL.BindTexture(TextureTarget.Texture1D, (uint)((TextureHandle)texId).TexHandle);
+ break;
+ case TextureType.Texture2D:
+ GL.BindTexture(TextureTarget.Texture2D, (uint)((TextureHandle)texId).TexHandle);
+ break;
+ case TextureType.Texture3D:
+ GL.BindTexture(TextureTarget.Texture3D, (uint)((TextureHandle)texId).TexHandle);
+ break;
+ case TextureType.TextureCubeMap:
+ GL.BindTexture(TextureTarget.TextureCubeMap, (uint)((TextureHandle)texId).TexHandle);
+ break;
+ case TextureType.ArrayTexture:
+ GL.BindTexture(TextureTarget.Texture2DArray, (uint)((TextureHandle)texId).TexHandle);
+ break;
+ case TextureType.Image2D:
+ default:
+ throw new ArgumentException($"Unknown texture target: {texTarget}.");
+ }
+ }
+
+
+ ///
+ /// Sets a texture active and binds it.
+ ///
+ /// The shader parameter, associated with this texture.
+ /// The texture handle.
+ /// The texture type, describing to which texture target the texture gets bound to.
+ public void SetActiveAndBindTexture(IShaderParam param, ITextureHandle texId, TextureType texTarget)
+ {
+ int iParam = ((ShaderParam)param).handle;
+ if (!_shaderParam2TexUnit.TryGetValue(iParam, out int texUnit))
+ {
+ _textureCountPerShader++;
+ texUnit = _textureCountPerShader;
+ _shaderParam2TexUnit[iParam] = texUnit;
+ }
+
+ GL.ActiveTexture(TextureUnit.Texture0 + texUnit);
+ BindTextureByTarget(texId, texTarget);
+ }
+
+ private void SetActiveAndBindImage(IShaderParam param, ITextureHandle texId, TextureType texTarget, ImagePixelFormat format, GLEnum access, out int texUnit)
+ {
+ int iParam = ((ShaderParam)param).handle;
+ if (!_shaderParam2TexUnit.TryGetValue(iParam, out texUnit))
+ {
+ _textureCountPerShader++;
+ texUnit = _textureCountPerShader;
+ _shaderParam2TexUnit[iParam] = texUnit;
+ }
+
+ var sizedIntFormat = GetSizedInteralFormat(format);
+
+ GL.ActiveTexture(TextureUnit.Texture0 + texUnit);
+ BindImage(texTarget, texId, texUnit, access, sizedIntFormat);
+ }
+
+ ///
+ /// Sets a texture active and binds it.
+ ///
+ /// The shader parameter, associated with this texture.
+ /// The texture handle.
+ /// The texture type, describing to which texture target the texture gets bound to.
+ /// The texture unit.
+ public void SetActiveAndBindTexture(IShaderParam param, ITextureHandle texId, TextureType texTarget, out int texUnit)
+ {
+ int iParam = ((ShaderParam)param).handle;
+ if (!_shaderParam2TexUnit.TryGetValue(iParam, out texUnit))
+ {
+ _textureCountPerShader++;
+ texUnit = _textureCountPerShader;
+ _shaderParam2TexUnit[iParam] = texUnit;
+ }
+
+ GL.ActiveTexture(TextureUnit.Texture0 + texUnit);
+ BindTextureByTarget(texId, texTarget);
+ }
+
+ ///
+ /// Sets a given Shader Parameter to a created texture
+ ///
+ /// Shader Parameter used for texture binding
+ /// An array of ITextureHandles returned from CreateTexture method or the ShaderEffectManager.
+ /// /// The texture type, describing to which texture target the texture gets bound to.
+ public void SetActiveAndBindTextureArray(IShaderParam param, ITextureHandle[] texIds, TextureType texTarget)
+ {
+ int iParam = ((ShaderParam)param).handle;
+ int[] texUnitArray = new int[texIds.Length];
+
+ if (!_shaderParam2TexUnit.TryGetValue(iParam, out int firstTexUnit))
+ {
+ _textureCountPerShader++;
+ firstTexUnit = _textureCountPerShader;
+ _textureCountPerShader += texIds.Length;
+ _shaderParam2TexUnit[iParam] = firstTexUnit;
+ }
+
+ for (int i = 0; i < texIds.Length; i++)
+ {
+ texUnitArray[i] = firstTexUnit + i;
+
+ GL.ActiveTexture(TextureUnit.Texture0 + firstTexUnit + i);
+ BindTextureByTarget(texIds[i], texTarget);
+ }
+ }
+
+ ///
+ /// Sets a texture active and binds it.
+ ///
+ /// The shader parameter, associated with this texture.
+ /// An array of ITextureHandles returned from CreateTexture method or the ShaderEffectManager.
+ /// The texture type, describing to which texture target the texture gets bound to.
+ /// The texture units.
+ public void SetActiveAndBindTextureArray(IShaderParam param, ITextureHandle[] texIds, TextureType texTarget, out int[] texUnitArray)
+ {
+ int iParam = ((ShaderParam)param).handle;
+ texUnitArray = new int[texIds.Length];
+
+ if (!_shaderParam2TexUnit.TryGetValue(iParam, out int firstTexUnit))
+ {
+ _textureCountPerShader++;
+ firstTexUnit = _textureCountPerShader;
+ _textureCountPerShader += texIds.Length;
+ _shaderParam2TexUnit[iParam] = firstTexUnit;
+ }
+
+ for (int i = 0; i < texIds.Length; i++)
+ {
+ texUnitArray[i] = firstTexUnit + i;
+
+ GL.ActiveTexture(TextureUnit.Texture0 + firstTexUnit + i);
+ BindTextureByTarget(texIds[i], texTarget);
+ }
+ }
+
+ ///
+ /// Sets a given Shader Parameter to a created texture
+ ///
+ /// Shader Parameter used for texture binding
+ /// An ITextureHandle probably returned from CreateTexture method
+ /// The texture type, describing to which texture target the texture gets bound to.
+ /// The internal sized format of the texture.
+ public void SetShaderParamImage(IShaderParam param, ITextureHandle texId, TextureType texTarget, ImagePixelFormat format)
+ {
+ SetActiveAndBindImage(param, texId, texTarget, format, GLEnum.ReadWrite, out int texUnit);
+ GL.Uniform1(((ShaderParam)param).handle, texUnit);
+ }
+
+ ///
+ /// Sets a given Shader Parameter to a created texture
+ ///
+ /// Shader Parameter used for texture binding
+ /// An ITextureHandle probably returned from CreateTexture method
+ /// The texture type, describing to which texture target the texture gets bound to.
+ public void SetShaderParamTexture(IShaderParam param, ITextureHandle texId, TextureType texTarget)
+ {
+ SetActiveAndBindTexture(param, texId, texTarget, out int texUnit);
+ GL.Uniform1(((ShaderParam)param).handle, texUnit);
+ }
+
+ ///
+ /// Sets a given Shader Parameter to a created texture
+ ///
+ /// Shader Parameter used for texture binding
+ /// An array of ITextureHandles probably returned from CreateTexture method
+ /// The texture type, describing to which texture target the texture gets bound to.
+ public unsafe void SetShaderParamTextureArray(IShaderParam param, ITextureHandle[] texIds, TextureType texTarget)
+ {
+ SetActiveAndBindTextureArray(param, texIds, texTarget, out int[] texUnitArray);
+
+ fixed (int* pFlt = &texUnitArray[0])
+ GL.Uniform1(((ShaderParam)param).handle, (uint)texUnitArray.Length, pFlt);
+ }
+
+ #endregion
+
+ #region Clear
+
+ ///
+ /// Clears the specified flags.
+ ///
+ /// The flags.
+ public void Clear(ClearFlags flags)
+ {
+ GL.Clear((ClearBufferMask)flags);
+ }
+
+ ///
+ /// Gets and sets the color of the background.
+ ///
+ ///
+ /// The color of the clear.
+ ///
+ public float4 ClearColor
+ {
+ get
+ {
+ var ret = new Span(new float[4]);
+ GL.GetFloat(GetPName.ColorClearValue, ret);
+ return new float4(ret[0], ret[1], ret[2], ret[3]);
+ }
+ set => GL.ClearColor(value.x, value.y, value.z, value.w);
+ }
+
+ ///
+ /// Gets and sets the clear depth value which is used to clear the depth buffer.
+ ///
+ ///
+ /// Specifies the depth value used when the depth buffer is cleared. The initial value is 1. This value is clamped to the range [0,1].
+ ///
+ public float ClearDepth
+ {
+ get
+ {
+ GL.GetFloat(GetPName.DepthClearValue, out float ret);
+ return ret;
+ }
+ set => GL.ClearDepth(value);
+ }
+
+ #endregion
+
+ #region Rendering related Members
+
+ ///
+ /// Creates a with the purpose of being used as CPU GBuffer representation.
+ ///
+ /// The texture resolution.
+ public IRenderTarget CreateGBufferTarget(TexRes res)
+ {
+ var gBufferRenderTarget = new RenderTarget(res);
+ gBufferRenderTarget.SetPositionTex();
+ gBufferRenderTarget.SetAlbedoTex();
+ gBufferRenderTarget.SetNormalTex();
+ gBufferRenderTarget.SetDepthTex();
+ gBufferRenderTarget.SetSpecularTex();
+ gBufferRenderTarget.SetEmissiveTex();
+ gBufferRenderTarget.SetSubsurfaceTex();
+
+ return gBufferRenderTarget;
+ }
+
+ ///
+ /// The clipping behavior against the Z position of a vertex can be turned off by activating depth clamping.
+ /// This is done with glEnable(GL_DEPTH_CLAMP). This will cause the clip-space Z to remain unclipped by the front and rear viewing volume.
+ /// See: https://www.khronos.org/opengl/wiki/Vertex_Post-Processing#Depth_clamping
+ ///
+ public void EnableDepthClamp()
+ {
+ GL.Enable(EnableCap.DepthClamp);
+ }
+
+ ///
+ /// Disables depths clamping.
+ ///
+ public void DisableDepthClamp()
+ {
+ GL.Disable(EnableCap.DepthClamp);
+ }
+
+ ///
+ /// Create one single multi-purpose attribute buffer
+ ///
+ ///
+ ///
+ ///
+ public IAttribImp CreateAttributeBuffer(float3[] attributes, string attributeName)
+ {
+ if (attributes == null || attributes.Length == 0)
+ {
+ throw new ArgumentException("Vertices must not be null or empty");
+ }
+
+ int vertsBytes = attributes.Length * 3 * sizeof(float);
+ GL.GenBuffers(1, out uint handle);
+
+ GL.BindBuffer(GLEnum.ArrayBuffer, (uint)handle);
+
+ GL.BufferData(GLEnum.ArrayBuffer, (nuint)vertsBytes, new ReadOnlySpan(attributes), GLEnum.StaticDraw);
+ GL.GetBufferParameter(GLEnum.ArrayBuffer, GLEnum.BufferSize, out int vboBytes);
+ if (vboBytes != vertsBytes)
+ throw new ApplicationException(string.Format(
+ "Problem uploading attribute buffer to VBO ('{2}'). Tried to upload {0} bytes, uploaded {1}.",
+ vertsBytes, vboBytes, attributeName));
+
+ return new AttributeImp { AttributeBufferObject = (int)handle };
+ }
+
+ ///
+ /// Remove an attribute buffer previously created with and release all associated resources
+ /// allocated on the GPU.
+ ///
+ /// The attribute handle
+ public void DeleteAttributeBuffer(IAttribImp attribHandle)
+ {
+ if (attribHandle != null)
+ {
+ int handle = ((AttributeImp)attribHandle).AttributeBufferObject;
+ if (handle != 0)
+ {
+ GL.DeleteBuffer((uint)handle);
+ ((AttributeImp)attribHandle).AttributeBufferObject = 0;
+ }
+ }
+ }
+
+ ///
+ /// Binds the VertexArrayObject onto the GL Render context and assigns its index to the passed instance.
+ ///
+ /// The instance.
+ public void SetVertexArrayObject(IMeshImp mr)
+ {
+ if (((MeshImp)mr).VertexArrayObject == 0)
+ ((MeshImp)mr).VertexArrayObject = (int)GL.GenVertexArray();
+
+ GL.BindVertexArray((uint)((MeshImp)mr).VertexArrayObject);
+ }
+
+ ///
+ /// Binds the vertices onto the GL Render context and assigns an VertexBuffer index to the passed instance.
+ ///
+ /// The instance.
+ /// The vertices.
+ /// Vertices must not be null or empty
+ ///
+ public void SetVertices(IMeshImp mr, float3[] vertices)
+ {
+ if (vertices == null || vertices.Length == 0)
+ {
+ throw new ArgumentException("Vertices must not be null or empty");
+ }
+
+ int vertsBytes = vertices.Length * 3 * sizeof(float);
+ if (((MeshImp)mr).VertexBufferObject == 0)
+ {
+ GL.GenBuffers(1, out uint bufferObj);
+ ((MeshImp)mr).VertexBufferObject = (int)bufferObj;
+ }
+
+ GL.BindBuffer(GLEnum.ArrayBuffer, (uint)((MeshImp)mr).VertexBufferObject);
+ GL.BufferData(GLEnum.ArrayBuffer, (nuint)(vertsBytes), new ReadOnlySpan(vertices), GLEnum.StaticDraw);
+ GL.GetBufferParameter(GLEnum.ArrayBuffer, GLEnum.BufferSize, out int vboBytes);
+ if (vboBytes != vertsBytes)
+ throw new ApplicationException(string.Format("Problem uploading vertex buffer to VBO (vertices). Tried to upload {0} bytes, uploaded {1}.", vertsBytes, vboBytes));
+ }
+
+ ///
+ /// Binds the tangents onto the GL Render context and assigns an TangentBuffer index to the passed instance.
+ ///
+ /// The instance.
+ /// The tangents.
+ /// Tangents must not be null or empty
+ ///
+ public void SetTangents(IMeshImp mr, float4[] tangents)
+ {
+ if (tangents == null || tangents.Length == 0)
+ {
+ throw new ArgumentException("Tangents must not be null or empty");
+ }
+
+ int tangentBytes = tangents.Length * 4 * sizeof(float);
+ if (((MeshImp)mr).TangentBufferObject == 0)
+ {
+ GL.GenBuffers(1, out uint bufferObj);
+ ((MeshImp)mr).TangentBufferObject = (int)bufferObj;
+ }
+
+ GL.BindBuffer(GLEnum.ArrayBuffer, (uint)((MeshImp)mr).TangentBufferObject);
+ GL.BufferData(GLEnum.ArrayBuffer, (nuint)(tangentBytes), new ReadOnlySpan(tangents), GLEnum.StaticDraw);
+ GL.GetBufferParameter(GLEnum.ArrayBuffer, GLEnum.BufferSize, out int vboBytes);
+ if (vboBytes != tangentBytes)
+ throw new ApplicationException(string.Format("Problem uploading vertex buffer to VBO (tangents). Tried to upload {0} bytes, uploaded {1}.", tangentBytes, vboBytes));
+ }
+
+ ///
+ /// Binds the bitangents onto the GL Render context and assigns an BiTangentBuffer index to the passed instance.
+ ///
+ /// The instance.
+ /// The BiTangents.
+ /// BiTangents must not be null or empty
+ ///
+ public void SetBiTangents(IMeshImp mr, float3[] bitangents)
+ {
+ if (bitangents == null || bitangents.Length == 0)
+ {
+ throw new ArgumentException("BiTangents must not be null or empty");
+ }
+
+ int bitangentBytes = bitangents.Length * 3 * sizeof(float);
+ if (((MeshImp)mr).BitangentBufferObject == 0)
+ {
+ GL.GenBuffers(1, out uint bufferObj);
+ ((MeshImp)mr).BitangentBufferObject = (int)bufferObj;
+ }
+
+ GL.BindBuffer(GLEnum.ArrayBuffer, (uint)((MeshImp)mr).BitangentBufferObject);
+ GL.BufferData(GLEnum.ArrayBuffer, (nuint)(bitangentBytes), new ReadOnlySpan(bitangents), GLEnum.StaticDraw);
+ GL.GetBufferParameter(GLEnum.ArrayBuffer, GLEnum.BufferSize, out int vboBytes);
+ if (vboBytes != bitangentBytes)
+ throw new ApplicationException(string.Format("Problem uploading vertex buffer to VBO (bitangents). Tried to upload {0} bytes, uploaded {1}.", bitangentBytes, vboBytes));
+ }
+
+ ///
+ /// Binds the normals onto the GL Render context and assigns an NormalBuffer index to the passed instance.
+ ///
+ /// The instance.
+ /// The normals.
+ /// Normals must not be null or empty
+ ///
+ public void SetNormals(IMeshImp mr, float3[] normals)
+ {
+ if (normals == null || normals.Length == 0)
+ {
+ throw new ArgumentException("Normals must not be null or empty");
+ }
+
+ int normsBytes = normals.Length * 3 * sizeof(float);
+ if (((MeshImp)mr).NormalBufferObject == 0)
+ {
+ GL.GenBuffers(1, out uint bufferObj);
+ ((MeshImp)mr).NormalBufferObject = (int)bufferObj;
+ }
+
+ GL.BindBuffer(GLEnum.ArrayBuffer, (uint)((MeshImp)mr).NormalBufferObject);
+ GL.BufferData(GLEnum.ArrayBuffer, (nuint)(normsBytes), new ReadOnlySpan(normals), GLEnum.StaticDraw);
+ GL.GetBufferParameter(GLEnum.ArrayBuffer, GLEnum.BufferSize, out int vboBytes);
+ if (vboBytes != normsBytes)
+ throw new ApplicationException(string.Format("Problem uploading normal buffer to VBO (normals). Tried to upload {0} bytes, uploaded {1}.", normsBytes, vboBytes));
+ }
+
+ ///
+ /// Binds the bone indices onto the GL Render context and assigns an BondeIndexBuffer index to the passed instance.
+ ///
+ /// The instance.
+ /// The bone indices.
+ /// BoneIndices must not be null or empty
+ ///
+ public void SetBoneIndices(IMeshImp mr, float4[] boneIndices)
+ {
+ if (boneIndices == null || boneIndices.Length == 0)
+ {
+ throw new ArgumentException("BoneIndices must not be null or empty");
+ }
+
+ int indicesBytes = boneIndices.Length * 4 * sizeof(float);
+ if (((MeshImp)mr).BoneIndexBufferObject == 0)
+ {
+ GL.GenBuffers(1, out uint bufferObj);
+ ((MeshImp)mr).BoneIndexBufferObject = (int)bufferObj;
+ }
+
+ GL.BindBuffer(GLEnum.ArrayBuffer, (uint)((MeshImp)mr).BoneIndexBufferObject);
+ GL.BufferData(GLEnum.ArrayBuffer, (nuint)(indicesBytes), new ReadOnlySpan(boneIndices), GLEnum.StaticDraw);
+ GL.GetBufferParameter(GLEnum.ArrayBuffer, GLEnum.BufferSize, out int vboBytes);
+ if (vboBytes != indicesBytes)
+ throw new ApplicationException(string.Format("Problem uploading bone indices buffer to VBO (bone indices). Tried to upload {0} bytes, uploaded {1}.", indicesBytes, vboBytes));
+ }
+
+ ///
+ /// Binds the bone weights onto the GL Render context and assigns an BondeWeightBuffer index to the passed instance.
+ ///
+ /// The instance.
+ /// The bone weights.
+ /// BoneWeights must not be null or empty
+ ///
+ public void SetBoneWeights(IMeshImp mr, float4[] boneWeights)
+ {
+ if (boneWeights == null || boneWeights.Length == 0)
+ {
+ throw new ArgumentException("BoneWeights must not be null or empty");
+ }
+
+ int weightsBytes = boneWeights.Length * 4 * sizeof(float);
+ if (((MeshImp)mr).BoneWeightBufferObject == 0)
+ {
+ GL.GenBuffers(1, out uint bufferObj);
+ ((MeshImp)mr).BoneWeightBufferObject = (int)bufferObj;
+ }
+
+ GL.BindBuffer(GLEnum.ArrayBuffer, (uint)((MeshImp)mr).BoneWeightBufferObject);
+ GL.BufferData(GLEnum.ArrayBuffer, (nuint)(weightsBytes), new ReadOnlySpan(boneWeights), GLEnum.StaticDraw);
+ GL.GetBufferParameter(GLEnum.ArrayBuffer, GLEnum.BufferSize, out int vboBytes);
+ if (vboBytes != weightsBytes)
+ throw new ApplicationException(string.Format("Problem uploading bone weights buffer to VBO (bone weights). Tried to upload {0} bytes, uploaded {1}.", weightsBytes, vboBytes));
+ }
+
+ ///
+ /// Binds the UV coordinates onto the GL Render context and assigns an UVBuffer index to the passed instance.
+ ///
+ /// The instance.
+ /// The UV's.
+ /// UVs must not be null or empty
+ ///
+ public void SetUVs(IMeshImp mr, float2[] uvs)
+ {
+ if (uvs == null || uvs.Length == 0)
+ {
+ throw new ArgumentException("UVs must not be null or empty");
+ }
+
+ int uvsBytes = uvs.Length * 2 * sizeof(float);
+ if (((MeshImp)mr).UVBufferObject == 0)
+ {
+ GL.GenBuffers(1, out uint bufferObj);
+ ((MeshImp)mr).UVBufferObject = (int)bufferObj;
+ }
+
+ GL.BindBuffer(GLEnum.ArrayBuffer, (uint)((MeshImp)mr).UVBufferObject);
+ GL.BufferData(GLEnum.ArrayBuffer, (nuint)(uvsBytes), new ReadOnlySpan(uvs), GLEnum.StaticDraw);
+ GL.GetBufferParameter(GLEnum.ArrayBuffer, GLEnum.BufferSize, out int vboBytes);
+ if (vboBytes != uvsBytes)
+ throw new ApplicationException(string.Format("Problem uploading uv buffer to VBO (uvs). Tried to upload {0} bytes, uploaded {1}.", uvsBytes, vboBytes));
+ }
+
+ ///
+ /// Binds the colors onto the GL Render context and assigns an ColorBuffer index to the passed instance.
+ ///
+ /// The instance.
+ /// The colors.
+ /// colors must not be null or empty
+ ///
+ public void SetColors(IMeshImp mr, uint[] colors)
+ {
+ if (colors == null || colors.Length == 0)
+ {
+ throw new ArgumentException("colors must not be null or empty");
+ }
+
+ int colsBytes = colors.Length * sizeof(uint);
+ if (((MeshImp)mr).ColorBufferObject == 0)
+ {
+ GL.GenBuffers(1, out uint bufferObj);
+ ((MeshImp)mr).ColorBufferObject = (int)bufferObj;
+ }
+
+ GL.BindBuffer(GLEnum.ArrayBuffer, (uint)((MeshImp)mr).ColorBufferObject);
+ GL.BufferData(GLEnum.ArrayBuffer, (nuint)(colsBytes), new ReadOnlySpan(colors), GLEnum.StaticDraw);
+ GL.GetBufferParameter(GLEnum.ArrayBuffer, GLEnum.BufferSize, out int vboBytes);
+ if (vboBytes != colsBytes)
+ throw new ApplicationException(string.Format("Problem uploading color buffer to VBO (colors). Tried to upload {0} bytes, uploaded {1}.", colsBytes, vboBytes));
+ }
+
+ ///
+ /// Binds the colors onto the GL Render context and assigns an ColorBuffer index to the passed instance.
+ ///
+ /// The instance.
+ /// The colors.
+ /// colors must not be null or empty
+ ///
+ public void SetColors1(IMeshImp mr, uint[] colors)
+ {
+ if (colors == null || colors.Length == 0)
+ {
+ throw new ArgumentException("colors must not be null or empty");
+ }
+
+ int colsBytes = colors.Length * sizeof(uint);
+ if (((MeshImp)mr).ColorBufferObject1 == 0)
+ {
+ GL.GenBuffers(1, out uint bufferObj);
+ ((MeshImp)mr).ColorBufferObject1 = (int)bufferObj;
+ }
+
+ GL.BindBuffer(GLEnum.ArrayBuffer, (uint)((MeshImp)mr).ColorBufferObject1);
+ GL.BufferData(GLEnum.ArrayBuffer, (nuint)(colsBytes), new ReadOnlySpan(colors), GLEnum.StaticDraw);
+ GL.GetBufferParameter(GLEnum.ArrayBuffer, GLEnum.BufferSize, out int vboBytes);
+ if (vboBytes != colsBytes)
+ throw new ApplicationException(string.Format("Problem uploading color buffer to VBO (colors). Tried to upload {0} bytes, uploaded {1}.", colsBytes, vboBytes));
+ }
+
+ ///
+ /// Binds the colors onto the GL Render context and assigns an ColorBuffer index to the passed instance.
+ ///
+ /// The instance.
+ /// The colors.
+ /// colors must not be null or empty
+ ///
+ public void SetColors2(IMeshImp mr, uint[] colors)
+ {
+ if (colors == null || colors.Length == 0)
+ {
+ throw new ArgumentException("colors must not be null or empty");
+ }
+
+ int colsBytes = colors.Length * sizeof(uint);
+ if (((MeshImp)mr).ColorBufferObject2 == 0)
+ {
+ GL.GenBuffers(1, out uint bufferObj);
+ ((MeshImp)mr).ColorBufferObject2 = (int)bufferObj;
+ }
+
+ GL.BindBuffer(GLEnum.ArrayBuffer, (uint)((MeshImp)mr).ColorBufferObject2);
+ GL.BufferData(GLEnum.ArrayBuffer, (nuint)(colsBytes), new ReadOnlySpan(colors), GLEnum.StaticDraw);
+ GL.GetBufferParameter(GLEnum.ArrayBuffer, GLEnum.BufferSize, out int vboBytes);
+ if (vboBytes != colsBytes)
+ throw new ApplicationException(string.Format("Problem uploading color buffer to VBO (colors). Tried to upload {0} bytes, uploaded {1}.", colsBytes, vboBytes));
+ }
+
+ ///
+ /// Binds the triangles onto the GL Render context and assigns an ElementBuffer index to the passed instance.
+ ///
+ /// The instance.
+ /// The triangle indices.
+ /// triangleIndices must not be null or empty
+ ///
+ public void SetTriangles(IMeshImp mr, ushort[] triangleIndices)
+ {
+ if (triangleIndices == null || triangleIndices.Length == 0)
+ {
+ throw new ArgumentException("triangleIndices must not be null or empty");
+ }
+ ((MeshImp)mr).NElements = triangleIndices.Length;
+ int trisBytes = triangleIndices.Length * sizeof(short);
+
+ if (((MeshImp)mr).ElementBufferObject == 0)
+ {
+ GL.GenBuffers(1, out uint bufferObj);
+ ((MeshImp)mr).ElementBufferObject = (int)bufferObj;
+ }
+ // Upload the index buffer (elements inside the vertex buffer, not color indices as per the IndexPointer function!)
+ GL.BindBuffer(GLEnum.ElementArrayBuffer, (uint)((MeshImp)mr).ElementBufferObject);
+ GL.BufferData(GLEnum.ElementArrayBuffer, (nuint)(trisBytes), new ReadOnlySpan(triangleIndices), GLEnum.StaticDraw);
+ GL.GetBufferParameter(GLEnum.ElementArrayBuffer, GLEnum.BufferSize, out int vboBytes);
+ if (vboBytes != trisBytes)
+ throw new ApplicationException(string.Format("Problem uploading vertex buffer to VBO (offsets). Tried to upload {0} bytes, uploaded {1}.", trisBytes, vboBytes));
+ }
+
+ ///
+ /// Deletes the buffer associated with the mesh implementation.
+ ///
+ /// The mesh which buffer respectively GPU memory should be deleted.
+ public void RemoveVertices(IMeshImp mr)
+ {
+ GL.DeleteVertexArray((uint)((MeshImp)mr).VertexArrayObject);
+ GL.DeleteBuffer((uint)((MeshImp)mr).VertexBufferObject);
+ ((MeshImp)mr).InvalidateVertices();
+ }
+
+ ///
+ /// Deletes the buffer associated with the mesh implementation.
+ ///
+ /// The mesh which buffer respectively GPU memory should be deleted.
+ public void RemoveNormals(IMeshImp mr)
+ {
+ GL.DeleteBuffer((uint)((MeshImp)mr).NormalBufferObject);
+ ((MeshImp)mr).InvalidateNormals();
+ }
+
+ ///
+ /// Deletes the buffer associated with the mesh implementation.
+ ///
+ /// The mesh which buffer respectively GPU memory should be deleted.
+ public void RemoveColors(IMeshImp mr)
+ {
+ GL.DeleteBuffer((uint)((MeshImp)mr).ColorBufferObject);
+ ((MeshImp)mr).InvalidateColors();
+ }
+
+ ///
+ /// Deletes the buffer associated with the mesh implementation.
+ ///
+ /// The mesh which buffer respectively GPU memory should be deleted.
+ public void RemoveColors1(IMeshImp mr)
+ {
+ var bufferObj = (uint)((MeshImp)mr).ColorBufferObject1;
+ GL.DeleteBuffers(1, bufferObj);
+ ((MeshImp)mr).InvalidateColors1();
+ }
+
+ ///
+ /// Deletes the buffer associated with the mesh implementation.
+ ///
+ /// The mesh which buffer respectively GPU memory should be deleted.
+ public void RemoveColors2(IMeshImp mr)
+ {
+ var bufferObj = (uint)((MeshImp)mr).ColorBufferObject2;
+ GL.DeleteBuffers(1, bufferObj);
+ ((MeshImp)mr).InvalidateColors2();
+ }
+
+ ///
+ /// Deletes the buffer associated with the mesh implementation.
+ ///
+ /// The mesh which buffer respectively GPU memory should be deleted.
+ public void RemoveUVs(IMeshImp mr)
+ {
+ GL.DeleteBuffer((uint)((MeshImp)mr).UVBufferObject);
+ ((MeshImp)mr).InvalidateUVs();
+ }
+
+ ///
+ /// Deletes the buffer associated with the mesh implementation.
+ ///
+ /// The mesh which buffer respectively GPU memory should be deleted.
+ public void RemoveTriangles(IMeshImp mr)
+ {
+ GL.DeleteBuffer((uint)((MeshImp)mr).ElementBufferObject);
+ ((MeshImp)mr).InvalidateTriangles();
+ }
+
+ ///
+ /// Deletes the buffer associated with the mesh implementation.
+ ///
+ /// The mesh which buffer respectively GPU memory should be deleted.
+ public void RemoveBoneWeights(IMeshImp mr)
+ {
+ GL.DeleteBuffer((uint)((MeshImp)mr).BoneWeightBufferObject);
+ ((MeshImp)mr).InvalidateBoneWeights();
+ }
+
+ ///
+ /// Deletes the buffer associated with the mesh implementation.
+ ///
+ /// The mesh which buffer respectively GPU memory should be deleted.
+ public void RemoveBoneIndices(IMeshImp mr)
+ {
+ GL.DeleteBuffer((uint)((MeshImp)mr).BoneIndexBufferObject);
+ ((MeshImp)mr).InvalidateBoneIndices();
+ }
+
+ ///
+ /// Deletes the buffer associated with the mesh implementation.
+ ///
+ /// The mesh which buffer respectively GPU memory should be deleted.
+ public void RemoveTangents(IMeshImp mr)
+ {
+ GL.DeleteBuffer((uint)((MeshImp)mr).TangentBufferObject);
+ ((MeshImp)mr).InvalidateTangents();
+ }
+
+ ///
+ /// Deletes the buffer associated with the mesh implementation.
+ ///
+ /// The mesh which buffer respectively GPU memory should be deleted.
+ public void RemoveBiTangents(IMeshImp mr)
+ {
+ GL.DeleteBuffer((uint)((MeshImp)mr).BitangentBufferObject);
+ ((MeshImp)mr).InvalidateBiTangents();
+ }
+
+ ///
+ /// Defines a barrier ordering memory transactions. At the moment it will insert all supported barriers.
+ ///
+ public void MemoryBarrier()
+ {
+ unchecked // (uint) as -1
+ {
+ GL.MemoryBarrier((uint)GLEnum.AllBarrierBits);
+ }
+ }
+
+ ///
+ /// Launch the bound Compute Shader Program.
+ ///
+ ///
+ /// The number of work groups to be launched in the X dimension.
+ /// The number of work groups to be launched in the Y dimension.
+ /// he number of work groups to be launched in the Z dimension.
+ public void DispatchCompute(int kernelIndex, int threadGroupsX, int threadGroupsY, int threadGroupsZ)
+ {
+ GL.DispatchCompute((uint)threadGroupsX, (uint)threadGroupsY, (uint)threadGroupsZ);
+ }
+
+ ///
+ /// Renders the specified .
+ ///
+ /// The instance.
+ public unsafe void Render(IMeshImp mr)
+ {
+ GL.BindVertexArray((uint)((MeshImp)mr).VertexArrayObject);
+
+ if (((MeshImp)mr).VertexBufferObject != 0)
+ {
+ GL.EnableVertexAttribArray((uint)AttributeLocations.VertexAttribLocation);
+ GL.BindBuffer(GLEnum.ArrayBuffer, (uint)((MeshImp)mr).VertexBufferObject);
+ GL.VertexAttribPointer((uint)AttributeLocations.VertexAttribLocation, 3, GLEnum.Float, false, 0, IntPtr.Zero.ToPointer());
+ }
+ if (((MeshImp)mr).ColorBufferObject != 0)
+ {
+ GL.EnableVertexAttribArray((uint)AttributeLocations.ColorAttribLocation);
+ GL.BindBuffer(GLEnum.ArrayBuffer, (uint)((MeshImp)mr).ColorBufferObject);
+ GL.VertexAttribPointer((uint)AttributeLocations.ColorAttribLocation, 4, GLEnum.UnsignedByte, true, 0, IntPtr.Zero.ToPointer());
+ }
+ if (((MeshImp)mr).ColorBufferObject1 != 0)
+ {
+ GL.EnableVertexAttribArray((uint)AttributeLocations.Color1AttribLocation);
+ GL.BindBuffer(GLEnum.ArrayBuffer, (uint)((MeshImp)mr).ColorBufferObject1);
+ GL.VertexAttribPointer((uint)AttributeLocations.Color1AttribLocation, 4, GLEnum.UnsignedByte, true, 0, IntPtr.Zero.ToPointer());
+ }
+ if (((MeshImp)mr).ColorBufferObject2 != 0)
+ {
+ GL.EnableVertexAttribArray((uint)AttributeLocations.Color2AttribLocation);
+ GL.BindBuffer(GLEnum.ArrayBuffer, (uint)((MeshImp)mr).ColorBufferObject2);
+ GL.VertexAttribPointer((uint)AttributeLocations.Color2AttribLocation, 4, GLEnum.UnsignedByte, true, 0, IntPtr.Zero.ToPointer());
+ }
+ if (((MeshImp)mr).UVBufferObject != 0)
+ {
+ GL.EnableVertexAttribArray((uint)AttributeLocations.UvAttribLocation);
+ GL.BindBuffer(GLEnum.ArrayBuffer, (uint)((MeshImp)mr).UVBufferObject);
+ GL.VertexAttribPointer((uint)AttributeLocations.UvAttribLocation, 2, GLEnum.Float, false, 0, IntPtr.Zero.ToPointer());
+ }
+ if (((MeshImp)mr).NormalBufferObject != 0)
+ {
+ GL.EnableVertexAttribArray((uint)AttributeLocations.NormalAttribLocation);
+ GL.BindBuffer(GLEnum.ArrayBuffer, (uint)((MeshImp)mr).NormalBufferObject);
+ GL.VertexAttribPointer((uint)AttributeLocations.NormalAttribLocation, 3, GLEnum.Float, false, 0, IntPtr.Zero.ToPointer());
+ }
+ if (((MeshImp)mr).TangentBufferObject != 0)
+ {
+ GL.EnableVertexAttribArray((uint)AttributeLocations.TangentAttribLocation);
+ GL.BindBuffer(GLEnum.ArrayBuffer, (uint)((MeshImp)mr).TangentBufferObject);
+ GL.VertexAttribPointer((uint)AttributeLocations.TangentAttribLocation, 3, GLEnum.Float, false, 0, IntPtr.Zero.ToPointer());
+ }
+ if (((MeshImp)mr).BitangentBufferObject != 0)
+ {
+ GL.EnableVertexAttribArray((uint)AttributeLocations.BitangentAttribLocation);
+ GL.BindBuffer(GLEnum.ArrayBuffer, (uint)((MeshImp)mr).BitangentBufferObject);
+ GL.VertexAttribPointer((uint)AttributeLocations.BitangentAttribLocation, 3, GLEnum.Float, false, 0, IntPtr.Zero.ToPointer());
+ }
+ if (((MeshImp)mr).BoneIndexBufferObject != 0)
+ {
+ GL.EnableVertexAttribArray((uint)AttributeLocations.BoneIndexAttribLocation);
+ GL.BindBuffer(GLEnum.ArrayBuffer, (uint)((MeshImp)mr).BoneIndexBufferObject);
+ GL.VertexAttribPointer((uint)AttributeLocations.BoneIndexAttribLocation, 4, GLEnum.Float, false, 0, IntPtr.Zero.ToPointer());
+ }
+ if (((MeshImp)mr).BoneWeightBufferObject != 0)
+ {
+ GL.EnableVertexAttribArray((uint)AttributeLocations.BoneWeightAttribLocation);
+ GL.BindBuffer(GLEnum.ArrayBuffer, (uint)((MeshImp)mr).BoneWeightBufferObject);
+ GL.VertexAttribPointer((uint)AttributeLocations.BoneWeightAttribLocation, 4, GLEnum.Float, false, 0, IntPtr.Zero.ToPointer());
+ }
+ if (((MeshImp)mr).ElementBufferObject != 0)
+ {
+ GL.BindBuffer(GLEnum.ElementArrayBuffer, (uint)((MeshImp)mr).ElementBufferObject);
+
+ switch (((MeshImp)mr).MeshType)
+ {
+ case Common.PrimitiveType.Triangles:
+ default:
+ GL.DrawElements(GLEnum.Triangles, (uint)((MeshImp)mr).NElements, GLEnum.UnsignedShort, IntPtr.Zero.ToPointer());
+ break;
+ case Common.PrimitiveType.Points:
+ // enable gl_PointSize to set the point size
+ if (!_isPtRenderingEnabled)
+ {
+ _isPtRenderingEnabled = true;
+ GL.Enable(EnableCap.ProgramPointSize);
+ //GL.Enable(EnableCap.PointSprite);
+ GL.Enable(GLEnum.VertexProgramPointSize);
+ }
+ GL.DrawElements(GLEnum.Points, (uint)((MeshImp)mr).NElements, GLEnum.UnsignedShort, IntPtr.Zero.ToPointer());
+ break;
+ case Common.PrimitiveType.Lines:
+ if (!_isLineSmoothEnabled)
+ {
+ GL.Enable(EnableCap.LineSmooth);
+ _isLineSmoothEnabled = true;
+ }
+ GL.DrawElements(GLEnum.Lines, (uint)((MeshImp)mr).NElements, GLEnum.UnsignedShort, IntPtr.Zero.ToPointer());
+ break;
+ case Common.PrimitiveType.LineLoop:
+ if (!_isLineSmoothEnabled)
+ {
+ GL.Enable(EnableCap.LineSmooth);
+ _isLineSmoothEnabled = true;
+ }
+ GL.DrawElements(GLEnum.LineLoop, (uint)((MeshImp)mr).NElements, GLEnum.UnsignedShort, IntPtr.Zero.ToPointer());
+ break;
+ case Common.PrimitiveType.LineStrip:
+ if (!_isLineSmoothEnabled)
+ {
+ GL.Enable(EnableCap.LineSmooth);
+ _isLineSmoothEnabled = true;
+ }
+ GL.DrawElements(GLEnum.LineStrip, (uint)((MeshImp)mr).NElements, GLEnum.UnsignedShort, IntPtr.Zero.ToPointer());
+ break;
+ case Common.PrimitiveType.Patches:
+ GL.DrawElements(GLEnum.Patches, (uint)((MeshImp)mr).NElements, GLEnum.UnsignedShort, IntPtr.Zero.ToPointer());
+ break;
+ case Common.PrimitiveType.QuadStrip:
+ //GL.DrawElements(GLEnum.QuadStrip, (uint)((MeshImp)mr).NElements, GLEnum.UnsignedShort, IntPtr.Zero.ToPointer());
+ break;
+ case Common.PrimitiveType.TriangleFan:
+ GL.DrawElements(GLEnum.TriangleFan, (uint)((MeshImp)mr).NElements, GLEnum.UnsignedShort, IntPtr.Zero.ToPointer());
+ break;
+ case Common.PrimitiveType.TriangleStrip:
+ GL.DrawElements(GLEnum.TriangleStrip, (uint)((MeshImp)mr).NElements, GLEnum.UnsignedShort, IntPtr.Zero.ToPointer());
+ break;
+ }
+ }
+
+ if (((MeshImp)mr).VertexBufferObject != 0)
+ GL.DisableVertexAttribArray((uint)AttributeLocations.VertexAttribLocation);
+ if (((MeshImp)mr).ColorBufferObject != 0)
+ GL.DisableVertexAttribArray((uint)AttributeLocations.ColorAttribLocation);
+ if (((MeshImp)mr).NormalBufferObject != 0)
+ GL.DisableVertexAttribArray((uint)AttributeLocations.NormalAttribLocation);
+ if (((MeshImp)mr).UVBufferObject != 0)
+ GL.DisableVertexAttribArray((uint)AttributeLocations.UvAttribLocation);
+ if (((MeshImp)mr).TangentBufferObject != 0)
+ GL.DisableVertexAttribArray((uint)AttributeLocations.TangentAttribLocation);
+ if (((MeshImp)mr).BitangentBufferObject != 0)
+ GL.DisableVertexAttribArray((uint)AttributeLocations.TangentAttribLocation);
+
+ GL.BindVertexArray(0);
+ }
+
+ ///
+ /// Gets the content of the buffer.
+ ///
+ /// The Rectangle where the content is draw into.
+ /// The texture identifier.
+ public void GetBufferContent(Common.Rectangle quad, ITextureHandle texId)
+ {
+ GL.BindTexture(TextureTarget.Texture2D, (uint)((TextureHandle)texId).TexHandle);
+ GL.CopyTexImage2D((GLEnum)TextureTarget.Texture2D, 0, GLEnum.Rgba, quad.Left, quad.Top, (uint)quad.Width, (uint)quad.Height, 0);
+ }
+
+ ///
+ /// Creates the mesh implementation.
+ ///
+ /// The instance.
+ public IMeshImp CreateMeshImp()
+ {
+ return new MeshImp();
+ }
+
+ internal static GLEnum BlendOperationToOgl(BlendOperation bo)
+ {
+ return bo switch
+ {
+ BlendOperation.Add => GLEnum.FuncAdd,
+ BlendOperation.Subtract => GLEnum.FuncSubtract,
+ BlendOperation.ReverseSubtract => GLEnum.FuncReverseSubtract,
+ BlendOperation.Minimum => GLEnum.Min,
+ BlendOperation.Maximum => GLEnum.Max,
+ _ => throw new ArgumentOutOfRangeException($"Invalid argument: {bo}"),
+ };
+ }
+
+ internal static BlendOperation BlendOperationFromOgl(GLEnum bom)
+ {
+ return bom switch
+ {
+ GLEnum.FuncAdd => BlendOperation.Add,
+ GLEnum.Min => BlendOperation.Minimum,
+ GLEnum.Max => BlendOperation.Maximum,
+ GLEnum.FuncSubtract => BlendOperation.Subtract,
+ GLEnum.FuncReverseSubtract => BlendOperation.ReverseSubtract,
+ _ => throw new ArgumentOutOfRangeException($"Invalid argument: {bom}"),
+ };
+ }
+
+ internal static GLEnum BlendToOgl(Blend blend, bool isForBlendFactorAlpha = false)
+ {
+ return blend switch
+ {
+ Blend.Zero => GLEnum.Zero,
+ Blend.One => GLEnum.One,
+ Blend.SourceColor => GLEnum.SrcColor,
+ Blend.InverseSourceColor => GLEnum.OneMinusSrcColor,
+ Blend.SourceAlpha => GLEnum.SrcAlpha,
+ Blend.InverseSourceAlpha => GLEnum.OneMinusSrcAlpha,
+ Blend.DestinationAlpha => GLEnum.DstAlpha,
+ Blend.InverseDestinationAlpha => GLEnum.OneMinusDstAlpha,
+ Blend.DestinationColor => GLEnum.DstColor,
+ Blend.InverseDestinationColor => GLEnum.OneMinusDstColor,
+ Blend.BlendFactor => ((isForBlendFactorAlpha) ? GLEnum.ConstantAlpha : GLEnum.ConstantColor),
+ Blend.InverseBlendFactor => ((isForBlendFactorAlpha) ? GLEnum.OneMinusConstantAlpha : GLEnum.OneMinusConstantColor),
+ // Ignored...
+ // case Blend.SourceAlphaSaturated:
+ // break;
+ //case Blend.Bothsrcalpha:
+ // break;
+ //case Blend.BothInverseSourceAlpha:
+ // break;
+ //case Blend.SourceColor2:
+ // break;
+ //case Blend.InverseSourceColor2:
+ // break;
+ _ => throw new ArgumentOutOfRangeException(nameof(blend)),
+ };
+ }
+
+ internal static Blend BlendFromOgl(int bf)
+ {
+ return bf switch
+ {
+ (int)GLEnum.Zero => Blend.Zero,
+ (int)GLEnum.One => Blend.One,
+ (int)GLEnum.SrcColor => Blend.SourceColor,
+ (int)GLEnum.OneMinusSrcColor => Blend.InverseSourceColor,
+ (int)GLEnum.SrcAlpha => Blend.SourceAlpha,
+ (int)GLEnum.OneMinusSrcAlpha => Blend.InverseSourceAlpha,
+ (int)GLEnum.DstAlpha => Blend.DestinationAlpha,
+ (int)GLEnum.OneMinusDstAlpha => Blend.InverseDestinationAlpha,
+ (int)GLEnum.DstColor => Blend.DestinationColor,
+ (int)GLEnum.OneMinusDstColor => Blend.InverseDestinationColor,
+ (int)GLEnum.ConstantAlpha or (int)GLEnum.ConstantColor => Blend.BlendFactor,
+ (int)GLEnum.OneMinusConstantAlpha or (int)GLEnum.OneMinusConstantColor => Blend.InverseBlendFactor,
+ _ => throw new ArgumentOutOfRangeException("blend"),
+ };
+ }
+
+ ///
+ /// Sets the RenderState object onto the current OpenGL based RenderContext.
+ ///
+ /// State of the render(enum).
+ /// The value. See for detailed information.
+ ///
+ /// value
+ /// or
+ /// value
+ /// or
+ /// value
+ /// or
+ /// renderState
+ ///
+ public void SetRenderState(RenderState renderState, uint value)
+ {
+ switch (renderState)
+ {
+ case RenderState.FillMode:
+ {
+ var pm = (FillMode)value switch
+ {
+ FillMode.Point => PolygonMode.Point,
+ FillMode.Wireframe => PolygonMode.Line,
+ FillMode.Solid => PolygonMode.Fill,
+ _ => throw new ArgumentOutOfRangeException(nameof(value)),
+ };
+ GL.PolygonMode(MaterialFace.FrontAndBack, pm);
+ return;
+ }
+ case RenderState.CullMode:
+ {
+ switch ((Cull)value)
+ {
+ case Cull.None:
+ if (_isCullEnabled)
+ {
+ _isCullEnabled = false;
+ GL.Disable(EnableCap.CullFace);
+ }
+ GL.FrontFace(FrontFaceDirection.Ccw);
+ break;
+ case Cull.Clockwise:
+ if (!_isCullEnabled)
+ {
+ _isCullEnabled = true;
+ GL.Enable(EnableCap.CullFace);
+ }
+ GL.FrontFace(FrontFaceDirection.CW);
+ break;
+ case Cull.Counterclockwise:
+ if (!_isCullEnabled)
+ {
+ _isCullEnabled = true;
+ GL.Enable(EnableCap.CullFace);
+ }
+ GL.FrontFace(FrontFaceDirection.Ccw);
+ break;
+ default:
+ throw new ArgumentOutOfRangeException(nameof(value));
+ }
+ }
+ break;
+ case RenderState.Clipping:
+ // clipping is always on in OpenGL - This state is simply ignored
+ break;
+ case RenderState.ZFunc:
+ {
+ DepthFunction df = GetDepthCompareFunc((Compare)value);
+ GL.DepthFunc(df);
+ }
+ break;
+ case RenderState.ZEnable:
+ if (value == 0)
+ GL.Disable(EnableCap.DepthTest);
+ else
+ GL.Enable(EnableCap.DepthTest);
+ break;
+ case RenderState.ZWriteEnable:
+ GL.DepthMask(value != 0);
+ break;
+ case RenderState.AlphaBlendEnable:
+ if (value == 0)
+ GL.Disable(EnableCap.Blend);
+ else
+ GL.Enable(EnableCap.Blend);
+ break;
+ case RenderState.BlendOperation:
+ {
+ _blendEquationRgb = BlendOperationToOgl((BlendOperation)value);
+ GL.BlendEquationSeparate(_blendEquationRgb, _blendEquationAlpha);
+ }
+ break;
+
+ case RenderState.BlendOperationAlpha:
+ {
+ _blendEquationAlpha = BlendOperationToOgl((BlendOperation)value);
+ GL.BlendEquationSeparate(_blendEquationRgb, (GLEnum)_blendEquationAlpha);
+ }
+ break;
+ case RenderState.SourceBlend:
+ {
+ _blendSrcRgb = BlendToOgl((Blend)value);
+ GL.BlendFuncSeparate(_blendSrcRgb, _blendDstRgb, _blendSrcAlpha, _blendDstAlpha);
+ }
+ break;
+ case RenderState.DestinationBlend:
+ {
+ _blendDstRgb = BlendToOgl((Blend)value);
+ GL.BlendFuncSeparate(_blendSrcRgb, _blendDstRgb, _blendSrcAlpha, _blendDstAlpha);
+ }
+ break;
+ case RenderState.SourceBlendAlpha:
+ {
+ _blendSrcAlpha = BlendToOgl((Blend)value);
+ GL.BlendFuncSeparate(_blendSrcRgb, _blendDstRgb, _blendSrcAlpha, _blendDstAlpha);
+ }
+ break;
+ case RenderState.DestinationBlendAlpha:
+ {
+ _blendDstAlpha = BlendToOgl((Blend)value);
+ GL.BlendFuncSeparate(_blendSrcRgb, _blendDstRgb, _blendSrcAlpha, _blendDstAlpha);
+ }
+ break;
+ case RenderState.BlendFactor:
+ GL.BlendColor(System.Drawing.Color.FromArgb((int)value));
+ break;
+ default:
+ throw new ArgumentOutOfRangeException(nameof(renderState));
+ }
+ }
+
+ ///
+ /// Gets the current RenderState that is applied to the current OpenGL based RenderContext.
+ ///
+ /// State of the render. See for further information.
+ ///
+ ///
+ /// pm;Value + ((PolygonMode)pm) + not handled
+ /// or
+ /// depFunc;Value + ((DepthFunction)depFunc) + not handled
+ /// or
+ /// renderState
+ ///
+ public uint GetRenderState(RenderState renderState)
+ {
+ switch (renderState)
+ {
+ case RenderState.FillMode:
+ {
+ GL.GetInteger(GetPName.PolygonMode, out int pm);
+ var ret = (PolygonMode)pm switch
+ {
+ PolygonMode.Point => FillMode.Point,
+ PolygonMode.Line => FillMode.Wireframe,
+ PolygonMode.Fill => FillMode.Solid,
+ _ => throw new ArgumentOutOfRangeException("pm", "Value " + ((PolygonMode)pm) + " not handled"),
+ };
+ return (uint)ret;
+ }
+ case RenderState.CullMode:
+ {
+ GL.GetInteger(GetPName.CullFace, out int cullFace);
+ if (cullFace == 0)
+ return (uint)Cull.None;
+ GL.GetInteger(GetPName.FrontFace, out int frontFace);
+ if (frontFace == (int)FrontFaceDirection.CW)
+ return (uint)Cull.Clockwise;
+ return (uint)Cull.Counterclockwise;
+ }
+ case RenderState.Clipping:
+ // clipping is always on in OpenGL - This state is simply ignored
+ return 1; // == true
+ case RenderState.ZFunc:
+ {
+ GL.GetInteger(GetPName.DepthFunc, out int depFunc);
+ var ret = (DepthFunction)depFunc switch
+ {
+ DepthFunction.Never => Compare.Never,
+ DepthFunction.Less => Compare.Less,
+ DepthFunction.Equal => Compare.Equal,
+ DepthFunction.Lequal => Compare.LessEqual,
+ DepthFunction.Greater => Compare.Greater,
+ DepthFunction.Notequal => Compare.NotEqual,
+ DepthFunction.Gequal => Compare.GreaterEqual,
+ DepthFunction.Always => Compare.Always,
+ _ => throw new ArgumentOutOfRangeException("depFunc", "Value " + ((DepthFunction)depFunc) + " not handled"),
+ };
+ return (uint)ret;
+ }
+ case RenderState.ZEnable:
+ {
+ GL.GetInteger(GetPName.DepthTest, out int depTest);
+ return (uint)(depTest);
+ }
+ case RenderState.ZWriteEnable:
+ {
+ GL.GetInteger(GetPName.DepthWritemask, out int depWriteMask);
+ return (uint)(depWriteMask);
+ }
+ case RenderState.AlphaBlendEnable:
+ {
+ GL.GetInteger(GetPName.Blend, out int blendEnable);
+ return (uint)(blendEnable);
+ }
+ case RenderState.BlendOperation:
+ {
+ GL.GetInteger(GetPName.BlendEquationRgb, out int rgbMode);
+ return (uint)BlendOperationFromOgl((GLEnum)rgbMode);
+ }
+ case RenderState.BlendOperationAlpha:
+ {
+ GL.GetInteger(GetPName.BlendEquationAlpha, out int alphaMode);
+ return (uint)BlendOperationFromOgl((GLEnum)alphaMode);
+ }
+ case RenderState.SourceBlend:
+ {
+ GL.GetInteger(GetPName.BlendSrcRgb, out int rgbSrc);
+ return (uint)BlendFromOgl(rgbSrc);
+ }
+ case RenderState.DestinationBlend:
+ {
+ GL.GetInteger(GetPName.BlendSrcRgb, out int rgbDst);
+ return (uint)BlendFromOgl(rgbDst);
+ }
+ case RenderState.SourceBlendAlpha:
+ {
+ GL.GetInteger(GetPName.BlendSrcAlpha, out int alphaSrc);
+ return (uint)BlendFromOgl(alphaSrc);
+ }
+ case RenderState.DestinationBlendAlpha:
+ {
+ GL.GetInteger(GetPName.BlendDstAlpha, out int alphaDst);
+ return (uint)BlendFromOgl(alphaDst);
+ }
+ case RenderState.BlendFactor:
+ int col;
+ GL.GetInteger(GetPName.BlendColorExt, out col);
+ return (uint)col;
+ default:
+ throw new ArgumentOutOfRangeException(nameof(renderState));
+ }
+ }
+
+ ///
+ /// Renders into the given texture.
+ ///
+ /// The texture.
+ /// The texture handle, associated with the given texture. Should be created by the TextureManager in the RenderContext.
+ public void SetRenderTarget(IWritableTexture tex, ITextureHandle texHandle)
+ {
+ if (((TextureHandle)texHandle).FrameBufferHandle == -1)
+ {
+ var fBuffer = GL.GenFramebuffer();
+ ((TextureHandle)texHandle).FrameBufferHandle = (int)fBuffer;
+ GL.BindFramebuffer(GLEnum.Framebuffer, fBuffer);
+
+ GL.BindTexture(TextureTarget.Texture2D, (uint)((TextureHandle)texHandle).TexHandle);
+
+ if (tex.TextureType != RenderTargetTextureTypes.Depth)
+ {
+ CreateDepthRenderBuffer(tex.Width, tex.Height);
+ GL.FramebufferTexture(GLEnum.Framebuffer, GLEnum.ColorAttachment0, (uint)((TextureHandle)texHandle).TexHandle, 0);
+ GL.DrawBuffer(DrawBufferMode.ColorAttachment0);
+ }
+ else
+ {
+ GL.FramebufferTexture(GLEnum.Framebuffer, GLEnum.DepthAttachment, (uint)((TextureHandle)texHandle).TexHandle, 0);
+ GL.DrawBuffer(DrawBufferMode.None);
+ GL.ReadBuffer(ReadBufferMode.None);
+ }
+ }
+ else
+ GL.BindFramebuffer(GLEnum.Framebuffer, (uint)((TextureHandle)texHandle).FrameBufferHandle);
+
+ if (GL.CheckFramebufferStatus(GLEnum.Framebuffer) != GLEnum.FramebufferComplete)
+ throw new Exception($"Error creating RenderTarget: {GL.GetError()}, {GL.CheckFramebufferStatus(GLEnum.Framebuffer)}");
+
+ GL.Clear(ClearBufferMask.DepthBufferBit | ClearBufferMask.ColorBufferBit);
+ }
+
+ ///
+ /// Renders into the given cube map.
+ ///
+ /// The texture.
+ /// The texture handle, associated with the given cube map. Should be created by the TextureManager in the RenderContext.
+ public void SetRenderTarget(IWritableCubeMap tex, ITextureHandle texHandle)
+ {
+ if (((TextureHandle)texHandle).FrameBufferHandle == -1)
+ {
+ var fBuffer = GL.GenFramebuffer();
+ ((TextureHandle)texHandle).FrameBufferHandle = (int)fBuffer;
+ GL.BindFramebuffer(GLEnum.Framebuffer, fBuffer);
+
+ GL.BindTexture(TextureTarget.TextureCubeMap, (uint)((TextureHandle)texHandle).TexHandle);
+
+ if (tex.TextureType != RenderTargetTextureTypes.Depth)
+ {
+ CreateDepthRenderBuffer(tex.Width, tex.Height);
+ GL.FramebufferTexture(GLEnum.Framebuffer, GLEnum.ColorAttachment0, (uint)((TextureHandle)texHandle).TexHandle, 0);
+ GL.DrawBuffer(DrawBufferMode.ColorAttachment0);
+ }
+ else
+ {
+ GL.FramebufferTexture(GLEnum.Framebuffer, GLEnum.DepthAttachment, (uint)((TextureHandle)texHandle).TexHandle, 0);
+ GL.DrawBuffer(DrawBufferMode.None);
+ GL.ReadBuffer(ReadBufferMode.None);
+ }
+ }
+ else
+ GL.BindFramebuffer(GLEnum.Framebuffer, (uint)((TextureHandle)texHandle).FrameBufferHandle);
+
+ if (GL.CheckFramebufferStatus(GLEnum.Framebuffer) != GLEnum.FramebufferComplete)
+ throw new Exception($"Error creating RenderTarget: {GL.GetError()}, {GL.CheckFramebufferStatus(GLEnum.Framebuffer)}");
+
+
+ GL.Clear(ClearBufferMask.DepthBufferBit | ClearBufferMask.ColorBufferBit);
+ }
+
+ ///
+ /// Renders into the given layer of the array texture.
+ ///
+ /// The array texture.
+ /// The layer to render to.
+ /// The texture handle, associated with the given texture. Should be created by the TextureManager in the RenderContext.
+ public void SetRenderTarget(IWritableArrayTexture tex, int layer, ITextureHandle texHandle)
+ {
+ if (((TextureHandle)texHandle).FrameBufferHandle == -1)
+ {
+ var fBuffer = GL.GenFramebuffer();
+ ((TextureHandle)texHandle).FrameBufferHandle = (int)fBuffer;
+ GL.BindFramebuffer(GLEnum.Framebuffer, fBuffer);
+
+ GL.BindTexture(TextureTarget.Texture2DArray, (uint)((TextureHandle)texHandle).TexHandle);
+
+ if (tex.TextureType != RenderTargetTextureTypes.Depth)
+ {
+ CreateDepthRenderBuffer(tex.Width, tex.Height);
+ GL.FramebufferTextureLayer(GLEnum.Framebuffer, GLEnum.ColorAttachment0, (uint)((TextureHandle)texHandle).TexHandle, 0, layer);
+ GL.DrawBuffer(DrawBufferMode.ColorAttachment0);
+ }
+ else
+ {
+ GL.FramebufferTextureLayer(GLEnum.Framebuffer, GLEnum.DepthAttachment, (uint)((TextureHandle)texHandle).TexHandle, 0, layer);
+ GL.DrawBuffer(DrawBufferMode.None);
+ GL.ReadBuffer(ReadBufferMode.None);
+ }
+ }
+ else
+ {
+ GL.BindFramebuffer(GLEnum.Framebuffer, (uint)((TextureHandle)texHandle).FrameBufferHandle);
+ GL.BindTexture(TextureTarget.Texture2DArray, (uint)((TextureHandle)texHandle).TexHandle);
+ GL.FramebufferTextureLayer(GLEnum.Framebuffer, GLEnum.DepthAttachment, (uint)((TextureHandle)texHandle).TexHandle, 0, layer);
+ }
+
+ if (GL.CheckFramebufferStatus(GLEnum.Framebuffer) != GLEnum.FramebufferComplete)
+ throw new Exception($"Error creating RenderTarget: {GL.GetError()}, {GL.CheckFramebufferStatus(GLEnum.Framebuffer)}");
+
+ GL.Clear(ClearBufferMask.DepthBufferBit | ClearBufferMask.ColorBufferBit);
+ }
+
+ ///
+ /// Renders into the given textures of the RenderTarget.
+ ///
+ /// The render target.
+ /// The texture handles, associated with the given textures. Each handle should be created by the TextureManager in the RenderContext.
+ public void SetRenderTarget(IRenderTarget renderTarget, ITextureHandle[] texHandles)
+ {
+ if (renderTarget == null || (renderTarget.RenderTextures.All(x => x == null)))
+ {
+ GL.BindFramebuffer(GLEnum.Framebuffer, 0);
+ return;
+ }
+
+ int gBuffer;
+
+ if (renderTarget.GBufferHandle == null)
+ {
+ renderTarget.GBufferHandle = new FrameBufferHandle();
+ gBuffer = CreateFrameBuffer(renderTarget, texHandles);
+ ((FrameBufferHandle)renderTarget.GBufferHandle).Handle = gBuffer;
+ }
+ else
+ {
+ gBuffer = ((FrameBufferHandle)renderTarget.GBufferHandle).Handle;
+ GL.BindFramebuffer(GLEnum.Framebuffer, (uint)gBuffer);
+ }
+
+ if (renderTarget.RenderTextures[(int)RenderTargetTextureTypes.Depth] == null && !renderTarget.IsDepthOnly)
+ {
+ int gDepthRenderbufferHandle;
+ if (renderTarget.DepthBufferHandle == null)
+ {
+ renderTarget.DepthBufferHandle = new RenderBufferHandle();
+ // Create and attach depth buffer (renderbuffer)
+ gDepthRenderbufferHandle = CreateDepthRenderBuffer((int)renderTarget.TextureResolution, (int)renderTarget.TextureResolution);
+ ((RenderBufferHandle)renderTarget.DepthBufferHandle).Handle = gDepthRenderbufferHandle;
+ }
+ else
+ {
+ gDepthRenderbufferHandle = ((RenderBufferHandle)renderTarget.DepthBufferHandle).Handle;
+ GL.BindRenderbuffer(GLEnum.Renderbuffer, (uint)gDepthRenderbufferHandle);
+ }
+ }
+
+ if (GL.CheckFramebufferStatus(GLEnum.Framebuffer) != GLEnum.FramebufferComplete)
+ {
+ throw new Exception($"Error creating RenderTarget: {GL.GetError()}, {GL.CheckFramebufferStatus(GLEnum.Framebuffer)}");
+ }
+
+ GL.Clear(ClearBufferMask.DepthBufferBit | ClearBufferMask.ColorBufferBit);
+ }
+
+ private int CreateDepthRenderBuffer(int width, int height)
+ {
+ GL.Enable(EnableCap.DepthTest);
+
+ GL.GenRenderbuffers(1, out uint gDepthRenderbufferHandle);
+ //((FrameBufferHandle)renderTarget.DepthBufferHandle).Handle = gDepthRenderbufferHandle;
+ GL.BindRenderbuffer(GLEnum.Renderbuffer, gDepthRenderbufferHandle);
+ GL.RenderbufferStorage(GLEnum.Renderbuffer, GLEnum.DepthComponent24, (uint)width, (uint)height);
+ GL.FramebufferRenderbuffer(GLEnum.Framebuffer, GLEnum.DepthAttachment, GLEnum.Renderbuffer, gDepthRenderbufferHandle);
+ return (int)gDepthRenderbufferHandle;
+ }
+
+ private int CreateFrameBuffer(IRenderTarget renderTarget, ITextureHandle[] texHandles)
+ {
+ var gBuffer = GL.GenFramebuffer();
+ GL.BindFramebuffer(GLEnum.Framebuffer, gBuffer);
+
+ int depthCnt = 0;
+
+ var depthTexPos = (int)RenderTargetTextureTypes.Depth;
+
+ if (!renderTarget.IsDepthOnly)
+ {
+ var attachments = new List();
+
+ //Textures
+ for (int i = 0; i < texHandles.Length; i++)
+ {
+ attachments.Add(GLEnum.ColorAttachment0 + i);
+
+ var texHandle = texHandles[i];
+ if (texHandle == null) continue;
+
+ if (i == depthTexPos)
+ {
+ GL.FramebufferTexture2D(GLEnum.Framebuffer, GLEnum.DepthAttachment + (depthCnt), TextureTarget.Texture2D, (uint)((TextureHandle)texHandle).TexHandle, 0);
+ depthCnt++;
+ }
+ else
+ GL.FramebufferTexture2D(GLEnum.Framebuffer, GLEnum.ColorAttachment0 + (i - depthCnt), TextureTarget.Texture2D, (uint)((TextureHandle)texHandle).TexHandle, 0);
+ }
+ GL.DrawBuffers((uint)attachments.Count, attachments.ToArray());
+ }
+ else //If a frame-buffer only has a depth texture we don't need draw buffers
+ {
+ var texHandle = texHandles[depthTexPos];
+
+ if (texHandle != null)
+ GL.FramebufferTexture2D(GLEnum.Framebuffer, GLEnum.DepthAttachment, TextureTarget.Texture2D, (uint)((TextureHandle)texHandle).TexHandle, 0);
+ else
+ throw new NullReferenceException("Texture handle is null!");
+
+ GL.DrawBuffer(DrawBufferMode.None);
+ GL.ReadBuffer(ReadBufferMode.None);
+ }
+
+ return (int)gBuffer;
+ }
+
+ ///
+ /// Detaches a texture from the frame buffer object, associated with the given render target.
+ ///
+ /// The render target.
+ /// Number of the fbo attachment. For example: attachment = 1 will detach the texture currently associated with .
+ /// Determines if the texture is a depth texture. In this case the texture currently associated with will be detached.
+ public void DetachTextureFromFbo(IRenderTarget renderTarget, bool isDepthTex, int attachment = 0)
+ {
+ ChangeFramebufferTexture2D(renderTarget, attachment, 0, isDepthTex);
+ }
+
+
+ ///
+ /// Attaches a texture to the frame buffer object, associated with the given render target.
+ ///
+ /// The render target.
+ /// Number of the fbo attachment. For example: attachment = 1 will attach the texture to .
+ /// Determines if the texture is a depth texture. In this case the texture is attached to .
+ /// The gpu handle of the texture.
+ public void AttacheTextureToFbo(IRenderTarget renderTarget, bool isDepthTex, ITextureHandle texHandle, int attachment = 0)
+ {
+ ChangeFramebufferTexture2D(renderTarget, attachment, ((TextureHandle)texHandle).TexHandle, isDepthTex);
+ }
+
+ private void ChangeFramebufferTexture2D(IRenderTarget renderTarget, int attachment, int handle, bool isDepth)
+ {
+ var boundFbo = GL.GetInteger(GLEnum.FramebufferBinding);
+ var rtFbo = ((FrameBufferHandle)renderTarget.GBufferHandle).Handle;
+
+ var isCurrentFbo = true;
+
+ if (boundFbo != rtFbo)
+ {
+ isCurrentFbo = false;
+ GL.BindFramebuffer(GLEnum.Framebuffer, (uint)rtFbo);
+ }
+
+ if (!isDepth)
+ GL.FramebufferTexture2D(GLEnum.Framebuffer, GLEnum.ColorAttachment0 + attachment, TextureTarget.Texture2D, (uint)handle, 0);
+ else
+ GL.FramebufferTexture2D(GLEnum.Framebuffer, GLEnum.DepthAttachment, TextureTarget.Texture2D, (uint)handle, 0);
+
+ if (GL.CheckFramebufferStatus(GLEnum.Framebuffer) != GLEnum.FramebufferComplete)
+ throw new Exception($"Error creating RenderTarget: {GL.GetError()}, {GL.CheckFramebufferStatus(GLEnum.Framebuffer)}");
+
+ if (!isCurrentFbo)
+ GL.BindFramebuffer(GLEnum.Framebuffer, (uint)boundFbo);
+ }
+
+
+ ///
+ /// Only pixels that lie within the scissor box can be modified by drawing commands.
+ /// Note that the Scissor test must be enabled for this to work.
+ ///
+ /// X Coordinate of the lower left point of the scissor box.
+ /// Y Coordinate of the lower left point of the scissor box.
+ /// Width of the scissor box.
+ /// Height of the scissor box.
+ public void Scissor(int x, int y, int width, int height)
+ {
+ GL.Scissor(x, y, (uint)width, (uint)height);
+ }
+
+ ///
+ /// Set the Viewport of the rendering output window by x,y position and width,height parameters.
+ /// The Viewport is the portion of the final image window.
+ ///
+ /// The x.
+ /// The y.
+ /// The width.
+ /// The height.
+ public void Viewport(int x, int y, int width, int height)
+ {
+ GL.Viewport(x, y, (uint)width, (uint)height);
+ }
+
+ ///
+ /// Enable or disable Color channels to be written to the frame buffer (final image).
+ /// Use this function as a color channel filter for the final image.
+ ///
+ /// if set to true [red].
+ /// if set to true [green].
+ /// if set to true [blue].
+ /// if set to true [alpha].
+ public void ColorMask(bool red, bool green, bool blue, bool alpha)
+ {
+ GL.ColorMask(red, green, blue, alpha);
+ }
+
+ ///
+ /// Returns the capabilities of the underlying graphics hardware
+ ///
+ ///
+ /// uint
+ public uint GetHardwareCapabilities(HardwareCapability capability)
+ {
+ return capability switch
+ {
+ HardwareCapability.CanRenderDeferred => 1U, //!GL.GetString(StringName.Extensions).Contains("EXT_framebuffer_object") ? 0U : 1U,
+ HardwareCapability.CanUseGeometryShaders => 1U,
+ _ => throw new ArgumentOutOfRangeException(nameof(capability), capability, null),
+ };
+ }
+
+ ///
+ /// Returns a human readable description of the underlying graphics hardware. This implementation reports GL_VENDOR, GL_RENDERER, GL_VERSION and GL_EXTENSIONS.
+ ///
+ ///
+ public string GetHardwareDescription()
+ {
+ return "";
+ //return "Vendor: " + GL.GetString(StringName.Vendor) + "\nRenderer: " + GL.GetString(StringName.Renderer) + "\nVersion: " + GL.GetString(StringName.Version) + "\nExtensions: " + GL.GetString(StringName.Extensions);
+ }
+
+ ///
+ /// Draws a Debug Line in 3D Space by using a start and end point (float3).
+ ///
+ /// The starting point of the DebugLine.
+ /// The endpoint of the DebugLine.
+ /// The color of the DebugLine.
+ public void DebugLine(float3 start, float3 end, float4 color)
+ {
+ //GL.Begin(GLEnum.Lines);
+ //GL.Color4(color.x, color.y, color.z, color.w);
+ //GL.Vertex3(start.x, start.y, start.z);
+ //GL.Color4(color.x, color.y, color.z, color.w);
+ //GL.Vertex3(end.x, end.y, end.z);
+ //GL.End();
+ }
+
+ #endregion
+
+ #region Shader Storage Buffer
+
+ ///
+ /// Connects the given SSBO to the currently active shader program.
+ ///
+ /// The handle of the current shader program.
+ /// The Storage Buffer object on the CPU.
+ /// The SSBO's name.
+ public void ConnectBufferToShaderStorage(IShaderHandle currentProgram, IStorageBuffer buffer, string ssboName)
+ {
+ var shaderProgram = (uint)((ShaderHandleImp)currentProgram).Handle;
+ var resInx = GL.GetProgramResourceIndex(shaderProgram, ProgramInterface.ShaderStorageBlock, ssboName);
+ GL.ShaderStorageBlockBinding(shaderProgram, resInx, (uint)buffer.BindingIndex);
+ GL.BindBufferBase(GLEnum.ShaderStorageBuffer, (uint)buffer.BindingIndex, (uint)((StorageBufferHandle)buffer.BufferHandle).Handle);
+ }
+
+ ///
+ /// Uploads the given data to the SSBO. If the buffer is not created on the GPU by no it will be.
+ ///
+ /// The data type.
+ /// The Storage Buffer Object on the CPU.
+ /// The data that will be uploaded.
+ public unsafe void StorageBufferSetData(IStorageBuffer storageBuffer, T[] data) where T : struct
+ {
+ throw new NotImplementedException();
+
+ if (storageBuffer.BufferHandle == null)
+ storageBuffer.BufferHandle = new StorageBufferHandle();
+ var bufferHandle = (StorageBufferHandle)storageBuffer.BufferHandle;
+ int dataBytes = storageBuffer.Count * storageBuffer.Size;
+
+ //1. Generate Buffer and or set the data
+ if (bufferHandle.Handle == -1)
+ {
+ bufferHandle.Handle = (int)GL.GenBuffer();
+ }
+
+ if (data == null || data.Length == 0)
+ {
+ throw new ArgumentException("Data must not be null or empty");
+ }
+
+
+ GL.BindBuffer(GLEnum.ShaderStorageBuffer, (uint)bufferHandle.Handle);
+ if (data != null && data.GetType().IsValueType)
+ {
+ // GL.BufferData(GLEnum.ShaderStorageBuffer, (nuint)dataBytes, in data, GLEnum.DynamicCopy);
+ }
+
+ GL.GetBufferParameter(GLEnum.ShaderStorageBuffer, GLEnum.BufferSize, out int bufferBytes);
+ if (bufferBytes != dataBytes)
+ throw new ApplicationException(string.Format("Problem uploading bone indices buffer to SSBO. Tried to upload {0} bytes, uploaded {1}.", bufferBytes, dataBytes));
+
+ GL.BindBuffer(GLEnum.ShaderStorageBuffer, 0);
+ }
+
+ ///
+ /// Deletes the shader storage buffer on the GPU.
+ ///
+ /// The buffer object.
+ public void DeleteStorageBuffer(IBufferHandle storageBufferHandle)
+ {
+ GL.DeleteBuffer((uint)((StorageBufferHandle)storageBufferHandle).Handle);
+ }
+
+ #endregion
+
+ #region Picking related Members
+
+ ///
+ /// Retrieves a sub-image of the given region.
+ ///
+ /// The x value of the start of the region.
+ /// The y value of the start of the region.
+ /// The width to copy.
+ /// The height to copy.
+ /// The specified sub-image
+ public IImageData GetPixelColor(int x, int y, int w = 1, int h = 1)
+ {
+ ImageData image = ImageData.CreateImage(w, h, ColorUint.Black);
+ GL.ReadPixels(x, y, (uint)w, (uint)h, GLEnum.Rgb, GLEnum.UnsignedByte, new Span(image.PixelData));
+ return image;
+ }
+
+ ///
+ /// Retrieves the Z-value at the given pixel position.
+ ///
+ /// The x value.
+ /// The y value.
+ /// The Z value at (x, y).
+ public float GetPixelDepth(int x, int y)
+ {
+ GL.ReadPixels(x, y, 1, 1, GLEnum.DepthComponent, GLEnum.UnsignedByte, out float depth);
+ return depth;
+ }
+
+
+
+ #endregion
+ }
+}
\ No newline at end of file
diff --git a/src/Engine/Imp/Graphics/Silk/TexturePixelInfo.cs b/src/Engine/Imp/Graphics/Silk/TexturePixelInfo.cs
new file mode 100644
index 000000000..694a52f48
--- /dev/null
+++ b/src/Engine/Imp/Graphics/Silk/TexturePixelInfo.cs
@@ -0,0 +1,14 @@
+using Fusee.Engine.Common;
+using Silk.NET.OpenGL;
+
+namespace Fusee.Engine.Imp.Graphics.SilkDesktop
+{
+ /// Type that sums up infos about the pixels OpenGl needs to create textures on the gpu.
+ internal struct TexturePixelInfo : ITexturePixelInfo
+ {
+ public GLEnum InternalFormat;
+ public PixelFormat Format;
+ public PixelType PxType;
+ public int RowAlignment;
+ }
+}
\ No newline at end of file
diff --git a/src/Engine/Imp/Graphics/Silk/WindowHandle.cs b/src/Engine/Imp/Graphics/Silk/WindowHandle.cs
new file mode 100644
index 000000000..2ecb1f2e5
--- /dev/null
+++ b/src/Engine/Imp/Graphics/Silk/WindowHandle.cs
@@ -0,0 +1,16 @@
+using Fusee.Engine.Common;
+using System;
+
+namespace Fusee.Engine.Imp.Graphics.SilkDesktop
+{
+ ///
+ /// Implementation of the cross-platform abstraction of the window handle.
+ ///
+ public class WindowHandle : IWindowHandle
+ {
+ ///
+ /// The Window Handle as IntPtr
+ ///
+ public IntPtr Handle { get; internal set; }
+ }
+}
\ No newline at end of file
diff --git a/src/Engine/Imp/Graphics/Silk/WindowsTouchDeviceImp.cs b/src/Engine/Imp/Graphics/Silk/WindowsTouchDeviceImp.cs
new file mode 100644
index 000000000..ad1884b48
--- /dev/null
+++ b/src/Engine/Imp/Graphics/Silk/WindowsTouchDeviceImp.cs
@@ -0,0 +1,629 @@
+using Fusee.Engine.Common;
+using Silk.NET.Windowing;
+using System;
+using System.Collections.Generic;
+using System.Runtime.InteropServices;
+
+namespace Fusee.Engine.Imp.Graphics.SilkDesktop
+{
+
+ ///
+ /// Input driver implementation supporting Windows 8 touch input as described in
+ /// https://msdn.microsoft.com/en-us/library/windows/desktop/hh454904(v=vs.85).aspx
+ ///
+ public class WindowsTouchInputDriverImp : IInputDriverImp
+ {
+ readonly IWindow _gameWindow;
+ readonly WindowsTouchInputDeviceImp _touch;
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The render canvas. Internally this must be a Windows canvas with a valid window handle.
+ ///
+ ///
+ /// RenderCanvas must be of type
+ public WindowsTouchInputDriverImp(IRenderCanvasImp renderCanvas)
+ {
+ if (renderCanvas == null)
+ throw new ArgumentNullException(nameof(renderCanvas));
+
+ if (!(renderCanvas is RenderCanvasImp))
+ throw new ArgumentException($"renderCanvas must be of type {typeof(RenderCanvasImp).FullName}", nameof(renderCanvas));
+
+ _gameWindow = ((RenderCanvasImp)renderCanvas)._gameWindow.window;
+ if (_gameWindow == null)
+ throw new ArgumentNullException(nameof(_gameWindow));
+
+
+ _touch = new WindowsTouchInputDeviceImp(_gameWindow);
+ }
+
+ ///
+ /// Retrieves a list of devices supported by this input driver.
+ ///
+ ///
+ /// The list of devices.
+ ///
+ ///
+ /// The devices yielded represent the current status. At any time other devices can connect or disconnect.
+ /// Listen to the and events to get
+ /// informed about new or vanishing devices. Drivers may implement "static" access to devices such that
+ /// devices are connected at driver instantiation and never disconnected (in this case
+ /// and are never fired).
+ ///
+ public IEnumerable Devices { get { yield return _touch; } }
+
+ ///
+ /// Gets the unique driver identifier.
+ ///
+ ///
+ /// The driver identifier.
+ ///
+ public string DriverId => GetType().FullName;
+
+ ///
+ /// Gets the driver description string.
+ ///
+ ///
+ /// A human-readable string describing the driver.
+ ///
+ public string DriverDesc => "Driver providing a touch device implementation for Windows 8 (and up) touch input.";
+
+#pragma warning disable 0067
+ ///
+ /// Not supported on this driver. Mouse and keyboard are considered to be connected all the time.
+ /// You can register handlers but they will never get called.
+ ///
+ public event EventHandler DeviceDisconnected;
+
+ ///
+ /// Not supported on this driver. Mouse and keyboard are considered to be connected all the time.
+ /// You can register handlers but they will never get called.
+ ///
+ public event EventHandler NewDeviceConnected;
+#pragma warning restore 0067
+
+
+ #region IDisposable Support
+ private bool disposedValue = false; // To detect redundant calls
+
+ ///
+ /// Part of the Dispose pattern.
+ ///
+ ///
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!disposedValue)
+ {
+ if (disposing)
+ {
+ // TODO: dispose managed state (managed objects).
+ }
+
+ // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
+ // TODO: set large fields to null.
+
+ disposedValue = true;
+ }
+ }
+
+ // TODO: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources.
+ // ~RenderCanvasInputDriverImp() {
+ // // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
+ // Dispose(false);
+ // }
+
+ // This code added to correctly implement the disposable pattern.
+ ///
+ /// Part of the dispose pattern.
+ ///
+ public void Dispose()
+ {
+ // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
+ Dispose(true);
+ // TODO: uncomment the following line if the finalizer is overridden above.
+ // GC.SuppressFinalize(this);
+ }
+ #endregion
+ }
+
+ ///
+ /// Touch input device implementation for the Windows platform. This implementation directly
+ /// sniffles at the render window's message pump (identified by the parameter passed
+ /// to the constructor) to receive
+ /// WM_POINTER messages.
+ ///
+ public class WindowsTouchInputDeviceImp : IInputDeviceImp
+ {
+ private readonly Dictionary _tpAxisDescs;
+ private readonly Dictionary _tpButtonDescs;
+ private readonly Dictionary _activeTouchpoints;
+ private readonly int _nTouchPointsSupported = 5;
+ private readonly HandleRef _handle;
+ private readonly IWindow _gameWindow;
+
+
+ #region Windows handling
+ // This helper static method is required because the 32-bit version of user32.dll does not contain this API
+ // (on any versions of Windows), so linking the method will fail at run-time. The bridge dispatches the request
+ // to the correct function (GetWindowLong in 32-bit mode and GetWindowLongPtr in 64-bit mode)
+ private static IntPtr SetWindowLongPtr(HandleRef hWnd, int nIndex, IntPtr dwNewLong)
+ {
+ if (IntPtr.Size == 8)
+ return SetWindowLongPtr64(hWnd, nIndex, dwNewLong);
+ else
+ return new IntPtr(SetWindowLong32(hWnd, nIndex, dwNewLong.ToInt32()));
+ }
+
+ [DllImport("user32.dll", EntryPoint = "SetWindowLong")]
+ private static extern int SetWindowLong32(HandleRef hWnd, int nIndex, int dwNewLong);
+
+ [DllImport("user32.dll", EntryPoint = "SetWindowLongPtr")]
+ private static extern IntPtr SetWindowLongPtr64(HandleRef hWnd, int nIndex, IntPtr dwNewLong);
+
+ private readonly int GWLP_WNDPROC = -4;
+
+ private enum WinMessage : int
+ {
+ WM_CLOSE = 0x0010,
+ WM_NCPOINTERUP = 0x0243,
+ WM_POINTERUPDATE = 0x0245,
+ WM_POINTERDOWN = 0x0246,
+ WM_POINTERUP = 0x0247,
+ }
+
+ // As defined in
+ [Flags]
+ private enum PointerMsgFlags : uint
+ {
+ // ReSharper disable InconsistentNaming
+ POINTER_MESSAGE_FLAG_NEW = 0x00000001, // New pointer
+ POINTER_MESSAGE_FLAG_INRANGE = 0x00000002, // Pointer has not departed
+ POINTER_MESSAGE_FLAG_INCONTACT = 0x00000004, // Pointer is in contact
+ POINTER_MESSAGE_FLAG_FIRSTBUTTON = 0x00000010, // Primary action
+ POINTER_MESSAGE_FLAG_SECONDBUTTON = 0x00000020, // Secondary action
+ POINTER_MESSAGE_FLAG_THIRDBUTTON = 0x00000040, // Third button
+ POINTER_MESSAGE_FLAG_FOURTHBUTTON = 0x00000080, // Fourth button
+ POINTER_MESSAGE_FLAG_FIFTHBUTTON = 0x00000100, // Fifth button
+ POINTER_MESSAGE_FLAG_PRIMARY = 0x00002000, // Pointer is primary
+ POINTER_MESSAGE_FLAG_CONFIDENCE = 0x00004000, // Pointer is considered unlikely to be accidental
+ POINTER_MESSAGE_FLAG_CANCELED = 0x00008000, // Pointer is departing in an abnormal manner
+ // ReSharper enable InconsistentNaming
+ }
+
+ private UInt16 LOWORD(UInt32 wParam) => unchecked((UInt16)wParam);
+ private UInt16 HIWORD(UInt32 wParam) => unchecked((UInt16)((wParam >> 16) & 0xFFFF));
+
+ private UInt16 GET_X_LPARAM(UInt32 lp) => LOWORD(lp);
+ private UInt16 GET_Y_LPARAM(UInt32 lp) => HIWORD(lp);
+
+ private int GET_POINTERID_WPARAM(UInt32 wParam) => LOWORD(wParam);
+
+
+ [DllImport("user32.dll")]
+ private static extern IntPtr DefWindowProc(IntPtr hWnd, int uMsg, IntPtr wParam, IntPtr lParam);
+
+ [DllImport("user32.dll")]
+ // private static extern IntPtr CallWindowProc(IntPtr lpPrevWndFunc, IntPtr hWnd, int Msg, int wParam, IntPtr lParam);
+ static extern IntPtr CallWindowProc(IntPtr lpPrevWndFunc, IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
+
+ [DllImport("user32.dll")]
+ private static extern IntPtr EnableMouseInPointer(bool fEnable);
+ ///
+ /// The touch point.
+ ///
+
+ [StructLayout(LayoutKind.Sequential)]
+ public struct POINT
+ {
+ ///
+ /// The X coordinate of the touch point.
+ ///
+ public int X;
+
+ ///
+ /// The Y coordinate of the touch point.
+ ///
+ public int Y;
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The x coordinate.
+ /// The y coordinate.
+ public POINT(int x, int y)
+ {
+ this.X = x;
+ this.Y = y;
+ }
+
+ ///
+ /// Initializes a new instance of the struct from a given .
+ ///
+ /// The to create the instance from.
+ public POINT(System.Drawing.Point pt) : this(pt.X, pt.Y) { }
+
+ ///
+ /// Converts a to a .
+ ///
+ /// The to convert.
+ public static implicit operator System.Drawing.Point(POINT p)
+ {
+ return new System.Drawing.Point(p.X, p.Y);
+ }
+
+ ///
+ /// Converts a to a .
+ ///
+ /// The to convert.
+ public static implicit operator POINT(System.Drawing.Point p)
+ {
+ return new POINT(p.X, p.Y);
+ }
+ }
+
+ [DllImport("user32.dll")]
+ static extern bool ScreenToClient(IntPtr hWnd, ref POINT lpPoint);
+
+ private delegate IntPtr WinProc(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);
+
+ private WinProc _newWinProc;
+
+ private IntPtr _oldWndProc = IntPtr.Zero;
+
+
+ private void DisconnectWindowsEvents()
+ {
+ if (_handle.Handle != IntPtr.Zero)
+ {
+ SetWindowLongPtr(_handle, GWLP_WNDPROC, _oldWndProc);
+ }
+ }
+
+
+ private IntPtr TouchWindowProc(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam)
+ {
+ unchecked
+ {
+ POINT winPoint;
+ switch (Msg)
+ {
+ case (int)WinMessage.WM_CLOSE: // Seems to be defect.
+ DisconnectWindowsEvents();
+ break;
+ case (int)WinMessage.WM_POINTERUPDATE:
+ winPoint = new POINT(GET_X_LPARAM((uint)lParam), GET_Y_LPARAM((uint)lParam));
+ ScreenToClient(hWnd, ref winPoint);
+ OnWindowsTouchMove(GET_POINTERID_WPARAM((uint)wParam), winPoint.X, winPoint.Y);
+ return IntPtr.Zero;
+ case (int)WinMessage.WM_POINTERUP:
+ winPoint = new POINT(GET_X_LPARAM((uint)lParam), GET_Y_LPARAM((uint)lParam));
+ ScreenToClient(hWnd, ref winPoint);
+ OnWindowsTouchEnd(GET_POINTERID_WPARAM((uint)wParam), winPoint.X, winPoint.Y);
+ return IntPtr.Zero;
+ case (int)WinMessage.WM_POINTERDOWN:
+ winPoint = new POINT(GET_X_LPARAM((uint)lParam), GET_Y_LPARAM((uint)lParam));
+ ScreenToClient(hWnd, ref winPoint);
+ OnWindowsTouchStart(GET_POINTERID_WPARAM((uint)wParam), winPoint.X, winPoint.Y);
+ return IntPtr.Zero;
+ case (int)WinMessage.WM_NCPOINTERUP:
+ winPoint = new POINT(GET_X_LPARAM((uint)lParam), GET_Y_LPARAM((uint)lParam));
+ ScreenToClient(hWnd, ref winPoint);
+ OnWindowsTouchCancel(GET_POINTERID_WPARAM((uint)wParam), winPoint.X, winPoint.Y);
+ return IntPtr.Zero;
+ }
+ }
+ return CallWindowProc(_oldWndProc, hWnd, Msg, wParam, lParam);
+ }
+
+ private void ConnectWindowsEvents()
+ {
+ OperatingSystem os = Environment.OSVersion;
+
+ // See https://msdn.microsoft.com/library/windows/desktop/ms724832.aspx : Apps, that do NOT target a specific windows version (like 8.1 or 10)
+ // retrieve Version# 6.2 (resembling Windows 8), which is the version where "Pointer" touch handling is first supported.
+ if (os.Platform == PlatformID.Win32NT
+ && (os.Version.Major > 6
+ || os.Version.Major == 6 && os.Version.Minor >= 2)
+ )
+ {
+ EnableMouseInPointer(false);
+ if (_handle.Handle != IntPtr.Zero)
+ {
+ _newWinProc = new WinProc(TouchWindowProc);
+ _oldWndProc = SetWindowLongPtr(_handle, GWLP_WNDPROC, Marshal.GetFunctionPointerForDelegate(_newWinProc));
+ }
+ }
+ }
+
+ private float GetWindowWidth()
+ {
+ return _gameWindow.Size.X;
+ }
+
+ private float GetWindowHeight()
+ {
+ return _gameWindow.Size.Y;
+ }
+ #endregion
+
+ private int NextFreeTouchIndex
+ {
+ get
+ {
+ for (int i = 0; i < _nTouchPointsSupported; i++)
+ if (!_activeTouchpoints.ContainsValue(i))
+ return i;
+
+ return -1;
+ }
+ }
+
+
+ #region Windows Callbacks
+ internal void OnWindowsTouchStart(int id, float x, float y)
+ {
+ // Diagnostics.Log($"TouchStart {id}");
+ if (_activeTouchpoints.ContainsKey(id))
+ throw new InvalidOperationException($"Windows Touch id {id} is already tracked. Cannot track another touchpoint using this id.");
+
+ var inx = NextFreeTouchIndex;
+ if (inx < 0)
+ return;
+
+ _activeTouchpoints[id] = inx;
+ ButtonValueChanged?.Invoke(this, new ButtonValueChangedArgs { Button = _tpButtonDescs[(int)TouchPoints.Touchpoint_0 + inx].ButtonDesc, Pressed = true });
+ AxisValueChanged?.Invoke(this, new AxisValueChangedArgs { Axis = _tpAxisDescs[(int)TouchAxes.Touchpoint_0_X + 2 * inx].AxisDesc, Value = x });
+ AxisValueChanged?.Invoke(this, new AxisValueChangedArgs { Axis = _tpAxisDescs[(int)TouchAxes.Touchpoint_0_Y + 2 * inx].AxisDesc, Value = y });
+ }
+
+ internal void OnWindowsTouchMove(int id, float x, float y)
+ {
+ // Diagnostics.Log($"TouchMove {id}");
+ if (!_activeTouchpoints.TryGetValue(id, out int inx))
+ return;
+
+ AxisValueChanged?.Invoke(this, new AxisValueChangedArgs { Axis = _tpAxisDescs[(int)TouchAxes.Touchpoint_0_X + 2 * inx].AxisDesc, Value = x });
+ AxisValueChanged?.Invoke(this, new AxisValueChangedArgs { Axis = _tpAxisDescs[(int)TouchAxes.Touchpoint_0_Y + 2 * inx].AxisDesc, Value = y });
+ }
+ internal void OnWindowsTouchEnd(int id, float x, float y)
+ {
+ // Diagnostics.Log($"TouchEnd {id}");
+ if (!_activeTouchpoints.TryGetValue(id, out int inx))
+ return;
+
+ AxisValueChanged?.Invoke(this, new AxisValueChangedArgs { Axis = _tpAxisDescs[(int)TouchAxes.Touchpoint_0_X + 2 * inx].AxisDesc, Value = x });
+ AxisValueChanged?.Invoke(this, new AxisValueChangedArgs { Axis = _tpAxisDescs[(int)TouchAxes.Touchpoint_0_Y + 2 * inx].AxisDesc, Value = y });
+ ButtonValueChanged?.Invoke(this, new ButtonValueChangedArgs { Button = _tpButtonDescs[(int)TouchPoints.Touchpoint_0 + inx].ButtonDesc, Pressed = false });
+ _activeTouchpoints.Remove(id);
+ }
+ internal void OnWindowsTouchCancel(int id, float x, float y)
+ {
+ // Diagnostics.Log($"TouchCancel {id}");
+ if (!_activeTouchpoints.TryGetValue(id, out int inx))
+ return;
+ ButtonValueChanged?.Invoke(this, new ButtonValueChangedArgs { Button = _tpButtonDescs[(int)TouchPoints.Touchpoint_0 + inx].ButtonDesc, Pressed = false });
+ _activeTouchpoints.Remove(id);
+ }
+ #endregion
+
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The game window to hook on to receive
+ /// WM_POINTER messages.
+ public WindowsTouchInputDeviceImp(IWindow gameWindow)
+ {
+ _gameWindow = gameWindow;
+ _handle = new HandleRef(_gameWindow, _gameWindow.Handle);
+ ConnectWindowsEvents();
+ _tpAxisDescs = new Dictionary(_nTouchPointsSupported * 2 + 5);
+ _activeTouchpoints = new Dictionary(_nTouchPointsSupported);
+
+ _tpAxisDescs[(int)TouchAxes.ActiveTouchpoints] = new AxisImpDescription
+ {
+ AxisDesc = new AxisDescription
+ {
+ Name = $"Active Touchpoints",
+ Id = (int)TouchAxes.ActiveTouchpoints,
+ Direction = AxisDirection.Unknown,
+ Nature = AxisNature.Unknown,
+ Bounded = AxisBoundedType.Unbound
+ },
+ PollAxis = true
+ };
+ _tpAxisDescs[(int)TouchAxes.MinX] = new AxisImpDescription
+ {
+ AxisDesc = new AxisDescription
+ {
+ Name = "MinX",
+ Id = (int)TouchAxes.MinX,
+ Direction = AxisDirection.X,
+ Nature = AxisNature.Position,
+ Bounded = AxisBoundedType.Unbound,
+ MinValueOrAxis = float.NaN,
+ MaxValueOrAxis = float.NaN
+ },
+ PollAxis = true
+ };
+ _tpAxisDescs[(int)TouchAxes.MaxX] = new AxisImpDescription
+ {
+ AxisDesc = new AxisDescription
+ {
+ Name = "MaxX",
+ Id = (int)TouchAxes.MaxX,
+ Direction = AxisDirection.X,
+ Nature = AxisNature.Position,
+ Bounded = AxisBoundedType.Unbound,
+ MinValueOrAxis = float.NaN,
+ MaxValueOrAxis = float.NaN
+ },
+ PollAxis = true
+ };
+ _tpAxisDescs[(int)TouchAxes.MinY] = new AxisImpDescription
+ {
+ AxisDesc = new AxisDescription
+ {
+ Name = "MinY",
+ Id = (int)TouchAxes.MinY,
+ Direction = AxisDirection.Y,
+ Nature = AxisNature.Position,
+ Bounded = AxisBoundedType.Unbound,
+ MinValueOrAxis = float.NaN,
+ MaxValueOrAxis = float.NaN
+ },
+ PollAxis = true
+ };
+ _tpAxisDescs[(int)TouchAxes.MaxY] = new AxisImpDescription
+ {
+ AxisDesc = new AxisDescription
+ {
+ Name = "MaxY",
+ Id = (int)TouchAxes.MaxY,
+ Direction = AxisDirection.Y,
+ Nature = AxisNature.Position,
+ Bounded = AxisBoundedType.Unbound,
+ MinValueOrAxis = float.NaN,
+ MaxValueOrAxis = float.NaN
+ },
+ PollAxis = true
+ };
+
+ for (var i = 0; i < _nTouchPointsSupported; i++)
+ {
+ int id = 2 * i + (int)TouchAxes.Touchpoint_0_X;
+ _tpAxisDescs[id] = new AxisImpDescription
+ {
+ AxisDesc = new AxisDescription
+ {
+ Name = $"Touchpoint {id} X",
+ Id = id,
+ Direction = AxisDirection.X,
+ Nature = AxisNature.Position,
+ Bounded = AxisBoundedType.OtherAxis,
+ MinValueOrAxis = (int)TouchAxes.MinX,
+ MaxValueOrAxis = (int)TouchAxes.MaxX
+ },
+ PollAxis = false
+ };
+ id++;
+ _tpAxisDescs[id] = new AxisImpDescription
+ {
+ AxisDesc = new AxisDescription
+ {
+ Name = $"Touchpoint {id} Y",
+ Id = id,
+ Direction = AxisDirection.Y,
+ Nature = AxisNature.Position,
+ Bounded = AxisBoundedType.OtherAxis,
+ MinValueOrAxis = (int)TouchAxes.MinY,
+ MaxValueOrAxis = (int)TouchAxes.MaxY
+ },
+ PollAxis = false
+ };
+ }
+
+ _tpButtonDescs = new Dictionary(_nTouchPointsSupported);
+ for (var i = 0; i < _nTouchPointsSupported; i++)
+ {
+ int id = i + (int)TouchPoints.Touchpoint_0;
+ _tpButtonDescs[id] = new ButtonImpDescription
+ {
+ ButtonDesc = new ButtonDescription()
+ {
+ Name = $"Touchpoint {i} Active",
+ Id = id,
+ },
+ PollButton = false
+ };
+ }
+ }
+
+ ///
+ /// Short description string for this device to be used in dialogs.
+ ///
+ public string Desc => "MS Windows standard Touch device.";
+
+ ///
+ /// Returns a (hopefully) unique ID for this driver. Uniqueness is granted by using the
+ /// full class name (including namespace).
+ ///
+ public string Id => GetType().FullName;
+
+ ///
+ /// Occurs on value changes of axes exhibited by this device.
+ /// Only applies for axes where the is set to false.
+ ///
+ public event EventHandler AxisValueChanged;
+
+ /// A touchpoints's contact state is communicated by a button.
+ ///
+ public event EventHandler ButtonValueChanged;
+
+
+ ///
+ /// Returns , just because it's a touch device :-).
+ ///
+ public DeviceCategory Category => DeviceCategory.Touch;
+ ///
+ /// Returns the number of axes. Up to five touchpoints (with two axes (X and Y) per Touchpoint plus
+ /// one axis carrying the number of currently touched touchpoints plus four axes describing the minimum and
+ /// maximum X and Y values.
+ ///
+ ///
+ /// The axes count.
+ ///
+ public int AxesCount => _nTouchPointsSupported * 2 + 5;
+
+ ///
+ /// Returns description information for all axes.
+ ///
+ public IEnumerable AxisImpDesc => _tpAxisDescs.Values;
+
+ ///
+ /// Retrieves values for the number of currently active touchpoints and the touches min and max values. The touchpoint X and Y axes themselves are event-based axes.
+ /// Do not query them here.
+ ///
+ /// The axis to retrieve information for.
+ /// The value at the given axis.
+ public float GetAxis(int iAxisId)
+ {
+ return iAxisId switch
+ {
+ (int)TouchAxes.ActiveTouchpoints => _activeTouchpoints.Count,
+ (int)TouchAxes.MinX => 0,
+ (int)TouchAxes.MaxX => GetWindowWidth(),
+ (int)TouchAxes.MinY => 0,
+ (int)TouchAxes.MaxY => GetWindowHeight(),
+ _ => throw new InvalidOperationException($"Unknown axis {iAxisId}. Probably an event based axis or unsupported by this device."),
+ };
+ }
+
+ ///
+ /// Retrieves the button count. One button for each of the up to five supported touchpoints signaling that the touchpoint currently has contact.
+ ///
+ ///
+ /// The button count.
+ ///
+ public int ButtonCount => _nTouchPointsSupported;
+
+ ///
+ /// Retrieve a description for each button.
+ ///
+ ///
+ /// The button imp description.
+ ///
+ public IEnumerable ButtonImpDesc => _tpButtonDescs.Values;
+
+ ///
+ /// Gets the button state. This device's buttons signal that a finger (nose, elbow, knee...) currently has contact with the scree surface.
+ ///
+ /// The button identifier.
+ /// true if the button is hit, else false
+ public bool GetButton(int iButtonId)
+ {
+ throw new InvalidOperationException($"Unknown button id {iButtonId}. This device supports no pollable buttons at all.");
+ }
+ }
+}
\ No newline at end of file