From 28c8e6076bf64b498ce2022a04bb26d6a636e022 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julius=20H=C3=A4ger?= Date: Fri, 22 Jul 2022 01:44:11 +0200 Subject: [PATCH 01/19] Started cleanup effort --- src/GLWpfControl/DxGLFramebuffer.cs | 131 ------------------ src/GLWpfControl/GLWpfControl.cs | 2 +- src/GLWpfControl/GLWpfControlRenderer.cs | 146 +++++++++++++++++---- src/GLWpfControl/GLWpfControlSettings.cs | 23 +--- src/GLWpfControl/UnstartedControlHelper.cs | 3 - 5 files changed, 124 insertions(+), 181 deletions(-) delete mode 100644 src/GLWpfControl/DxGLFramebuffer.cs 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 10dce38..392c513 100644 --- a/src/GLWpfControl/GLWpfControl.cs +++ b/src/GLWpfControl/GLWpfControl.cs @@ -88,7 +88,7 @@ public void Start(GLWpfControlSettings settings) if (_settings != null) { throw new InvalidOperationException($"{nameof(Start)} must only be called once for a given {nameof(GLWpfControl)}"); } - _settings = settings.Copy(); + _settings = (GLWpfControlSettings)settings.Clone(); _needsRedraw = settings.RenderContinuously; _renderer = new GLWpfControlRenderer(_settings); _renderer.GLRender += timeDelta => Render?.Invoke(timeDelta); diff --git a/src/GLWpfControl/GLWpfControlRenderer.cs b/src/GLWpfControl/GLWpfControlRenderer.cs index 2e066c7..1d4083e 100644 --- a/src/GLWpfControl/GLWpfControlRenderer.cs +++ b/src/GLWpfControl/GLWpfControlRenderer.cs @@ -5,6 +5,7 @@ using System.Windows.Media; using OpenTK.Graphics.OpenGL; using OpenTK.Graphics.Wgl; +using OpenTK.Platform.Windows; using OpenTK.Wpf.Interop; namespace OpenTK.Wpf @@ -12,45 +13,132 @@ namespace OpenTK.Wpf /// Renderer that uses DX_Interop for a fast-path. internal sealed class GLWpfControlRenderer { - + private readonly Stopwatch _stopwatch = Stopwatch.StartNew(); private readonly DxGlContext _context; - + public event Action GLRender; public event Action GLAsyncRender; - - private DxGLFramebuffer _framebuffer; + + //private DxGLFramebuffer _framebuffer; + + /// 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 => _framebuffer?.GLFramebufferHandle ?? 0; + public int FrameBufferHandle { get; private set; } /// The OpenGL Framebuffer width - public int Width => _framebuffer?.FramebufferWidth ?? 0; + public int Width => D3dImage == null ? FramebufferWidth : 0; /// The OpenGL Framebuffer height - public int Height => _framebuffer?.FramebufferHeight ?? 0; - - private TimeSpan _lastFrameStamp; + public int Height => D3dImage == null ? FramebufferHeight : 0; + + public D3DImage D3dImage { get; private set; } + + public IntPtr DxRenderTargetHandle { get; private set; } + + public IntPtr DxInteropRegisteredHandle { get; private set; } + public int GLFramebufferHandle { get; private set; } + private int GLSharedTextureHandle { get; set; } + private int GLDepthRenderBufferHandle { 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); } - 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 (D3dImage == null || FramebufferWidth != width || FramebufferHeight != height) { + //D3dImage?.Dispose(); + if (D3dImage != null) + { + GL.DeleteFramebuffer(GLFramebufferHandle); + GL.DeleteRenderbuffer(GLDepthRenderBufferHandle); + GL.DeleteTexture(GLSharedTextureHandle); + Wgl.DXUnregisterObjectNV(_context.GlDeviceHandle, DxInteropRegisteredHandle); + DXInterop.Release(DxRenderTargetHandle); + } + D3dImage = null; + if (width > 0 && height > 0) { - _framebuffer = new DxGLFramebuffer(_context, width, height, dpiScaleX, dpiScaleY, format); + //_framebuffer = new DxGLFramebuffer(_context, width, height, dpiScaleX, dpiScaleY, format); + + 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 Render(DrawingContext drawingContext) { - if (_framebuffer == null) { + if (D3dImage == null) { return; } var curFrameStamp = _stopwatch.Elapsed; @@ -58,31 +146,31 @@ public void Render(DrawingContext drawingContext) { _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(); + Wgl.DXLockObjectsNV(_context.GlDeviceHandle, 1, new[] { DxInteropRegisteredHandle }); + 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(); + Wgl.DXUnlockObjectsNV(_context.GlDeviceHandle, 1, new[] { DxInteropRegisteredHandle }); + D3dImage.SetBackBuffer(D3DResourceType.IDirect3DSurface9, DxRenderTargetHandle, true); + 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 + drawingContext.PushTransform(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(FlipYTransform); // Apply a scale where the Y axis is -1. This will rotate the image by 180 deg // 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 + var rect = new Rect(0, 0, D3dImage.Width, D3dImage.Height); + drawingContext.DrawImage(D3dImage, rect); // Draw the image source - drawingContext.Pop(); // Remove the scale transform - drawingContext.Pop(); // Remove the translation transform + drawingContext.Pop(); // Remove the scale transform + drawingContext.Pop(); // Remove the translation transform } } } diff --git a/src/GLWpfControl/GLWpfControlSettings.cs b/src/GLWpfControl/GLWpfControlSettings.cs index db54252..b4d584f 100644 --- a/src/GLWpfControl/GLWpfControlSettings.cs +++ b/src/GLWpfControl/GLWpfControlSettings.cs @@ -3,7 +3,7 @@ using OpenTK.Windowing.Common; namespace OpenTK.Wpf { - public sealed class GLWpfControlSettings { + public sealed class GLWpfControlSettings : ICloneable { /// 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; @@ -33,22 +33,6 @@ public sealed class GLWpfControlSettings { /// 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; - } - /// Determines if two settings would result in the same context being created. [Pure] internal static bool WouldResultInSameContext([NotNull] GLWpfControlSettings a, [NotNull] GLWpfControlSettings b) { @@ -71,5 +55,10 @@ internal static bool WouldResultInSameContext([NotNull] GLWpfControlSettings a, return true; } + + public object Clone() + { + return this.MemberwiseClone(); + } } } diff --git a/src/GLWpfControl/UnstartedControlHelper.cs b/src/GLWpfControl/UnstartedControlHelper.cs index b8e2b10..3f4fd53 100644 --- a/src/GLWpfControl/UnstartedControlHelper.cs +++ b/src/GLWpfControl/UnstartedControlHelper.cs @@ -10,7 +10,6 @@ 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) @@ -38,7 +37,5 @@ public static void DrawUnstartedControlHelper(GLWpfControl control, DrawingConte drawingContext.DrawText(ft, new Point(0, 0)); } } - - } } From 1631d01dc77e824f4f3b0125e2256d9b62c860da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julius=20H=C3=A4ger?= Date: Mon, 1 Aug 2022 16:45:00 +0200 Subject: [PATCH 02/19] Updated to .net 5.0. Added error checking for d3d9 calls. Slowly removing bad abstractions --- src/Example/Example.csproj | 2 +- src/GLWpfControl/DXGLContext.cs | 56 +++++++++---------- src/GLWpfControl/GLWpfControl.cs | 70 +++++++++++++----------- src/GLWpfControl/GLWpfControl.csproj | 5 +- src/GLWpfControl/GLWpfControlSettings.cs | 4 +- src/GLWpfControl/Interop/DXInterop.cs | 7 ++- src/GLWpfControl/Interop/Enums.cs | 2 +- 7 files changed, 76 insertions(+), 70 deletions(-) diff --git a/src/Example/Example.csproj b/src/Example/Example.csproj index 175d442..c9d1175 100644 --- a/src/Example/Example.csproj +++ b/src/Example/Example.csproj @@ -2,7 +2,7 @@ WinExe - netcoreapp3.1-windows + netcoreapp5.0-windows true false Example.App diff --git a/src/GLWpfControl/DXGLContext.cs b/src/GLWpfControl/DXGLContext.cs index d573124..c088048 100644 --- a/src/GLWpfControl/DXGLContext.cs +++ b/src/GLWpfControl/DXGLContext.cs @@ -1,8 +1,8 @@ using System; +using System.Runtime.InteropServices; using System.Threading; using System.Windows; using System.Windows.Interop; -using JetBrains.Annotations; using OpenTK.Graphics.Wgl; using OpenTK.Windowing.Common; using OpenTK.Windowing.Desktop; @@ -31,14 +31,16 @@ internal sealed class DxGlContext : IDisposable { /// 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; + + private static NativeWindow GlfwWindow; + private static HwndSource HwndSource; + /// The number of active controls using the shared context. private static int _sharedContextReferenceCount; + public DxGlContext(GLWpfControlSettings settings) { - public DxGlContext([NotNull] GLWpfControlSettings settings) { - DXInterop.Direct3DCreate9Ex(DXInterop.DefaultSdkVersion, out var dxContextHandle); + DXInterop.CheckHResult(DXInterop.Direct3DCreate9Ex(DXInterop.DefaultSdkVersion, out var dxContextHandle)); DxContextHandle = dxContextHandle; var deviceParameters = new PresentationParameters @@ -59,17 +61,18 @@ public DxGlContext([NotNull] GLWpfControlSettings settings) { MultiSampleType = MultisampleType.None }; - DXInterop.CreateDeviceEx( - dxContextHandle, - 0, - DeviceType.HAL, // use hardware rasterization - IntPtr.Zero, - CreateFlags.HardwareVertexProcessing | - CreateFlags.Multithreaded | - CreateFlags.PureDevice, - ref deviceParameters, - IntPtr.Zero, - out var dxDeviceHandle); + DXInterop.CheckHResult( + DXInterop.CreateDeviceEx( + dxContextHandle, + 0, + DeviceType.HAL, // use hardware rasterization + IntPtr.Zero, + CreateFlags.HardwareVertexProcessing | + CreateFlags.Multithreaded | + CreateFlags.PureDevice, + ref deviceParameters, + IntPtr.Zero, + out var dxDeviceHandle)); DxDeviceHandle = dxDeviceHandle; // if the graphics context is null, we use the shared context. @@ -106,7 +109,7 @@ private static IGraphicsContext GetOrCreateSharedOpenGLContext(GLWpfControlSetti nws.Profile = settings.GraphicsProfile; nws.WindowBorder = WindowBorder.Hidden; nws.WindowState = WindowState.Minimized; - var glfwWindow = new NativeWindow(nws); + GlfwWindow = new NativeWindow(nws); var 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. @@ -114,18 +117,16 @@ private static IGraphicsContext GetOrCreateSharedOpenGLContext(GLWpfControlSetti // 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); + HwndSource = new HwndSource(0, 0, 0, 0, 0, "GLWpfControl", baseHandle); - _sharedContext = glfwWindow.Context; + _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(); } + + // FIXME: + // This has a race condition where we think we still have the + // shared context available but it's been deleted when we get here. Interlocked.Increment(ref _sharedContextReferenceCount); return _sharedContext; } @@ -134,9 +135,8 @@ 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(); - } + GlfwWindow.Dispose(); + HwndSource.Dispose(); } } } diff --git a/src/GLWpfControl/GLWpfControl.cs b/src/GLWpfControl/GLWpfControl.cs index 392c513..84749a1 100644 --- a/src/GLWpfControl/GLWpfControl.cs +++ b/src/GLWpfControl/GLWpfControl.cs @@ -6,8 +6,11 @@ using System.Windows.Controls; using System.Windows.Input; using System.Windows.Media; -using JetBrains.Annotations; using OpenTK.Wpf.Interop; +using System.Windows.Interop; + + +#nullable enable namespace OpenTK.Wpf { @@ -42,8 +45,8 @@ public class GLWpfControl : FrameworkElement // Fields // ----------------------------------- - [CanBeNull] private GLWpfControlSettings _settings; - [CanBeNull] private GLWpfControlRenderer _renderer; + private GLWpfControlSettings? _settings; + private GLWpfControlRenderer? _renderer; private bool _needsRedraw; // ----------------------------------- @@ -113,29 +116,6 @@ public void Start(GLWpfControlSettings settings) 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); @@ -163,7 +143,7 @@ internal void OnKeyUp(object sender, KeyEventArgs e) } - private void OnCompTargetRender(object sender, EventArgs e) + private void OnCompTargetRender(object? sender, EventArgs e) { var currentRenderTime = (e as RenderingEventArgs)?.RenderingTime; if(currentRenderTime == _lastRenderTime) @@ -173,7 +153,7 @@ 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; if (_needsRedraw) { @@ -184,23 +164,48 @@ private void OnCompTargetRender(object sender, EventArgs e) } protected override void OnRender(DrawingContext drawingContext) { + base.OnRender(drawingContext); + var isDesignMode = DesignerProperties.GetIsInDesignMode(this); if (isDesignMode) { DesignTimeHelper.DrawDesignTimeHelper(this, drawingContext); } - else if(_renderer != null) { - SetupRenderSize(); + else if (_renderer != null) { + if (_settings != null) + { + 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); + } + _renderer?.Render(drawingContext); } else { UnstartedControlHelper.DrawUnstartedControlHelper(this, drawingContext); } - - base.OnRender(drawingContext); } protected override void OnRenderSizeChanged(SizeChangedInfo info) { + base.OnRenderSizeChanged(info); + var isInDesignMode = DesignerProperties.GetIsInDesignMode(this); if (isInDesignMode) { return; @@ -210,7 +215,6 @@ protected override void OnRenderSizeChanged(SizeChangedInfo info) { InvalidateVisual(); } - base.OnRenderSizeChanged(info); } } } diff --git a/src/GLWpfControl/GLWpfControl.csproj b/src/GLWpfControl/GLWpfControl.csproj index 71e648d..b82583d 100644 --- a/src/GLWpfControl/GLWpfControl.csproj +++ b/src/GLWpfControl/GLWpfControl.csproj @@ -1,17 +1,16 @@  - netcoreapp3.1 + net5.0-windows true OpenTK.Wpf false - - + bin\$(Configuration)\$(TargetFramework) diff --git a/src/GLWpfControl/GLWpfControlSettings.cs b/src/GLWpfControl/GLWpfControlSettings.cs index b4d584f..a87fe39 100644 --- a/src/GLWpfControl/GLWpfControlSettings.cs +++ b/src/GLWpfControl/GLWpfControlSettings.cs @@ -1,5 +1,5 @@ using System; -using JetBrains.Annotations; +using System.Diagnostics.Contracts; using OpenTK.Windowing.Common; namespace OpenTK.Wpf { @@ -35,7 +35,7 @@ public sealed class GLWpfControlSettings : ICloneable { /// Determines if two settings would result in the same context being created. [Pure] - internal static bool WouldResultInSameContext([NotNull] GLWpfControlSettings a, [NotNull] GLWpfControlSettings b) { + internal static bool WouldResultInSameContext(GLWpfControlSettings a, GLWpfControlSettings b) { if (a.MajorVersion != b.MajorVersion) { return false; } diff --git a/src/GLWpfControl/Interop/DXInterop.cs b/src/GLWpfControl/Interop/DXInterop.cs index 94e0712..f5c53cd 100644 --- a/src/GLWpfControl/Interop/DXInterop.cs +++ b/src/GLWpfControl/Interop/DXInterop.cs @@ -5,6 +5,11 @@ namespace OpenTK.Wpf.Interop { internal static class DXInterop { + public static void CheckHResult(int hresult) + { + Marshal.ThrowExceptionForHR(hresult); + } + public const uint DefaultSdkVersion = 32; private const int CreateDeviceEx_Offset = 20; private const int CreateRenderTarget_Offset = 28; @@ -40,7 +45,5 @@ public static uint Release(IntPtr resourceHandle) NativeRelease method = Marshal.GetDelegateForFunctionPointer(functionPointer); return method(resourceHandle); } - } - } diff --git a/src/GLWpfControl/Interop/Enums.cs b/src/GLWpfControl/Interop/Enums.cs index b16c40f..658b6c8 100644 --- a/src/GLWpfControl/Interop/Enums.cs +++ b/src/GLWpfControl/Interop/Enums.cs @@ -3,7 +3,7 @@ namespace OpenTK.Wpf.Interop { [Flags] - internal enum CreateFlags + internal enum CreateFlags : uint { Multithreaded = 4, PureDevice = 16, From 513f980c69c99d69f02c54666ef47034a6e7ba93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julius=20H=C3=A4ger?= Date: Mon, 1 Aug 2022 16:55:29 +0200 Subject: [PATCH 03/19] Moved design time and unstarted rendering helpers into GLWpfControl.cs --- src/GLWpfControl/DesignTimeHelper.cs | 35 ------------ src/GLWpfControl/GLWpfControl.cs | 66 ++++++++++++++++++++-- src/GLWpfControl/UnstartedControlHelper.cs | 41 -------------- 3 files changed, 62 insertions(+), 80 deletions(-) delete mode 100644 src/GLWpfControl/DesignTimeHelper.cs delete mode 100644 src/GLWpfControl/UnstartedControlHelper.cs 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/GLWpfControl.cs b/src/GLWpfControl/GLWpfControl.cs index 84749a1..356e68d 100644 --- a/src/GLWpfControl/GLWpfControl.cs +++ b/src/GLWpfControl/GLWpfControl.cs @@ -168,7 +168,7 @@ protected override void OnRender(DrawingContext drawingContext) { var isDesignMode = DesignerProperties.GetIsInDesignMode(this); if (isDesignMode) { - DesignTimeHelper.DrawDesignTimeHelper(this, drawingContext); + DrawDesignTimeHelper(this, drawingContext); } else if (_renderer != null) { if (_settings != null) @@ -192,13 +192,13 @@ protected override void OnRender(DrawingContext drawingContext) { var format = _settings.TransparentBackground ? Format.A8R8G8B8 : Format.X8R8G8B8; - _renderer?.SetSize((int)RenderSize.Width, (int)RenderSize.Height, dpiScaleX, dpiScaleY, format); + _renderer.SetSize((int)RenderSize.Width, (int)RenderSize.Height, dpiScaleX, dpiScaleY, format); } - _renderer?.Render(drawingContext); + _renderer.Render(drawingContext); } else { - UnstartedControlHelper.DrawUnstartedControlHelper(this, drawingContext); + DrawUnstartedControlHelper(this, drawingContext); } } @@ -216,5 +216,63 @@ protected override void OnRenderSizeChanged(SizeChangedInfo info) InvalidateVisual(); } } + + + + 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"; + 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)); + } + } + + internal 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/UnstartedControlHelper.cs b/src/GLWpfControl/UnstartedControlHelper.cs deleted file mode 100644 index 3f4fd53..0000000 --- a/src/GLWpfControl/UnstartedControlHelper.cs +++ /dev/null @@ -1,41 +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)); - } - } - } -} From 8e9d4f74731eab03bea2ea634174b793408185fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julius=20H=C3=A4ger?= Date: Mon, 1 Aug 2022 17:11:42 +0200 Subject: [PATCH 04/19] Whitespace cleanup --- src/GLWpfControl/DXGLContext.cs | 3 +-- src/GLWpfControl/Interop/Enums.cs | 8 +------- 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/src/GLWpfControl/DXGLContext.cs b/src/GLWpfControl/DXGLContext.cs index c088048..76ea186 100644 --- a/src/GLWpfControl/DXGLContext.cs +++ b/src/GLWpfControl/DXGLContext.cs @@ -95,8 +95,7 @@ private static IGraphicsContext GetOrCreateSharedOpenGLContext(GLWpfControlSetti $" 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; nws.StartFocused = false; diff --git a/src/GLWpfControl/Interop/Enums.cs b/src/GLWpfControl/Interop/Enums.cs index 658b6c8..664bdad 100644 --- a/src/GLWpfControl/Interop/Enums.cs +++ b/src/GLWpfControl/Interop/Enums.cs @@ -8,7 +8,6 @@ internal enum CreateFlags : uint Multithreaded = 4, PureDevice = 16, HardwareVertexProcessing = 64, - } internal enum DeviceType @@ -17,7 +16,6 @@ internal enum DeviceType /// Hardware rasterization. Shading is done with software, hardware, or mixed transform and lighting. /// HAL = 1, - } internal enum Format @@ -30,20 +28,16 @@ internal enum Format /// /// 32-bit RGB pixel format, where 8 bits are reserved for each color. /// - X8R8G8B8 = 22, - + X8R8G8B8 = 22, } internal enum MultisampleType { None = 0, - } internal enum SwapEffect { Discard = 1, - } - } From 57e58df8beaa27e0119ef21557af36808922f39e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julius=20H=C3=A4ger?= Date: Mon, 13 Mar 2023 13:52:09 +0100 Subject: [PATCH 05/19] Implemented proper COM interop. Switched back to .net core 3.1. --- src/Example/Example.csproj | 4 +- src/GLWpfControl/DXGLContext.cs | 88 ++++--- src/GLWpfControl/GLWpfControl.cs | 77 +++--- src/GLWpfControl/GLWpfControl.csproj | 5 +- src/GLWpfControl/GLWpfControlRenderer.cs | 49 ++-- src/GLWpfControl/GLWpfControlSettings.cs | 32 ++- src/GLWpfControl/Interop/DXInterop.cs | 310 +++++++++++++++++++++-- 7 files changed, 430 insertions(+), 135 deletions(-) diff --git a/src/Example/Example.csproj b/src/Example/Example.csproj index c9d1175..bd75293 100644 --- a/src/Example/Example.csproj +++ b/src/Example/Example.csproj @@ -2,7 +2,7 @@ WinExe - netcoreapp5.0-windows + netcoreapp3.1 true false Example.App @@ -13,6 +13,6 @@ - + diff --git a/src/GLWpfControl/DXGLContext.cs b/src/GLWpfControl/DXGLContext.cs index 76ea186..cac8fb2 100644 --- a/src/GLWpfControl/DXGLContext.cs +++ b/src/GLWpfControl/DXGLContext.cs @@ -11,37 +11,37 @@ 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. + /// 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; private static NativeWindow GlfwWindow; private static HwndSource HwndSource; - - /// The number of active controls using the shared context. - private static int _sharedContextReferenceCount; - public DxGlContext(GLWpfControlSettings settings) { + /// The number of active controls using the shared context. + private static int _sharedContextReferenceCount; - DXInterop.CheckHResult(DXInterop.Direct3DCreate9Ex(DXInterop.DefaultSdkVersion, out var dxContextHandle)); - DxContextHandle = dxContextHandle; + public DxGlContext(GLWpfControlSettings settings) + { + DXInterop.CheckHResult(DXInterop.Direct3DCreate9Ex(DXInterop.DefaultSdkVersion, out DXInterop.IDirect3D9Ex dxContext)); + DxContext = dxContext; var deviceParameters = new PresentationParameters { @@ -61,19 +61,16 @@ public DxGlContext(GLWpfControlSettings settings) { MultiSampleType = MultisampleType.None }; - DXInterop.CheckHResult( - DXInterop.CreateDeviceEx( - dxContextHandle, - 0, - DeviceType.HAL, // use hardware rasterization - IntPtr.Zero, - CreateFlags.HardwareVertexProcessing | - CreateFlags.Multithreaded | - CreateFlags.PureDevice, - ref deviceParameters, - IntPtr.Zero, - out var dxDeviceHandle)); - DxDeviceHandle = dxDeviceHandle; + dxContext.CreateDeviceEx( + 0, + DeviceType.HAL, + IntPtr.Zero, + CreateFlags.HardwareVertexProcessing | CreateFlags.Multithreaded | CreateFlags.PureDevice, + ref deviceParameters, + IntPtr.Zero, + out DXInterop.IDirect3DDevice9Ex dxDevice); + + DxDevice = dxDevice; // if the graphics context is null, we use the shared context. if (settings.ContextToUse != null) { @@ -83,20 +80,24 @@ public DxGlContext(GLWpfControlSettings settings) { GraphicsContext = GetOrCreateSharedOpenGLContext(settings); } - GlDeviceHandle = Wgl.DXOpenDeviceNV(dxDeviceHandle); + GLDeviceHandle = Wgl.DXOpenDeviceNV(dxDevice.Handle); } - private static IGraphicsContext GetOrCreateSharedOpenGLContext(GLWpfControlSettings settings) { - if (_sharedContext != null) { + private static IGraphicsContext GetOrCreateSharedOpenGLContext(GLWpfControlSettings settings) + { + if (_sharedContext != null) + { var isSameContext = GLWpfControlSettings.WouldResultInSameContext(settings, _sharedContextSettings); - if (!isSameContext) { + 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 { + else + { var nws = NativeWindowSettings.Default; nws.StartFocused = false; nws.StartVisible = false; @@ -130,10 +131,13 @@ private static IGraphicsContext GetOrCreateSharedOpenGLContext(GLWpfControlSetti return _sharedContext; } - public void Dispose() { + 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) { + if (ReferenceEquals(_sharedContext, GraphicsContext)) + { + if (Interlocked.Decrement(ref _sharedContextReferenceCount) == 0) + { GlfwWindow.Dispose(); HwndSource.Dispose(); } diff --git a/src/GLWpfControl/GLWpfControl.cs b/src/GLWpfControl/GLWpfControl.cs index 8dc2d4f..ea4649b 100644 --- a/src/GLWpfControl/GLWpfControl.cs +++ b/src/GLWpfControl/GLWpfControl.cs @@ -9,7 +9,6 @@ using OpenTK.Wpf.Interop; using System.Windows.Interop; - #nullable enable namespace OpenTK.Wpf @@ -23,59 +22,62 @@ namespace OpenTK.Wpf /// public class GLWpfControl : FrameworkElement { - // ----------------------------------- - // EVENTS - // ----------------------------------- - + /// /// 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; + public event Action? Ready; - // ----------------------------------- - // Fields - // ----------------------------------- - private GLWpfControlSettings? _settings; private GLWpfControlRenderer? _renderer; - // ----------------------------------- - // Properties - // ----------------------------------- - + /// /// 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; + get => _settings?.RenderContinuously ?? throw new InvalidOperationException("The control has not been started yet!"); + set + { + if (_settings == null) throw new InvalidOperationException("The control has not been started yet!"); + _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); + private TimeSpan? _lastRenderTime = TimeSpan.FromSeconds(-1); public bool CanInvokeOnHandledEvents { get; set; } = true; @@ -88,7 +90,11 @@ public GLWpfControl() : base() { } + /// /// Starts the control and rendering, using the settings provided. + /// + /// + /// public void Start(GLWpfControlSettings settings) { if (_settings != null) { @@ -109,10 +115,10 @@ 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); - } + { + EventManager.RegisterClassHandler(typeof(Control), Keyboard.KeyDownEvent, new KeyEventHandler(OnKeyDown), CanInvokeOnHandledEvents); + EventManager.RegisterClassHandler(typeof(Control), Keyboard.KeyUpEvent, new KeyEventHandler(OnKeyUp), CanInvokeOnHandledEvents); + } Loaded += (a, b) => { InvalidateVisual(); @@ -137,6 +143,7 @@ internal void OnKeyDown(object sender, KeyEventArgs e) RaiseEvent(args); } } + internal void OnKeyUp(object sender, KeyEventArgs e) { if (e.OriginalSource != this) @@ -147,10 +154,9 @@ internal void OnKeyUp(object sender, KeyEventArgs 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 @@ -159,19 +165,21 @@ private void OnCompTargetRender(object? sender, EventArgs e) return; } - _lastRenderTime = currentRenderTime.Value; + _lastRenderTime = currentRenderTime; if (RenderContinuously) InvalidateVisual(); } - protected override void OnRender(DrawingContext drawingContext) { + protected override void OnRender(DrawingContext drawingContext) + { base.OnRender(drawingContext); var isDesignMode = DesignerProperties.GetIsInDesignMode(this); if (isDesignMode) { DrawDesignTimeHelper(this, drawingContext); } - else if (_renderer != null) { + else if (_renderer != null) + { if (_settings != null) { var dpiScaleX = 1.0; @@ -198,7 +206,8 @@ protected override void OnRender(DrawingContext drawingContext) { _renderer.Render(drawingContext); } - else { + else + { DrawUnstartedControlHelper(this, drawingContext); } } @@ -218,8 +227,6 @@ protected override void OnRenderSizeChanged(SizeChangedInfo info) } } - - internal static void DrawDesignTimeHelper(GLWpfControl control, DrawingContext drawingContext) { if (control.Visibility == Visibility.Visible && control.ActualWidth > 0 && control.ActualHeight > 0) @@ -264,13 +271,13 @@ internal static void DrawUnstartedControlHelper(GLWpfControl control, DrawingCon 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 + + // FIXME: Fix scaling! 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/GLWpfControl.csproj b/src/GLWpfControl/GLWpfControl.csproj index baf6b85..440c82f 100644 --- a/src/GLWpfControl/GLWpfControl.csproj +++ b/src/GLWpfControl/GLWpfControl.csproj @@ -1,16 +1,17 @@  - net5.0-windows + netcoreapp3.1 true OpenTK.Wpf false - + bin\$(Configuration)\$(TargetFramework) + true diff --git a/src/GLWpfControl/GLWpfControlRenderer.cs b/src/GLWpfControl/GLWpfControlRenderer.cs index 1d4083e..b987c5a 100644 --- a/src/GLWpfControl/GLWpfControlRenderer.cs +++ b/src/GLWpfControl/GLWpfControlRenderer.cs @@ -28,18 +28,18 @@ internal sealed class GLWpfControlRenderer { /// The height of this buffer in pixels. public int FramebufferHeight { get; private set; } - /// The OpenGL framebuffer handle. + /// The OpenGL framebuffer handle. public int FrameBufferHandle { get; private set; } - /// The OpenGL Framebuffer width + /// The OpenGL Framebuffer width public int Width => D3dImage == null ? FramebufferWidth : 0; - - /// The OpenGL Framebuffer height + + /// The OpenGL Framebuffer height public int Height => D3dImage == null ? FramebufferHeight : 0; public D3DImage D3dImage { get; private set; } - public IntPtr DxRenderTargetHandle { get; private set; } + public DXInterop.IDirect3DSurface9 DxRenderTarget { get; private set; } public IntPtr DxInteropRegisteredHandle { get; private set; } @@ -57,46 +57,48 @@ public GLWpfControlRenderer(GLWpfControlSettings settings) _context = new DxGlContext(settings); } - public void SetSize(int width, int height, double dpiScaleX, double dpiScaleY, Format format) { - if (D3dImage == null || FramebufferWidth != width || FramebufferHeight != height) { + public void SetSize(int width, int height, double dpiScaleX, double dpiScaleY, Format format) + { + if (D3dImage == null || FramebufferWidth != width || FramebufferHeight != height) + { //D3dImage?.Dispose(); if (D3dImage != null) { GL.DeleteFramebuffer(GLFramebufferHandle); GL.DeleteRenderbuffer(GLDepthRenderBufferHandle); GL.DeleteTexture(GLSharedTextureHandle); - Wgl.DXUnregisterObjectNV(_context.GlDeviceHandle, DxInteropRegisteredHandle); - DXInterop.Release(DxRenderTargetHandle); + Wgl.DXUnregisterObjectNV(_context.GLDeviceHandle, DxInteropRegisteredHandle); + DxRenderTarget.Release(); } D3dImage = null; - if (width > 0 && height > 0) { + if (width > 0 && height > 0) + { //_framebuffer = new DxGLFramebuffer(_context, width, height, dpiScaleX, dpiScaleY, format); 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, + _context.DxDevice.CreateRenderTarget( FramebufferWidth, FramebufferHeight, format, MultisampleType.None, 0, false, - out var dxRenderTargetHandle, + out DXInterop.IDirect3DSurface9 dxRenderTarget, ref dxSharedHandle); - DxRenderTargetHandle = dxRenderTargetHandle; + DxRenderTarget = dxRenderTarget; - Wgl.DXSetResourceShareHandleNV(dxRenderTargetHandle, dxSharedHandle); + Wgl.DXSetResourceShareHandleNV(dxRenderTarget.Handle, dxSharedHandle); GLFramebufferHandle = GL.GenFramebuffer(); GLSharedTextureHandle = GL.GenTexture(); var genHandle = Wgl.DXRegisterObjectNV( - _context.GlDeviceHandle, - dxRenderTargetHandle, + _context.GLDeviceHandle, + dxRenderTarget.Handle, (uint)GLSharedTextureHandle, (uint)TextureTarget.Texture2D, WGL_NV_DX_interop.AccessReadWrite); @@ -132,13 +134,14 @@ public void SetSize(int width, int height, double dpiScaleX, double dpiScaleY, F TranslateTransform = new TranslateTransform(0, height); FlipYTransform = new ScaleTransform(1, -1); - } } } - public void Render(DrawingContext drawingContext) { - if (D3dImage == null) { + public void Render(DrawingContext drawingContext) + { + if (D3dImage == null) + { return; } var curFrameStamp = _stopwatch.Elapsed; @@ -147,7 +150,7 @@ public void Render(DrawingContext drawingContext) { // Lock the interop object, DX calls to the framebuffer are no longer valid D3dImage.Lock(); - Wgl.DXLockObjectsNV(_context.GlDeviceHandle, 1, new[] { DxInteropRegisteredHandle }); + Wgl.DXLockObjectsNV(_context.GLDeviceHandle, 1, new[] { DxInteropRegisteredHandle }); GL.BindFramebuffer(FramebufferTarget.Framebuffer, GLFramebufferHandle); GL.Viewport(0, 0, FramebufferWidth, FramebufferHeight); @@ -156,8 +159,8 @@ public void Render(DrawingContext drawingContext) { 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[] { DxInteropRegisteredHandle }); - D3dImage.SetBackBuffer(D3DResourceType.IDirect3DSurface9, DxRenderTargetHandle, true); + Wgl.DXUnlockObjectsNV(_context.GLDeviceHandle, 1, new[] { DxInteropRegisteredHandle }); + D3dImage.SetBackBuffer(D3DResourceType.IDirect3DSurface9, DxRenderTarget.Handle, true); D3dImage.AddDirtyRect(new Int32Rect(0, 0, FramebufferWidth, FramebufferHeight)); D3dImage.Unlock(); diff --git a/src/GLWpfControl/GLWpfControlSettings.cs b/src/GLWpfControl/GLWpfControlSettings.cs index a87fe39..0cc42fa 100644 --- a/src/GLWpfControl/GLWpfControlSettings.cs +++ b/src/GLWpfControl/GLWpfControlSettings.cs @@ -3,37 +3,53 @@ using OpenTK.Windowing.Common; namespace OpenTK.Wpf { - public sealed class GLWpfControlSettings : ICloneable { + 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. + /// + [CLSCompliant(false)] public IGraphicsContext ContextToUse { get; set; } + /// /// May be null. If so, default bindings context will be used. + /// + [CLSCompliant(false)] public IBindingsContext BindingsContext { get; set; } + [CLSCompliant(false)] public ContextFlags GraphicsContextFlags { get; set; } = ContextFlags.Default; + + [CLSCompliant(false)] public ContextProfile GraphicsProfile { get; set; } = ContextProfile.Any; public int MajorVersion { get; set; } = 3; public int MinorVersion { get; set; } = 3; - /// If we are using an external context for the control. + /// If we are using an external context for the control. public bool IsUsingExternalContext => ContextToUse != null; - - /// Determines if two settings would result in the same context being created. + + /// Determines if two settings would result in the same context being created. [Pure] internal static bool WouldResultInSameContext(GLWpfControlSettings a, GLWpfControlSettings b) { if (a.MajorVersion != b.MajorVersion) { @@ -56,9 +72,13 @@ internal static bool WouldResultInSameContext(GLWpfControlSettings a, GLWpfContr } - public object Clone() + /// + /// Makes a shallow clone of this object. + /// + /// The cloned object. + public GLWpfControlSettings Clone() { - return this.MemberwiseClone(); + return (GLWpfControlSettings)this.MemberwiseClone(); } } } diff --git a/src/GLWpfControl/Interop/DXInterop.cs b/src/GLWpfControl/Interop/DXInterop.cs index f5c53cd..34b0469 100644 --- a/src/GLWpfControl/Interop/DXInterop.cs +++ b/src/GLWpfControl/Interop/DXInterop.cs @@ -1,49 +1,309 @@ -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 { + public const uint DefaultSdkVersion = 32; + + [DllImport("d3d9.dll")] + public 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 uint NativeRelease(IntPtr resourceHandle); + public static void CheckHResult(int hresult) { Marshal.ThrowExceptionForHR(hresult); } - public const uint DefaultSdkVersion = 32; - private const int CreateDeviceEx_Offset = 20; - private const int CreateRenderTarget_Offset = 28; - private const int Release_Offset = 2; + public static readonly Guid IID_IDirect3D9Ex = new Guid(0x02177241, 0x69FC, 0x400C, 0x8F, 0xF1, 0x93, 0xA4, 0x4D, 0xF6, 0x86, 0x1D); - 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); - private delegate uint NativeRelease(IntPtr resourceHandle); + public unsafe struct IUnknown + { + public struct _VTable + { + public IntPtr QueryInterface; + public IntPtr AddRef; + public IntPtr Release; + } - [DllImport("d3d9.dll")] - public static extern int Direct3DCreate9Ex(uint SdkVersion, out IntPtr ctx); + public _VTable** VTable; + + public 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 static int CreateDeviceEx(IntPtr contextHandle, int adapter, DeviceType deviceType, IntPtr focusWindowHandle, CreateFlags behaviorFlags, ref PresentationParameters presentationParameters, IntPtr fullscreenDisplayMode, out IntPtr deviceHandle) + public uint Release() + { + NativeRelease method = Marshal.GetDelegateForFunctionPointer((*VTable)->Release); + // FIXME: Figure out how we want to reference things + return method((IntPtr)VTable); + } + } + + public unsafe struct IDirect3D9Ex { - 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); + 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; + + public 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 static int CreateRenderTarget(IntPtr deviceHandle, int width, int height, Format format, MultisampleType multisample, int multisampleQuality, bool lockable, out IntPtr surfaceHandle, ref IntPtr sharedHandle) + public unsafe struct IDirect3DDevice9Ex { - 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); + 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; + + public 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 static uint Release(IntPtr resourceHandle) + public unsafe struct IDirect3DSurface9 { - IntPtr vTable = Marshal.ReadIntPtr(resourceHandle, 0); - IntPtr functionPointer = Marshal.ReadIntPtr(vTable, Release_Offset * IntPtr.Size); - NativeRelease method = Marshal.GetDelegateForFunctionPointer(functionPointer); - return method(resourceHandle); + 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; + + public 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); + } } } } From e70ea99f36a815d65e394c9615cb90e82db52734 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julius=20H=C3=A4ger?= Date: Mon, 13 Mar 2023 14:00:54 +0100 Subject: [PATCH 06/19] Fixed some style issues and made COM error checking part of DxInterop. --- .editorconfig | 4 ++++ GLWpfControl.sln | 5 +++++ src/Example/Example.csproj | 29 +++++++++++++++------------ src/GLWpfControl/DXGLContext.cs | 2 +- src/GLWpfControl/GLWpfControl.csproj | 27 +++++++++++++------------ src/GLWpfControl/Interop/DXInterop.cs | 13 ++++++++++-- 6 files changed, 51 insertions(+), 29 deletions(-) create mode 100644 .editorconfig 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/src/Example/Example.csproj b/src/Example/Example.csproj index bd75293..281b14e 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 - true - false - Example.App - + + + - - - + + + - - - + + + diff --git a/src/GLWpfControl/DXGLContext.cs b/src/GLWpfControl/DXGLContext.cs index cac8fb2..cb6fb64 100644 --- a/src/GLWpfControl/DXGLContext.cs +++ b/src/GLWpfControl/DXGLContext.cs @@ -40,7 +40,7 @@ internal sealed class DxGlContext : IDisposable public DxGlContext(GLWpfControlSettings settings) { - DXInterop.CheckHResult(DXInterop.Direct3DCreate9Ex(DXInterop.DefaultSdkVersion, out DXInterop.IDirect3D9Ex dxContext)); + DXInterop.Direct3DCreate9Ex(DXInterop.DefaultSdkVersion, out DXInterop.IDirect3D9Ex dxContext); DxContext = dxContext; var deviceParameters = new PresentationParameters diff --git a/src/GLWpfControl/GLWpfControl.csproj b/src/GLWpfControl/GLWpfControl.csproj index 440c82f..5e25566 100644 --- a/src/GLWpfControl/GLWpfControl.csproj +++ b/src/GLWpfControl/GLWpfControl.csproj @@ -1,17 +1,18 @@  - - netcoreapp3.1 - true - OpenTK.Wpf - false - + + netcoreapp3.1 + true + OpenTK.Wpf + false + 9.0 + - - - + + + - - bin\$(Configuration)\$(TargetFramework) - true - + + bin\$(Configuration)\$(TargetFramework) + true + diff --git a/src/GLWpfControl/Interop/DXInterop.cs b/src/GLWpfControl/Interop/DXInterop.cs index 34b0469..3380ad7 100644 --- a/src/GLWpfControl/Interop/DXInterop.cs +++ b/src/GLWpfControl/Interop/DXInterop.cs @@ -13,10 +13,19 @@ 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; - [DllImport("d3d9.dll")] - public static extern int Direct3DCreate9Ex(uint SdkVersion, out IDirect3D9Ex ctx); + 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); From bb42362290fb06f2b0d7cd222bfb5bd3daa87ada Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julius=20H=C3=A4ger?= Date: Mon, 13 Mar 2023 17:18:59 +0100 Subject: [PATCH 07/19] Move comments --- src/GLWpfControl/GLWpfControlRenderer.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/GLWpfControl/GLWpfControlRenderer.cs b/src/GLWpfControl/GLWpfControlRenderer.cs index b987c5a..5a88c78 100644 --- a/src/GLWpfControl/GLWpfControlRenderer.cs +++ b/src/GLWpfControl/GLWpfControlRenderer.cs @@ -165,12 +165,14 @@ public void Render(DrawingContext drawingContext) D3dImage.Unlock(); // Transforms are applied in reverse order - drawingContext.PushTransform(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(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 rotate the image by 180 deg + drawingContext.PushTransform(FlipYTransform); // dpi scaled rectangle from the image var rect = new Rect(0, 0, D3dImage.Width, D3dImage.Height); - drawingContext.DrawImage(D3dImage, rect); // Draw the image source + drawingContext.DrawImage(D3dImage, rect); // Draw the image source drawingContext.Pop(); // Remove the scale transform drawingContext.Pop(); // Remove the translation transform From 84f0b734ef59b41fe0f29a959261fbe7ee156fcf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julius=20H=C3=A4ger?= Date: Mon, 1 May 2023 18:15:19 +0200 Subject: [PATCH 08/19] Added code to check if DX rendertarget is created with multisamples --- src/GLWpfControl/GLWpfControlRenderer.cs | 10 +- src/GLWpfControl/Interop/DXInterop.cs | 21 ++++ src/GLWpfControl/Interop/Enums.cs | 145 ++++++++++++++++++++++- 3 files changed, 172 insertions(+), 4 deletions(-) diff --git a/src/GLWpfControl/GLWpfControlRenderer.cs b/src/GLWpfControl/GLWpfControlRenderer.cs index 5a88c78..25e9983 100644 --- a/src/GLWpfControl/GLWpfControlRenderer.cs +++ b/src/GLWpfControl/GLWpfControlRenderer.cs @@ -84,7 +84,7 @@ public void SetSize(int width, int height, double dpiScaleX, double dpiScaleY, F FramebufferWidth, FramebufferHeight, format, - MultisampleType.None, + MultisampleType.D3DMULTISAMPLE_NONE, 0, false, out DXInterop.IDirect3DSurface9 dxRenderTarget, @@ -93,6 +93,12 @@ public void SetSize(int width, int height, double dpiScaleX, double dpiScaleY, F Wgl.DXSetResourceShareHandleNV(dxRenderTarget.Handle, dxSharedHandle); + { + DxRenderTarget.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}"); + } + GLFramebufferHandle = GL.GenFramebuffer(); GLSharedTextureHandle = GL.GenTexture(); @@ -160,7 +166,7 @@ public void Render(DrawingContext drawingContext) // 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[] { DxInteropRegisteredHandle }); - D3dImage.SetBackBuffer(D3DResourceType.IDirect3DSurface9, DxRenderTarget.Handle, true); + D3dImage.SetBackBuffer(System.Windows.Interop.D3DResourceType.IDirect3DSurface9, DxRenderTarget.Handle, true); D3dImage.AddDirtyRect(new Int32Rect(0, 0, FramebufferWidth, FramebufferHeight)); D3dImage.Unlock(); diff --git a/src/GLWpfControl/Interop/DXInterop.cs b/src/GLWpfControl/Interop/DXInterop.cs index 9bfaa75..944b3cc 100644 --- a/src/GLWpfControl/Interop/DXInterop.cs +++ b/src/GLWpfControl/Interop/DXInterop.cs @@ -31,6 +31,8 @@ public static void Direct3DCreate9Ex(uint SdkVersion, out IDirect3D9Ex context) 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 uint NativeRelease(IntPtr resourceHandle); + private delegate uint NativeGetDesc(IDirect3DSurface9 surfaceHandle, out D3DSURFACE_DESC pDesc); + public static void CheckHResult(int hresult) { Marshal.ThrowExceptionForHR(hresult); @@ -277,6 +279,18 @@ public void CreateRenderTarget(int width, int height, Format format, Multisample } } + 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; + } + public unsafe struct IDirect3DSurface9 { public struct _VTable @@ -307,6 +321,13 @@ public struct _VTable public 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); diff --git a/src/GLWpfControl/Interop/Enums.cs b/src/GLWpfControl/Interop/Enums.cs index 664bdad..a810ca7 100644 --- a/src/GLWpfControl/Interop/Enums.cs +++ b/src/GLWpfControl/Interop/Enums.cs @@ -18,6 +18,93 @@ internal enum DeviceType 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 { Unknown = 0, @@ -31,13 +118,67 @@ internal enum Format X8R8G8B8 = 22, } - 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, + } } From c07acf1974a45bd1f64b8487b7c0af7bdb063381 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julius=20H=C3=A4ger?= Date: Mon, 1 May 2023 18:17:31 +0200 Subject: [PATCH 09/19] Fixed compile error after rename --- src/GLWpfControl/DXGLContext.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/GLWpfControl/DXGLContext.cs b/src/GLWpfControl/DXGLContext.cs index cb6fb64..5191a61 100644 --- a/src/GLWpfControl/DXGLContext.cs +++ b/src/GLWpfControl/DXGLContext.cs @@ -58,7 +58,7 @@ public DxGlContext(GLWpfControlSettings settings) Flags = 0, FullScreen_RefreshRateInHz = 0, MultiSampleQuality = 0, - MultiSampleType = MultisampleType.None + MultiSampleType = MultisampleType.D3DMULTISAMPLE_NONE }; dxContext.CreateDeviceEx( From 920cefd59e5b915a419036d4ecc714bcf11700e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julius=20H=C3=A4ger?= Date: Thu, 30 May 2024 00:08:55 +0200 Subject: [PATCH 10/19] Bunch of cleaup. Enabled MSAA framebuffer sharing. Removed the one shared context, every control gets it's own OpenGL context. --- src/Example/Example.csproj | 2 +- src/Example/ExampleScene.cs | 132 +++++++++++++++++--- src/Example/MainWindow.xaml.cs | 13 +- src/Example/TabbedMainWindowTest.xaml.cs | 25 ++-- src/GLWpfControl/DXGLContext.cs | 152 ++++++++++++++--------- src/GLWpfControl/GLWpfControl.cs | 28 +++-- src/GLWpfControl/GLWpfControl.csproj | 2 +- src/GLWpfControl/GLWpfControlRenderer.cs | 147 ++++++++++++++-------- src/GLWpfControl/GLWpfControlSettings.cs | 60 +++++---- src/GLWpfControl/Interop/DXInterop.cs | 21 +++- src/GLWpfControl/Interop/Enums.cs | 6 + 11 files changed, 403 insertions(+), 185 deletions(-) diff --git a/src/Example/Example.csproj b/src/Example/Example.csproj index 281b14e..0372812 100644 --- a/src/Example/Example.csproj +++ b/src/Example/Example.csproj @@ -16,6 +16,6 @@ - + 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.cs b/src/Example/MainWindow.xaml.cs index fc606a2..e24f924 100644 --- a/src/Example/MainWindow.xaml.cs +++ b/src/Example/MainWindow.xaml.cs @@ -7,20 +7,27 @@ namespace Example { /// Interaction logic for MainWindow.xaml /// public sealed partial class MainWindow { + + ExampleScene mainScene = new ExampleScene(); + ExampleScene insetScene = new ExampleScene(); + public MainWindow() { InitializeComponent(); - var mainSettings = new GLWpfControlSettings {MajorVersion = 2, MinorVersion = 1}; + var mainSettings = new GLWpfControlSettings {MajorVersion = 2, MinorVersion = 1, Samples = 4}; OpenTkControl.Start(mainSettings); + mainScene.Initialize(); + var insetSettings = new GLWpfControlSettings {MajorVersion = 2, MinorVersion = 1, RenderContinuously = false}; InsetControl.Start(insetSettings); + 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..5fe6ca0 100644 --- a/src/Example/TabbedMainWindowTest.xaml.cs +++ b/src/Example/TabbedMainWindowTest.xaml.cs @@ -9,31 +9,36 @@ namespace Example { /// public sealed partial class TabbedMainWindowTest { + 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}; + var mainSettings = new GLWpfControlSettings {MajorVersion = 4, MinorVersion = 5, 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(); + + var insetSettings = new GLWpfControlSettings {MajorVersion = 4, MinorVersion = 5, 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}; - Control3.Start(transparentSettings); - } + scene2.Initialize(); - private void OpenTkControl_OnRender(TimeSpan delta) { - ExampleScene.Render(); + var transparentSettings = new GLWpfControlSettings { MajorVersion = 4, MinorVersion = 5, Profile = ContextProfile.Compatability, ContextFlags = ContextFlags.Debug, TransparentBackground = true}; + Control3.Start(transparentSettings); + scene3.Initialize(); } private void Control2_OnRender(TimeSpan delta) { - ExampleScene.Render(); + scene1.Render(); } private void Control1_OnRender(TimeSpan delta) { - ExampleScene.Render(); + 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 5191a61..1875415 100644 --- a/src/GLWpfControl/DXGLContext.cs +++ b/src/GLWpfControl/DXGLContext.cs @@ -1,25 +1,29 @@ using System; +using System.ComponentModel; +using System.Diagnostics; using System.Runtime.InteropServices; using System.Threading; using System.Windows; using System.Windows.Interop; +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; +#nullable enable + 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. + /// 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. + /// 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. @@ -27,29 +31,26 @@ internal sealed class DxGlContext : IDisposable /// 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; - private static NativeWindow GlfwWindow; - private static HwndSource HwndSource; + /// The GLFW window that provides the OpenGL context. Null if a context was provided externally. + private NativeWindow? GlfwWindow { get; } - /// The number of active controls using the shared context. - private static int _sharedContextReferenceCount; +#if DEBUG + private readonly static DebugProc DebugProcCallback = Window_DebugProc; +#endif 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, @@ -58,14 +59,14 @@ public DxGlContext(GLWpfControlSettings settings) Flags = 0, FullScreen_RefreshRateInHz = 0, MultiSampleQuality = 0, - MultiSampleType = MultisampleType.D3DMULTISAMPLE_NONE + MultiSampleType = MultisampleType.D3DMULTISAMPLE_NONE, }; dxContext.CreateDeviceEx( 0, DeviceType.HAL, IntPtr.Zero, - CreateFlags.HardwareVertexProcessing | CreateFlags.Multithreaded | CreateFlags.PureDevice, + CreateFlags.HardwareVertexProcessing | CreateFlags.Multithreaded | CreateFlags.PureDevice | CreateFlags.FpuPreserve, ref deviceParameters, IntPtr.Zero, out DXInterop.IDirect3DDevice9Ex dxDevice); @@ -77,71 +78,98 @@ public DxGlContext(GLWpfControlSettings settings) GraphicsContext = settings.ContextToUse; } else { - GraphicsContext = GetOrCreateSharedOpenGLContext(settings); - } - - GLDeviceHandle = Wgl.DXOpenDeviceNV(dxDevice.Handle); - } - - 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. + // 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; GlfwWindow = new NativeWindow(nws); - var provider = settings.BindingsContext ?? new GLFWBindingsContext(); + 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; - HwndSource = new HwndSource(0, 0, 0, 0, 0, "GLWpfControl", baseHandle); - - _sharedContext = GlfwWindow.Context; - _sharedContextSettings = settings; - _sharedContext.MakeCurrent(); + +#if DEBUG + GL.DebugMessageCallback(DebugProcCallback, IntPtr.Zero); + GL.Enable(EnableCap.DebugOutput); + GL.Enable(EnableCap.DebugOutputSynchronous); +#endif } - // FIXME: - // This has a race condition where we think we still have the - // shared context available but it's been deleted when we get here. - Interlocked.Increment(ref _sharedContextReferenceCount); - return _sharedContext; + GLDeviceHandle = Wgl.DXOpenDeviceNV(dxDevice.Handle); + if (GLDeviceHandle == IntPtr.Zero) + { + throw new Win32Exception(DXInterop.GetLastError()); + } } public void Dispose() { - // we only dispose of the graphics context if we're using the shared one. - if (ReferenceEquals(_sharedContext, GraphicsContext)) + if (Wgl.DXCloseDeviceNV(DxDevice.Handle) == false) + { + throw new Win32Exception(DXInterop.GetLastError()); + } + GlfwWindow?.Dispose(); + DxDevice.Release(); + DxContext.Release(); + } + +#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) { - if (Interlocked.Decrement(ref _sharedContextReferenceCount) == 0) + switch (severity) { - GlfwWindow.Dispose(); - HwndSource.Dispose(); + 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/GLWpfControl.cs b/src/GLWpfControl/GLWpfControl.cs index ea4649b..b43283c 100644 --- a/src/GLWpfControl/GLWpfControl.cs +++ b/src/GLWpfControl/GLWpfControl.cs @@ -100,7 +100,7 @@ public void Start(GLWpfControlSettings settings) if (_settings != null) { throw new InvalidOperationException($"{nameof(Start)} must only be called once for a given {nameof(GLWpfControl)}"); } - _settings = (GLWpfControlSettings)settings.Clone(); + _settings = settings.Clone(); _renderer = new GLWpfControlRenderer(_settings); _renderer.GLRender += timeDelta => Render?.Invoke(timeDelta); _renderer.GLAsyncRender += () => AsyncRender?.Invoke(); @@ -129,7 +129,12 @@ public void Start(GLWpfControlSettings settings) 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 @@ -182,26 +187,33 @@ protected override void OnRender(DrawingContext drawingContext) { if (_settings != null) { - var dpiScaleX = 1.0; - var dpiScaleY = 1.0; + double dpiScaleX = 1.0; + double dpiScaleY = 1.0; if (_settings.UseDeviceDpi) { - var presentationSource = PresentationSource.FromVisual(this); + 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"); - var transformToDevice = presentationSource.CompositionTarget.TransformToDevice; + Matrix transformToDevice = presentationSource.CompositionTarget.TransformToDevice; dpiScaleX = transformToDevice.M11; dpiScaleY = transformToDevice.M22; } } + + Format format = _settings.TransparentBackground ? Format.A8R8G8B8 : Format.X8R8G8B8; - var 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.SetSize((int)RenderSize.Width, (int)RenderSize.Height, dpiScaleX, dpiScaleY, format); + _renderer.ReallocateFramebufferIfNeeded(RenderSize.Width, RenderSize.Height, dpiScaleX, dpiScaleY, format, msaaType); } _renderer.Render(drawingContext); diff --git a/src/GLWpfControl/GLWpfControl.csproj b/src/GLWpfControl/GLWpfControl.csproj index 5e25566..2ad017e 100644 --- a/src/GLWpfControl/GLWpfControl.csproj +++ b/src/GLWpfControl/GLWpfControl.csproj @@ -8,7 +8,7 @@ - + diff --git a/src/GLWpfControl/GLWpfControlRenderer.cs b/src/GLWpfControl/GLWpfControlRenderer.cs index 25e9983..4b5131b 100644 --- a/src/GLWpfControl/GLWpfControlRenderer.cs +++ b/src/GLWpfControl/GLWpfControlRenderer.cs @@ -1,5 +1,6 @@ using System; using System.Diagnostics; +using System.Runtime.CompilerServices; using System.Windows; using System.Windows.Interop; using System.Windows.Media; @@ -10,8 +11,7 @@ namespace OpenTK.Wpf { - - /// Renderer that uses DX_Interop for a fast-path. + /// Renderer that uses DX_Interop for a fast-path. internal sealed class GLWpfControlRenderer { private readonly Stopwatch _stopwatch = Stopwatch.StartNew(); @@ -31,6 +31,9 @@ internal sealed class GLWpfControlRenderer { /// 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; @@ -39,13 +42,15 @@ internal sealed class GLWpfControlRenderer { public D3DImage D3dImage { get; private set; } - public DXInterop.IDirect3DSurface9 DxRenderTarget { get; private set; } + public DXInterop.IDirect3DSurface9 DxColorRenderTarget { get; private set; } + public DXInterop.IDirect3DSurface9 DxDepthStencilRenderTarget { get; private set; } - public IntPtr DxInteropRegisteredHandle { get; private set; } + public IntPtr DxInteropColorRenderTargetRegisteredHandle { get; private set; } + public IntPtr DxInteropDepthStencilRenderTargetRegisteredHandle { get; private set; } public int GLFramebufferHandle { get; private set; } - private int GLSharedTextureHandle { get; set; } - private int GLDepthRenderBufferHandle { get; set; } + private int GLSharedColorRenderbufferHandle { get; set; } + private int GLSharedDepthRenderRenderbufferHandle { get; set; } public TranslateTransform TranslateTransform { get; private set; } public ScaleTransform FlipYTransform { get; private set; } @@ -57,84 +62,116 @@ public GLWpfControlRenderer(GLWpfControlSettings settings) _context = new DxGlContext(settings); } - public void SetSize(int width, int height, double dpiScaleX, double dpiScaleY, Format format) + public void ReallocateFramebufferIfNeeded(double width, double height, double dpiScaleX, double dpiScaleY, Format format, MultisampleType msaaType) { - if (D3dImage == null || FramebufferWidth != width || FramebufferHeight != height) + int newWidth = (int)Math.Ceiling(width * dpiScaleX); + int newHeight = (int)Math.Ceiling(height * dpiScaleY); + + if (D3dImage == null || FramebufferWidth != newWidth || FramebufferHeight != newHeight || MultisampleType != msaaType) { - //D3dImage?.Dispose(); if (D3dImage != null) { GL.DeleteFramebuffer(GLFramebufferHandle); - GL.DeleteRenderbuffer(GLDepthRenderBufferHandle); - GL.DeleteTexture(GLSharedTextureHandle); - Wgl.DXUnregisterObjectNV(_context.GLDeviceHandle, DxInteropRegisteredHandle); - DxRenderTarget.Release(); + 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) { - //_framebuffer = new DxGLFramebuffer(_context, width, height, dpiScaleX, dpiScaleY, format); - - FramebufferWidth = (int)Math.Ceiling(width * dpiScaleX); - FramebufferHeight = (int)Math.Ceiling(height * dpiScaleY); + FramebufferWidth = newWidth; + FramebufferHeight = newHeight; + MultisampleType = msaaType; - var dxSharedHandle = IntPtr.Zero; // Unused windows-vista legacy sharing handle. Must always be null. _context.DxDevice.CreateRenderTarget( FramebufferWidth, FramebufferHeight, format, - MultisampleType.D3DMULTISAMPLE_NONE, + msaaType, + 0, + false, + out DXInterop.IDirect3DSurface9 dxColorRenderTarget, + ref Unsafe.NullRef()); + DxColorRenderTarget = dxColorRenderTarget; + + _context.DxDevice.CreateDepthStencilSurface( + FramebufferWidth, + FramebufferHeight, + Format.D24S8, + msaaType, 0, false, - out DXInterop.IDirect3DSurface9 dxRenderTarget, - ref dxSharedHandle); - DxRenderTarget = dxRenderTarget; + out DXInterop.IDirect3DSurface9 dxDepthStencilRenderTarget, + ref Unsafe.NullRef()); + DxDepthStencilRenderTarget = dxDepthStencilRenderTarget; + +#if DEBUG + { + DxColorRenderTarget.GetDesc(out DXInterop.D3DSURFACE_DESC desc); - Wgl.DXSetResourceShareHandleNV(dxRenderTarget.Handle, dxSharedHandle); + Debug.WriteLine($"Render target desc: {desc.Format}, {desc.Type}, {desc.Usage}, {desc.Pool}, {desc.MultiSampleType}, {desc.MultiSampleQuality}, {desc.Width}, {desc.Height}"); + } { - DxRenderTarget.GetDesc(out DXInterop.D3DSURFACE_DESC desc); + 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(); - GLSharedTextureHandle = GL.GenTexture(); - var genHandle = Wgl.DXRegisterObjectNV( + TextureTarget colorTextureTarget = msaaType == MultisampleType.D3DMULTISAMPLE_NONE ? TextureTarget.Texture2D : TextureTarget.Texture2DMultisample; + + GLSharedColorRenderbufferHandle = GL.GenRenderbuffer(); + DxInteropColorRenderTargetRegisteredHandle = Wgl.DXRegisterObjectNV( _context.GLDeviceHandle, - dxRenderTarget.Handle, - (uint)GLSharedTextureHandle, - (uint)TextureTarget.Texture2D, + dxColorRenderTarget.Handle, + (uint)GLSharedColorRenderbufferHandle, + (uint)RenderbufferTarget.Renderbuffer, WGL_NV_DX_interop.AccessReadWrite); - DxInteropRegisteredHandle = genHandle; + 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.FramebufferTexture2D( + + GL.FramebufferRenderbuffer( FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment0, - TextureTarget.Texture2D, - GLSharedTextureHandle, 0); - - GLDepthRenderBufferHandle = GL.GenRenderbuffer(); - GL.BindRenderbuffer(RenderbufferTarget.Renderbuffer, GLDepthRenderBufferHandle); - GL.RenderbufferStorage(RenderbufferTarget.Renderbuffer, RenderbufferStorage.Depth24Stencil8, FramebufferWidth, FramebufferHeight); + 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, - GLDepthRenderBufferHandle); + GLSharedDepthRenderRenderbufferHandle); + GL.FramebufferRenderbuffer( FramebufferTarget.Framebuffer, FramebufferAttachment.StencilAttachment, RenderbufferTarget.Renderbuffer, - GLDepthRenderBufferHandle); + GLSharedDepthRenderRenderbufferHandle); - GL.BindFramebuffer(FramebufferTarget.Framebuffer, 0); + // 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); @@ -150,13 +187,19 @@ public void Render(DrawingContext drawingContext) { return; } + var curFrameStamp = _stopwatch.Elapsed; var deltaT = curFrameStamp - _lastFrameStamp; _lastFrameStamp = curFrameStamp; // Lock the interop object, DX calls to the framebuffer are no longer valid D3dImage.Lock(); - Wgl.DXLockObjectsNV(_context.GLDeviceHandle, 1, new[] { DxInteropRegisteredHandle }); + 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); @@ -165,23 +208,29 @@ public void Render(DrawingContext drawingContext) 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[] { DxInteropRegisteredHandle }); - D3dImage.SetBackBuffer(System.Windows.Interop.D3DResourceType.IDirect3DSurface9, DxRenderTarget.Handle, true); + 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 // 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 rotate the image by 180 deg + // Apply a scale where the Y axis is -1. This will flip the image vertically. drawingContext.PushTransform(FlipYTransform); - // dpi scaled rectangle from the image + // Dpi scaled rectangle from the image var rect = new Rect(0, 0, D3dImage.Width, D3dImage.Height); - drawingContext.DrawImage(D3dImage, rect); // Draw the image source + // 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(); } } } diff --git a/src/GLWpfControl/GLWpfControlSettings.cs b/src/GLWpfControl/GLWpfControlSettings.cs index 0cc42fa..71faefb 100644 --- a/src/GLWpfControl/GLWpfControlSettings.cs +++ b/src/GLWpfControl/GLWpfControlSettings.cs @@ -2,6 +2,8 @@ using System.Diagnostics.Contracts; using OpenTK.Windowing.Common; +#nullable enable + namespace OpenTK.Wpf { public sealed class GLWpfControlSettings { @@ -29,49 +31,53 @@ public sealed class GLWpfControlSettings { /// for managing the lifetime and disposal of. /// [CLSCompliant(false)] - public IGraphicsContext ContextToUse { get; set; } + public IGraphicsContext? ContextToUse { get; set; } /// /// May be null. If so, default bindings context will be used. /// [CLSCompliant(false)] - public IBindingsContext BindingsContext { get; set; } + public IBindingsContext? BindingsContext { get; set; } + + /// + /// The OpenGL context flags to use. Same as . + /// + [CLSCompliant(false)] + [Obsolete("Use ContextFlags instead.")] + public ContextFlags GraphicsContextFlags { get => ContextFlags; set => ContextFlags = value; } + /// + /// The OpenGL context flags to use. will always be set for DirectX interop purposes. + /// [CLSCompliant(false)] - public ContextFlags GraphicsContextFlags { get; set; } = ContextFlags.Default; + public ContextFlags ContextFlags { get; set; } = ContextFlags.Default; + /// + /// The OpenGL profile to use. Same as . + /// [CLSCompliant(false)] - public ContextProfile GraphicsProfile { get; set; } = ContextProfile.Any; + [Obsolete("Use Profile instead.")] + public ContextProfile GraphicsProfile { get => Profile; set => Profile = value; } + /// + /// The OpenGL profile to use. + /// + [CLSCompliant(false)] + public ContextProfile Profile { get; set; } = ContextProfile.Any; + + /// The major OpenGL version number. public int MajorVersion { get; set; } = 3; + /// The minor OpenGL version number. public int MinorVersion { get; set; } = 3; + /// + /// 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; + /// If we are using an external context for the control. public bool IsUsingExternalContext => ContextToUse != null; - /// Determines if two settings would result in the same context being created. - [Pure] - internal static bool WouldResultInSameContext(GLWpfControlSettings a, GLWpfControlSettings b) { - if (a.MajorVersion != b.MajorVersion) { - return false; - } - - if (a.MinorVersion != b.MinorVersion) { - return false; - } - - if (a.GraphicsProfile != b.GraphicsProfile) { - return false; - } - - if (a.GraphicsContextFlags != b.GraphicsContextFlags) { - return false; - } - - return true; - - } - /// /// Makes a shallow clone of this object. /// diff --git a/src/GLWpfControl/Interop/DXInterop.cs b/src/GLWpfControl/Interop/DXInterop.cs index 944b3cc..1e2e166 100644 --- a/src/GLWpfControl/Interop/DXInterop.cs +++ b/src/GLWpfControl/Interop/DXInterop.cs @@ -18,6 +18,9 @@ internal static class DXInterop public const uint DefaultSdkVersion = 32; + [DllImport("Kernel32.dll")] + public static extern int GetLastError(); + public static void Direct3DCreate9Ex(uint SdkVersion, out IDirect3D9Ex context) { int result = Direct3DCreate9Ex(SdkVersion, out context); @@ -29,6 +32,7 @@ public static void Direct3DCreate9Ex(uint SdkVersion, out IDirect3D9Ex context) 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); private delegate uint NativeGetDesc(IDirect3DSurface9 surfaceHandle, out D3DSURFACE_DESC pDesc); @@ -51,7 +55,7 @@ public struct _VTable public _VTable** VTable; - public IntPtr Handle => (IntPtr)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 }; @@ -94,7 +98,7 @@ public struct _VTable public _VTable** VTable; - public IntPtr Handle => (IntPtr)VTable; + public readonly IntPtr Handle => (IntPtr)VTable; public static explicit operator IDirect3D9Ex(IntPtr ptr) => new IDirect3D9Ex() { VTable = (_VTable**)ptr }; @@ -260,7 +264,7 @@ public struct _VTable public _VTable** VTable; - public IntPtr Handle => (IntPtr)VTable; + public readonly IntPtr Handle => (IntPtr)VTable; public uint Release() { @@ -277,6 +281,15 @@ public void CreateRenderTarget(int width, int height, Format format, Multisample 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); + } } public struct D3DSURFACE_DESC @@ -319,7 +332,7 @@ public struct _VTable public _VTable** VTable; - public IntPtr Handle => (IntPtr)VTable; + public readonly IntPtr Handle => (IntPtr)VTable; public uint GetDesc(out D3DSURFACE_DESC pDesc) { diff --git a/src/GLWpfControl/Interop/Enums.cs b/src/GLWpfControl/Interop/Enums.cs index a810ca7..9567b22 100644 --- a/src/GLWpfControl/Interop/Enums.cs +++ b/src/GLWpfControl/Interop/Enums.cs @@ -5,6 +5,7 @@ namespace OpenTK.Wpf.Interop [Flags] internal enum CreateFlags : uint { + FpuPreserve = 2, Multithreaded = 4, PureDevice = 16, HardwareVertexProcessing = 64, @@ -116,6 +117,11 @@ internal enum Format /// 32-bit RGB pixel format, where 8 bits are reserved for each color. /// 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 : int From 9c51f6fd95e2eb97e27249d4b49f9a741d112b76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julius=20H=C3=A4ger?= Date: Thu, 30 May 2024 01:30:27 +0200 Subject: [PATCH 11/19] Fixed paket file. Cleaned csproj. Other cleanup. --- src/Example/MainWindow.xaml | 4 ++-- src/Example/TabbedMainWindowTest.xaml.cs | 6 +++--- src/GLWpfControl/GLWpfControl.cs | 10 +--------- src/GLWpfControl/GLWpfControl.csproj | 7 ++----- src/GLWpfControl/paket | 4 ++-- 5 files changed, 10 insertions(+), 21 deletions(-) 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/TabbedMainWindowTest.xaml.cs b/src/Example/TabbedMainWindowTest.xaml.cs index 5fe6ca0..529803d 100644 --- a/src/Example/TabbedMainWindowTest.xaml.cs +++ b/src/Example/TabbedMainWindowTest.xaml.cs @@ -15,15 +15,15 @@ public sealed partial class TabbedMainWindowTest public TabbedMainWindowTest() { InitializeComponent(); - var mainSettings = new GLWpfControlSettings {MajorVersion = 4, MinorVersion = 5, Profile = ContextProfile.Compatability, ContextFlags = ContextFlags.Debug}; + var mainSettings = new GLWpfControlSettings {MajorVersion = 4, MinorVersion = 1, Profile = ContextProfile.Compatability, ContextFlags = ContextFlags.Debug}; Control1.Start(mainSettings); scene1.Initialize(); - var insetSettings = new GLWpfControlSettings {MajorVersion = 4, MinorVersion = 5, Profile = ContextProfile.Compatability, ContextFlags = ContextFlags.Debug, Samples = 8}; + var insetSettings = new GLWpfControlSettings {MajorVersion = 4, MinorVersion = 1, Profile = ContextProfile.Compatability, ContextFlags = ContextFlags.Debug, Samples = 8}; Control2.Start(insetSettings); scene2.Initialize(); - var transparentSettings = new GLWpfControlSettings { MajorVersion = 4, MinorVersion = 5, Profile = ContextProfile.Compatability, ContextFlags = ContextFlags.Debug, TransparentBackground = true}; + var transparentSettings = new GLWpfControlSettings { MajorVersion = 4, MinorVersion = 1, Profile = ContextProfile.Compatability, ContextFlags = ContextFlags.Debug, TransparentBackground = true}; Control3.Start(transparentSettings); scene3.Initialize(); } diff --git a/src/GLWpfControl/GLWpfControl.cs b/src/GLWpfControl/GLWpfControl.cs index ecb8479..9b0d8d6 100644 --- a/src/GLWpfControl/GLWpfControl.cs +++ b/src/GLWpfControl/GLWpfControl.cs @@ -40,27 +40,19 @@ public class GLWpfControl : FrameworkElement /// public event Action? Ready; - // ----------------------------------- - // Fields - // ----------------------------------- - /// /// Represents the dependency property for . /// 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. /// diff --git a/src/GLWpfControl/GLWpfControl.csproj b/src/GLWpfControl/GLWpfControl.csproj index 2ad017e..e12e288 100644 --- a/src/GLWpfControl/GLWpfControl.csproj +++ b/src/GLWpfControl/GLWpfControl.csproj @@ -5,14 +5,11 @@ OpenTK.Wpf false 9.0 + true + bin\$(Configuration)\$(TargetFramework) - - - bin\$(Configuration)\$(TargetFramework) - true - 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 From 3bb8d1efff0b17cda844634d03267c752c82bb6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julius=20H=C3=A4ger?= Date: Thu, 30 May 2024 01:31:12 +0200 Subject: [PATCH 12/19] Fixed nullability issue. --- src/GLWpfControl/GLWpfControl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/GLWpfControl/GLWpfControl.cs b/src/GLWpfControl/GLWpfControl.cs index 9b0d8d6..2041422 100644 --- a/src/GLWpfControl/GLWpfControl.cs +++ b/src/GLWpfControl/GLWpfControl.cs @@ -194,7 +194,7 @@ internal void OnKeyUp(object sender, KeyEventArgs e) } } - private void OnCompTargetRender(object sender, EventArgs e) + private void OnCompTargetRender(object? sender, EventArgs e) { TimeSpan? currentRenderTime = (e as RenderingEventArgs)?.RenderingTime; if(currentRenderTime == _lastRenderTime) From 4b0913fcf8ad054edd86254eaa83ba8c5e569412 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julius=20H=C3=A4ger?= Date: Thu, 30 May 2024 01:47:45 +0200 Subject: [PATCH 13/19] Implemented IDisposable. --- src/GLWpfControl/DXGLContext.cs | 7 +++++++ src/GLWpfControl/GLWpfControl.cs | 18 ++++++++++++++---- src/GLWpfControl/GLWpfControlRenderer.cs | 7 ++++++- 3 files changed, 27 insertions(+), 5 deletions(-) diff --git a/src/GLWpfControl/DXGLContext.cs b/src/GLWpfControl/DXGLContext.cs index 1875415..ed51643 100644 --- a/src/GLWpfControl/DXGLContext.cs +++ b/src/GLWpfControl/DXGLContext.cs @@ -110,6 +110,11 @@ public DxGlContext(GLWpfControlSettings settings) } } + ~DxGlContext() + { + Dispose(); + } + public void Dispose() { if (Wgl.DXCloseDeviceNV(DxDevice.Handle) == false) @@ -119,6 +124,8 @@ public void Dispose() GlfwWindow?.Dispose(); DxDevice.Release(); DxContext.Release(); + + GC.SuppressFinalize(this); } #if DEBUG diff --git a/src/GLWpfControl/GLWpfControl.cs b/src/GLWpfControl/GLWpfControl.cs index 2041422..4e95fd8 100644 --- a/src/GLWpfControl/GLWpfControl.cs +++ b/src/GLWpfControl/GLWpfControl.cs @@ -20,7 +20,7 @@ namespace OpenTK.Wpf /// /// Please do not extend this class. It has no support for that. /// - public class GLWpfControl : FrameworkElement + public class GLWpfControl : FrameworkElement, IDisposable { /// /// Called whenever rendering should occur. @@ -155,10 +155,9 @@ public void Start(GLWpfControlSettings settings) EventManager.RegisterClassHandler(typeof(Control), Keyboard.KeyUpEvent, new KeyEventHandler(OnKeyUp), CanInvokeOnHandledEvents); } - Loaded += (a, b) => { - InvalidateVisual(); - }; + Loaded += (a, b) => InvalidateVisual(); Unloaded += (a, b) => OnUnloaded(); + Ready?.Invoke(); } @@ -172,6 +171,17 @@ private void OnUnloaded() _renderer?.ReallocateFramebufferIfNeeded(0, 0, 1, 1, Format.X8R8G8B8, MultisampleType.D3DMULTISAMPLE_NONE); } + /// + /// 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() + { + _renderer?.Dispose(); + _isStarted = false; + } + // 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) diff --git a/src/GLWpfControl/GLWpfControlRenderer.cs b/src/GLWpfControl/GLWpfControlRenderer.cs index 4b5131b..a59d1db 100644 --- a/src/GLWpfControl/GLWpfControlRenderer.cs +++ b/src/GLWpfControl/GLWpfControlRenderer.cs @@ -12,7 +12,7 @@ namespace OpenTK.Wpf { /// Renderer that uses DX_Interop for a fast-path. - internal sealed class GLWpfControlRenderer { + internal sealed class GLWpfControlRenderer : IDisposable { private readonly Stopwatch _stopwatch = Stopwatch.StartNew(); private readonly DxGlContext _context; @@ -232,5 +232,10 @@ public void Render(DrawingContext drawingContext) drawingContext.Pop(); drawingContext.Pop(); } + + public void Dispose() + { + _context.Dispose(); + } } } From 8a539b2f0ba65d7cf249238b775df20e576e642b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julius=20H=C3=A4ger?= Date: Thu, 30 May 2024 02:20:36 +0200 Subject: [PATCH 14/19] Added context sharing setting. --- src/GLWpfControl/DXGLContext.cs | 1 + src/GLWpfControl/GLWpfControl.cs | 7 +++++++ src/GLWpfControl/GLWpfControlRenderer.cs | 15 ++++++++++++--- src/GLWpfControl/GLWpfControlSettings.cs | 7 +++++++ 4 files changed, 27 insertions(+), 3 deletions(-) diff --git a/src/GLWpfControl/DXGLContext.cs b/src/GLWpfControl/DXGLContext.cs index ed51643..acdca52 100644 --- a/src/GLWpfControl/DXGLContext.cs +++ b/src/GLWpfControl/DXGLContext.cs @@ -82,6 +82,7 @@ public DxGlContext(GLWpfControlSettings settings) nws.StartFocused = false; nws.StartVisible = false; nws.NumberOfSamples = 0; + 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.ContextFlags; diff --git a/src/GLWpfControl/GLWpfControl.cs b/src/GLWpfControl/GLWpfControl.cs index 4e95fd8..bfc7a87 100644 --- a/src/GLWpfControl/GLWpfControl.cs +++ b/src/GLWpfControl/GLWpfControl.cs @@ -8,6 +8,7 @@ using System.Windows.Media; using OpenTK.Wpf.Interop; using System.Windows.Interop; +using OpenTK.Windowing.Common; #nullable enable @@ -94,6 +95,12 @@ public bool RenderContinuously { /// public int FrameBufferHeight => _renderer?.Height ?? 0; + /// + /// 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); public bool CanInvokeOnHandledEvents { get; set; } = true; diff --git a/src/GLWpfControl/GLWpfControlRenderer.cs b/src/GLWpfControl/GLWpfControlRenderer.cs index a59d1db..ea48e0a 100644 --- a/src/GLWpfControl/GLWpfControlRenderer.cs +++ b/src/GLWpfControl/GLWpfControlRenderer.cs @@ -7,18 +7,22 @@ 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 : IDisposable { private readonly Stopwatch _stopwatch = Stopwatch.StartNew(); + private readonly DxGlContext _context; - public event Action GLRender; - public event Action GLAsyncRender; + public event Action? GLRender; + public event Action? GLAsyncRender; //private DxGLFramebuffer _framebuffer; @@ -40,7 +44,9 @@ internal sealed class GLWpfControlRenderer : IDisposable { /// The OpenGL Framebuffer height public int Height => D3dImage == null ? FramebufferHeight : 0; - public D3DImage D3dImage { get; private set; } + 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; } @@ -60,6 +66,9 @@ internal sealed class GLWpfControlRenderer : IDisposable { 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) diff --git a/src/GLWpfControl/GLWpfControlSettings.cs b/src/GLWpfControl/GLWpfControlSettings.cs index 71faefb..0525718 100644 --- a/src/GLWpfControl/GLWpfControlSettings.cs +++ b/src/GLWpfControl/GLWpfControlSettings.cs @@ -1,6 +1,7 @@ using System; using System.Diagnostics.Contracts; using OpenTK.Windowing.Common; +using OpenTK.Windowing.Desktop; #nullable enable @@ -33,6 +34,12 @@ public sealed class GLWpfControlSettings { [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. /// From 8edd8b168ee5e7f3e2940c612c5eea0865ace193 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julius=20H=C3=A4ger?= Date: Thu, 30 May 2024 18:54:00 +0200 Subject: [PATCH 15/19] Set Focusable=true by default. Deprecate direct event registering. --- src/Example/TabbedMainWindowTest.xaml.cs | 14 +++++-- src/GLWpfControl/GLWpfControl.cs | 47 +++++++----------------- src/GLWpfControl/GLWpfControlRenderer.cs | 2 - 3 files changed, 24 insertions(+), 39 deletions(-) diff --git a/src/Example/TabbedMainWindowTest.xaml.cs b/src/Example/TabbedMainWindowTest.xaml.cs index 529803d..fbec45c 100644 --- a/src/Example/TabbedMainWindowTest.xaml.cs +++ b/src/Example/TabbedMainWindowTest.xaml.cs @@ -1,4 +1,5 @@ using System; +using System.Diagnostics; using System.Runtime.CompilerServices; using OpenTK.Windowing.Common; using OpenTK.Wpf; @@ -15,17 +16,24 @@ public sealed partial class TabbedMainWindowTest public TabbedMainWindowTest() { InitializeComponent(); - var mainSettings = new GLWpfControlSettings {MajorVersion = 4, MinorVersion = 1, Profile = ContextProfile.Compatability, ContextFlags = ContextFlags.Debug}; + GLWpfControlSettings mainSettings = new GLWpfControlSettings {MajorVersion = 4, MinorVersion = 1, Profile = ContextProfile.Compatability, ContextFlags = ContextFlags.Debug}; Control1.Start(mainSettings); scene1.Initialize(); - var insetSettings = new GLWpfControlSettings {MajorVersion = 4, MinorVersion = 1, Profile = ContextProfile.Compatability, ContextFlags = ContextFlags.Debug, Samples = 8}; + GLWpfControlSettings insetSettings = new GLWpfControlSettings {MajorVersion = 4, MinorVersion = 1, Profile = ContextProfile.Compatability, ContextFlags = ContextFlags.Debug, Samples = 8}; Control2.Start(insetSettings); scene2.Initialize(); - var transparentSettings = new GLWpfControlSettings { MajorVersion = 4, MinorVersion = 1, Profile = ContextProfile.Compatability, ContextFlags = ContextFlags.Debug, TransparentBackground = true}; + 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; + } + + private void Control1_KeyDown(object sender, System.Windows.Input.KeyEventArgs e) + { + Debug.WriteLine(e.Key); } private void Control2_OnRender(TimeSpan delta) { diff --git a/src/GLWpfControl/GLWpfControl.cs b/src/GLWpfControl/GLWpfControl.cs index bfc7a87..dedba2c 100644 --- a/src/GLWpfControl/GLWpfControl.cs +++ b/src/GLWpfControl/GLWpfControl.cs @@ -23,6 +23,12 @@ namespace OpenTK.Wpf /// public class GLWpfControl : FrameworkElement, IDisposable { + static GLWpfControl() + { + // Default to Focusable=true. + FocusableProperty.OverrideMetadata(typeof(GLWpfControl), new FrameworkPropertyMetadata(true)); + } + /// /// Called whenever rendering should occur. /// @@ -102,9 +108,11 @@ public bool RenderContinuously { 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; /// @@ -155,13 +163,6 @@ 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(); Unloaded += (a, b) => OnUnloaded(); @@ -189,28 +190,6 @@ public void Dispose() _isStarted = false; } - // 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) - { - if (e.OriginalSource != this) - { - KeyEventArgs args = new KeyEventArgs(e.KeyboardDevice, e.InputSource, e.Timestamp, e.Key); - args.RoutedEvent = Keyboard.KeyUpEvent; - RaiseEvent(args); - } - } - private void OnCompTargetRender(object? sender, EventArgs e) { TimeSpan? currentRenderTime = (e as RenderingEventArgs)?.RenderingTime; @@ -335,9 +314,9 @@ internal static void DrawUnstartedControlHelper(GLWpfControl control, DrawingCon const string unstartedLabelText = "OpenGL content. Call Start() on the control to begin rendering."; const int size = 12; var tf = new Typeface("Arial"); - - // FIXME: Fix scaling! - var ft = new FormattedText(unstartedLabelText, CultureInfo.InvariantCulture, FlowDirection.LeftToRight, tf, size, Brushes.White) + + DpiScale dpi = VisualTreeHelper.GetDpi(control); + var ft = new FormattedText(unstartedLabelText, CultureInfo.InvariantCulture, FlowDirection.LeftToRight, tf, size, Brushes.White, dpi.PixelsPerDip) { TextAlignment = TextAlignment.Left, MaxTextWidth = width diff --git a/src/GLWpfControl/GLWpfControlRenderer.cs b/src/GLWpfControl/GLWpfControlRenderer.cs index ea48e0a..bff4428 100644 --- a/src/GLWpfControl/GLWpfControlRenderer.cs +++ b/src/GLWpfControl/GLWpfControlRenderer.cs @@ -24,8 +24,6 @@ internal sealed class GLWpfControlRenderer : IDisposable { public event Action? GLRender; public event Action? GLAsyncRender; - //private DxGLFramebuffer _framebuffer; - /// The width of this buffer in pixels. public int FramebufferWidth { get; private set; } From 20cecb216fac1d214be288a3c070a1c806091cec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julius=20H=C3=A4ger?= Date: Thu, 30 May 2024 19:24:42 +0200 Subject: [PATCH 16/19] Updated readme to reflect changes to keyboard event handling. Enabled nullable reference types for the project. Removed use of var. --- README.md | 53 ++++++---------------- src/Example/TabbedMainWindowTest.xaml.cs | 31 ++++++++++--- src/GLWpfControl/DXGLContext.cs | 2 - src/GLWpfControl/GLWpfControl.cs | 40 ++++++++-------- src/GLWpfControl/GLWpfControl.csproj | 1 + src/GLWpfControl/GLWpfControlRenderer.cs | 6 +-- src/GLWpfControl/GLWpfControlSettings.cs | 2 - src/GLWpfControl/NonReloadingTabControl.cs | 43 ++++++++++-------- 8 files changed, 86 insertions(+), 92 deletions(-) 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/TabbedMainWindowTest.xaml.cs b/src/Example/TabbedMainWindowTest.xaml.cs index fbec45c..2568cef 100644 --- a/src/Example/TabbedMainWindowTest.xaml.cs +++ b/src/Example/TabbedMainWindowTest.xaml.cs @@ -1,20 +1,24 @@ 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 { + // FIXME: Make this example make use of context sharing... ExampleScene scene1 = new ExampleScene(); ExampleScene scene2 = new ExampleScene(); ExampleScene scene3 = new ExampleScene(); - public TabbedMainWindowTest() { + public TabbedMainWindowTest() + { InitializeComponent(); GLWpfControlSettings mainSettings = new GLWpfControlSettings {MajorVersion = 4, MinorVersion = 1, Profile = ContextProfile.Compatability, ContextFlags = ContextFlags.Debug}; Control1.Start(mainSettings); @@ -29,18 +33,33 @@ public TabbedMainWindowTest() { scene3.Initialize(); Control1.KeyDown += Control1_KeyDown; + Control1.MouseMove += Control1_MouseMove; + + Keyboard.AddPreviewKeyDownHandler(this, Keyboard_PreviewKeyDown); + } + + private void Keyboard_PreviewKeyDown(object sender, KeyEventArgs e) + { + Debug.WriteLine($"Preview key down: {e.Key}"); } - private void Control1_KeyDown(object sender, System.Windows.Input.KeyEventArgs e) + private void Control1_MouseMove(object sender, MouseEventArgs e) + { + Debug.WriteLine(e.GetPosition(Control1)); + } + + private void Control1_KeyDown(object sender, KeyEventArgs e) { Debug.WriteLine(e.Key); } - private void Control2_OnRender(TimeSpan delta) { + private void Control2_OnRender(TimeSpan delta) + { scene1.Render(); } - private void Control1_OnRender(TimeSpan delta) { + private void Control1_OnRender(TimeSpan delta) + { scene2.Render(); } diff --git a/src/GLWpfControl/DXGLContext.cs b/src/GLWpfControl/DXGLContext.cs index acdca52..5a76204 100644 --- a/src/GLWpfControl/DXGLContext.cs +++ b/src/GLWpfControl/DXGLContext.cs @@ -13,8 +13,6 @@ using OpenTK.Wpf.Interop; using WindowState = OpenTK.Windowing.Common.WindowState; -#nullable enable - namespace OpenTK.Wpf { /// This contains the DirectX and OpenGL contexts used in this control. diff --git a/src/GLWpfControl/GLWpfControl.cs b/src/GLWpfControl/GLWpfControl.cs index dedba2c..0fac0de 100644 --- a/src/GLWpfControl/GLWpfControl.cs +++ b/src/GLWpfControl/GLWpfControl.cs @@ -10,8 +10,6 @@ using System.Windows.Interop; using OpenTK.Windowing.Common; -#nullable enable - namespace OpenTK.Wpf { /// @@ -210,7 +208,7 @@ protected override void OnRender(DrawingContext drawingContext) { base.OnRender(drawingContext); - var isDesignMode = DesignerProperties.GetIsInDesignMode(this); + bool isDesignMode = DesignerProperties.GetIsInDesignMode(this); if (isDesignMode) { DrawDesignTimeHelper(this, drawingContext); } @@ -259,8 +257,9 @@ protected override void OnRenderSizeChanged(SizeChangedInfo info) { base.OnRenderSizeChanged(info); - var isInDesignMode = DesignerProperties.GetIsInDesignMode(this); - if (isInDesignMode) { + bool isInDesignMode = DesignerProperties.GetIsInDesignMode(this); + if (isInDesignMode) + { return; } @@ -274,19 +273,18 @@ internal static void DrawDesignTimeHelper(GLWpfControl control, DrawingContext d { 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) + 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 }; -#pragma warning restore 618 - var redPen = new Pen(Brushes.DarkBlue, 2.0); - var rect = new Rect(1, 1, width - 1, height - 1); + 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), @@ -302,8 +300,8 @@ internal static void DrawUnstartedControlHelper(GLWpfControl control, DrawingCon { if (control.Visibility == Visibility.Visible && control.ActualWidth > 0 && control.ActualHeight > 0) { - var width = control.ActualWidth; - var height = control.ActualHeight; + 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 @@ -311,12 +309,12 @@ internal static void DrawUnstartedControlHelper(GLWpfControl control, DrawingCon return; } - const string unstartedLabelText = "OpenGL content. Call Start() on the control to begin rendering."; - const int size = 12; - var tf = new Typeface("Arial"); + 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); - var ft = new FormattedText(unstartedLabelText, CultureInfo.InvariantCulture, FlowDirection.LeftToRight, tf, size, Brushes.White, dpi.PixelsPerDip) + FormattedText ft = new FormattedText(UnstartedLabelText, CultureInfo.InvariantCulture, FlowDirection.LeftToRight, tf, Size, Brushes.White, dpi.PixelsPerDip) { TextAlignment = TextAlignment.Left, MaxTextWidth = width diff --git a/src/GLWpfControl/GLWpfControl.csproj b/src/GLWpfControl/GLWpfControl.csproj index e12e288..4e69e89 100644 --- a/src/GLWpfControl/GLWpfControl.csproj +++ b/src/GLWpfControl/GLWpfControl.csproj @@ -6,6 +6,7 @@ false 9.0 true + enable bin\$(Configuration)\$(TargetFramework) diff --git a/src/GLWpfControl/GLWpfControlRenderer.cs b/src/GLWpfControl/GLWpfControlRenderer.cs index bff4428..baf850e 100644 --- a/src/GLWpfControl/GLWpfControlRenderer.cs +++ b/src/GLWpfControl/GLWpfControlRenderer.cs @@ -195,8 +195,8 @@ public void Render(DrawingContext drawingContext) 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 @@ -231,7 +231,7 @@ public void Render(DrawingContext drawingContext) drawingContext.PushTransform(FlipYTransform); // Dpi scaled rectangle from the image - var rect = new Rect(0, 0, D3dImage.Width, D3dImage.Height); + Rect rect = new Rect(0, 0, D3dImage.Width, D3dImage.Height); // Draw the image source drawingContext.DrawImage(D3dImage, rect); diff --git a/src/GLWpfControl/GLWpfControlSettings.cs b/src/GLWpfControl/GLWpfControlSettings.cs index 0525718..a250b29 100644 --- a/src/GLWpfControl/GLWpfControlSettings.cs +++ b/src/GLWpfControl/GLWpfControlSettings.cs @@ -3,8 +3,6 @@ using OpenTK.Windowing.Common; using OpenTK.Windowing.Desktop; -#nullable enable - namespace OpenTK.Wpf { public sealed class GLWpfControlSettings { 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; } From 85cc8ca991404d2274ef922073b18e308bbf7b80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julius=20H=C3=A4ger?= Date: Thu, 30 May 2024 19:34:00 +0200 Subject: [PATCH 17/19] Fixed warnings --- src/GLWpfControl/Interop/DXInterop.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/GLWpfControl/Interop/DXInterop.cs b/src/GLWpfControl/Interop/DXInterop.cs index 1e2e166..ca6466d 100644 --- a/src/GLWpfControl/Interop/DXInterop.cs +++ b/src/GLWpfControl/Interop/DXInterop.cs @@ -46,12 +46,14 @@ public static void CheckHResult(int hresult) public unsafe struct IUnknown { +#pragma warning disable CS0649 public struct _VTable { public IntPtr QueryInterface; public IntPtr AddRef; public IntPtr Release; } +#pragma warning restore CS0649 public _VTable** VTable; @@ -70,6 +72,7 @@ public uint Release() public unsafe struct IDirect3D9Ex { +#pragma warning disable CS0649 public struct _VTable { public IntPtr QueryInterface; @@ -97,6 +100,7 @@ public struct _VTable } public _VTable** VTable; +#pragma warning restore CS0649 public readonly IntPtr Handle => (IntPtr)VTable; @@ -121,6 +125,7 @@ public void CreateDeviceEx(int adapter, DeviceType deviceType, IntPtr focusWindo public unsafe struct IDirect3DDevice9Ex { +#pragma warning disable CS0649 public struct _VTable { /*** IUnknown methods ***/ @@ -263,6 +268,7 @@ public struct _VTable } public _VTable** VTable; +#pragma warning restore CS0649 public readonly IntPtr Handle => (IntPtr)VTable; @@ -292,6 +298,7 @@ public void CreateDepthStencilSurface(int width, int height, Format format, Mult } } +#pragma warning disable CS0649 public struct D3DSURFACE_DESC { public D3DFormat Format; @@ -303,9 +310,11 @@ public struct D3DSURFACE_DESC public uint Width; public uint Height; } +#pragma warning restore CS0649 public unsafe struct IDirect3DSurface9 { +#pragma warning disable CS0649 public struct _VTable { /*** IUnknown methods ***/ @@ -331,6 +340,7 @@ public struct _VTable } public _VTable** VTable; +#pragma warning restore CS0649 public readonly IntPtr Handle => (IntPtr)VTable; From 3551c5eb6c6d9a5c4129904d858d453657a3d862 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julius=20H=C3=A4ger?= Date: Thu, 30 May 2024 19:39:36 +0200 Subject: [PATCH 18/19] Small style cleanup --- src/GLWpfControl/GLWpfControlRenderer.cs | 4 ++-- src/GLWpfControl/GLWpfControlSettings.cs | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/GLWpfControl/GLWpfControlRenderer.cs b/src/GLWpfControl/GLWpfControlRenderer.cs index baf850e..fae4e7a 100644 --- a/src/GLWpfControl/GLWpfControlRenderer.cs +++ b/src/GLWpfControl/GLWpfControlRenderer.cs @@ -15,8 +15,8 @@ namespace OpenTK.Wpf { /// Renderer that uses DX_Interop for a fast-path. - internal sealed class GLWpfControlRenderer : IDisposable { - + internal sealed class GLWpfControlRenderer : IDisposable + { private readonly Stopwatch _stopwatch = Stopwatch.StartNew(); private readonly DxGlContext _context; diff --git a/src/GLWpfControl/GLWpfControlSettings.cs b/src/GLWpfControl/GLWpfControlSettings.cs index a250b29..206797f 100644 --- a/src/GLWpfControl/GLWpfControlSettings.cs +++ b/src/GLWpfControl/GLWpfControlSettings.cs @@ -4,8 +4,8 @@ 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. From d619ff47c57017578b3fbeddb33f8df7b3b50d9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julius=20H=C3=A4ger?= Date: Thu, 30 May 2024 19:54:15 +0200 Subject: [PATCH 19/19] Fix Example function order. --- src/Example/TabbedMainWindowTest.xaml.cs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/Example/TabbedMainWindowTest.xaml.cs b/src/Example/TabbedMainWindowTest.xaml.cs index 2568cef..411291f 100644 --- a/src/Example/TabbedMainWindowTest.xaml.cs +++ b/src/Example/TabbedMainWindowTest.xaml.cs @@ -33,7 +33,6 @@ public TabbedMainWindowTest() scene3.Initialize(); Control1.KeyDown += Control1_KeyDown; - Control1.MouseMove += Control1_MouseMove; Keyboard.AddPreviewKeyDownHandler(this, Keyboard_PreviewKeyDown); } @@ -43,22 +42,17 @@ private void Keyboard_PreviewKeyDown(object sender, KeyEventArgs e) Debug.WriteLine($"Preview key down: {e.Key}"); } - private void Control1_MouseMove(object sender, MouseEventArgs e) - { - Debug.WriteLine(e.GetPosition(Control1)); - } - private void Control1_KeyDown(object sender, KeyEventArgs e) { Debug.WriteLine(e.Key); } - private void Control2_OnRender(TimeSpan delta) + private void Control1_OnRender(TimeSpan delta) { scene1.Render(); } - private void Control1_OnRender(TimeSpan delta) + private void Control2_OnRender(TimeSpan delta) { scene2.Render(); }