WPF 使用 Silk.NET 進行 DirectX 渲染入門

lindexi發表於2022-01-06

本文告訴大家如何使用 dotnet 基金會新開源的 Silk.NET 庫呼叫 DirectX 進行渲染的方法。此庫是對 DirectX 的底層基礎封裝,用上了 dotnet 和 C# 的各個新特性,相對來說基礎效能較好,也許後續可以考慮作為 SharpDx 的代替

本文將告訴大家如何使用 Silk.NET 建立 DirectX 的各個物件,進行初始化邏輯,再對接 Direct2D 進行介面繪製。當前是 2021.12.23 此時 Silk.NET 還沒有完成 Direct2D 的封裝,為了方便演示,本文使用了 SharpDx 的 D2D 代替

本文非新手友好,如果是剛接觸 DirectX 那我推薦先閱讀 WPF 使用 SharpDx 渲染部落格導航

當前 SharpDx 已不維護,我正在找代替的專案,詳細請看 SharpDx 的代替專案

剛好找到了 dotnet 基金會下的 Silk.NET 庫,此庫是新寫的,用上了很多 dotnet 和 C# 的新特性,例如通過 COM 呼叫 DirectX 的實現邏輯是通過了 delegate* unmanaged 新特性,這是 C# 9 的新特性,請看 Function pointers - C# 9.0 draft specifications Microsoft Docs

程式碼的寫法如下

        public ID3D11Device
        (
            void** lpVtbl = null
        ) : this()
        {
            if (lpVtbl is not null)
            {
                LpVtbl = lpVtbl;
            }
        }

        public void** LpVtbl;

        public readonly unsafe int QueryInterface(Guid* riid, void** ppvObject)
        {
            var @this = (ID3D11Device*) Unsafe.AsPointer(ref Unsafe.AsRef(in this));
            int ret = default;
            ret = ((delegate* unmanaged[Cdecl]<ID3D11Device*, Guid*, void**, int>)LpVtbl[0])(@this, riid, ppvObject);
            return ret;
        }

通過以上的程式碼,特別是 ((delegate* unmanaged[Cdecl]<ID3D12Device*, Guid*, void**, int>)LpVtbl[0])(@this, riid, ppvObject); 這句如此複雜的程式碼,即可減少 COM 預設 dotnet 封裝的 RCW 封裝層的封送損耗。當然了,這部分不是本文的重點,細節請看 Runtime Callable Wrapper Microsoft Docs

大家只需要知道,此庫的實現裡面,可以很大減少呼叫 COM 時的額外損耗。但這也帶來了一點坑,例如呼叫方也只能採用不安全程式碼呼叫,寫法也有點詭異

根據 Surface sharing between Windows graphics APIs - Win32 apps 文件,為了在 WPF 的 D3DImage 上進行 D2D 繪製,就需要通過 D3D11 進行轉接,好在此轉接也只是指標的傳輸而已,基本沒有啥效能損耗。為了在 WPF 上使用到 D2D 就需要執行如下步驟:

  • 建立 D3D11 裝置
  • 通過 DXGI 關聯 D2D 裝置
  • 建立 D3D9 裝置

如官方文件的轉換圖

使用 DirectX 時,初始化引數的程式碼將會特別多。由於 Silk.NET 只是對 DirectX 的底層封裝,沒有細節隱藏,也就是說使用過程的複雜度也會特別多

在開始之前,先準備一個空 WPF 專案,基於 dotnet 6 框架。安裝好如下庫,可編輯 csproj 檔案,修改為如下程式碼

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>net6.0-windows</TargetFramework>
    <Nullable>enable</Nullable>
    <UseWPF>true</UseWPF>
    <AllowUnsafeBlocks>True</AllowUnsafeBlocks>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="SharpDX.Direct2D1" Version="4.2.0" />
    <PackageReference Include="Silk.NET.Direct3D11" Version="2.11.0" />
    <PackageReference Include="Silk.NET.Direct3D9" Version="2.11.0" />
    <PackageReference Include="Silk.NET.DXGI" Version="2.11.0" />
  </ItemGroup>

