diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..f0492fe --- /dev/null +++ b/.editorconfig @@ -0,0 +1,4 @@ +[*.cs] + +# IDE0090: Use 'new(...)' +csharp_style_implicit_object_creation_when_type_is_apparent = false diff --git a/GLWpfControl.sln b/GLWpfControl.sln index 9419d7b..d46765c 100644 --- a/GLWpfControl.sln +++ b/GLWpfControl.sln @@ -23,6 +23,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GLWpfControl", "src\GLWpfCo EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Example", "src\Example\Example.csproj", "{7E12D07B-0DD6-4288-A4F8-92EB75848ECF}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{441E95E4-0A78-4121-A68C-C778C8050489}" + ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU diff --git a/README.md b/README.md index f209166..ee156e1 100644 --- a/README.md +++ b/README.md @@ -7,11 +7,11 @@ Supported configurations: - .Net Framework for OpenTK 3.x (the 3.x series NuGet packages) - .Net Core for OpenTK 4.x (the 4.x series NuGet packages) -Since version 3.0.0, we're using full OpenGL/DirectX interop via OpenGL extensions - [NV_DX_interop](https://www.khronos.org/registry/OpenGL/extensions/NV/WGL_NV_DX_interop.txt). This should run almost everywhere with **AMAZING PERFORMANCE** and is fully supported on Intel, AMD and Nvidia graphics. +Since version 3.0.0, we're using full OpenGL/DirectX interop via OpenGL extensions - [NV_DX_interop](https://www.khronos.org/registry/OpenGL/extensions/NV/WGL_NV_DX_interop.txt). +This should run almost everywhere with **AMAZING PERFORMANCE** and is fully supported on Intel, AMD and Nvidia graphics. -This offers a way more clean solution than embedding GLControl and totally solves [the airspace problem](https://stackoverflow.com/questions/8006092/controls-dont-show-over-winforms-host). As controls can be layered, nested and structured over your 3D view. - -This package is intended to supercede the legacy *GLControl* completely, and we strongly encourage upgrading to this native WPF control instead. +This offers a way more clean solution than embedding GLControl and totally solves [the airspace problem](https://stackoverflow.com/questions/8006092/controls-dont-show-over-winforms-host). +As controls can be layered, nested and structured over your 3D view. ## Getting started: @@ -56,44 +56,14 @@ This package is intended to supercede the legacy *GLControl* completely, and we ``` For additional examples, see [MainWindow.xaml](https://github.com/opentk/GLWpfControl/blob/master/src/Example/MainWindow.xaml) and [MainWindow.xaml.cs](https://github.com/opentk/GLWpfControl/blob/master/src/Example/MainWindow.xaml.cs) in the example project. -### I'm having trouble with Keyboard and Mouse Input!? - -The current design has some issues based around polling for keyboard and mouse input due to the way the control was initially designed. - -If you want to handle keyboard input for the control when it is not focused, this is a feature we're currently looking into fixing. However, if you just want to handle keyboard input when the control has focus there's a little additional logic that must be implemented: - -1. Before calling `Start()` add the following to ensure that you can hook onto events via the control: - -```csharp -this.glWpfControl.RegisterToEventsDirectly = false; -this.glWpfControl.CanInvokeOnHandledEvents = false; +### I can't receive keyboard input when my control doesn't have keyboard focus! -this.glWpfControl.MouseDown += this.GlWpfControl_MouseDown; -this.glWpfControl.MouseEnter += this.GlWpfControl_MouseEnter; -this.glWpfControl.MouseLeave += this.GlWpfControl_MouseLeave; -``` +WPF by design only sends keyboard events to the control that has keybaord focus. To be able to get keyboard focus a control needs to have `Focusable==true` (this is the default for GLWpfControl) and `IsVisible==true`. -2. Next, in the mouse event handlers you want to call `Focus()` for the control: +If you however need to get keyboard events idependent of keyboard focus you will have to use the `Keyboard.AddPreview*` functions. +These functions allow you to register a preview event that is called before the control with keyboard focus gets the keyboard event. -```csharp - private void GlWpfControl_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e) - { - // When the mouse is leaving, lose focus so the keyboard cannot invoke events. - var scope = FocusManager.GetFocusScope(this.glWpfControl); - FocusManager.SetFocusedElement(scope, null); - System.Windows.Input.Keyboard.ClearFocus(); - } - - private void GlWpfControl_MouseEnter(object sender, System.Windows.Input.MouseEventArgs e) - { - this.glWpfControl.Focus(); - } - - private void GlWpfControl_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e) - { - this.glWpfControl.Focus(); - } -``` +See [Example](./src/Example/TabbedMainWindowTest.xaml.cs) for an example how to set this up. ## Build instructions @@ -115,4 +85,7 @@ this.glWpfControl.MouseLeave += this.GlWpfControl_MouseLeave; #### DX-Hijacking rendering -It's possible to bypass the RTT that takes place in WPF D3dImage by stealing the actual D3d handle from WPF and drawing manually. This is incredibly challenging, but would offer APEX performance as zero indirection is required. Currently more of an idea than a work in progress. Contributions welcome - Drop by the [Discord](https://discord.gg/6HqD48s) server if you want to give this a shot! +It's possible to bypass the RTT that takes place in WPF D3dImage by stealing the actual D3d handle from WPF and drawing manually. +This is incredibly challenging, but would offer APEX performance as zero indirection is required. +Currently more of an idea than a work in progress. +Contributions welcome - Drop by the [Discord](https://discord.gg/6HqD48s) server if you want to give this a shot! diff --git a/src/Example/Example.csproj b/src/Example/Example.csproj index 8171e80..0372812 100644 --- a/src/Example/Example.csproj +++ b/src/Example/Example.csproj @@ -1,18 +1,21 @@  + + WinExe + netcoreapp3.1 + true + false + Example.App + - - WinExe - netcoreapp3.1-windows - true - false - Example.App - + + + - - - + + + - - - + + + diff --git a/src/Example/ExampleScene.cs b/src/Example/ExampleScene.cs index 1f6c0b8..11f8182 100644 --- a/src/Example/ExampleScene.cs +++ b/src/Example/ExampleScene.cs @@ -1,40 +1,132 @@ using System; using System.Diagnostics; +using System.Runtime.CompilerServices; using OpenTK.Graphics.OpenGL; using OpenTK.Mathematics; namespace Example { /// Example class handling the rendering for OpenGL. - public static class ExampleScene { + public class ExampleScene { private static readonly Stopwatch _stopwatch = Stopwatch.StartNew(); - public static void Ready() { - Console.WriteLine("GlWpfControl is now ready"); - GL.Enable(EnableCap.Blend); - GL.Enable(EnableCap.DepthTest); - GL.Enable(EnableCap.ScissorTest); + private int Program; + + private int VAO; + private int VBO; + + struct Vertex + { + public Vector2 Position; + public Color4 Color; + + public Vertex(Vector2 position, Color4 color) + { + Position = position; + Color = color; + } + } + + private static readonly Vertex[] vertices = + { + new Vertex((0.0f, 0.5f), Color4.Red), + new Vertex((0.58f, -0.5f), Color4.Green), + new Vertex((-0.58f, -0.5f), Color4.Blue), + }; + + private static readonly string VertexShaderSource = +@"#version 330 core + +in vec2 vPosition; +in vec4 vColor; + +out vec4 fColor; + +void main() +{ + gl_Position = vec4(vPosition, 0, 1); + fColor = vColor; +} +"; + + private static readonly string FragmentShaderSource = +@"#version 330 core + +in vec4 fColor; + +out vec4 oColor; + +void main() +{ + oColor = fColor; +} +"; + + public void Initialize() + { + Program = GL.CreateProgram(); + + int vertexShader = GL.CreateShader(ShaderType.VertexShader); + GL.ShaderSource(vertexShader, VertexShaderSource); + GL.CompileShader(vertexShader); + GL.GetShader(vertexShader, ShaderParameter.CompileStatus, out int success); + if (success == 0) + { + string log = GL.GetShaderInfoLog(vertexShader); + Debug.WriteLine($"Vertex shader compile error: {log}"); + } + + int fragmentShader = GL.CreateShader(ShaderType.FragmentShader); + GL.ShaderSource(fragmentShader, FragmentShaderSource); + GL.CompileShader(fragmentShader); + GL.GetShader(fragmentShader, ShaderParameter.CompileStatus, out success); + if (success == 0) + { + string log = GL.GetShaderInfoLog(fragmentShader); + Debug.WriteLine($"Fragment shader compile error: {log}"); + } + + GL.AttachShader(Program, vertexShader); + GL.AttachShader(Program, fragmentShader); + GL.LinkProgram(Program); + GL.GetProgram(Program, GetProgramParameterName.LinkStatus, out success); + if (success == 0) + { + string log = GL.GetProgramInfoLog(Program); + Debug.WriteLine($"Program link error: {log}"); + } + + GL.DetachShader(Program, vertexShader); + GL.DetachShader(Program, fragmentShader); + + GL.DeleteShader(vertexShader); + GL.DeleteShader(fragmentShader); + + int positionLocation = GL.GetAttribLocation(Program, "vPosition"); + int colorLocation = GL.GetAttribLocation(Program, "vColor"); + + VAO = GL.GenVertexArray(); + GL.BindVertexArray(VAO); + + VBO = GL.GenBuffer(); + GL.BindBuffer(BufferTarget.ArrayBuffer, VBO); + GL.BufferData(BufferTarget.ArrayBuffer, vertices.Length * Unsafe.SizeOf(), vertices, BufferUsageHint.StaticDraw); + + GL.EnableVertexAttribArray(positionLocation); + GL.VertexAttribPointer(positionLocation, 2, VertexAttribPointerType.Float, false, Unsafe.SizeOf(), 0); + + GL.EnableVertexAttribArray(colorLocation); + GL.VertexAttribPointer(colorLocation, 4, VertexAttribPointerType.Float, false, Unsafe.SizeOf(), Unsafe.SizeOf()); } - public static void Render(float alpha = 1.0f) { + public void Render(float alpha = 1.0f) { var hue = (float) _stopwatch.Elapsed.TotalSeconds * 0.15f % 1; var c = Color4.FromHsv(new Vector4(alpha * hue, alpha * 0.75f, alpha * 0.75f, alpha)); GL.ClearColor(c); GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); - GL.LoadIdentity(); - GL.Begin(PrimitiveType.Triangles); - - GL.Color4(Color4.Red); - GL.Vertex2(0.0f, 0.5f); - - GL.Color4(Color4.Green); - GL.Vertex2(0.58f, -0.5f); - - GL.Color4(Color4.Blue); - GL.Vertex2(-0.58f, -0.5f); - GL.End(); - GL.Finish(); + GL.UseProgram(Program); + GL.DrawArrays(PrimitiveType.Triangles, 0, 3); } } } diff --git a/src/Example/MainWindow.xaml b/src/Example/MainWindow.xaml index 294eb18..1baa41a 100644 --- a/src/Example/MainWindow.xaml +++ b/src/Example/MainWindow.xaml @@ -15,8 +15,8 @@ Render="OpenTkControl_OnRender"> - + diff --git a/src/Example/MainWindow.xaml.cs b/src/Example/MainWindow.xaml.cs index 95721a3..0346265 100644 --- a/src/Example/MainWindow.xaml.cs +++ b/src/Example/MainWindow.xaml.cs @@ -7,11 +7,16 @@ namespace Example { /// Interaction logic for MainWindow.xaml /// public sealed partial class MainWindow { + + ExampleScene mainScene = new ExampleScene(); + ExampleScene insetScene = new ExampleScene(); + public MainWindow() { InitializeComponent(); // You can start and rely on the Settings property that may be set in XAML or elsewhere in the codebase. OpenTkControl.Start(); + mainScene.Initialize(); // Or, you can suppy a settings object directly. InsetControl.Start(new GLWpfControlSettings() @@ -20,14 +25,15 @@ public MainWindow() { MinorVersion = 1, RenderContinuously = false, }); + insetScene.Initialize(); } private void OpenTkControl_OnRender(TimeSpan delta) { - ExampleScene.Render(); + mainScene.Render(); } private void InsetControl_OnRender(TimeSpan delta) { - ExampleScene.Render(); + insetScene.Render(); } private void RedrawButton_OnClick(object sender, RoutedEventArgs e) { diff --git a/src/Example/TabbedMainWindowTest.xaml.cs b/src/Example/TabbedMainWindowTest.xaml.cs index 2408f3a..411291f 100644 --- a/src/Example/TabbedMainWindowTest.xaml.cs +++ b/src/Example/TabbedMainWindowTest.xaml.cs @@ -1,39 +1,65 @@ using System; +using System.Diagnostics; using System.Runtime.CompilerServices; +using System.Windows.Input; using OpenTK.Windowing.Common; using OpenTK.Wpf; -namespace Example { +namespace Example +{ /// - /// Interaction logic for TabbedMainWindowTest.xaml + /// Interaction logic for TabbedMainWindowTest.xaml /// public sealed partial class TabbedMainWindowTest { - public TabbedMainWindowTest() { + // FIXME: Make this example make use of context sharing... + ExampleScene scene1 = new ExampleScene(); + ExampleScene scene2 = new ExampleScene(); + ExampleScene scene3 = new ExampleScene(); + + public TabbedMainWindowTest() + { InitializeComponent(); - var mainSettings = new GLWpfControlSettings {MajorVersion = 4, MinorVersion = 5, GraphicsProfile = ContextProfile.Compatability, GraphicsContextFlags = ContextFlags.Debug}; + GLWpfControlSettings mainSettings = new GLWpfControlSettings {MajorVersion = 4, MinorVersion = 1, Profile = ContextProfile.Compatability, ContextFlags = ContextFlags.Debug}; Control1.Start(mainSettings); - var insetSettings = new GLWpfControlSettings {MajorVersion = 4, MinorVersion = 5, GraphicsProfile = ContextProfile.Compatability, GraphicsContextFlags = ContextFlags.Debug}; + scene1.Initialize(); + + GLWpfControlSettings insetSettings = new GLWpfControlSettings {MajorVersion = 4, MinorVersion = 1, Profile = ContextProfile.Compatability, ContextFlags = ContextFlags.Debug, Samples = 8}; Control2.Start(insetSettings); - var transparentSettings = new GLWpfControlSettings { MajorVersion = 4, MinorVersion = 5, GraphicsProfile = ContextProfile.Compatability, GraphicsContextFlags = ContextFlags.Debug, TransparentBackground = true}; + scene2.Initialize(); + + GLWpfControlSettings transparentSettings = new GLWpfControlSettings { MajorVersion = 4, MinorVersion = 1, Profile = ContextProfile.Compatability, ContextFlags = ContextFlags.Debug, TransparentBackground = true}; Control3.Start(transparentSettings); + scene3.Initialize(); + + Control1.KeyDown += Control1_KeyDown; + + Keyboard.AddPreviewKeyDownHandler(this, Keyboard_PreviewKeyDown); } - private void OpenTkControl_OnRender(TimeSpan delta) { - ExampleScene.Render(); + private void Keyboard_PreviewKeyDown(object sender, KeyEventArgs e) + { + Debug.WriteLine($"Preview key down: {e.Key}"); + } + + private void Control1_KeyDown(object sender, KeyEventArgs e) + { + Debug.WriteLine(e.Key); } - private void Control2_OnRender(TimeSpan delta) { - ExampleScene.Render(); + private void Control1_OnRender(TimeSpan delta) + { + scene1.Render(); } - private void Control1_OnRender(TimeSpan delta) { - ExampleScene.Render(); + private void Control2_OnRender(TimeSpan delta) + { + scene2.Render(); } private void Control3_OnRender(TimeSpan delta) { - ExampleScene.Render(0.0f); + scene3.Render(0.0f); } } } diff --git a/src/GLWpfControl/DXGLContext.cs b/src/GLWpfControl/DXGLContext.cs index c70892d..5a76204 100644 --- a/src/GLWpfControl/DXGLContext.cs +++ b/src/GLWpfControl/DXGLContext.cs @@ -1,53 +1,54 @@ using System; +using System.ComponentModel; +using System.Diagnostics; +using System.Runtime.InteropServices; using System.Threading; using System.Windows; using System.Windows.Interop; -using JetBrains.Annotations; +using OpenTK.Graphics.OpenGL; using OpenTK.Graphics.Wgl; using OpenTK.Windowing.Common; using OpenTK.Windowing.Desktop; using OpenTK.Windowing.GraphicsLibraryFramework; using OpenTK.Wpf.Interop; -using Window = System.Windows.Window; using WindowState = OpenTK.Windowing.Common.WindowState; -namespace OpenTK.Wpf { - +namespace OpenTK.Wpf +{ /// This contains the DirectX and OpenGL contexts used in this control. - internal sealed class DxGlContext : IDisposable { - - /// The directX context. This is basically the root of all DirectX state. - public IntPtr DxContextHandle { get; } - - /// The directX device handle. This is the graphics card we're running on. - public IntPtr DxDeviceHandle { get; } - - /// The OpenGL Context. This is basically the root of all OpenGL state. + internal sealed class DxGlContext : IDisposable + { + /// The DirectX context. This is basically the root of all DirectX state. + public DXInterop.IDirect3D9Ex DxContext { get; } + + /// The DirectX device handle. This is the graphics card we're running on. + public DXInterop.IDirect3DDevice9Ex DxDevice { get; } + + /// The OpenGL Context. This is basically the root of all OpenGL state. public IGraphicsContext GraphicsContext { get; } - - /// An OpenGL handle to the DirectX device. Created and used by the WGL_dx_interop extension. - public IntPtr GlDeviceHandle { get; } - /// The shared context we (may) want to lazily create/use. - private static IGraphicsContext _sharedContext; - private static GLWpfControlSettings _sharedContextSettings; - /// List of extra resources to dispose along with the shared context. - private static IDisposable[] _sharedContextResources; - /// The number of active controls using the shared context. - private static int _sharedContextReferenceCount; + /// An OpenGL handle to the DirectX device. Created and used by the WGL_dx_interop extension. + public IntPtr GLDeviceHandle { get; } + + /// The GLFW window that provides the OpenGL context. Null if a context was provided externally. + private NativeWindow? GlfwWindow { get; } +#if DEBUG + private readonly static DebugProc DebugProcCallback = Window_DebugProc; +#endif - public DxGlContext([NotNull] GLWpfControlSettings settings) { - DXInterop.Direct3DCreate9Ex(DXInterop.DefaultSdkVersion, out var dxContextHandle); - DxContextHandle = dxContextHandle; + public DxGlContext(GLWpfControlSettings settings) + { + DXInterop.Direct3DCreate9Ex(DXInterop.DefaultSdkVersion, out DXInterop.IDirect3D9Ex dxContext); + DxContext = dxContext; - var deviceParameters = new PresentationParameters + PresentationParameters deviceParameters = new PresentationParameters { Windowed = 1, SwapEffect = SwapEffect.Discard, DeviceWindowHandle = IntPtr.Zero, PresentationInterval = 0, - BackBufferFormat = Format.X8R8G8B8, // this is like A8 R8 G8 B8, but avoids issues with Gamma correction being applied twice. + BackBufferFormat = Format.X8R8G8B8, BackBufferWidth = 1, BackBufferHeight = 1, AutoDepthStencilFormat = Format.Unknown, @@ -56,89 +57,125 @@ public DxGlContext([NotNull] GLWpfControlSettings settings) { Flags = 0, FullScreen_RefreshRateInHz = 0, MultiSampleQuality = 0, - MultiSampleType = MultisampleType.None + MultiSampleType = MultisampleType.D3DMULTISAMPLE_NONE, }; - DXInterop.CreateDeviceEx( - dxContextHandle, + dxContext.CreateDeviceEx( 0, - DeviceType.HAL, // use hardware rasterization + DeviceType.HAL, IntPtr.Zero, - CreateFlags.HardwareVertexProcessing | - CreateFlags.Multithreaded | - CreateFlags.PureDevice, + CreateFlags.HardwareVertexProcessing | CreateFlags.Multithreaded | CreateFlags.PureDevice | CreateFlags.FpuPreserve, ref deviceParameters, IntPtr.Zero, - out var dxDeviceHandle); - DxDeviceHandle = dxDeviceHandle; + out DXInterop.IDirect3DDevice9Ex dxDevice); + + DxDevice = dxDevice; // if the graphics context is null, we use the shared context. if (settings.ContextToUse != null) { GraphicsContext = settings.ContextToUse; } else { - GraphicsContext = GetOrCreateSharedOpenGLContext(settings); - } - - GlDeviceHandle = Wgl.DXOpenDeviceNV(dxDeviceHandle); - } - - private static IGraphicsContext GetOrCreateSharedOpenGLContext(GLWpfControlSettings settings) { - if (_sharedContext != null) { - var isSameContext = GLWpfControlSettings.WouldResultInSameContext(settings, _sharedContextSettings); - if (!isSameContext) { - throw new ArgumentException($"The provided {nameof(GLWpfControlSettings)} would result " + - $"in a different context creation to one previously created. To fix this, " + - $"either ensure all of your context settings are identical, or provide an " + - $"external context via the '{nameof(GLWpfControlSettings.ContextToUse)}' field."); - } - } - - else { - var nws = NativeWindowSettings.Default; + NativeWindowSettings nws = NativeWindowSettings.Default; nws.StartFocused = false; nws.StartVisible = false; nws.NumberOfSamples = 0; - // if we ask GLFW for 1.0, we should get the highest level context available with full compat. + nws.SharedContext = settings.SharedContext; + // If we ask GLFW for 1.0, we should get the highest level context available with full compat. nws.APIVersion = new Version(settings.MajorVersion, settings.MinorVersion); - nws.Flags = ContextFlags.Offscreen | settings.GraphicsContextFlags; - // we have to ask for any compat in this case. - nws.Profile = settings.GraphicsProfile; + nws.Flags = ContextFlags.Offscreen | settings.ContextFlags; + // We have to ask for any compat in this case. + nws.Profile = settings.Profile; nws.WindowBorder = WindowBorder.Hidden; nws.WindowState = WindowState.Minimized; - var glfwWindow = new NativeWindow(nws); - var provider = settings.BindingsContext ?? new GLFWBindingsContext(); + GlfwWindow = new NativeWindow(nws); + GraphicsContext = GlfwWindow.Context; + GraphicsContext.MakeCurrent(); + + IBindingsContext provider = settings.BindingsContext ?? new GLFWBindingsContext(); Wgl.LoadBindings(provider); - // we're already in a window context, so we can just cheat by creating a new dependency object here rather than passing any around. - var depObject = new DependencyObject(); - // retrieve window handle/info - var window = Window.GetWindow(depObject); - var baseHandle = window is null ? IntPtr.Zero : new WindowInteropHelper(window).Handle; - var hwndSource = new HwndSource(0, 0, 0, 0, 0, "GLWpfControl", baseHandle); - - _sharedContext = glfwWindow.Context; - _sharedContextSettings = settings; - _sharedContextResources = new IDisposable[] {hwndSource, glfwWindow}; - // GL init - // var mode = new GraphicsMode(ColorFormat.Empty, 0, 0, 0, 0, 0, false); - // _commonContext = new GraphicsContext(mode, _windowInfo, _settings.MajorVersion, _settings.MinorVersion, - // _settings.GraphicsContextFlags); - // _commonContext.LoadAll(); - _sharedContext.MakeCurrent(); + +#if DEBUG + GL.DebugMessageCallback(DebugProcCallback, IntPtr.Zero); + GL.Enable(EnableCap.DebugOutput); + GL.Enable(EnableCap.DebugOutputSynchronous); +#endif } - Interlocked.Increment(ref _sharedContextReferenceCount); - return _sharedContext; + + GLDeviceHandle = Wgl.DXOpenDeviceNV(dxDevice.Handle); + if (GLDeviceHandle == IntPtr.Zero) + { + throw new Win32Exception(DXInterop.GetLastError()); + } + } + + ~DxGlContext() + { + Dispose(); + } + + public void Dispose() + { + if (Wgl.DXCloseDeviceNV(DxDevice.Handle) == false) + { + throw new Win32Exception(DXInterop.GetLastError()); + } + GlfwWindow?.Dispose(); + DxDevice.Release(); + DxContext.Release(); + + GC.SuppressFinalize(this); } - public void Dispose() { - // we only dispose of the graphics context if we're using the shared one. - if (ReferenceEquals(_sharedContext, GraphicsContext)) { - if (Interlocked.Decrement(ref _sharedContextReferenceCount) == 0) { - foreach (var resource in _sharedContextResources) { - resource.Dispose(); - } +#if DEBUG + private static void Window_DebugProc(DebugSource source, DebugType type, int id, DebugSeverity severity, int length, IntPtr messagePtr, IntPtr userParam) + { + string message = Marshal.PtrToStringAnsi(messagePtr, length); + + bool showMessage = true; + + switch (source) + { + case DebugSource.DebugSourceApplication: + showMessage = false; + break; + case DebugSource.DontCare: + case DebugSource.DebugSourceApi: + case DebugSource.DebugSourceWindowSystem: + case DebugSource.DebugSourceShaderCompiler: + case DebugSource.DebugSourceThirdParty: + case DebugSource.DebugSourceOther: + default: + showMessage = true; + break; + } + + if (showMessage) + { + switch (severity) + { + case DebugSeverity.DontCare: + Debug.Print($"[DontCare] {message}"); + break; + case DebugSeverity.DebugSeverityNotification: + //Debug.Print($"[Notification] [{source}] {message}"); + break; + case DebugSeverity.DebugSeverityHigh: + Debug.Print($"[Error] [{source}] {message}"); + //Debug.Break(); + break; + case DebugSeverity.DebugSeverityMedium: + Debug.Print($"[Warning] [{source}] {message}"); + break; + case DebugSeverity.DebugSeverityLow: + Debug.Print($"[Info] [{source}] {message}"); + break; + default: + Debug.Print($"[default] {message}"); + break; } } } +#endif } } diff --git a/src/GLWpfControl/DesignTimeHelper.cs b/src/GLWpfControl/DesignTimeHelper.cs deleted file mode 100644 index 4dcbd51..0000000 --- a/src/GLWpfControl/DesignTimeHelper.cs +++ /dev/null @@ -1,35 +0,0 @@ -using System; -using System.Globalization; -using System.Windows; -using System.Windows.Media; - -namespace OpenTK.Wpf { - internal static class DesignTimeHelper { - - public static void DrawDesignTimeHelper(GLWpfControl control, DrawingContext drawingContext) { - if (control.Visibility == Visibility.Visible && control.ActualWidth > 0 && control.ActualHeight > 0) { - const string labelText = "GL WPF CONTROL"; - var width = control.ActualWidth; - var height = control.ActualHeight; - var size = 1.5 * Math.Min(width, height) / labelText.Length; - var tf = new Typeface("Arial"); - #pragma warning disable 618 - var ft = new FormattedText(labelText, CultureInfo.InvariantCulture, FlowDirection.LeftToRight, tf, size, Brushes.White) { - TextAlignment = TextAlignment.Center - }; - #pragma warning restore 618 - var redPen = new Pen(Brushes.DarkBlue, 2.0); - var rect = new Rect(1, 1, width - 1, height - 1); - drawingContext.DrawRectangle(Brushes.Black, redPen, rect); - drawingContext.DrawLine(new Pen(Brushes.DarkBlue, 2.0), - new Point(0.0, 0.0), - new Point(control.ActualWidth, control.ActualHeight)); - drawingContext.DrawLine(new Pen(Brushes.DarkBlue, 2.0), - new Point(control.ActualWidth, 0.0), - new Point(0.0, control.ActualHeight)); - drawingContext.DrawText(ft, new Point(width / 2, (height - ft.Height) / 2)); - } - } - - } -} diff --git a/src/GLWpfControl/DxGLFramebuffer.cs b/src/GLWpfControl/DxGLFramebuffer.cs deleted file mode 100644 index 88ccdea..0000000 --- a/src/GLWpfControl/DxGLFramebuffer.cs +++ /dev/null @@ -1,131 +0,0 @@ -using System; -using System.Windows.Interop; -using System.Windows.Media; -using JetBrains.Annotations; -using OpenTK.Graphics.OpenGL; -using OpenTK.Graphics.Wgl; -using OpenTK.Platform.Windows; -using OpenTK.Wpf.Interop; - -namespace OpenTK.Wpf { - - /// Class containing the DirectX Render Surface and OpenGL Framebuffer Object - /// Instances of this class are created and deleted as required by the renderer. - /// Note that this does not implement the full pattern, - /// as OpenGL resources cannot be freed from the finalizer thread. - /// The calling class must correctly dispose of this by calling - /// Prior to releasing references. - internal sealed class DxGLFramebuffer : IDisposable { - - private DxGlContext DxGlContext { get; } - - /// The width of this buffer in pixels - public int FramebufferWidth { get; } - - /// The height of this buffer in pixels - public int FramebufferHeight { get; } - - /// The width of the element in device-independent pixels - public int Width { get; } - - /// The height of the element in device-independent pixels - public int Height { get; } - - /// The DirectX Render target (framebuffer) handle. - public IntPtr DxRenderTargetHandle { get; } - - /// The OpenGL Framebuffer handle - public int GLFramebufferHandle { get; } - - /// The OpenGL shared texture handle (with DX) - private int GLSharedTextureHandle { get; } - - /// The OpenGL depth render buffer handle. - private int GLDepthRenderBufferHandle { get; } - - /// Specific wgl_dx_interop handle that marks the framebuffer as ready for interop. - public IntPtr DxInteropRegisteredHandle { get; } - - - public D3DImage D3dImage { get; } - - public TranslateTransform TranslateTransform { get; } - public ScaleTransform FlipYTransform { get; } - - - public DxGLFramebuffer([NotNull] DxGlContext context, int width, int height, double dpiScaleX, double dpiScaleY, Format format) { - DxGlContext = context; - Width = width; - Height = height; - FramebufferWidth = (int)Math.Ceiling(width * dpiScaleX); - FramebufferHeight = (int)Math.Ceiling(height * dpiScaleY); - - var dxSharedHandle = IntPtr.Zero; // Unused windows-vista legacy sharing handle. Must always be null. - DXInterop.CreateRenderTarget( - context.DxDeviceHandle, - FramebufferWidth, - FramebufferHeight, - format, - MultisampleType.None, - 0, - false, - out var dxRenderTargetHandle, - ref dxSharedHandle); - - DxRenderTargetHandle = dxRenderTargetHandle; - - Wgl.DXSetResourceShareHandleNV(dxRenderTargetHandle, dxSharedHandle); - - GLFramebufferHandle = GL.GenFramebuffer(); - GLSharedTextureHandle = GL.GenTexture(); - - var genHandle = Wgl.DXRegisterObjectNV( - context.GlDeviceHandle, - dxRenderTargetHandle, - (uint)GLSharedTextureHandle, - (uint)TextureTarget.Texture2D, - WGL_NV_DX_interop.AccessReadWrite); - - DxInteropRegisteredHandle = genHandle; - - GL.BindFramebuffer(FramebufferTarget.Framebuffer, GLFramebufferHandle); - GL.FramebufferTexture2D( - FramebufferTarget.Framebuffer, - FramebufferAttachment.ColorAttachment0, - TextureTarget.Texture2D, - GLSharedTextureHandle, 0); - - GLDepthRenderBufferHandle = GL.GenRenderbuffer(); - GL.BindRenderbuffer(RenderbufferTarget.Renderbuffer, GLDepthRenderBufferHandle); - GL.RenderbufferStorage(RenderbufferTarget.Renderbuffer, RenderbufferStorage.Depth24Stencil8, FramebufferWidth, FramebufferHeight); - - GL.FramebufferRenderbuffer( - FramebufferTarget.Framebuffer, - FramebufferAttachment.DepthAttachment, - RenderbufferTarget.Renderbuffer, - GLDepthRenderBufferHandle); - GL.FramebufferRenderbuffer( - FramebufferTarget.Framebuffer, - FramebufferAttachment.StencilAttachment, - RenderbufferTarget.Renderbuffer, - GLDepthRenderBufferHandle); - - GL.BindFramebuffer(FramebufferTarget.Framebuffer, 0); - - - D3dImage = new D3DImage(96.0 * dpiScaleX, 96.0 * dpiScaleY); - - TranslateTransform = new TranslateTransform(0, height); - FlipYTransform = new ScaleTransform(1, -1); - } - - - public void Dispose() { - GL.DeleteFramebuffer(GLFramebufferHandle); - GL.DeleteRenderbuffer(GLDepthRenderBufferHandle); - GL.DeleteTexture(GLSharedTextureHandle); - Wgl.DXUnregisterObjectNV(DxGlContext.GlDeviceHandle, DxInteropRegisteredHandle); - DXInterop.Release(DxRenderTargetHandle); - } - } -} diff --git a/src/GLWpfControl/GLWpfControl.cs b/src/GLWpfControl/GLWpfControl.cs index 208f5c6..0fac0de 100644 --- a/src/GLWpfControl/GLWpfControl.cs +++ b/src/GLWpfControl/GLWpfControl.cs @@ -6,8 +6,9 @@ using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; -using JetBrains.Annotations; using OpenTK.Wpf.Interop; +using System.Windows.Interop; +using OpenTK.Windowing.Common; namespace OpenTK.Wpf { @@ -18,29 +19,31 @@ namespace OpenTK.Wpf /// /// Please do not extend this class. It has no support for that. /// - public class GLWpfControl : FrameworkElement + public class GLWpfControl : FrameworkElement, IDisposable { - // ----------------------------------- - // EVENTS - // ----------------------------------- + static GLWpfControl() + { + // Default to Focusable=true. + FocusableProperty.OverrideMetadata(typeof(GLWpfControl), new FrameworkPropertyMetadata(true)); + } + /// /// Called whenever rendering should occur. - public event Action Render; + /// + public event Action? Render; + /// /// Called once per frame after render. This does not synchronize with the copy to the screen. /// This is only for extremely advanced use, where a non-display out task needs to run. /// Examples of these are an async Pixel Buffer Object transfer or Transform Feedback. /// If you do not know what these are, do not use this function. - public event Action AsyncRender; + /// + public event Action? AsyncRender; /// /// Gets called after the control has finished initializing and is ready to render /// - public event Action Ready; - - // ----------------------------------- - // Fields - // ----------------------------------- + public event Action? Ready; /// /// Represents the dependency property for . @@ -48,17 +51,13 @@ public class GLWpfControl : FrameworkElement public static readonly DependencyProperty SettingsProperty = DependencyProperty.Register( "Settings", typeof(GLWpfControlSettings), typeof(GLWpfControl)); - [CanBeNull] private GLWpfControlRenderer _renderer; + private GLWpfControlRenderer? _renderer; /// /// Indicates whether the function has been invoked. /// private bool _isStarted; - // ----------------------------------- - // Properties - // ----------------------------------- - /// /// Gets or sets the settings used when initializing the control. /// @@ -74,29 +73,44 @@ public GLWpfControlSettings Settings /// The OpenGL Framebuffer Object used internally by this component. /// Bind to this instead of the default framebuffer when using this component along with other FrameBuffers for the final pass. /// If no framebuffer is available (because this control is not visible, etc etc, then it should be 0). + /// public int Framebuffer => _renderer?.FrameBufferHandle ?? 0; + /// /// If this control is rendering continuously. /// If this is false, then redrawing will only occur when is called. + /// public bool RenderContinuously { get => Settings.RenderContinuously; set => Settings.RenderContinuously = value; } + /// /// Pixel width of the underlying OpenGL framebuffer. /// It could differ from UIElement.RenderSize if UseDeviceDpi setting is set. /// To be used for operations related to OpenGL viewport calls (glViewport, glScissor, ...). + /// public int FrameBufferWidth => _renderer?.Width ?? 0; - + + /// /// Pixel height of the underlying OpenGL framebuffer. /// It could differ from UIElement.RenderSize if UseDeviceDpi setting is set. /// To be used for operations related to OpenGL viewport calls (glViewport, glScissor, ...). + /// public int FrameBufferHeight => _renderer?.Height ?? 0; - private TimeSpan _lastRenderTime = TimeSpan.FromSeconds(-1); - + /// + /// The currently used OpenGL context, or null if no OpenGL context is created. + /// + [CLSCompliant(false)] + public IGraphicsContext? Context => _renderer?.GLContext; + + private TimeSpan? _lastRenderTime = TimeSpan.FromSeconds(-1); + + [Obsolete("This property has no effect. See RegisterToEventsDirectly.")] public bool CanInvokeOnHandledEvents { get; set; } = true; - + + [Obsolete("If you want to receive keyboard events without having focus you can use EventManager.RegisterClassHandler yourself. The control is by default focusable and will get key events when focused. This property will have no effect.")] public bool RegisterToEventsDirectly { get; set; } = true; /// @@ -134,7 +148,7 @@ public void Start(GLWpfControlSettings settings) _isStarted = true; - Settings = settings.Copy(); + Settings = settings.Clone(); _renderer = new GLWpfControlRenderer(Settings); _renderer.GLRender += timeDelta => Render?.Invoke(timeDelta); _renderer.GLAsyncRender += () => AsyncRender?.Invoke(); @@ -147,73 +161,36 @@ public void Start(GLWpfControlSettings settings) } }; - // Inheriting directly from a FrameworkElement has issues with receiving certain events -- register for these events directly - if (RegisterToEventsDirectly) - { - EventManager.RegisterClassHandler(typeof(Control), Keyboard.KeyDownEvent, new KeyEventHandler(OnKeyDown), CanInvokeOnHandledEvents); - EventManager.RegisterClassHandler(typeof(Control), Keyboard.KeyUpEvent, new KeyEventHandler(OnKeyUp), CanInvokeOnHandledEvents); - } - - Loaded += (a, b) => { - InvalidateVisual(); - }; + Loaded += (a, b) => InvalidateVisual(); Unloaded += (a, b) => OnUnloaded(); + Ready?.Invoke(); } - private void SetupRenderSize() { - if (_renderer == null || Settings == null) { - return; - } - - var dpiScaleX = 1.0; - var dpiScaleY = 1.0; - - if (Settings.UseDeviceDpi) { - var presentationSource = PresentationSource.FromVisual(this); - // this can be null in the case of not having any visual on screen, such as a tabbed view. - if (presentationSource != null) { - Debug.Assert(presentationSource.CompositionTarget != null, "presentationSource.CompositionTarget != null"); - - var transformToDevice = presentationSource.CompositionTarget.TransformToDevice; - dpiScaleX = transformToDevice.M11; - dpiScaleY = transformToDevice.M22; - } - } - - var format = Settings.TransparentBackground ? Format.A8R8G8B8 : Format.X8R8G8B8; - _renderer?.SetSize((int) RenderSize.Width, (int) RenderSize.Height, dpiScaleX, dpiScaleY, format); - } - private void OnUnloaded() { - _renderer?.SetSize(0,0, 1, 1, Format.X8R8G8B8); + // FIXME: Make this a separate function for releasing resources... + // Currently this works as we are passing a zero width and height + // which causes the renderer to not reallocate the framebuffer + // after the previous one has been deleted. + // - Noggin_bops 2024-05-29 + _renderer?.ReallocateFramebufferIfNeeded(0, 0, 1, 1, Format.X8R8G8B8, MultisampleType.D3DMULTISAMPLE_NONE); } - // Raise the events so they're received if you subscribe to the base control's events - // There are others that should probably be sent -- focus doesn't seem to work for whatever reason - internal void OnKeyDown(object sender, KeyEventArgs e) - { - if (e.OriginalSource != this) - { - KeyEventArgs args = new KeyEventArgs(e.KeyboardDevice, e.InputSource, e.Timestamp, e.Key); - args.RoutedEvent = Keyboard.KeyDownEvent; - RaiseEvent(args); - } - } - internal void OnKeyUp(object sender, KeyEventArgs e) + /// + /// Disposes the native resources allocated by this control. + /// After this function has been called this control will no longer render anything + /// until has been called again. + /// + public void Dispose() { - if (e.OriginalSource != this) - { - KeyEventArgs args = new KeyEventArgs(e.KeyboardDevice, e.InputSource, e.Timestamp, e.Key); - args.RoutedEvent = Keyboard.KeyUpEvent; - RaiseEvent(args); - } + _renderer?.Dispose(); + _isStarted = false; } - private void OnCompTargetRender(object sender, EventArgs e) + private void OnCompTargetRender(object? sender, EventArgs e) { - var currentRenderTime = (e as RenderingEventArgs)?.RenderingTime; + TimeSpan? currentRenderTime = (e as RenderingEventArgs)?.RenderingTime; if(currentRenderTime == _lastRenderTime) { // It's possible for Rendering to call back twice in the same frame @@ -221,32 +198,68 @@ private void OnCompTargetRender(object sender, EventArgs e) // Reference: https://docs.microsoft.com/en-us/dotnet/desktop/wpf/advanced/walkthrough-hosting-direct3d9-content-in-wpf?view=netframeworkdesktop-4.8#to-import-direct3d9-content return; } - - _lastRenderTime = currentRenderTime.Value; + + _lastRenderTime = currentRenderTime; if (RenderContinuously) InvalidateVisual(); } - protected override void OnRender(DrawingContext drawingContext) { - var isDesignMode = DesignerProperties.GetIsInDesignMode(this); + protected override void OnRender(DrawingContext drawingContext) + { + base.OnRender(drawingContext); + + bool isDesignMode = DesignerProperties.GetIsInDesignMode(this); if (isDesignMode) { - DesignTimeHelper.DrawDesignTimeHelper(this, drawingContext); + DrawDesignTimeHelper(this, drawingContext); } - else if(_renderer != null) { - SetupRenderSize(); - _renderer?.Render(drawingContext); + else if (_renderer != null) + { + if (Settings != null) + { + double dpiScaleX = 1.0; + double dpiScaleY = 1.0; + + if (Settings.UseDeviceDpi) + { + PresentationSource presentationSource = PresentationSource.FromVisual(this); + // this can be null in the case of not having any visual on screen, such as a tabbed view. + if (presentationSource != null) + { + Debug.Assert(presentationSource.CompositionTarget != null, "presentationSource.CompositionTarget != null"); + + Matrix transformToDevice = presentationSource.CompositionTarget.TransformToDevice; + dpiScaleX = transformToDevice.M11; + dpiScaleY = transformToDevice.M22; + } + } + + Format format = Settings.TransparentBackground ? Format.A8R8G8B8 : Format.X8R8G8B8; + + MultisampleType msaaType = MultisampleType.D3DMULTISAMPLE_NONE; + // 2 to 16 are valid msaa values, clamp to 16. + if (Settings.Samples >= 2 && Settings.Samples <= 16) + msaaType = MultisampleType.D3DMULTISAMPLE_NONE + Settings.Samples; + else if (Settings.Samples > 16) + msaaType = MultisampleType.D3DMULTISAMPLE_16_SAMPLES; + + _renderer.ReallocateFramebufferIfNeeded(RenderSize.Width, RenderSize.Height, dpiScaleX, dpiScaleY, format, msaaType); + } + + _renderer.Render(drawingContext); } - else { - UnstartedControlHelper.DrawUnstartedControlHelper(this, drawingContext); + else + { + DrawUnstartedControlHelper(this, drawingContext); } - - base.OnRender(drawingContext); } protected override void OnRenderSizeChanged(SizeChangedInfo info) { - var isInDesignMode = DesignerProperties.GetIsInDesignMode(this); - if (isInDesignMode) { + base.OnRenderSizeChanged(info); + + bool isInDesignMode = DesignerProperties.GetIsInDesignMode(this); + if (isInDesignMode) + { return; } @@ -254,8 +267,61 @@ protected override void OnRenderSizeChanged(SizeChangedInfo info) { InvalidateVisual(); } + } - base.OnRenderSizeChanged(info); + internal static void DrawDesignTimeHelper(GLWpfControl control, DrawingContext drawingContext) + { + if (control.Visibility == Visibility.Visible && control.ActualWidth > 0 && control.ActualHeight > 0) + { + const string LabelText = "GL WPF CONTROL"; + double width = control.ActualWidth; + double height = control.ActualHeight; + double size = 1.5 * Math.Min(width, height) / LabelText.Length; + Typeface tf = new Typeface("Arial"); + DpiScale dpi = VisualTreeHelper.GetDpi(control); + FormattedText ft = new FormattedText(LabelText, CultureInfo.InvariantCulture, FlowDirection.LeftToRight, tf, size, Brushes.White, dpi.PixelsPerDip) + { + TextAlignment = TextAlignment.Center + }; + Pen redPen = new Pen(Brushes.DarkBlue, 2.0); + Rect rect = new Rect(1, 1, width - 1, height - 1); + drawingContext.DrawRectangle(Brushes.Black, redPen, rect); + drawingContext.DrawLine(new Pen(Brushes.DarkBlue, 2.0), + new Point(0.0, 0.0), + new Point(control.ActualWidth, control.ActualHeight)); + drawingContext.DrawLine(new Pen(Brushes.DarkBlue, 2.0), + new Point(control.ActualWidth, 0.0), + new Point(0.0, control.ActualHeight)); + drawingContext.DrawText(ft, new Point(width / 2, (height - ft.Height) / 2)); + } + } + + internal static void DrawUnstartedControlHelper(GLWpfControl control, DrawingContext drawingContext) + { + if (control.Visibility == Visibility.Visible && control.ActualWidth > 0 && control.ActualHeight > 0) + { + double width = control.ActualWidth; + double height = control.ActualHeight; + drawingContext.DrawRectangle(Brushes.Gray, null, new Rect(0, 0, width, height)); + + if (!Debugger.IsAttached) // Do not show the message if we're not debugging + { + return; + } + + const string UnstartedLabelText = "OpenGL content. Call Start() on the control to begin rendering."; + const int Size = 12; + Typeface tf = new Typeface("Arial"); + + DpiScale dpi = VisualTreeHelper.GetDpi(control); + FormattedText ft = new FormattedText(UnstartedLabelText, CultureInfo.InvariantCulture, FlowDirection.LeftToRight, tf, Size, Brushes.White, dpi.PixelsPerDip) + { + TextAlignment = TextAlignment.Left, + MaxTextWidth = width + }; + + drawingContext.DrawText(ft, new Point(0, 0)); + } } } } diff --git a/src/GLWpfControl/GLWpfControl.csproj b/src/GLWpfControl/GLWpfControl.csproj index a0c83f1..4e69e89 100644 --- a/src/GLWpfControl/GLWpfControl.csproj +++ b/src/GLWpfControl/GLWpfControl.csproj @@ -1,17 +1,16 @@  - - netcoreapp3.1 - true - OpenTK.Wpf - false - + + netcoreapp3.1 + true + OpenTK.Wpf + false + 9.0 + true + enable + bin\$(Configuration)\$(TargetFramework) + - - - - - - - bin\$(Configuration)\$(TargetFramework) - + + + diff --git a/src/GLWpfControl/GLWpfControlRenderer.cs b/src/GLWpfControl/GLWpfControlRenderer.cs index 2e066c7..fae4e7a 100644 --- a/src/GLWpfControl/GLWpfControlRenderer.cs +++ b/src/GLWpfControl/GLWpfControlRenderer.cs @@ -1,88 +1,248 @@ using System; using System.Diagnostics; +using System.Runtime.CompilerServices; using System.Windows; using System.Windows.Interop; using System.Windows.Media; using OpenTK.Graphics.OpenGL; using OpenTK.Graphics.Wgl; +using OpenTK.Platform.Windows; +using OpenTK.Windowing.Common; using OpenTK.Wpf.Interop; +#nullable enable + namespace OpenTK.Wpf { - - /// Renderer that uses DX_Interop for a fast-path. - internal sealed class GLWpfControlRenderer { - + /// Renderer that uses DX_Interop for a fast-path. + internal sealed class GLWpfControlRenderer : IDisposable + { private readonly Stopwatch _stopwatch = Stopwatch.StartNew(); + private readonly DxGlContext _context; - - public event Action GLRender; - public event Action GLAsyncRender; - - private DxGLFramebuffer _framebuffer; - - /// The OpenGL framebuffer handle. - public int FrameBufferHandle => _framebuffer?.GLFramebufferHandle ?? 0; - - /// The OpenGL Framebuffer width - public int Width => _framebuffer?.FramebufferWidth ?? 0; - - /// The OpenGL Framebuffer height - public int Height => _framebuffer?.FramebufferHeight ?? 0; - - private TimeSpan _lastFrameStamp; + public event Action? GLRender; + public event Action? GLAsyncRender; + + /// The width of this buffer in pixels. + public int FramebufferWidth { get; private set; } + + /// The height of this buffer in pixels. + public int FramebufferHeight { get; private set; } + + /// The OpenGL framebuffer handle. + public int FrameBufferHandle { get; private set; } + + /// The DirectX multisample type. + public MultisampleType MultisampleType { get; private set; } + + /// The OpenGL Framebuffer width + public int Width => D3dImage == null ? FramebufferWidth : 0; + + /// The OpenGL Framebuffer height + public int Height => D3dImage == null ? FramebufferHeight : 0; + + public IGraphicsContext? GLContext => _context.GraphicsContext; + + public D3DImage? D3dImage { get; private set; } + + public DXInterop.IDirect3DSurface9 DxColorRenderTarget { get; private set; } + public DXInterop.IDirect3DSurface9 DxDepthStencilRenderTarget { get; private set; } + + public IntPtr DxInteropColorRenderTargetRegisteredHandle { get; private set; } + public IntPtr DxInteropDepthStencilRenderTargetRegisteredHandle { get; private set; } + + public int GLFramebufferHandle { get; private set; } + private int GLSharedColorRenderbufferHandle { get; set; } + private int GLSharedDepthRenderRenderbufferHandle { get; set; } + + public TranslateTransform TranslateTransform { get; private set; } + public ScaleTransform FlipYTransform { get; private set; } + + private TimeSpan _lastFrameStamp; public GLWpfControlRenderer(GLWpfControlSettings settings) { _context = new DxGlContext(settings); + // Placeholder transforms. + TranslateTransform = new TranslateTransform(0, 0); + FlipYTransform = new ScaleTransform(1, 1); } + public void ReallocateFramebufferIfNeeded(double width, double height, double dpiScaleX, double dpiScaleY, Format format, MultisampleType msaaType) + { + int newWidth = (int)Math.Ceiling(width * dpiScaleX); + int newHeight = (int)Math.Ceiling(height * dpiScaleY); + + if (D3dImage == null || FramebufferWidth != newWidth || FramebufferHeight != newHeight || MultisampleType != msaaType) + { + if (D3dImage != null) + { + GL.DeleteFramebuffer(GLFramebufferHandle); + GL.DeleteRenderbuffer(GLSharedDepthRenderRenderbufferHandle); + GL.DeleteRenderbuffer(GLSharedColorRenderbufferHandle); + Wgl.DXUnregisterObjectNV(_context.GLDeviceHandle, DxInteropColorRenderTargetRegisteredHandle); + Wgl.DXUnregisterObjectNV(_context.GLDeviceHandle, DxInteropDepthStencilRenderTargetRegisteredHandle); + DxColorRenderTarget.Release(); + DxDepthStencilRenderTarget.Release(); + } + D3dImage = null; + + if (width > 0 && height > 0) + { + FramebufferWidth = newWidth; + FramebufferHeight = newHeight; + MultisampleType = msaaType; + + _context.DxDevice.CreateRenderTarget( + FramebufferWidth, + FramebufferHeight, + format, + msaaType, + 0, + false, + out DXInterop.IDirect3DSurface9 dxColorRenderTarget, + ref Unsafe.NullRef()); + DxColorRenderTarget = dxColorRenderTarget; - public void SetSize(int width, int height, double dpiScaleX, double dpiScaleY, Format format) { - if (_framebuffer == null || _framebuffer.Width != width || _framebuffer.Height != height) { - _framebuffer?.Dispose(); - _framebuffer = null; - if (width > 0 && height > 0) { - _framebuffer = new DxGLFramebuffer(_context, width, height, dpiScaleX, dpiScaleY, format); + _context.DxDevice.CreateDepthStencilSurface( + FramebufferWidth, + FramebufferHeight, + Format.D24S8, + msaaType, + 0, + false, + out DXInterop.IDirect3DSurface9 dxDepthStencilRenderTarget, + ref Unsafe.NullRef()); + DxDepthStencilRenderTarget = dxDepthStencilRenderTarget; + +#if DEBUG + { + DxColorRenderTarget.GetDesc(out DXInterop.D3DSURFACE_DESC desc); + + Debug.WriteLine($"Render target desc: {desc.Format}, {desc.Type}, {desc.Usage}, {desc.Pool}, {desc.MultiSampleType}, {desc.MultiSampleQuality}, {desc.Width}, {desc.Height}"); + } + + { + DxDepthStencilRenderTarget.GetDesc(out DXInterop.D3DSURFACE_DESC desc); + + Debug.WriteLine($"Render target desc: {desc.Format}, {desc.Type}, {desc.Usage}, {desc.Pool}, {desc.MultiSampleType}, {desc.MultiSampleQuality}, {desc.Width}, {desc.Height}"); + } +#endif + + GLFramebufferHandle = GL.GenFramebuffer(); + + TextureTarget colorTextureTarget = msaaType == MultisampleType.D3DMULTISAMPLE_NONE ? TextureTarget.Texture2D : TextureTarget.Texture2DMultisample; + + GLSharedColorRenderbufferHandle = GL.GenRenderbuffer(); + DxInteropColorRenderTargetRegisteredHandle = Wgl.DXRegisterObjectNV( + _context.GLDeviceHandle, + dxColorRenderTarget.Handle, + (uint)GLSharedColorRenderbufferHandle, + (uint)RenderbufferTarget.Renderbuffer, + WGL_NV_DX_interop.AccessReadWrite); + + GLSharedDepthRenderRenderbufferHandle = GL.GenRenderbuffer(); + DxInteropDepthStencilRenderTargetRegisteredHandle = Wgl.DXRegisterObjectNV( + _context.GLDeviceHandle, + dxDepthStencilRenderTarget.Handle, + (uint)GLSharedDepthRenderRenderbufferHandle, + (uint)RenderbufferTarget.Renderbuffer, + WGL_NV_DX_interop.AccessReadWrite); + + GL.BindFramebuffer(FramebufferTarget.Framebuffer, GLFramebufferHandle); + + GL.FramebufferRenderbuffer( + FramebufferTarget.Framebuffer, + FramebufferAttachment.ColorAttachment0, + RenderbufferTarget.Renderbuffer, + GLSharedColorRenderbufferHandle); + + // FIXME: If we have a combined format, maybe set both at the same time? + GL.FramebufferRenderbuffer( + FramebufferTarget.Framebuffer, + FramebufferAttachment.DepthAttachment, + RenderbufferTarget.Renderbuffer, + GLSharedDepthRenderRenderbufferHandle); + + GL.FramebufferRenderbuffer( + FramebufferTarget.Framebuffer, + FramebufferAttachment.StencilAttachment, + RenderbufferTarget.Renderbuffer, + GLSharedDepthRenderRenderbufferHandle); + + // FIXME: This will report unsupported but it will not do that in Render()...? + FramebufferErrorCode status = GL.CheckFramebufferStatus(FramebufferTarget.DrawFramebuffer); + if (status != FramebufferErrorCode.FramebufferComplete) + { + Debug.WriteLine($"Framebuffer is not complete: {status}"); + } + + GL.BindFramebuffer(FramebufferTarget.Framebuffer, 0); + + D3dImage = new D3DImage(96.0 * dpiScaleX, 96.0 * dpiScaleY); + + TranslateTransform = new TranslateTransform(0, height); + FlipYTransform = new ScaleTransform(1, -1); } } } - public void Render(DrawingContext drawingContext) { - if (_framebuffer == null) { + public void Render(DrawingContext drawingContext) + { + if (D3dImage == null) + { return; } - var curFrameStamp = _stopwatch.Elapsed; - var deltaT = curFrameStamp - _lastFrameStamp; + + TimeSpan curFrameStamp = _stopwatch.Elapsed; + TimeSpan deltaT = curFrameStamp - _lastFrameStamp; _lastFrameStamp = curFrameStamp; // Lock the interop object, DX calls to the framebuffer are no longer valid - _framebuffer.D3dImage.Lock(); - Wgl.DXLockObjectsNV(_context.GlDeviceHandle, 1, new[] { _framebuffer.DxInteropRegisteredHandle }); - GL.BindFramebuffer(FramebufferTarget.Framebuffer, _framebuffer.GLFramebufferHandle); - GL.Viewport(0, 0, _framebuffer.FramebufferWidth, _framebuffer.FramebufferHeight); + D3dImage.Lock(); + D3dImage.SetBackBuffer(System.Windows.Interop.D3DResourceType.IDirect3DSurface9, DxColorRenderTarget.Handle, true); + bool success = Wgl.DXLockObjectsNV(_context.GLDeviceHandle, 2, new[] { DxInteropColorRenderTargetRegisteredHandle, DxInteropDepthStencilRenderTargetRegisteredHandle }); + if (success == false) + { + Debug.WriteLine("Failed to lock objects!"); + } + GL.BindFramebuffer(FramebufferTarget.Framebuffer, GLFramebufferHandle); + GL.Viewport(0, 0, FramebufferWidth, FramebufferHeight); GLRender?.Invoke(deltaT); GL.BindFramebuffer(FramebufferTarget.Framebuffer, 0); GLAsyncRender?.Invoke(); // Unlock the interop object, this acts as a synchronization point. OpenGL draws to the framebuffer are no longer valid. - Wgl.DXUnlockObjectsNV(_context.GlDeviceHandle, 1, new[] { _framebuffer.DxInteropRegisteredHandle }); - _framebuffer.D3dImage.SetBackBuffer(D3DResourceType.IDirect3DSurface9, _framebuffer.DxRenderTargetHandle, true); - _framebuffer.D3dImage.AddDirtyRect(new Int32Rect(0, 0, _framebuffer.FramebufferWidth, _framebuffer.FramebufferHeight)); - _framebuffer.D3dImage.Unlock(); + success = Wgl.DXUnlockObjectsNV(_context.GLDeviceHandle, 2, new[] { DxInteropColorRenderTargetRegisteredHandle, DxInteropDepthStencilRenderTargetRegisteredHandle }); + if (success == false) + { + Debug.WriteLine("Failed to unlock objects!"); + } + + D3dImage.AddDirtyRect(new Int32Rect(0, 0, FramebufferWidth, FramebufferHeight)); + D3dImage.Unlock(); // Transforms are applied in reverse order - drawingContext.PushTransform(_framebuffer.TranslateTransform); // Apply translation to the image on the Y axis by the height. This assures that in the next step, where we apply a negative scale the image is still inside of the window - drawingContext.PushTransform(_framebuffer.FlipYTransform); // Apply a scale where the Y axis is -1. This will rotate the image by 180 deg + // Apply translation to the image on the Y axis by the height. This assures that in the next step, where we apply a negative scale the image is still inside of the window + drawingContext.PushTransform(TranslateTransform); + // Apply a scale where the Y axis is -1. This will flip the image vertically. + drawingContext.PushTransform(FlipYTransform); - // dpi scaled rectangle from the image - var rect = new Rect(0, 0, _framebuffer.D3dImage.Width, _framebuffer.D3dImage.Height); - drawingContext.DrawImage(_framebuffer.D3dImage, rect); // Draw the image source + // Dpi scaled rectangle from the image + Rect rect = new Rect(0, 0, D3dImage.Width, D3dImage.Height); + // Draw the image source + drawingContext.DrawImage(D3dImage, rect); - drawingContext.Pop(); // Remove the scale transform - drawingContext.Pop(); // Remove the translation transform + // Remove the scale transform and the translation transform + drawingContext.Pop(); + drawingContext.Pop(); + } + + public void Dispose() + { + _context.Dispose(); } } } diff --git a/src/GLWpfControl/GLWpfControlSettings.cs b/src/GLWpfControl/GLWpfControlSettings.cs index db54252..206797f 100644 --- a/src/GLWpfControl/GLWpfControlSettings.cs +++ b/src/GLWpfControl/GLWpfControlSettings.cs @@ -1,75 +1,95 @@ using System; -using JetBrains.Annotations; +using System.Diagnostics.Contracts; using OpenTK.Windowing.Common; +using OpenTK.Windowing.Desktop; namespace OpenTK.Wpf { - public sealed class GLWpfControlSettings { + public sealed class GLWpfControlSettings + { + /// /// If the render event is fired continuously whenever required. /// Disable this if you want manual control over when the rendered surface is updated. + /// public bool RenderContinuously { get; set; } = true; + /// /// If this is set to false, the control will render without any DPI scaling. /// This will result in higher performance and a worse image quality on systems with >100% DPI settings, such as 'Retina' laptop screens with 4K UHD at small sizes. /// This setting may be useful to get extra performance on mobile platforms. + /// public bool UseDeviceDpi { get; set; } = true; + /// /// If this parameter is set to true, the alpha channel of the color passed to the function GL.ClearColor /// will determine the level of transparency of this control + /// public bool TransparentBackground { get; set; } = false; + /// /// May be null. If defined, an external context will be used, of which the caller is responsible /// for managing the lifetime and disposal of. - public IGraphicsContext ContextToUse { get; set; } + /// + [CLSCompliant(false)] + public IGraphicsContext? ContextToUse { get; set; } + /// + /// A optional context for context sharing. + /// + [CLSCompliant(false)] + public IGLFWGraphicsContext? SharedContext { get; set; } + + /// /// May be null. If so, default bindings context will be used. - public IBindingsContext BindingsContext { get; set; } + /// + [CLSCompliant(false)] + public IBindingsContext? BindingsContext { get; set; } - public ContextFlags GraphicsContextFlags { get; set; } = ContextFlags.Default; - public ContextProfile GraphicsProfile { get; set; } = ContextProfile.Any; + /// + /// The OpenGL context flags to use. Same as . + /// + [CLSCompliant(false)] + [Obsolete("Use ContextFlags instead.")] + public ContextFlags GraphicsContextFlags { get => ContextFlags; set => ContextFlags = value; } - public int MajorVersion { get; set; } = 3; - public int MinorVersion { get; set; } = 3; + /// + /// The OpenGL context flags to use. will always be set for DirectX interop purposes. + /// + [CLSCompliant(false)] + public ContextFlags ContextFlags { get; set; } = ContextFlags.Default; - /// If we are using an external context for the control. - public bool IsUsingExternalContext => ContextToUse != null; - - /// Creates a copy of the settings. - internal GLWpfControlSettings Copy() { - var c = new GLWpfControlSettings { - ContextToUse = ContextToUse, - BindingsContext = BindingsContext, - GraphicsContextFlags = GraphicsContextFlags, - GraphicsProfile = GraphicsProfile, - MajorVersion = MajorVersion, - MinorVersion = MinorVersion, - RenderContinuously = RenderContinuously, - UseDeviceDpi = UseDeviceDpi, - TransparentBackground = TransparentBackground - }; - return c; - } + /// + /// The OpenGL profile to use. Same as . + /// + [CLSCompliant(false)] + [Obsolete("Use Profile instead.")] + public ContextProfile GraphicsProfile { get => Profile; set => Profile = value; } - /// Determines if two settings would result in the same context being created. - [Pure] - internal static bool WouldResultInSameContext([NotNull] GLWpfControlSettings a, [NotNull] GLWpfControlSettings b) { - if (a.MajorVersion != b.MajorVersion) { - return false; - } + /// + /// The OpenGL profile to use. + /// + [CLSCompliant(false)] + public ContextProfile Profile { get; set; } = ContextProfile.Any; - if (a.MinorVersion != b.MinorVersion) { - return false; - } - - if (a.GraphicsProfile != b.GraphicsProfile) { - return false; - } + /// The major OpenGL version number. + public int MajorVersion { get; set; } = 3; + /// The minor OpenGL version number. + public int MinorVersion { get; set; } = 3; - if (a.GraphicsContextFlags != b.GraphicsContextFlags) { - return false; - } + /// + /// How many MSAA samples should the framebuffer have in the range [0, 16]. 0 and 1 result in no MSAA. + /// + public int Samples { get; set; } = 0; - return true; + /// If we are using an external context for the control. + public bool IsUsingExternalContext => ContextToUse != null; + /// + /// Makes a shallow clone of this object. + /// + /// The cloned object. + public GLWpfControlSettings Clone() + { + return (GLWpfControlSettings)this.MemberwiseClone(); } } } diff --git a/src/GLWpfControl/Interop/DXInterop.cs b/src/GLWpfControl/Interop/DXInterop.cs index 94e0712..ca6466d 100644 --- a/src/GLWpfControl/Interop/DXInterop.cs +++ b/src/GLWpfControl/Interop/DXInterop.cs @@ -1,46 +1,362 @@ -using System; +using OpenTK.Graphics.OpenGL; +using System; +using System.Reflection.Metadata; +using System.Runtime.CompilerServices; +using System.Runtime.ConstrainedExecution; using System.Runtime.InteropServices; +using System.Windows.Media.Media3D; +using System.Windows.Media; +using System.Windows; +using System.Windows.Interop; namespace OpenTK.Wpf.Interop { internal static class DXInterop { + // We disable this so we can do struct _VTable +#pragma warning disable IDE1006 // Naming Styles + public const uint DefaultSdkVersion = 32; - private const int CreateDeviceEx_Offset = 20; - private const int CreateRenderTarget_Offset = 28; - private const int Release_Offset = 2; - private delegate int NativeCreateDeviceEx(IntPtr contextHandle, int adapter, DeviceType deviceType, IntPtr focusWindowHandle, CreateFlags behaviorFlags, ref PresentationParameters presentationParameters, IntPtr fullscreenDisplayMode, out IntPtr deviceHandle); - private delegate int NativeCreateRenderTarget(IntPtr deviceHandle, int width, int height, Format format, MultisampleType multisample, int multisampleQuality, bool lockable, out IntPtr surfaceHandle, ref IntPtr sharedHandle); + [DllImport("Kernel32.dll")] + public static extern int GetLastError(); + + public static void Direct3DCreate9Ex(uint SdkVersion, out IDirect3D9Ex context) + { + int result = Direct3DCreate9Ex(SdkVersion, out context); + CheckHResult(result); + + [DllImport("d3d9.dll")] + static extern int Direct3DCreate9Ex(uint SdkVersion, out IDirect3D9Ex ctx); + } + + private delegate int NativeCreateDeviceEx(IDirect3D9Ex contextHandle, int adapter, DeviceType deviceType, IntPtr focusWindowHandle, CreateFlags behaviorFlags, ref PresentationParameters presentationParameters, IntPtr fullscreenDisplayMode, out IDirect3DDevice9Ex deviceHandle); + private delegate int NativeCreateRenderTarget(IDirect3DDevice9Ex deviceHandle, int width, int height, Format format, MultisampleType multisample, int multisampleQuality, bool lockable, out IDirect3DSurface9 surfaceHandle, ref IntPtr sharedHandle); + private delegate int NativeCreateDepthStencilSurface(IDirect3DDevice9Ex deviceHandle, int width, int height, Format format, MultisampleType multisample, int multisampleQuality, bool discard, out IDirect3DSurface9 surfaceHandle, ref IntPtr sharedHandle); private delegate uint NativeRelease(IntPtr resourceHandle); - [DllImport("d3d9.dll")] - public static extern int Direct3DCreate9Ex(uint SdkVersion, out IntPtr ctx); + private delegate uint NativeGetDesc(IDirect3DSurface9 surfaceHandle, out D3DSURFACE_DESC pDesc); - public static int CreateDeviceEx(IntPtr contextHandle, int adapter, DeviceType deviceType, IntPtr focusWindowHandle, CreateFlags behaviorFlags, ref PresentationParameters presentationParameters, IntPtr fullscreenDisplayMode, out IntPtr deviceHandle) + public static void CheckHResult(int hresult) { - IntPtr vTable = Marshal.ReadIntPtr(contextHandle, 0); - IntPtr functionPointer = Marshal.ReadIntPtr(vTable, CreateDeviceEx_Offset * IntPtr.Size); - NativeCreateDeviceEx method = Marshal.GetDelegateForFunctionPointer(functionPointer); - return method(contextHandle, adapter, deviceType, focusWindowHandle, behaviorFlags, ref presentationParameters, fullscreenDisplayMode, out deviceHandle); + Marshal.ThrowExceptionForHR(hresult); } - public static int CreateRenderTarget(IntPtr deviceHandle, int width, int height, Format format, MultisampleType multisample, int multisampleQuality, bool lockable, out IntPtr surfaceHandle, ref IntPtr sharedHandle) + public static readonly Guid IID_IDirect3D9Ex = new Guid(0x02177241, 0x69FC, 0x400C, 0x8F, 0xF1, 0x93, 0xA4, 0x4D, 0xF6, 0x86, 0x1D); + + public unsafe struct IUnknown { - IntPtr vTable = Marshal.ReadIntPtr(deviceHandle, 0); - IntPtr functionPointer = Marshal.ReadIntPtr(vTable, CreateRenderTarget_Offset * IntPtr.Size); - NativeCreateRenderTarget method = Marshal.GetDelegateForFunctionPointer(functionPointer); - return method(deviceHandle, width, height, format, multisample, multisampleQuality, lockable, out surfaceHandle, ref sharedHandle); +#pragma warning disable CS0649 + public struct _VTable + { + public IntPtr QueryInterface; + public IntPtr AddRef; + public IntPtr Release; + } +#pragma warning restore CS0649 + + public _VTable** VTable; + + public readonly IntPtr Handle => (IntPtr)VTable; + + // FIXME: This is only temporary while we have COM objects refered to by IntPtr + public static explicit operator IUnknown(IntPtr ptr) => new IUnknown() { VTable = (_VTable**)ptr }; + + public uint Release() + { + NativeRelease method = Marshal.GetDelegateForFunctionPointer((*VTable)->Release); + // FIXME: Figure out how we want to reference things + return method((IntPtr)VTable); + } } - public static uint Release(IntPtr resourceHandle) + public unsafe struct IDirect3D9Ex { - IntPtr vTable = Marshal.ReadIntPtr(resourceHandle, 0); - IntPtr functionPointer = Marshal.ReadIntPtr(vTable, Release_Offset * IntPtr.Size); - NativeRelease method = Marshal.GetDelegateForFunctionPointer(functionPointer); - return method(resourceHandle); +#pragma warning disable CS0649 + public struct _VTable + { + public IntPtr QueryInterface; + public IntPtr AddRef; + public IntPtr Release; + public IntPtr RegisterSoftwareDevice; + public IntPtr GetAdapterCount; + public IntPtr GetAdapterIdentifier; + public IntPtr GetAdapterModeCount; + public IntPtr EnumAdapterModes; + public IntPtr GetAdapterDisplayMode; + public IntPtr CheckDeviceType; + public IntPtr CheckDeviceFormat; + public IntPtr CheckDeviceMultiSampleType; + public IntPtr CheckDepthStencilMatch; + public IntPtr CheckDeviceFormatConversion; + public IntPtr GetDeviceCaps; + public IntPtr GetAdapterMonitor; + public IntPtr CreateDevice; + public IntPtr GetAdapterModeCountEx; + public IntPtr EnumAdapterModesEx; + public IntPtr GetAdapterDisplayModeEx; + public IntPtr CreateDeviceEx; + public IntPtr GetAdapterLUID; + } + + public _VTable** VTable; +#pragma warning restore CS0649 + + public readonly IntPtr Handle => (IntPtr)VTable; + + public static explicit operator IDirect3D9Ex(IntPtr ptr) => new IDirect3D9Ex() { VTable = (_VTable**)ptr }; + + public uint Release() + { + NativeRelease method = Marshal.GetDelegateForFunctionPointer((*VTable)->Release); + // FIXME: Figure out how we want to reference things + return method((IntPtr)VTable); + } + + public void CreateDeviceEx(int adapter, DeviceType deviceType, IntPtr focusWindowHandle, CreateFlags behaviorFlags, ref PresentationParameters presentationParameters, IntPtr fullscreenDisplayMode, out IDirect3DDevice9Ex deviceHandle) + { + NativeCreateDeviceEx method = Marshal.GetDelegateForFunctionPointer((*VTable)->CreateDeviceEx); + + int result = method(this, adapter, deviceType, focusWindowHandle, behaviorFlags, ref presentationParameters, fullscreenDisplayMode, out deviceHandle); + + CheckHResult(result); + } } - } + public unsafe struct IDirect3DDevice9Ex + { +#pragma warning disable CS0649 + public struct _VTable + { + /*** IUnknown methods ***/ + public IntPtr QueryInterface; + public IntPtr AddRef; + public IntPtr Release; + + /*** IDirect3DDevice9 methods ***/ + public IntPtr TestCooperativeLevel; + public IntPtr GetAvailableTextureMem; + public IntPtr EvictManagedResources; + public IntPtr GetDirect3D; + public IntPtr GetDeviceCaps; + public IntPtr GetDisplayMode; + public IntPtr GetCreationParameters; + public IntPtr SetCursorProperties; + public IntPtr SetCursorPosition; + public IntPtr ShowCursor; + public IntPtr CreateAdditionalSwapChain; + public IntPtr GetSwapChain; + public IntPtr GetNumberOfSwapChains; + public IntPtr Reset; + public IntPtr Present; + public IntPtr GetBackBuffer; + public IntPtr GetRasterStatus; + public IntPtr SetDialogBoxMode; + public IntPtr SetGammaRamp; + public IntPtr GetGammaRamp; + public IntPtr CreateTexture; + public IntPtr CreateVolumeTexture; + public IntPtr CreateCubeTexture; + public IntPtr CreateVertexBuffer; + public IntPtr CreateIndexBuffer; + public IntPtr CreateRenderTarget; + public IntPtr CreateDepthStencilSurface; + public IntPtr UpdateSurface; + public IntPtr UpdateTexture; + public IntPtr GetRenderTargetData; + public IntPtr GetFrontBufferData; + public IntPtr StretchRect; + public IntPtr ColorFill; + public IntPtr CreateOffscreenPlainSurface; + public IntPtr SetRenderTarget; + public IntPtr GetRenderTarget; + public IntPtr SetDepthStencilSurface; + public IntPtr GetDepthStencilSurface; + public IntPtr BeginScene; + public IntPtr EndScene; + public IntPtr Clear; + public IntPtr SetTransform; + public IntPtr GetTransform; + public IntPtr MultiplyTransform; + public IntPtr SetViewport; + public IntPtr GetViewport; + public IntPtr SetMaterial; + public IntPtr GetMaterial; + public IntPtr SetLight; + public IntPtr GetLight; + public IntPtr LightEnable; + public IntPtr GetLightEnable; + public IntPtr SetClipPlane; + public IntPtr GetClipPlane; + public IntPtr SetRenderState; + public IntPtr GetRenderState; + public IntPtr CreateStateBlock; + public IntPtr BeginStateBlock; + public IntPtr EndStateBlock; + public IntPtr SetClipStatus; + public IntPtr GetClipStatus; + public IntPtr GetTexture; + public IntPtr SetTexture; + public IntPtr GetTextureStageState; + public IntPtr SetTextureStageState; + public IntPtr GetSamplerState; + public IntPtr SetSamplerState; + public IntPtr ValidateDevice; + public IntPtr SetPaletteEntries; + public IntPtr GetPaletteEntries; + public IntPtr SetCurrentTexturePalette; + public IntPtr GetCurrentTexturePalette; + public IntPtr SetScissorRect; + public IntPtr GetScissorRect; + public IntPtr SetSoftwareVertexProcessing; + public IntPtr GetSoftwareVertexProcessing; + public IntPtr SetNPatchMode; + public IntPtr GetNPatchMode; + public IntPtr DrawPrimitive; + public IntPtr DrawIndexedPrimitive; + public IntPtr DrawPrimitiveUP; + public IntPtr DrawIndexedPrimitiveUP; + public IntPtr ProcessVertices; + public IntPtr CreateVertexDeclaration; + public IntPtr SetVertexDeclaration; + public IntPtr GetVertexDeclaration; + public IntPtr SetFVF; + public IntPtr GetFVF; + public IntPtr CreateVertexShader; + public IntPtr SetVertexShader; + public IntPtr GetVertexShader; + public IntPtr SetVertexShaderConstantF; + public IntPtr GetVertexShaderConstantF; + public IntPtr SetVertexShaderConstantI; + public IntPtr GetVertexShaderConstantI; + public IntPtr SetVertexShaderConstantB; + public IntPtr GetVertexShaderConstantB; + public IntPtr SetStreamSource; + public IntPtr GetStreamSource; + public IntPtr SetStreamSourceFreq; + public IntPtr GetStreamSourceFreq; + public IntPtr SetIndices; + public IntPtr GetIndices; + public IntPtr CreatePixelShader; + public IntPtr SetPixelShader; + public IntPtr GetPixelShader; + public IntPtr SetPixelShaderConstantF; + public IntPtr GetPixelShaderConstantF; + public IntPtr SetPixelShaderConstantI; + public IntPtr GetPixelShaderConstantI; + public IntPtr SetPixelShaderConstantB; + public IntPtr GetPixelShaderConstantB; + public IntPtr DrawRectPatch; + public IntPtr DrawTriPatch; + public IntPtr DeletePatch; + public IntPtr CreateQuery; + public IntPtr SetConvolutionMonoKernel; + public IntPtr ComposeRects; + public IntPtr PresentEx; + public IntPtr GetGPUThreadPriority; + public IntPtr SetGPUThreadPriority; + public IntPtr WaitForVBlank; + public IntPtr CheckResourceResidency; + public IntPtr SetMaximumFrameLatency; + public IntPtr GetMaximumFrameLatency; + public IntPtr CheckDeviceState; + public IntPtr CreateRenderTargetEx; + public IntPtr CreateOffscreenPlainSurfaceEx; + public IntPtr CreateDepthStencilSurfaceEx; + public IntPtr ResetEx; + public IntPtr GetDisplayModeEx; + } + + public _VTable** VTable; +#pragma warning restore CS0649 + public readonly IntPtr Handle => (IntPtr)VTable; + + public uint Release() + { + NativeRelease method = Marshal.GetDelegateForFunctionPointer((*VTable)->Release); + // FIXME: Figure out how we want to reference things + return method((IntPtr)VTable); + } + + public void CreateRenderTarget(int width, int height, Format format, MultisampleType multisample, int multisampleQuality, bool lockable, out IDirect3DSurface9 surfaceHandle, ref IntPtr sharedHandle) + { + NativeCreateRenderTarget method = Marshal.GetDelegateForFunctionPointer((*VTable)->CreateRenderTarget); + + int result = method(this, width, height, format, multisample, multisampleQuality, lockable, out surfaceHandle, ref sharedHandle); + + CheckHResult(result); + } + + public void CreateDepthStencilSurface(int width, int height, Format format, MultisampleType multisample, int multisampleQuality, bool discard, out IDirect3DSurface9 surfaceHandle, ref IntPtr sharedHandle) + { + NativeCreateDepthStencilSurface method = Marshal.GetDelegateForFunctionPointer((*VTable)->CreateDepthStencilSurface); + + int result = method(this, width, height, format, multisample, multisampleQuality, discard, out surfaceHandle, ref sharedHandle); + + CheckHResult(result); + } + } + +#pragma warning disable CS0649 + public struct D3DSURFACE_DESC + { + public D3DFormat Format; + public D3DResourceType Type; + public D3DUsage Usage; + public D3DPool Pool; + public MultisampleType MultiSampleType; + public uint MultiSampleQuality; + public uint Width; + public uint Height; + } +#pragma warning restore CS0649 + + public unsafe struct IDirect3DSurface9 + { +#pragma warning disable CS0649 + public struct _VTable + { + /*** IUnknown methods ***/ + public IntPtr QueryInterface; + public IntPtr AddRef; + public IntPtr Release; + + /*** IDirect3DResource9 methods ***/ + public IntPtr GetDevice; + public IntPtr SetPrivateData; + public IntPtr GetPrivateData; + public IntPtr FreePrivateData; + public IntPtr SetPriority; + public IntPtr GetPriority; + public IntPtr PreLoad; + public new IntPtr GetType; + public IntPtr GetContainer; + public IntPtr GetDesc; + public IntPtr LockRect; + public IntPtr UnlockRect; + public IntPtr GetDC; + public IntPtr ReleaseDC; + } + + public _VTable** VTable; +#pragma warning restore CS0649 + + public readonly IntPtr Handle => (IntPtr)VTable; + + public uint GetDesc(out D3DSURFACE_DESC pDesc) + { + NativeGetDesc method = Marshal.GetDelegateForFunctionPointer((*VTable)->GetDesc); + + return method(this, out pDesc); + } + + public uint Release() + { + NativeRelease method = Marshal.GetDelegateForFunctionPointer((*VTable)->Release); + // FIXME: Figure out how we want to reference things + return method((IntPtr)VTable); + } + } + } } diff --git a/src/GLWpfControl/Interop/Enums.cs b/src/GLWpfControl/Interop/Enums.cs index b16c40f..9567b22 100644 --- a/src/GLWpfControl/Interop/Enums.cs +++ b/src/GLWpfControl/Interop/Enums.cs @@ -3,12 +3,12 @@ namespace OpenTK.Wpf.Interop { [Flags] - internal enum CreateFlags + internal enum CreateFlags : uint { + FpuPreserve = 2, Multithreaded = 4, PureDevice = 16, HardwareVertexProcessing = 64, - } internal enum DeviceType @@ -17,7 +17,93 @@ internal enum DeviceType /// Hardware rasterization. Shading is done with software, hardware, or mixed transform and lighting. /// HAL = 1, + } + + internal enum D3DFormat + { + D3DFMT_UNKNOWN = 0, + + D3DFMT_R8G8B8 = 20, + D3DFMT_A8R8G8B8 = 21, + D3DFMT_X8R8G8B8 = 22, + D3DFMT_R5G6B5 = 23, + D3DFMT_X1R5G5B5 = 24, + D3DFMT_A1R5G5B5 = 25, + D3DFMT_A4R4G4B4 = 26, + D3DFMT_R3G3B2 = 27, + D3DFMT_A8 = 28, + D3DFMT_A8R3G3B2 = 29, + D3DFMT_X4R4G4B4 = 30, + D3DFMT_A2B10G10R10 = 31, + D3DFMT_A8B8G8R8 = 32, + D3DFMT_X8B8G8R8 = 33, + D3DFMT_G16R16 = 34, + D3DFMT_A2R10G10B10 = 35, + D3DFMT_A16B16G16R16 = 36, + + D3DFMT_A8P8 = 40, + D3DFMT_P8 = 41, + + D3DFMT_L8 = 50, + D3DFMT_A8L8 = 51, + D3DFMT_A4L4 = 52, + + D3DFMT_V8U8 = 60, + D3DFMT_L6V5U5 = 61, + D3DFMT_X8L8V8U8 = 62, + D3DFMT_Q8W8V8U8 = 63, + D3DFMT_V16U16 = 64, + D3DFMT_A2W10V10U10 = 67, + + D3DFMT_UYVY = 'U' | 'Y' << 8 | 'V' << 16 | 'Y' << 24, + D3DFMT_R8G8_B8G8 = 'R' | 'G' << 8 | 'B' << 16 | 'G' << 24, + D3DFMT_YUY2 = 'Y' | 'U' << 8 | 'Y' << 16 | '2' << 24, + D3DFMT_G8R8_G8B8 = 'G' | 'R' << 8 | 'G' << 16 | 'B' << 24, + D3DFMT_DXT1 = 'D' | 'X' << 8 | 'T' << 16 | '1' << 24, + D3DFMT_DXT2 = 'D' | 'X' << 8 | 'T' << 16 | '2' << 24, + D3DFMT_DXT3 = 'D' | 'X' << 8 | 'T' << 16 | '3' << 24, + D3DFMT_DXT4 = 'D' | 'X' << 8 | 'T' << 16 | '4' << 24, + D3DFMT_DXT5 = 'D' | 'X' << 8 | 'T' << 16 | '5' << 24, + + D3DFMT_D16_LOCKABLE = 70, + D3DFMT_D32 = 71, + D3DFMT_D15S1 = 73, + D3DFMT_D24S8 = 75, + D3DFMT_D24X8 = 77, + D3DFMT_D24X4S4 = 79, + D3DFMT_D16 = 80, + + D3DFMT_D32F_LOCKABLE = 82, + D3DFMT_D24FS8 = 83, + + D3DFMT_D32_LOCKABLE = 84, + D3DFMT_S8_LOCKABLE = 85, + + D3DFMT_L16 = 81, + + D3DFMT_VERTEXDATA = 100, + D3DFMT_INDEX16 = 101, + D3DFMT_INDEX32 = 102, + + D3DFMT_Q16W16V16U16 = 110, + + D3DFMT_MULTI2_ARGB8 = 'M' | 'E' << 8 | 'T' << 16 | '1' << 24, + + D3DFMT_R16F = 111, + D3DFMT_G16R16F = 112, + D3DFMT_A16B16G16R16F = 113, + + D3DFMT_R32F = 114, + D3DFMT_G32R32F = 115, + D3DFMT_A32B32G32R32F = 116, + + D3DFMT_CxV8U8 = 117, + D3DFMT_A1 = 118, + D3DFMT_A2B10G10R10_XR_BIAS = 119, + D3DFMT_BINARYBUFFER = 199, + + D3DFMT_FORCE_DWORD = 0x7fffffff } internal enum Format @@ -30,20 +116,75 @@ internal enum Format /// /// 32-bit RGB pixel format, where 8 bits are reserved for each color. /// - X8R8G8B8 = 22, + X8R8G8B8 = 22, + /// + /// 32-bit z-buffer bit depth using 24 bits for the depth channel and 8 bits for the stencil channel. + /// + D24S8 = 75, } - internal enum MultisampleType + internal enum MultisampleType : int { - None = 0, - + D3DMULTISAMPLE_NONE = 0, + D3DMULTISAMPLE_NONMASKABLE = 1, + D3DMULTISAMPLE_2_SAMPLES = 2, + D3DMULTISAMPLE_3_SAMPLES = 3, + D3DMULTISAMPLE_4_SAMPLES = 4, + D3DMULTISAMPLE_5_SAMPLES = 5, + D3DMULTISAMPLE_6_SAMPLES = 6, + D3DMULTISAMPLE_7_SAMPLES = 7, + D3DMULTISAMPLE_8_SAMPLES = 8, + D3DMULTISAMPLE_9_SAMPLES = 9, + D3DMULTISAMPLE_10_SAMPLES = 10, + D3DMULTISAMPLE_11_SAMPLES = 11, + D3DMULTISAMPLE_12_SAMPLES = 12, + D3DMULTISAMPLE_13_SAMPLES = 13, + D3DMULTISAMPLE_14_SAMPLES = 14, + D3DMULTISAMPLE_15_SAMPLES = 15, + D3DMULTISAMPLE_16_SAMPLES = 16, + D3DMULTISAMPLE_FORCE_DWORD = unchecked((int)0xffffffff), } internal enum SwapEffect { Discard = 1, + } + internal enum D3DResourceType + { + D3DRTYPE_SURFACE = 1, + D3DRTYPE_VOLUME = 2, + D3DRTYPE_TEXTURE = 3, + D3DRTYPE_VOLUMETEXTURE = 4, + D3DRTYPE_CUBETEXTURE = 5, + D3DRTYPE_VERTEXBUFFER = 6, + D3DRTYPE_INDEXBUFFER = 7, + D3DRTYPE_FORCE_DWORD = 0x7fffffff + } + + internal enum D3DPool + { + D3DPOOL_DEFAULT = 0, + D3DPOOL_MANAGED = 1, + D3DPOOL_SYSTEMMEM = 2, + D3DPOOL_SCRATCH = 3, + D3DPOOL_FORCE_DWORD = 0x7fffffff } + [Flags] + internal enum D3DUsage : uint + { + /// + /// The resource will be a depth stencil buffer. D3DUSAGE_DEPTHSTENCIL can only be used with D3DPOOL_DEFAULT. + /// + D3DUSAGE_DEPTHSTENCIL = 0x00000002, + + /// + /// The resource will be a render target. D3DUSAGE_RENDERTARGET can only be used with D3DPOOL_DEFAULT. + /// + D3DUSAGE_RENDERTARGET = 0x00000001, + + D3DUSAGE_DYNAMIC = 0x00000200, + } } diff --git a/src/GLWpfControl/NonReloadingTabControl.cs b/src/GLWpfControl/NonReloadingTabControl.cs index e3d69b6..a7b6b6d 100644 --- a/src/GLWpfControl/NonReloadingTabControl.cs +++ b/src/GLWpfControl/NonReloadingTabControl.cs @@ -12,7 +12,7 @@ namespace OpenTK.Wpf [TemplatePart(Name = "PART_ItemsHolder", Type = typeof(Panel))] public class NonReloadingTabControl : TabControl { - private Panel _itemsHolderPanel; + private Panel? _itemsHolderPanel; public NonReloadingTabControl() { // This is necessary so that we get the initial databound selected item @@ -24,7 +24,7 @@ public NonReloadingTabControl() { /// /// /// - private void ItemContainerGenerator_StatusChanged(object sender, EventArgs e) + private void ItemContainerGenerator_StatusChanged(object? sender, EventArgs e) { if (ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated) { @@ -64,9 +64,9 @@ protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e) case NotifyCollectionChangedAction.Remove: if (e.OldItems != null) { - foreach (var item in e.OldItems) + foreach (object? item in e.OldItems) { - ContentPresenter cp = FindChildContentPresenter(item); + ContentPresenter? cp = FindChildContentPresenter(item); if (cp != null) _itemsHolderPanel.Children.Remove(cp); } @@ -95,41 +95,46 @@ private void UpdateSelectedItem() return; // Generate a ContentPresenter if necessary - TabItem item = GetSelectedTabItem(); + TabItem? item = GetSelectedTabItem(); if (item != null) CreateChildContentPresenter(item); // show the right child - foreach (ContentPresenter child in _itemsHolderPanel.Children) - child.Visibility = ((child.Tag as TabItem).IsSelected) ? Visibility.Visible : Visibility.Collapsed; + foreach (ContentPresenter? child in _itemsHolderPanel.Children) + { + if (child != null) + { + child.Visibility = ((child.Tag as TabItem)?.IsSelected ?? false) ? Visibility.Visible : Visibility.Collapsed; + } + } } - private ContentPresenter CreateChildContentPresenter(object item) + private ContentPresenter? CreateChildContentPresenter(object item) { if (item == null) return null; - ContentPresenter cp = FindChildContentPresenter(item); + ContentPresenter? cp = FindChildContentPresenter(item); if (cp != null) return cp; // the actual child to be added. cp.Tag is a reference to the TabItem cp = new ContentPresenter(); - cp.Content = (item is TabItem) ? (item as TabItem).Content : item; + cp.Content = (item is TabItem tabItem) ? tabItem.Content : item; cp.ContentTemplate = SelectedContentTemplate; cp.ContentTemplateSelector = SelectedContentTemplateSelector; cp.ContentStringFormat = SelectedContentStringFormat; cp.Visibility = Visibility.Collapsed; cp.Tag = (item is TabItem) ? item : (ItemContainerGenerator.ContainerFromItem(item)); - _itemsHolderPanel.Children.Add(cp); + _itemsHolderPanel?.Children.Add(cp); return cp; } - private ContentPresenter FindChildContentPresenter(object data) + private ContentPresenter? FindChildContentPresenter(object? data) { - if (data is TabItem) - data = (data as TabItem).Content; + if (data is TabItem tabItem) + data = tabItem.Content; if (data == null) return null; @@ -137,24 +142,26 @@ private ContentPresenter FindChildContentPresenter(object data) if (_itemsHolderPanel == null) return null; - foreach (ContentPresenter cp in _itemsHolderPanel.Children) + foreach (ContentPresenter? cp in _itemsHolderPanel.Children) { - if (cp.Content == data) + if (cp?.Content == data) return cp; } return null; } - protected TabItem GetSelectedTabItem() + protected TabItem? GetSelectedTabItem() { object selectedItem = SelectedItem; if (selectedItem == null) return null; - TabItem item = selectedItem as TabItem; + TabItem? item = selectedItem as TabItem; if (item == null) + { item = ItemContainerGenerator.ContainerFromIndex(SelectedIndex) as TabItem; + } return item; } diff --git a/src/GLWpfControl/UnstartedControlHelper.cs b/src/GLWpfControl/UnstartedControlHelper.cs deleted file mode 100644 index b8e2b10..0000000 --- a/src/GLWpfControl/UnstartedControlHelper.cs +++ /dev/null @@ -1,44 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Globalization; -using System.Text; -using System.Windows; -using System.Windows.Media; - -namespace OpenTK.Wpf -{ - internal static class UnstartedControlHelper - { - - public static void DrawUnstartedControlHelper(GLWpfControl control, DrawingContext drawingContext) - { - if (control.Visibility == Visibility.Visible && control.ActualWidth > 0 && control.ActualHeight > 0) - { - var width = control.ActualWidth; - var height = control.ActualHeight; - drawingContext.DrawRectangle(Brushes.Gray, null, new Rect(0, 0, width, height)); - - if(!Debugger.IsAttached) // Do not show the message if we're not debugging - { - return; - } - - const string unstartedLabelText = "OpenGL content. Call Start() on the control to begin rendering."; - const int size = 12; - var tf = new Typeface("Arial"); -#pragma warning disable 618 - var ft = new FormattedText(unstartedLabelText, CultureInfo.InvariantCulture, FlowDirection.LeftToRight, tf, size, Brushes.White) - { - TextAlignment = TextAlignment.Left, - MaxTextWidth = width - }; -#pragma warning restore 618 - - drawingContext.DrawText(ft, new Point(0, 0)); - } - } - - - } -} diff --git a/src/GLWpfControl/paket b/src/GLWpfControl/paket index 1bb7066..60e9b26 100644 --- a/src/GLWpfControl/paket +++ b/src/GLWpfControl/paket @@ -8,7 +8,7 @@ owners authors Team OpenTK summary - A native WPF control for OpenTK 4.3.0+ + A native WPF control for OpenTK 4.8.0+ projectUrl https://www.opentk.net iconUrl @@ -23,7 +23,7 @@ copyright include-pdbs true dependencies - OpenTK ~> 4.0 >= 4.3.0 + OpenTK ~> 4.0 >= 4.8.0 files bin\Release\netcoreapp3.1\netcoreapp3.1\GLWpfControl.dll ==> lib\netcoreapp3.1