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