</Project>

以上程式碼關鍵在於 AllowUnsafeBlocks 需要開啟,用於開啟不安全程式碼給 Silk.NET 呼叫程式碼所使用。當前 Silk.NET 還沒有完成 D2D 封裝,本文將使用 SharpDX.Direct2D1 庫輔助編寫 D2D 的程式碼

在 XAML 介面新增 D3DImage 如下面程式碼

<Window x:Class="RawluharkewalQeaninanel.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:RawluharkewalQeaninanel"
        xmlns:interop="clr-namespace:System.Windows.Interop;assembly=PresentationCore"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Grid>
        <Image>
            <Image.Source>
                <interop:D3DImage x:Name="D3DImage"></interop:D3DImage>
            </Image.Source>
        </Image>
    </Grid>
</Window>

為了等待視窗等初始化完成,將在 Loaded 時進行實際的初始化程式碼

        public MainWindow()
        {
            InitializeComponent();

            Loaded += MainWindow_Loaded;
        }

在 MainWindow_Loaded 上新增本文的關鍵邏輯

按照順序,先建立 D3D11 裝置和初始化。開始前,考慮到名稱空間十分複雜,為了方便理解,先定義引用,如以下程式碼

using Silk.NET.Core.Native;

using D3D11 = Silk.NET.Direct3D11;
using D3D9 = Silk.NET.Direct3D9;
using DXGI = Silk.NET.DXGI;
using D2D = SharpDX.Direct2D1;
using SharpDXDXGI = SharpDX.DXGI;
using SharpDXMathematics = SharpDX.Mathematics.Interop;

雖然加上此名稱空間引用會讓程式碼寫的時候,稍微複雜一點,但好在清晰

定義完成之後,開始建立 D3D11 裝置。 建立過程中,需要先設定引數,程式碼如下

        private void MainWindow_Loaded(object sender, RoutedEventArgs e)
        {
            // 根據 [Surface sharing between Windows graphics APIs - Win32 apps](https://docs.microsoft.com/en-us/windows/win32/direct3darticles/surface-sharing-between-windows-graphics-apis?WT.mc_id=WD-MVP-5003260 ) 文件

            var width = ImageWidth;
            var height = ImageHeight;

            // 2021.12.23 不能在 x86 下執行,會炸掉。參閱 https://github.com/dotnet/Silk.NET/issues/731

            var texture2DDesc = new D3D11.Texture2DDesc()
            {
                BindFlags = (uint) (D3D11.BindFlag.BindRenderTarget | D3D11.BindFlag.BindShaderResource),
                Format = DXGI.Format.FormatB8G8R8A8Unorm, // 最好使用此格式,否則還需要後續轉換
                Width = (uint) width,
                Height = (uint) height,
                MipLevels = 1,
                SampleDesc = new DXGI.SampleDesc(1, 0),
                Usage = D3D11.Usage.UsageDefault,
                MiscFlags = (uint) D3D11.ResourceMiscFlag.ResourceMiscShared,
                // The D3D11_RESOURCE_MISC_FLAG cannot be used when creating resources with D3D11_CPU_ACCESS flags.
                CPUAccessFlags = 0, //(uint) D3D11.CpuAccessFlag.None,
                ArraySize = 1
            };

            // 忽略程式碼
        }
        private int ImageWidth => (int) ActualWidth;
        private int ImageHeight => (int) ActualHeight;

需要特別說明以上程式碼的一個註釋,當前 Silk.NET 對 X86 的支援較弱,除錯模式下執行將會炸掉應用,非除錯模式下沒啥問題。其原因是 Silk.NET 對於 COM 封裝在定義上是不對的,我給官方報告了此問題,請看 https://github.com/dotnet/Silk.NET/issues/731

