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


本文告訴大家如何使用 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 ),不得用於商業目的,基於本文修改后的作品務必以相同的許可發布。如有任何疑問,請與我聯系


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM