[WPF] 使用Silk.NET绘制D3D9或OpenGL内容并完美解决空域问题。

  • [WPF] 使用Silk.NET绘制D3D9或OpenGL内容并完美解决空域问题。已关闭评论
  • 234 次浏览
  • A+
所属分类:.NET技术
摘要

   首先创建一个抽象类 FramebufferBase ,该类主要记录当前控件宽高和图像资源。


可扩展渲染控件实现的基本思路(D3D、OpenGL绘制所使用的基类):

[WPF] 使用Silk.NET绘制D3D9或OpenGL内容并完美解决空域问题。

 

 

 

首先创建一个抽象类 FramebufferBase,该类主要记录当前控件宽高和图像资源。

[WPF] 使用Silk.NET绘制D3D9或OpenGL内容并完美解决空域问题。[WPF] 使用Silk.NET绘制D3D9或OpenGL内容并完美解决空域问题。

public abstract class FramebufferBase : IDisposable {     public abstract int FramebufferWidth { get; }      public abstract int FramebufferHeight { get; }      public abstract D3DImage D3dImage { get; }      public abstract void Dispose(); }

View Code

接下来创建一个基本绘制控件,我这边取名为GameBase

public abstract class GameBase<TFrame> : Control where TFrame : FramebufferBase

当我们在绘制3d内容的时候,总是会先在绘制前做一个准备,比如加载Shader,设置顶点、纹理等等。。。

所以我们应该加入 准备阶段事件 和 绘制事件。

当然如果当前帧绘制完成后,我们也可以做一些操作为下一次渲染做准备。

[WPF] 使用Silk.NET绘制D3D9或OpenGL内容并完美解决空域问题。[WPF] 使用Silk.NET绘制D3D9或OpenGL内容并完美解决空域问题。

public abstract event Action Ready; public abstract event Action<TimeSpan> Render; public abstract event Action<object, TimeSpan> UpdateFrame;

View Code

创建三个抽象方法 OnStart、OnDraw、OnSizeChanged

因为D3D和OpenGL创建帧和绘制的方式不太一致,所以需要提出来在继承类中做实现。

[WPF] 使用Silk.NET绘制D3D9或OpenGL内容并完美解决空域问题。[WPF] 使用Silk.NET绘制D3D9或OpenGL内容并完美解决空域问题。

protected abstract void OnStart(); protected abstract void OnDraw(DrawingContext drawingContext); protected abstract void OnSizeChanged(SizeChangedInfo sizeInfo);

View Code

重载OnRenderSizeChanged、OnRender方法

因为新版本VS加入后了设计时预览,所以我判断了下(DesignerProperties.GetIsInDesignMode)。

[WPF] 使用Silk.NET绘制D3D9或OpenGL内容并完美解决空域问题。[WPF] 使用Silk.NET绘制D3D9或OpenGL内容并完美解决空域问题。

protected override void OnRenderSizeChanged(SizeChangedInfo sizeInfo) {     if (!DesignerProperties.GetIsInDesignMode(this))     {         OnSizeChanged(sizeInfo);     } }  protected override void OnRender(DrawingContext drawingContext) {   if (DesignerProperties.GetIsInDesignMode(this))   {     DesignTimeHelper.DrawDesign(this, drawingContext);   }   else   {     if (Framebuffer != null && Framebuffer.D3dImage.IsFrontBufferAvailable)     {       OnDraw(drawingContext);        _stopwatch.Restart();     }   } }

View Code

创建一个Start方法

CompositionTarget.Rendering事件用于帧绘制并计算帧率。

[WPF] 使用Silk.NET绘制D3D9或OpenGL内容并完美解决空域问题。[WPF] 使用Silk.NET绘制D3D9或OpenGL内容并完美解决空域问题。

public void Start() {   if (!DesignerProperties.GetIsInDesignMode(this))   {     IsVisibleChanged += (_, e) =>     {       if ((bool)e.NewValue)       {         CompositionTarget.Rendering += CompositionTarget_Rendering;       }       else       {         CompositionTarget.Rendering -= CompositionTarget_Rendering;       }     };      Loaded += (_, _) => InvalidateVisual();      OnStart();   } } private void CompositionTarget_Rendering(object sender, EventArgs e) {   RenderingEventArgs args = (RenderingEventArgs)e;    if (_lastRenderTime != args.RenderingTime)   {     InvalidateVisual();      _fpsSample.Add(Convert.ToInt32(1000.0d / (args.RenderingTime.TotalMilliseconds - _lastRenderTime.TotalMilliseconds)));     // 样本数 30     if (_fpsSample.Count == 30)     {       Fps = Convert.ToInt32(_fpsSample.Average());       _fpsSample.Clear();     }      _lastRenderTime = args.RenderingTime;   } }

View Code

初期阶段,做这些准备就够了

剩下一些变量和依赖属性

[WPF] 使用Silk.NET绘制D3D9或OpenGL内容并完美解决空域问题。[WPF] 使用Silk.NET绘制D3D9或OpenGL内容并完美解决空域问题。

public static readonly DependencyProperty FpsProperty = DependencyProperty.Register(nameof(Fps), typeof(int), typeof(GameBase<TFrame>), new PropertyMetadata(0));      protected readonly Stopwatch _stopwatch = Stopwatch.StartNew(); private readonly List<int> _fpsSample = new();  protected TimeSpan _lastRenderTime = TimeSpan.FromSeconds(-1); protected TimeSpan _lastFrameStamp;  protected TFrame Framebuffer { get; set; } public int Fps {     get { return (int)GetValue(FpsProperty); }     set { SetValue(FpsProperty, value); } }

View Code

OK,基本思路就这样,接下来我将讲解具体实现。

D3D9绘制:

使用库:Silk.NET.Direct3D9

创建RenderContext类,此类主要功能是创建d3d设备及绘制格式。

创建一个d3d9的实例。

IDirect3D9Ex* direct3D9;

D3D9.GetApi().Direct3DCreate9Ex(D3D9.SdkVersion, &direct3D9);

获取屏幕基本信息。

Displaymodeex pMode = new((uint)sizeof(Displaymodeex)); direct3D9->GetAdapterDisplayModeEx(D3D9.AdapterDefault, ref pMode, null);

创建d3d9设备。

重要参数:

BackBufferFormat 这个要与获取的屏幕信息里的格式一致。

[WPF] 使用Silk.NET绘制D3D9或OpenGL内容并完美解决空域问题。[WPF] 使用Silk.NET绘制D3D9或OpenGL内容并完美解决空域问题。

PresentParameters presentParameters = new() {   Windowed = 1,   SwapEffect = Swapeffect.Discard,   HDeviceWindow = 0,   PresentationInterval = 0,   BackBufferFormat = pMode.Format,   BackBufferWidth = 1,   BackBufferHeight = 1,   AutoDepthStencilFormat = Format.Unknown,   BackBufferCount = 1,   EnableAutoDepthStencil = 0,   Flags = 0,   FullScreenRefreshRateInHz = 0,   MultiSampleQuality = 0,   MultiSampleType = MultisampleType.MultisampleNone }; direct3D9->CreateDeviceEx(D3D9.AdapterDefault, Devtype.Hal, 0, D3D9.CreateHardwareVertexprocessing | D3D9.CreateMultithreaded | D3D9.CreatePuredevice, ref presentParameters, (Displaymodeex*)IntPtr.Zero, &device);

View Code

完整代码:

[WPF] 使用Silk.NET绘制D3D9或OpenGL内容并完美解决空域问题。[WPF] 使用Silk.NET绘制D3D9或OpenGL内容并完美解决空域问题。

public unsafe class RenderContext {     public IDirect3DDevice9Ex* Device { get; }      public Format Format { get; }      public RenderContext()     {         IDirect3D9Ex* direct3D9;         IDirect3DDevice9Ex* device;         D3D9.GetApi().Direct3DCreate9Ex(D3D9.SdkVersion, &direct3D9);          Displaymodeex pMode = new((uint)sizeof(Displaymodeex));         direct3D9->GetAdapterDisplayModeEx(D3D9.AdapterDefault, ref pMode, null);          PresentParameters presentParameters = new()         {             Windowed = 1,             SwapEffect = Swapeffect.Discard,             HDeviceWindow = 0,             PresentationInterval = 0,             BackBufferFormat = pMode.Format,             BackBufferWidth = 1,             BackBufferHeight = 1,             AutoDepthStencilFormat = Format.Unknown,             BackBufferCount = 1,             EnableAutoDepthStencil = 0,             Flags = 0,             FullScreenRefreshRateInHz = 0,             MultiSampleQuality = 0,             MultiSampleType = MultisampleType.MultisampleNone         };         direct3D9->CreateDeviceEx(D3D9.AdapterDefault, Devtype.Hal, 0, D3D9.CreateHardwareVertexprocessing | D3D9.CreateMultithreaded | D3D9.CreatePuredevice, ref presentParameters, (Displaymodeex*)IntPtr.Zero, &device);          Device = device;         Format = pMode.Format;     } }

View Code

继承FramebufferBase创建Framebuffer

这里就是根据传入的宽高创建一个新的Surface并绑定到D3DImage

[WPF] 使用Silk.NET绘制D3D9或OpenGL内容并完美解决空域问题。[WPF] 使用Silk.NET绘制D3D9或OpenGL内容并完美解决空域问题。

public unsafe class Framebuffer : FramebufferBase {     public RenderContext Context { get; }      public override int FramebufferWidth { get; }      public override int FramebufferHeight { get; }      public override D3DImage D3dImage { get; }      public Framebuffer(RenderContext context, int framebufferWidth, int framebufferHeight)     {         Context = context;         FramebufferWidth = framebufferWidth;         FramebufferHeight = framebufferHeight;          IDirect3DSurface9* surface;         context.Device->CreateRenderTarget((uint)FramebufferWidth, (uint)FramebufferHeight, context.Format, MultisampleType.MultisampleNone, 0, 0, &surface, null);         context.Device->SetRenderTarget(0, surface);          D3dImage = new D3DImage();         D3dImage.Lock();         D3dImage.SetBackBuffer(D3DResourceType.IDirect3DSurface9, (IntPtr)surface);         D3dImage.Unlock();     }      public override void Dispose()     {         GC.SuppressFinalize(this);     } }

View Code

创建GameControl,并继承GameBase

public unsafe class GameControl : GameBase<Framebuffer>

private RenderContext _context;  public IDirect3DDevice9Ex* Device { get; private set; } public Format Format { get; private set; }  public override event Action Ready; public override event Action<TimeSpan> Render; public override event Action<object, TimeSpan> UpdateFrame;

重载OnStart方法

在使用时,OnStart只调用一次并创建RenderContext

[WPF] 使用Silk.NET绘制D3D9或OpenGL内容并完美解决空域问题。[WPF] 使用Silk.NET绘制D3D9或OpenGL内容并完美解决空域问题。

protected override void OnStart() {   if (_context == null)   {     _context = new RenderContext();     Device = _context.Device;     Format = _context.Format;      Ready?.Invoke();   } }

View Code

重载OnSizeChanged方法

每当控件大小方式改变时,将重新创建Framebuffer。

[WPF] 使用Silk.NET绘制D3D9或OpenGL内容并完美解决空域问题。[WPF] 使用Silk.NET绘制D3D9或OpenGL内容并完美解决空域问题。

protected override void OnSizeChanged(SizeChangedInfo sizeInfo) {   if (_context != null && sizeInfo.NewSize.Width > 0 && sizeInfo.NewSize.Height > 0)   {     Framebuffer?.Dispose();     Framebuffer = new Framebuffer(_context, (int)sizeInfo.NewSize.Width, (int)sizeInfo.NewSize.Height);   } }

View Code

重载OnDraw方法

首先锁定D3dImage,执行Render进行绘制。

绘制完成后,刷新D3dImage并解锁。

将D3dImage资源绘制到控件上。

执行UpdateFrame,告诉使用者,已经绘制完成。

[WPF] 使用Silk.NET绘制D3D9或OpenGL内容并完美解决空域问题。[WPF] 使用Silk.NET绘制D3D9或OpenGL内容并完美解决空域问题。

protected override void OnDraw(DrawingContext drawingContext) {   Framebuffer.D3dImage.Lock();    Render?.Invoke(_stopwatch.Elapsed - _lastFrameStamp);    Framebuffer.D3dImage.AddDirtyRect(new Int32Rect(0, 0, Framebuffer.FramebufferWidth, Framebuffer.FramebufferHeight));   Framebuffer.D3dImage.Unlock();    Rect rect = new(0, 0, Framebuffer.D3dImage.Width, Framebuffer.D3dImage.Height);   drawingContext.DrawImage(Framebuffer.D3dImage, rect);    UpdateFrame?.Invoke(this, _stopwatch.Elapsed - _lastFrameStamp); }

View Code

使用方式:

[WPF] 使用Silk.NET绘制D3D9或OpenGL内容并完美解决空域问题。[WPF] 使用Silk.NET绘制D3D9或OpenGL内容并完美解决空域问题。

<UserControl x:Class="SilkWPF.Direct3D9.Sample.MiniTri"              xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"              xmlns:d="http://schemas.microsoft.com/expression/blend/2008"              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"              xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"              xmlns:direct3D9="clr-namespace:SilkWPF.Direct3D9"              mc:Ignorable="d"              d:DesignHeight="450"              d:DesignWidth="800">     <Grid>         <direct3D9:GameControl x:Name="Game" />         <TextBlock HorizontalAlignment="Left"                    VerticalAlignment="Top"                    Margin="10,5,0,0"                    FontSize="30"                    Foreground="Green"                    Text="{Binding ElementName=Game, Path=Fps}" />     </Grid> </UserControl>

Xaml

[WPF] 使用Silk.NET绘制D3D9或OpenGL内容并完美解决空域问题。[WPF] 使用Silk.NET绘制D3D9或OpenGL内容并完美解决空域问题。

using Silk.NET.Direct3D9; using Silk.NET.Maths; using SilkWPF.Common; using System.Diagnostics; using System.Numerics; using System.Runtime.InteropServices; using System.Windows.Controls;  namespace SilkWPF.Direct3D9.Sample;  /// <summary> /// MiniTri.xaml 的交互逻辑 /// </summary> public unsafe partial class MiniTri : UserControl {     [StructLayout(LayoutKind.Sequential)]     struct Vertex     {         public Vector4 Position;         public uint Color;     }      private readonly Stopwatch _stopwatch = Stopwatch.StartNew();     private readonly Vertex[] _vertices =     {         new Vertex() { Color = (uint)SilkColor.Red.ToBgra(), Position = new Vector4(400.0f, 100.0f, 0.5f, 1.0f) },         new Vertex() { Color = (uint)SilkColor.Blue.ToBgra(), Position = new Vector4(650.0f, 500.0f, 0.5f, 1.0f) },         new Vertex() { Color = (uint)SilkColor.Green.ToBgra(), Position = new Vector4(150.0f, 500.0f, 0.5f, 1.0f) }     };     private readonly Vertexelement9[] _vertexelements =     {         new Vertexelement9(0, 0, 3, 0, 9, 0),         new Vertexelement9(0, 16, 4, 0, 10, 0),         new Vertexelement9(255, 0, 17, 0, 0, 0)     };      private IDirect3DVertexBuffer9* _ppVertexBuffer;     private IDirect3DVertexDeclaration9* _ppDecl;      public MiniTri()     {         InitializeComponent();          Game.Ready += Game_Ready;         Game.Render += Game_Render;         Game.Start();     }      private void Game_Ready()     {         fixed (Vertex* ptr = &_vertices[0])         {             fixed (Vertexelement9* vertexElems = &_vertexelements[0])             {                 void* ppbData;                 Game.Device->CreateVertexBuffer(3 * 20, D3D9.UsageWriteonly, 0, Pool.Default, ref _ppVertexBuffer, null);                 _ppVertexBuffer->Lock(0, 0, &ppbData, 0);                 System.Runtime.CompilerServices.Unsafe.CopyBlockUnaligned(ppbData, ptr, (uint)(sizeof(Vertex) * _vertices.Length));                 _ppVertexBuffer->Unlock();                  Game.Device->CreateVertexDeclaration(vertexElems, ref _ppDecl);             }         }     }      private void Game_Render(TimeSpan obj)     {         float hue = (float)_stopwatch.Elapsed.TotalSeconds * 0.15f % 1;         Vector4 vector = new(1.0f * hue, 1.0f * 0.75f, 1.0f * 0.75f, 1.0f);          Game.Device->Clear(0, null, D3D9.ClearTarget, (uint)SilkColor.FromHsv(vector).ToBgra(), 1.0f, 0);         Game.Device->BeginScene();          Game.Device->SetStreamSource(0, _ppVertexBuffer, 0, 20);         Game.Device->SetVertexDeclaration(_ppDecl);         Game.Device->DrawPrimitive(Primitivetype.Trianglelist, 0, 1);          Game.Device->EndScene();         Game.Device->Present((Rectangle<int>*)IntPtr.Zero, (Rectangle<int>*)IntPtr.Zero, 1, (RGNData*)IntPtr.Zero);     } }

C#

运行代码,你将得到一个渐变颜色的三角形(amd处理器上对d3d9的支持特别差,使用MediaElement播放视频也卡的不行)。

显示帧数比较低,不用太在意(amd出来背锅)。

[WPF] 使用Silk.NET绘制D3D9或OpenGL内容并完美解决空域问题。

 接下来时绘制OpenGL内容:

分割一下 ——————————————————————————————————————————————————————————————————

OpenGL绘制:

实现思路:

[WPF] 使用Silk.NET绘制D3D9或OpenGL内容并完美解决空域问题。

使用库:Silk.NET.Direct3D9、OpenTK

可能大家比较奇怪,为什么不用Silk.NET.OpenGL,

目前Silk中的Wgl函数并不完整,我这里需要一些wgl的扩展函数用于关联D3D9设备。

所以我就先使用OpenTK做绘制。

创建一个OpenGL的配置信息类 Settings

[WPF] 使用Silk.NET绘制D3D9或OpenGL内容并完美解决空域问题。[WPF] 使用Silk.NET绘制D3D9或OpenGL内容并完美解决空域问题。

public class Settings {     public int MajorVersion { get; set; } = 3;      public int MinorVersion { get; set; } = 3;      public ContextFlags GraphicsContextFlags { get; set; } = ContextFlags.Default;      public ContextProfile GraphicsProfile { get; set; } = ContextProfile.Core;      public IGraphicsContext ContextToUse { get; set; }      public static bool WouldResultInSameContext([NotNull] Settings a, [NotNull] Settings 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;      } }

View Code

 创建RenderContext

具体实现与d3d差不太多,主要是创建设备。

不过要注意GetOrCreateSharedOpenGLContext方法,他是静态的,

我们在初始化wgl时需要一个窗体,所以我在这里让所有绘制控件都使用一个窗体。

[WPF] 使用Silk.NET绘制D3D9或OpenGL内容并完美解决空域问题。[WPF] 使用Silk.NET绘制D3D9或OpenGL内容并完美解决空域问题。

public unsafe class RenderContext {     private static IGraphicsContext _sharedContext;     private static Settings _sharedContextSettings;     private static int _sharedContextReferenceCount;      public Format Format { get; }      public IntPtr DxDeviceHandle { get; }      public IntPtr GlDeviceHandle { get; }      public IGraphicsContext GraphicsContext { get; }      public RenderContext(Settings settings)     {         IDirect3D9Ex* direct3D9;         IDirect3DDevice9Ex* device;         D3D9.GetApi().Direct3DCreate9Ex(D3D9.SdkVersion, &direct3D9);          Displaymodeex pMode = new((uint)sizeof(Displaymodeex));         direct3D9->GetAdapterDisplayModeEx(D3D9.AdapterDefault, ref pMode, null);         Format = pMode.Format;          PresentParameters presentParameters = new()         {             Windowed = 1,             SwapEffect = Swapeffect.Discard,             HDeviceWindow = 0,             PresentationInterval = 0,             BackBufferFormat = Format,             BackBufferWidth = 1,             BackBufferHeight = 1,             AutoDepthStencilFormat = Format.Unknown,             BackBufferCount = 1,             EnableAutoDepthStencil = 0,             Flags = 0,             FullScreenRefreshRateInHz = 0,             MultiSampleQuality = 0,             MultiSampleType = MultisampleType.MultisampleNone         };         direct3D9->CreateDeviceEx(D3D9.AdapterDefault, Devtype.Hal, 0, D3D9.CreateHardwareVertexprocessing | D3D9.CreateMultithreaded | D3D9.CreatePuredevice, ref presentParameters, (Displaymodeex*)IntPtr.Zero, &device);          DxDeviceHandle = (IntPtr)device;          GraphicsContext = GetOrCreateSharedOpenGLContext(settings);         GlDeviceHandle = Wgl.DXOpenDeviceNV((IntPtr)device);     }      private static IGraphicsContext GetOrCreateSharedOpenGLContext(Settings settings)     {         if (_sharedContext == null)         {             NativeWindowSettings windowSettings = NativeWindowSettings.Default;             windowSettings.StartFocused = false;             windowSettings.StartVisible = false;             windowSettings.NumberOfSamples = 0;             windowSettings.APIVersion = new Version(settings.MajorVersion, settings.MinorVersion);             windowSettings.Flags = ContextFlags.Offscreen | settings.GraphicsContextFlags;             windowSettings.Profile = settings.GraphicsProfile;             windowSettings.WindowBorder = WindowBorder.Hidden;             windowSettings.WindowState = WindowState.Minimized;             NativeWindow nativeWindow = new(windowSettings);             Wgl.LoadBindings(new GLFWBindingsContext());              _sharedContext = nativeWindow.Context;             _sharedContextSettings = settings;              _sharedContext.MakeCurrent();         }         else         {             if (!Settings.WouldResultInSameContext(settings, _sharedContextSettings))             {                 throw new ArgumentException($"The provided {nameof(Settings)} 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(Settings.ContextToUse)}' field.");             }         }          Interlocked.Increment(ref _sharedContextReferenceCount);          return _sharedContext;     } }

View Code

创建Framebuffer

这里主要用d3d创建一个Surface,

gl根据Surface生成一个Frame。 

[WPF] 使用Silk.NET绘制D3D9或OpenGL内容并完美解决空域问题。[WPF] 使用Silk.NET绘制D3D9或OpenGL内容并完美解决空域问题。

public unsafe class Framebuffer : FramebufferBase {     public RenderContext Context { get; }      public override int FramebufferWidth { get; }      public override int FramebufferHeight { get; }      public int GLFramebufferHandle { get; }      public int GLSharedTextureHandle { get; }      public int GLDepthRenderBufferHandle { get; }      public IntPtr DxInteropRegisteredHandle { get; }      public override D3DImage D3dImage { get; }      public TranslateTransform TranslateTransform { get; }      public ScaleTransform FlipYTransform { get; }      public Framebuffer(RenderContext context, int framebufferWidth, int framebufferHeight)     {         Context = context;         FramebufferWidth = framebufferWidth;         FramebufferHeight = framebufferHeight;          IDirect3DDevice9Ex* device = (IDirect3DDevice9Ex*)context.DxDeviceHandle;         IDirect3DSurface9* surface;         void* surfacePtr = (void*)IntPtr.Zero;         device->CreateRenderTarget((uint)FramebufferWidth, (uint)FramebufferHeight, context.Format, MultisampleType.MultisampleNone, 0, 0, &surface, &surfacePtr);          Wgl.DXSetResourceShareHandleNV((IntPtr)surface, (IntPtr)surfacePtr);         GLFramebufferHandle = GL.GenFramebuffer();         GLSharedTextureHandle = GL.GenTexture();          DxInteropRegisteredHandle = Wgl.DXRegisterObjectNV(context.GlDeviceHandle, (IntPtr)surface, (uint)GLSharedTextureHandle, (uint)TextureTarget.Texture2D, WGL_NV_DX_interop.AccessReadWrite);          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();         D3dImage.Lock();         D3dImage.SetBackBuffer(D3DResourceType.IDirect3DSurface9, (IntPtr)surface);         D3dImage.Unlock();          TranslateTransform = new TranslateTransform(0, FramebufferHeight);         FlipYTransform = new ScaleTransform(1, -1);     }      public override void Dispose()     {         GL.DeleteFramebuffer(GLFramebufferHandle);         GL.DeleteRenderbuffer(GLDepthRenderBufferHandle);         GL.DeleteTexture(GLSharedTextureHandle);         Wgl.DXUnregisterObjectNV(Context.GlDeviceHandle, DxInteropRegisteredHandle);          GC.SuppressFinalize(this);     } }

View Code

创建GameControl并继承GameBase

重载OnDraw方法

跟d3d一样,绘制前需要锁定Frame。

绘制完成后进行刷新。

这里有一点不同的是,OpenGL的顶点在左下角(0,0)

所以要对图像进行翻转。

[WPF] 使用Silk.NET绘制D3D9或OpenGL内容并完美解决空域问题。[WPF] 使用Silk.NET绘制D3D9或OpenGL内容并完美解决空域问题。

protected override void OnDraw(DrawingContext drawingContext) {   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);   Render?.Invoke(_stopwatch.Elapsed - _lastFrameStamp);    GL.BindFramebuffer(FramebufferTarget.Framebuffer, 0);   Wgl.DXUnlockObjectsNV(_context.GlDeviceHandle, 1, new[] { Framebuffer.DxInteropRegisteredHandle });    Framebuffer.D3dImage.AddDirtyRect(new Int32Rect(0, 0, Framebuffer.FramebufferWidth, Framebuffer.FramebufferHeight));   Framebuffer.D3dImage.Unlock();    drawingContext.PushTransform(Framebuffer.TranslateTransform);   drawingContext.PushTransform(Framebuffer.FlipYTransform);    Rect rect = new(0, 0, Framebuffer.D3dImage.Width, Framebuffer.D3dImage.Height);   drawingContext.DrawImage(Framebuffer.D3dImage, rect);    drawingContext.Pop();   drawingContext.Pop();    UpdateFrame?.Invoke(this, _stopwatch.Elapsed - _lastFrameStamp); }

View Code

其余函数与d3d基本一致,我就不说了(没想到写文章这么累)

完整代码:

[WPF] 使用Silk.NET绘制D3D9或OpenGL内容并完美解决空域问题。[WPF] 使用Silk.NET绘制D3D9或OpenGL内容并完美解决空域问题。

public class GameControl : GameBase<Framebuffer> {     private RenderContext _context;      public Settings Setting { get; set; } = new Settings();      public override event Action Ready;     public override event Action<TimeSpan> Render;     public override event Action<object, TimeSpan> UpdateFrame;      protected override void OnStart()     {         if (_context == null)         {             _context = new RenderContext(Setting);              Ready?.Invoke();         }     }      protected override void OnSizeChanged(SizeChangedInfo sizeInfo)     {         if (_context != null && sizeInfo.NewSize.Width > 0 && sizeInfo.NewSize.Height > 0)         {             Framebuffer?.Dispose();             Framebuffer = new Framebuffer(_context, (int)sizeInfo.NewSize.Width, (int)sizeInfo.NewSize.Height);         }     }      protected override void OnDraw(DrawingContext drawingContext)     {         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);         Render?.Invoke(_stopwatch.Elapsed - _lastFrameStamp);          GL.BindFramebuffer(FramebufferTarget.Framebuffer, 0);         Wgl.DXUnlockObjectsNV(_context.GlDeviceHandle, 1, new[] { Framebuffer.DxInteropRegisteredHandle });          Framebuffer.D3dImage.AddDirtyRect(new Int32Rect(0, 0, Framebuffer.FramebufferWidth, Framebuffer.FramebufferHeight));         Framebuffer.D3dImage.Unlock();          drawingContext.PushTransform(Framebuffer.TranslateTransform);         drawingContext.PushTransform(Framebuffer.FlipYTransform);          Rect rect = new(0, 0, Framebuffer.D3dImage.Width, Framebuffer.D3dImage.Height);         drawingContext.DrawImage(Framebuffer.D3dImage, rect);          drawingContext.Pop();         drawingContext.Pop();          UpdateFrame?.Invoke(this, _stopwatch.Elapsed - _lastFrameStamp);     } }

View Code

使用示例:

[WPF] 使用Silk.NET绘制D3D9或OpenGL内容并完美解决空域问题。

 [WPF] 使用Silk.NET绘制D3D9或OpenGL内容并完美解决空域问题。

本文使用的代码地址:

qian-o/SilkWPF (github.com)