問題的原因是在 Silk.NET 裡面,定義對 DirectX 的呼叫,使用的是 Cdecl 方式呼叫,然而在 DirectX 的定義裡,需要採用 Stdcall 來呼叫才是正確的。此行為將在 X86 下導致呼叫棧的內容不對,本應該清理的內容沒有正確清理。這部分細節請參閱 stdcall Microsoft Docscdecl Microsoft Docs 官方文件

建立引數裡,為了方便在 WPF 裡使用,要求最好使用 FormatB8G8R8A8Unorm 格式。以上引數差不多是固定寫法,各個引數的細節請看 DirectX 官方文件

接下來通過 D3D11 型別的 GetApi 方法獲取 D3D11 物件,此物件的獲取是 Silk.NET 的封裝,不屬於 DirectX 的內容

            D3D11.D3D11 d3D11 = D3D11.D3D11.GetApi();

因為 Silk.NET 的封裝特別底層,需要開啟不安全程式碼才能建立物件,為了方便編寫程式碼,將在 class 上加上 unsafe 讓此類的所有程式碼在使用不安全程式碼,不需要再加上 unsafe 即可使用

    public unsafe partial class MainWindow : Window
    {
    }

建立 D3D11 裝置的程式碼如下

            D3D11.ID3D11Device* pD3D11Device;
            D3D11.ID3D11DeviceContext* pD3D11DeviceContext;
            D3DFeatureLevel pD3DFeatureLevel = default;

            var hr = d3D11.CreateDevice((DXGI.IDXGIAdapter*) IntPtr.Zero, D3DDriverType.D3DDriverTypeHardware,
                Software: 0,
                Flags: (uint) D3D11.CreateDeviceFlag.CreateDeviceBgraSupport,
                (D3DFeatureLevel*) IntPtr.Zero,
                FeatureLevels: 0, // D3DFeatureLevel 的長度
                SDKVersion: 7,
                (D3D11.ID3D11Device**) &pD3D11Device, // 參閱 [C# 從零開始寫 SharpDx 應用 聊聊功能等級](https://blog.lindexi.com/post/C-%E4%BB%8E%E9%9B%B6%E5%BC%80%E5%A7%8B%E5%86%99-SharpDx-%E5%BA%94%E7%94%A8-%E8%81%8A%E8%81%8A%E5%8A%9F%E8%83%BD%E7%AD%89%E7%BA%A7.html )
                ref pD3DFeatureLevel,
                (D3D11.ID3D11DeviceContext**) &pD3D11DeviceContext
            );
            SilkMarshal.ThrowHResult(hr);

可以看到程式碼裡面大量用到不安全程式碼

在建立完成了 D3D11 裝置之後,即可開始建立 Texture 物件。我們的步驟是建立出 Texture 用來共享和給 D2D 繪製用,但 D2D 繪製在的是 Texture 的 IDXGISurface 平面上

建立 Texture2D 程式碼如下

            D3D11.ID3D11Texture2D* pD3D11Texture2D;
            hr = pD3D11Device->CreateTexture2D(ref texture2DDesc, (D3D11.SubresourceData*) IntPtr.Zero, &pD3D11Texture2D);
            SilkMarshal.ThrowHResult(hr);

此 ID3D11Texture2D 就是作為後續 D2D 繪製的 IDXGISurface 物件

            var renderTarget = pD3D11Texture2D;
            DXGI.IDXGISurface* pDXGISurface;
            var dxgiSurfaceGuid = DXGI.IDXGISurface.Guid;
            renderTarget->QueryInterface(ref dxgiSurfaceGuid, (void**) &pDXGISurface);

接下來部分就是 SharpDx 的啦,當前 Silk.NET 還沒有封裝好 D2D 部分,於是這裡就和 WPF 使用 SharpDX 部落格的方法差不多,只是建立 SharpDX 的 Surface 程式碼稍微修改而已

            var surface = new SharpDXDXGI.Surface(new IntPtr((void*) pDXGISurface));

其他邏輯如下

            var d2DFactory = new D2D.Factory();

            var renderTargetProperties =
                new D2D.RenderTargetProperties(new D2D.PixelFormat(SharpDXDXGI.Format.Unknown, D2D.AlphaMode.Premultiplied));
            _d2DRenderTarget = new D2D.RenderTarget(d2DFactory, surface, renderTargetProperties);

        private D2D.RenderTarget _d2DRenderTarget;

拿到了 D2D.RenderTarget 就可以進行 D2D 繪製。但是在開始前,還需要關聯到 WPF 的 D3DImage 才能渲染。為了關聯 D3DImage 就需要繼續建立 D3D9 裝置,如下面程式碼,呼叫 SetRenderTarget 將 D3D11 建立的 ID3D11Texture2D 作為 D3D9 的共享紋理,從而讓 D2D 的內容可以在 D3DImage 上使用

            SetRenderTarget(renderTarget);

在 SetRenderTarget 的程式碼是從 ID3D11Texture2D 轉到 IDirect3DSurface9 上,將 IDirect3DSurface9 作為 D3DImage 的 BackBuffer 給 WPF 使用

        private void SetRenderTarget(D3D11.ID3D11Texture2D* target)
        {
        }

從 ID3D11Texture2D 轉到 IDirect3DSurface9 上有如下步驟:

  • 獲取共享指標
  • 建立 D3D9 裝置
  • 通過 D3D9 裝置,使用共享指標建立紋理,通過紋理獲取平面

獲取共享指標是為了讓 D3D9 的紋理共享 D3D11 的資源,獲取程式碼如下

            DXGI.IDXGIResource* pDXGIResource;
            var dxgiResourceGuid = DXGI.IDXGIResource.Guid;
            target->QueryInterface(ref dxgiResourceGuid, (void**) &pDXGIResource);

            void* sharedHandle;
            var hr = pDXGIResource->GetSharedHandle(&sharedHandle);
            SilkMarshal.ThrowHResult(hr);

建立 D3D9 之前,需要使用 Silk.NET 的 D3D9 類的 GetApi 物件獲取 D3D9 物件。這是 Silk.NET 的設計,可以看到此庫很多型別都有 GetApi 方法

            var d3d9 = D3D9.D3D9.GetApi();

建立 D3D9 裝置之前,需要先建立 IDirect3D9Ex 物件

            D3D9.IDirect3D9Ex* pDirect3D9Ex;
            hr = d3d9.Direct3DCreate9Ex(SDKVersion: 32, &pDirect3D9Ex);
            SilkMarshal.ThrowHResult(hr);
            var d3DContext = pDirect3D9Ex;

建立 D3D9 裝置之前,也需要初始化引數,有一些引數需要和 D3D11 建立的引數相同,需要先獲取 D3D11 的引數

            D3D11.Texture2DDesc texture2DDescription = default;
            target->GetDesc(ref texture2DDescription);

初始化建立 D3D9 的建立引數

            var presentParameters = new D3D9.PresentParameters()
            {
                Windowed = 1,// true
                SwapEffect = D3D9.Swapeffect.SwapeffectDiscard,
                HDeviceWindow = GetDesktopWindow(),
                PresentationInterval = D3D9.D3D9.PresentIntervalDefault,
            };

            // 設定使用多執行緒方式,這樣的效能才足夠
            uint createFlags = D3D9.D3D9.CreateHardwareVertexprocessing | D3D9.D3D9.CreateMultithreaded | D3D9.D3D9.CreateFpuPreserve;

        [DllImport("user32.dll", SetLastError = false)]
        public static extern IntPtr GetDesktopWindow();

拿到建立引數,建立 D3D9 裝置

            D3D9.IDirect3DDevice9Ex* pDirect3DDevice9Ex;
            hr = d3DContext->CreateDeviceEx(Adapter: 0, 
                DeviceType: D3D9.Devtype.DevtypeHal,// 使用硬體渲染
                hFocusWindow: IntPtr.Zero, 
                createFlags,
                ref presentParameters, 
                pFullscreenDisplayMode: (D3D9.Displaymodeex*) IntPtr.Zero, 
                &pDirect3DDevice9Ex);
            SilkMarshal.ThrowHResult(hr);

            var d3DDevice = pDirect3DDevice9Ex;

拿到 D3D9 裝置,開始建立紋理

            D3D9.IDirect3DTexture9* pDirect3DTexture9;
            hr = d3DDevice->CreateTexture(texture2DDescription.Width, texture2DDescription.Height, Levels: 1,
                D3D9.D3D9.UsageRendertarget, 
                D3D9.Format.FmtA8R8G8B8, // 這是必須要求的顏色,不能使用其他顏色
                D3D9.Pool.PoolDefault, 
                &pDirect3DTexture9,
                &sharedHandle);
            SilkMarshal.ThrowHResult(hr);
            _renderTarget = pDirect3DTexture9;

        private D3D9.IDirect3DTexture9* _renderTarget;

紋理有要求顏色格式,也要求尺寸和 D3D11 的相同

通過紋理可以拿到 IDirect3DSurface9 物件

            D3D9.IDirect3DSurface9* pDirect3DSurface9;
            _renderTarget->GetSurfaceLevel(0, &pDirect3DSurface9);
            _pDirect3DSurface9 = pDirect3DSurface9;

將 IDirect3DSurface9 作為 D3DImage 的 BackBuffer 即可完成初始化

            D3DImage.Lock();
            D3DImage.SetBackBuffer(D3DResourceType.IDirect3DSurface9, new IntPtr(pDirect3DSurface9));
            D3DImage.Unlock();

在 MainWindow_Loaded 設定將一個檢視陣列繫結到管道的光柵化階段

            var viewport = new D3D11.Viewport(0, 0, width, height, 0, 1);
            pD3D11DeviceContext->RSSetViewports(NumViewports: 1, ref viewport);

開始測試 D2D 的渲染,通過測試 D2D 即可瞭解是否建立初始化成功。在 WPF 的 CompositionTarget 的 Rendering 進行 D2D 繪製

            CompositionTarget.Rendering += CompositionTarget_Rendering;

        private void CompositionTarget_Rendering(object? sender, EventArgs e)
        {
            _d2DRenderTarget.BeginDraw();

            OnRender(_d2DRenderTarget);

            _d2DRenderTarget.EndDraw();

            D3DImage.Lock();

            D3DImage.AddDirtyRect(new Int32Rect(0, 0, D3DImage.PixelWidth, D3DImage.PixelHeight));

            D3DImage.Unlock();
        }

在 OnRender 方法加上 D2D 的繪製內容,這就是測試邏輯,請根據自己的需求編寫

        private void OnRender(D2D.RenderTarget renderTarget)
        {
            var brush = new D2D.SolidColorBrush(_d2DRenderTarget, new SharpDXMathematics.RawColor4(1, 0, 0, 1));

            renderTarget.Clear(null);

            renderTarget.DrawRectangle(new SharpDXMathematics.RawRectangleF(_x, _y, _x + 10, _y + 10), brush);

            _x = _x + _dx;
            _y = _y + _dy;
            if (_x >= ActualWidth - 10 || _x <= 0)
            {
                _dx = -_dx;
            }

            if (_y >= ActualHeight - 10 || _y <= 0)
            {
                _dy = -_dy;
            }
        }

        private float _x;
        private float _y;
        private float _dx = 1;
        private float _dy = 1;

按照微軟官方的推薦,在 CompositionTarget_Rendering 裡,如果進行 DirectX 的邏輯,需要判斷是否進入了多次,但本文這裡只是測試邏輯,忽略官方給出的邏輯

執行程式碼即可看到介面上有一個矩形顯示

也許後續我會封裝一個 Silk.NET 的 DirectX 給 WPF 使用的控制元件

#nullable disable

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Media;

using Silk.NET.Core.Native;

using D3D11 = Silk.NET.Direct3D11;
using D3D9 = Silk.NET.Direct3D9;
using DXGI = Silk.NET.DXGI;
using D2D = SharpDX.Direct2D1;
using SharpDXDXGI = SharpDX.DXGI;
using SharpDXMathematics = SharpDX.Mathematics.Interop;

namespace RawluharkewalQeaninanel
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>
    public unsafe partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            Loaded += MainWindow_Loaded;
        }

        private void MainWindow_Loaded(object sender, RoutedEventArgs e)
        {
            // 根據 [Surface sharing between Windows graphics APIs - Win32 apps](https://docs.microsoft.com/en-us/windows/win32/direct3darticles/surface-sharing-between-windows-graphics-apis?WT.mc_id=WD-MVP-5003260 ) 文件

            var width = ImageWidth;
            var height = ImageHeight;

            // 2021.12.23 不能在 x86 下執行,會炸掉。參閱 https://github.com/dotnet/Silk.NET/issues/731

            var texture2DDesc = new D3D11.Texture2DDesc()
            {
                BindFlags = (uint) (D3D11.BindFlag.BindRenderTarget | D3D11.BindFlag.BindShaderResource),
                Format = DXGI.Format.FormatB8G8R8A8Unorm, // 最好使用此格式,否則還需要後續轉換
                Width = (uint) width,
                Height = (uint) height,
                MipLevels = 1,
                SampleDesc = new DXGI.SampleDesc(1, 0),
                Usage = D3D11.Usage.UsageDefault,
                MiscFlags = (uint) D3D11.ResourceMiscFlag.ResourceMiscShared,
                // The D3D11_RESOURCE_MISC_FLAG cannot be used when creating resources with D3D11_CPU_ACCESS flags.
                CPUAccessFlags = 0, //(uint) D3D11.CpuAccessFlag.None,
                ArraySize = 1
            };

            D3D11.ID3D11Device* pD3D11Device;
            D3D11.ID3D11DeviceContext* pD3D11DeviceContext;
            D3DFeatureLevel pD3DFeatureLevel = default;
            D3D11.D3D11 d3D11 = D3D11.D3D11.GetApi();

            var hr = d3D11.CreateDevice((DXGI.IDXGIAdapter*) IntPtr.Zero, D3DDriverType.D3DDriverTypeHardware,
                Software: 0,
                Flags: (uint) D3D11.CreateDeviceFlag.CreateDeviceBgraSupport,
                (D3DFeatureLevel*) IntPtr.Zero,
                FeatureLevels: 0, // D3DFeatureLevel 的長度
                SDKVersion: 7,
                (D3D11.ID3D11Device**) &pD3D11Device, // 參閱 [C# 從零開始寫 SharpDx 應用 聊聊功能等級](https://blog.lindexi.com/post/C-%E4%BB%8E%E9%9B%B6%E5%BC%80%E5%A7%8B%E5%86%99-SharpDx-%E5%BA%94%E7%94%A8-%E8%81%8A%E8%81%8A%E5%8A%9F%E8%83%BD%E7%AD%89%E7%BA%A7.html )
                ref pD3DFeatureLevel,
                (D3D11.ID3D11DeviceContext**) &pD3D11DeviceContext
            );
            SilkMarshal.ThrowHResult(hr);

            Debugger.Launch();
            Debugger.Break();

            _pD3D11Device = pD3D11Device;
            _pD3D11DeviceContext = pD3D11DeviceContext;

            D3D11.ID3D11Texture2D* pD3D11Texture2D;
            hr = pD3D11Device->CreateTexture2D(ref texture2DDesc, (D3D11.SubresourceData*) IntPtr.Zero, &pD3D11Texture2D);
            SilkMarshal.ThrowHResult(hr);

            var renderTarget = pD3D11Texture2D;
            _pD3D11Texture2D = pD3D11Texture2D;

            DXGI.IDXGISurface* pDXGISurface;
            var dxgiSurfaceGuid = DXGI.IDXGISurface.Guid;
            renderTarget->QueryInterface(ref dxgiSurfaceGuid, (void**) &pDXGISurface);
            _pDXGISurface = pDXGISurface;

            var d2DFactory = new D2D.Factory();

            var renderTargetProperties =
                new D2D.RenderTargetProperties(new D2D.PixelFormat(SharpDXDXGI.Format.Unknown, D2D.AlphaMode.Premultiplied));
            var surface = new SharpDXDXGI.Surface(new IntPtr((void*) pDXGISurface));
            _d2DRenderTarget = new D2D.RenderTarget(d2DFactory, surface, renderTargetProperties);

            SetRenderTarget(renderTarget);

            var viewport = new D3D11.Viewport(0, 0, width, height, 0, 1);
            pD3D11DeviceContext->RSSetViewports(NumViewports: 1, ref viewport);

            CompositionTarget.Rendering += CompositionTarget_Rendering;
        }

        private void CompositionTarget_Rendering(object? sender, EventArgs e)
        {
            _d2DRenderTarget.BeginDraw();

            OnRender(_d2DRenderTarget);

            _d2DRenderTarget.EndDraw();

            D3DImage.Lock();

            D3DImage.AddDirtyRect(new Int32Rect(0, 0, D3DImage.PixelWidth, D3DImage.PixelHeight));

            D3DImage.Unlock();
        }

        private void OnRender(D2D.RenderTarget renderTarget)
        {
            var brush = new D2D.SolidColorBrush(_d2DRenderTarget, new SharpDXMathematics.RawColor4(1, 0, 0, 1));

            renderTarget.Clear(null);

            renderTarget.DrawRectangle(new SharpDXMathematics.RawRectangleF(_x, _y, _x + 10, _y + 10), brush);

            _x = _x + _dx;
            _y = _y + _dy;
            if (_x >= ActualWidth - 10 || _x <= 0)
            {
                _dx = -_dx;
            }

            if (_y >= ActualHeight - 10 || _y <= 0)
            {
                _dy = -_dy;
            }
        }

        private float _x;
        private float _y;
        private float _dx = 1;
        private float _dy = 1;

        private void SetRenderTarget(D3D11.ID3D11Texture2D* target)
        {
            DXGI.IDXGIResource* pDXGIResource;
            var dxgiResourceGuid = DXGI.IDXGIResource.Guid;
            target->QueryInterface(ref dxgiResourceGuid, (void**) &pDXGIResource);

            D3D11.Texture2DDesc texture2DDescription = default;
            target->GetDesc(ref texture2DDescription);

            void* sharedHandle;
            var hr = pDXGIResource->GetSharedHandle(&sharedHandle);
            SilkMarshal.ThrowHResult(hr);

            var d3d9 = D3D9.D3D9.GetApi();
            D3D9.IDirect3D9Ex* pDirect3D9Ex;
            hr = d3d9.Direct3DCreate9Ex(SDKVersion: 32, &pDirect3D9Ex);
            SilkMarshal.ThrowHResult(hr);
            var d3DContext = pDirect3D9Ex;
            _pDirect3D9Ex = pDirect3D9Ex;

            var presentParameters = new D3D9.PresentParameters()
            {
                Windowed = 1,// true
                SwapEffect = D3D9.Swapeffect.SwapeffectDiscard,
                HDeviceWindow = GetDesktopWindow(),
                PresentationInterval = D3D9.D3D9.PresentIntervalDefault,
            };

            // 設定使用多執行緒方式,這樣的效能才足夠
            uint createFlags = D3D9.D3D9.CreateHardwareVertexprocessing | D3D9.D3D9.CreateMultithreaded | D3D9.D3D9.CreateFpuPreserve;

            D3D9.IDirect3DDevice9Ex* pDirect3DDevice9Ex;
            hr = d3DContext->CreateDeviceEx(Adapter: 0, 
                DeviceType: D3D9.Devtype.DevtypeHal,// 使用硬體渲染
                hFocusWindow: IntPtr.Zero, 
                createFlags,
                ref presentParameters, 
                pFullscreenDisplayMode: (D3D9.Displaymodeex*) IntPtr.Zero, 
                &pDirect3DDevice9Ex);
            SilkMarshal.ThrowHResult(hr);

            var d3DDevice = pDirect3DDevice9Ex;

            D3D9.IDirect3DTexture9* pDirect3DTexture9;
            hr = d3DDevice->CreateTexture(texture2DDescription.Width, texture2DDescription.Height, Levels: 1,
                D3D9.D3D9.UsageRendertarget, 
                D3D9.Format.FmtA8R8G8B8, // 這是必須要求的顏色,不能使用其他顏色
                D3D9.Pool.PoolDefault, 
                &pDirect3DTexture9,
                &sharedHandle);
            SilkMarshal.ThrowHResult(hr);
            _renderTarget = pDirect3DTexture9;

            D3D9.IDirect3DSurface9* pDirect3DSurface9;
            _renderTarget->GetSurfaceLevel(0, &pDirect3DSurface9);
            _pDirect3DSurface9 = pDirect3DSurface9;

            D3DImage.Lock();
            D3DImage.SetBackBuffer(D3DResourceType.IDirect3DSurface9, new IntPtr(pDirect3DSurface9));
            D3DImage.Unlock();
        }

        // 這些欄位的另一個作用是防止回收
        private D2D.RenderTarget _d2DRenderTarget;

        private D3D11.ID3D11Device* _pD3D11Device;
        private D3D11.ID3D11DeviceContext* _pD3D11DeviceContext;
        private D3D11.ID3D11Texture2D* _pD3D11Texture2D;
        private DXGI.IDXGISurface* _pDXGISurface;

        private D3D9.IDirect3D9Ex* _pDirect3D9Ex;
        private D3D9.IDirect3DTexture9* PDirect3DTexture9 => _renderTarget;
        private D3D9.IDirect3DTexture9* _renderTarget;
        private D3D9.IDirect3DSurface9* _pDirect3DSurface9;

        private int ImageWidth => (int) ActualWidth;
        private int ImageHeight => (int) ActualHeight;

        [DllImport("user32.dll", SetLastError = false)]
        public static extern IntPtr GetDesktopWindow();
    }
}

本文所有程式碼放在githubgitee 歡迎訪問

可以通過如下方式獲取本文的原始碼,先建立一個空資料夾,接著使用命令列 cd 命令進入此空資料夾,在命令列裡面輸入以下程式碼,即可獲取到本文的程式碼

git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin f4c2f884b3fb006676aeef7e249055c5e2d8766d

以上使用的是 gitee 的源,如果 gitee 不能訪問,請替換為 github 的源

git remote remove origin
git remote add origin https://github.com/lindexi/lindexi_gd.git

獲取程式碼之後,進入 RawluharkewalQeaninanel 資料夾

知識共享許可協議
本作品採用知識共享署名-非商業性使用-相同方式共享 4.0 國際許可協議進行許可。歡迎轉載、使用、重新發布,但務必保留文章署名林德熙(包含連結:http://blog.csdn.net/lindexi_gd ),不得用於商業目的,基於本文修改後的作品務必以相同的許可釋出。如有任何疑問,請與我聯絡

相關文章