博客 / 詳情

返回

dotnet Vortice 無需交換鏈與 DirectComposition 對接渲染層

在 DirectComposition 裏面提供了 Commit 機制,一次 Commit 的所有內容都能在相同的一幀在屏幕顯示出來,如此可以非常方便地完成渲染對齊任務

通過 WaitForCommitCompletion 方法可以等待 Commit 內容完成渲染,此方法作用相當於等待交換鏈寫法的等待垂直同步實現

在 上一篇博客 中,採用了傳統的 DXGI 交換鏈與 DirectComposition 對接

在本文這裏將去掉交換鏈,可以很大簡化對接渲染的邏輯。採用 DXGI 交換鏈對接的方式,可以比較方便對接原有的程序,且可以實現更高幀率的控制。而採用 DirectComposition 的 Commit 寫法,可以更好利用 DirectComposition 機制,實現多表面合成以及更加實時的合成器動畫

本文將給出最簡實現對接的代碼邏輯,其步驟如下

  1. 創建 Win32 窗口
  2. 創建 DirectComposition 設備和關聯窗口,獲取渲染表面
  3. 執行渲染邏輯

為了保持本文簡潔,我將不在正文部分貼出非關鍵部分的代碼,在本文末尾給出全部核心代碼。本文的全部核心代碼部分不到 200 行,適合一口氣完成。本文也使用了到了一些庫,為了防止大家不知道項目如何配置的,在本文末尾也給出整個項目全部代碼和配置的下載方法

基礎庫

按照 .NET 的慣例,開始之前先安裝基礎庫,安裝之後的 csproj 項目文件代碼大概如下

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

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>net10.0</TargetFramework>
    <Nullable>enable</Nullable>
    <IsAotCompatible>true</IsAotCompatible>
    <PublishAot>true</PublishAot>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Vortice.Direct2D1" Version="3.8.2" />
    <PackageReference Include="Vortice.Direct3D11" Version="3.8.2" />
    <PackageReference Include="Vortice.DirectComposition" Version="3.8.2" />
    <PackageReference Include="Vortice.DXGI" Version="3.8.2" />
    <PackageReference Include="Vortice.Win32" Version="2.3.0" />

    <PackageReference Include="Microsoft.Windows.CsWin32" Version="0.3.257">
      <PrivateAssets>all</PrivateAssets>
      <IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
    </PackageReference>

    <PackageReference Include="MicroCom.Runtime" Version="0.11.0" />

  </ItemGroup>

</Project>

創建 Win32 窗口

創建 Win32 窗口僅僅只是想拿到窗口句柄,不是本文重點,這裏就忽略 CreateWindow 方法的實現

        // 創建窗口
        HWND window = CreateWindow();
        // 顯示窗口
        ShowWindow(window, SHOW_WINDOW_CMD.SW_NORMAL);

以上代碼的 ShowWindow 是標準的 Win32 方法,由 CsWin32 庫生成。定義如下

		[DllImport("USER32.dll", ExactSpelling = true),DefaultDllImportSearchPaths(DllImportSearchPath.System32)]
		[SupportedOSPlatform("windows5.0")]
		internal static extern winmdroot.Foundation.BOOL ShowWindow(winmdroot.Foundation.HWND hWnd, winmdroot.UI.WindowsAndMessaging.SHOW_WINDOW_CMD nCmdShow);

為了直接使用方法,在本文這裏直接在命名空間引用靜態類,代碼如下

using static Windows.Win32.PInvoke;

創建 DirectComposition 設備

先使用以下快速地代碼創建 ID3D11Device 設備。正常來説,還是會嘗試遍歷獲取顯示適配器用來手動創建設備。本文這裏使用的是比較不穩妥的簡化寫法。正確且常用的寫法代碼稍多,在本文這裏 ID3D11Device 不是主角。如對此感興趣,請參閲 渲染博客導航

        var result = D3D11.D3D11CreateDevice(null, DriverType.Hardware, DeviceCreationFlags.BgraSupport,
            featureLevels: [], out ID3D11Device iD3D11Device, out var feature,
            out ID3D11DeviceContext iD3D11DeviceContext);
        result.CheckError();

        _ = feature;
        iD3D11DeviceContext.Dispose(); // 用不着就先釋放。釋放不代表立刻回收資源,只是表示業務層不需要用到,減少引用計數

在本文這裏只用到了 ID3D11Device 設備,於是就選擇立刻釋放 ID3D11DeviceContext 的引用

拿到 ID3D11Device 設備,就可以調用 DComp.DCompositionCreateDevice3 創建 IDCompositionDevice 設備,代碼如下

        IDCompositionDevice compositionDevice = DComp.DCompositionCreateDevice3<IDCompositionDevice>(iD3D11Device);

這裏的 IDCompositionDevice 應該就是 IDirectCompositionDevice 的縮寫

對接窗口

通過 CreateTargetForHwnd 方法可以將 DirectComposition 關聯到窗口上,代碼如下

        IDCompositionDevice compositionDevice = DComp.DCompositionCreateDevice3<IDCompositionDevice>(iD3D11Device);
        compositionDevice.CreateTargetForHwnd(window, topmost: true, out IDCompositionTarget compositionTarget);

這裏的 CreateTargetForHwnd 的第二個參數比較迷惑,以上的 CreateTargetForHwnd 參數 topmost 不是值窗口置頂,而是:如果視覺樹應顯示在由 hwnd 參數指定的窗口子元素之上,則為 TRUE;否則,視覺樹將顯示在子元素之後。原文:

TRUE if the visual tree should be displayed on top of the children of the window specified by the hwnd parameter; otherwise, the visual tree is displayed behind the children.

詳細請看 https://learn.microsoft.com/en-us/windows/win32/api/dcomp/nf-dcomp-idcompositiondevice-createtargetforhwnd

創建視覺對象

創建視覺對象之前,需要獲取當前窗口的尺寸,代碼如下

        RECT windowRect;
        PInvoke.GetClientRect(window, &windowRect);
        var clientSize = new SizeI(windowRect.right - windowRect.left, windowRect.bottom - windowRect.top);

創建視覺對象只需簡單地調用 IDCompositionDevice.CreateVisual 方法,然而此時視覺對象還沒有內容,可通過 IDCompositionDevice.CreateVirtualSurface 創建表面來作為內容,簡單寫法如下

        IDCompositionVisual compositionVisual = compositionDevice.CreateVisual();
        IDCompositionVirtualSurface surface = compositionDevice.CreateVirtualSurface((uint) clientSize.Width,
            (uint) clientSize.Height, Format.B8G8R8A8_UNorm, AlphaMode.Premultiplied);
        compositionVisual.SetContent(surface);

創建表面時,可用 CreateVirtualSurface 或 CreateSurface 方法。兩者不同的是 CreateVirtualSurface 創建的是稀疏表面,而 CreateSurface 是大數組(矩陣),在調用 BeginDraw 之前 IDCompositionVirtualSurface 不會分別空間,且 IDCompositionVirtualSurface 還能 Resize 重新設置大小。而 CreateSurface 則就不能。使用 CreateSurface 的例子如下

var createSurfaceResult = compositionDevice.CreateSurface((uint) clientSize.Width,
            (uint) clientSize.Height, Format.B8G8R8A8_UNorm, AlphaMode.Premultiplied, out IDCompositionSurface? dCompositionSurface);
createSurfaceResult.CheckError();

創建表面需要傳染顏色格式和對 AlphaMode 的處理,本文這裏傳入的是 Premultiplied 採用預乘方法。對 Premultiplied 簡單來説就是最終輸出的值裏的 RGB 分量都乘以透明度。更多細節請參閲 支持的像素格式和 Alpha 模式 - Win32 apps - Microsoft Learn

傳入 Premultiplied 預乘時,會更多佔用 DWM 的資源,在 4K 的全屏窗口上,對比 Premultiplied 預乘與忽略 AlphaMode 的性能,可以看到預乘會比忽略佔用多非常多的 DWM 資源。如果自己的應用是無需窗口背景透明的,還請設置為忽略 AlphaMode 模式

配置視覺對象

完成視覺對象創建之後,可將此視覺對象設置為窗口的根內容。在 DirectComposition 裏可以設置視覺樹,一個視覺對象上可以添加很多個視覺對象,但只有其中一個可以成為 IDCompositionTarget 的 Root 視覺對象

        compositionTarget.SetRoot(compositionVisual);
        compositionDevice.Commit(); // 非必須

對接 D2D 渲染

以上代碼就完成了視覺對象的創建和在窗口上顯示的基礎邏輯。為了能夠繪製漂亮的界面,在本文這裏將和 D2D 進行對接

為了能夠和 D2D 進行對接,需要給 D2D 一個繪製表面。從 IDCompositionVirtualSurface 或 IDCompositionSurface 表面調用 BeginDraw 方法,即可獲取到 IDXGISurface 表面,從而讓 D2D 在此表面上繪製,基礎邏輯如下

IDXGISurface dxgiSurface = surface.BeginDraw<IDXGISurface>(null, out var updateOffset);

再按照 D2D 的初始化方法,將 D2D 的 ID2D1RenderTarget 創建出來,代碼如下

        // 工廠創建只需一次
        Vortice.Direct2D1.ID2D1Factory1 d2DFactory = Vortice.Direct2D1.D2D1.D2D1CreateFactory<Vortice.Direct2D1.ID2D1Factory1>();

        // 以下循環為每一幀執行
        while (!_isMainWindowClosed)
        {
            using IDXGISurface dxgiSurface = surface.BeginDraw<IDXGISurface>(null, out var updateOffset);

            var renderTargetProperties = new Vortice.Direct2D1.RenderTargetProperties()
            {
                PixelFormat = new PixelFormat(Format.B8G8R8A8_UNorm, Vortice.DCommon.AlphaMode.Premultiplied),
                Type = Vortice.Direct2D1.RenderTargetType.Hardware,
            };

            using Vortice.Direct2D1.ID2D1RenderTarget d2D1RenderTarget =
                d2DFactory.CreateDxgiSurfaceRenderTarget(dxgiSurface, renderTargetProperties);

            Vortice.Direct2D1.ID2D1RenderTarget renderTarget = d2D1RenderTarget;

            ... // 忽略其他代碼
        }

拿到 ID2D1RenderTarget 即可進行 D2D 的繪製,在本文這裏使用了名為 D2DRenderDemo 的輔助類進行繪製,具體代碼可以在後文找到

        // 以下循環為每一幀執行
        while (!_isMainWindowClosed)
        {
            using IDXGISurface dxgiSurface = surface.BeginDraw<IDXGISurface>(null, out var updateOffset);

            var renderTargetProperties = new Vortice.Direct2D1.RenderTargetProperties()
            {
                PixelFormat = new PixelFormat(Format.B8G8R8A8_UNorm, Vortice.DCommon.AlphaMode.Premultiplied),
                Type = Vortice.Direct2D1.RenderTargetType.Hardware,
            };

            using Vortice.Direct2D1.ID2D1RenderTarget d2D1RenderTarget =
                d2DFactory.CreateDxgiSurfaceRenderTarget(dxgiSurface, renderTargetProperties);

            Vortice.Direct2D1.ID2D1RenderTarget renderTarget = d2D1RenderTarget;

            renderTarget.BeginDraw();

            // 在這裏編寫繪製邏輯
            renderTarget.Clear(new Color4(0f));
            d2DRenderDemo.Draw(renderTarget, clientSize);

            renderTarget.EndDraw();

            ... // 忽略其他代碼
        }

當 D2D 繪製完成之後,需要調用 IDCompositionVirtualSurface 或 IDCompositionSurface 的 EndDraw 方法。再調用 IDCompositionDevice 的 Commit 方法將內容提交出去。當所有的窗口都完成繪製和 Commit 之後,調用 IDCompositionDevice.WaitForCommitCompletion 等待 DWM 消費。調用 IDCompositionDevice.WaitForCommitCompletion 約等於等待垂直同步,等待界面刷新

            renderTarget.BeginDraw();

            // 在這裏編寫繪製邏輯
            renderTarget.Clear(new Color4(0f));
            d2DRenderDemo.Draw(renderTarget, clientSize);

            renderTarget.EndDraw();
            surface.EndDraw();

            compositionDevice.Commit();
            compositionDevice.WaitForCommitCompletion();

在本文這裏,由於只有一個窗口,於是在 Commit 之後即可立刻調用 WaitForCommitCompletion 方法了

整個渲染代碼,即每一幀跑的代碼如下

        Vortice.Direct2D1.ID2D1Factory1 d2DFactory = Vortice.Direct2D1.D2D1.D2D1CreateFactory<Vortice.Direct2D1.ID2D1Factory1>();

        var d2DRenderDemo = new D2DRenderDemo();

        while (!_isMainWindowClosed)
        {
            using IDXGISurface dxgiSurface = surface.BeginDraw<IDXGISurface>(null, out var updateOffset);
            _ = updateOffset;

            var renderTargetProperties = new Vortice.Direct2D1.RenderTargetProperties()
            {
                PixelFormat = new PixelFormat(Format.B8G8R8A8_UNorm, Vortice.DCommon.AlphaMode.Premultiplied),
                Type = Vortice.Direct2D1.RenderTargetType.Hardware,
            };

            using Vortice.Direct2D1.ID2D1RenderTarget d2D1RenderTarget =
                d2DFactory.CreateDxgiSurfaceRenderTarget(dxgiSurface, renderTargetProperties);

            Vortice.Direct2D1.ID2D1RenderTarget renderTarget = d2D1RenderTarget;

            renderTarget.BeginDraw();

            // 在這裏編寫繪製邏輯
            renderTarget.Clear(new Color4(0f));
            d2DRenderDemo.Draw(renderTarget, clientSize);

            renderTarget.EndDraw();
            surface.EndDraw();

            compositionDevice.Commit();
            compositionDevice.WaitForCommitCompletion();

            while (true)
            {
                var success = PInvoke.PeekMessage(out var message, window, 0, 0, PEEK_MESSAGE_REMOVE_TYPE.PM_REMOVE);
                if (!success)
                {
                    break;
                }

                PInvoke.TranslateMessage(&message);
                PInvoke.DispatchMessage(&message);
            }
        }

以上代碼也跑了 PeekMessage 方法防止窗口未響應

在每次進入繪製的時候調用 renderTarget.Clear(new Color4(0f)); 可以解決雙緩存帶來的閃爍問題,即界面內容被分別不同步地繪製到兩個紋理表面上。通過 Clear 確保每次都是重新繪製,解決此問題

什麼時候可以不做清理而進行繪製?進行某些性能優化的時候,且此時應該確保繪製同步。或再開一個表面紋理,通過同步的方式再將最終界面繪製

核心代碼

核心的代碼被我放在一個文件裏面,代碼如下

using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;

using Vortice.DCommon;
using Vortice.Direct3D;
using Vortice.Direct3D11;
using Vortice.DirectComposition;
using Vortice.DXGI;
using Vortice.Mathematics;

using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.Graphics.Gdi;
using Windows.Win32.UI.WindowsAndMessaging;

using AlphaMode = Vortice.DXGI.AlphaMode;
using D2D = Vortice.Direct2D1;

namespace DijallnemrecerkuCheberewhibair;

[SupportedOSPlatform("windows6.1")]
class DirectCompositionDemo
{
    public unsafe void Run()
    {
        var window = CreateWindow();
        PInvoke.ShowWindow(window, SHOW_WINDOW_CMD.SW_MAXIMIZE);

        var result = D3D11.D3D11CreateDevice(null, DriverType.Hardware, DeviceCreationFlags.BgraSupport,
            featureLevels: [], out ID3D11Device iD3D11Device, out var feature,
            out ID3D11DeviceContext iD3D11DeviceContext);
        result.CheckError();

        _ = feature;
        iD3D11DeviceContext.Dispose(); // 用不着就先釋放。釋放不代表立刻回收資源,只是表示業務層不需要用到,減少引用計數

        IDCompositionDevice compositionDevice = DComp.DCompositionCreateDevice3<IDCompositionDevice>(iD3D11Device);
        compositionDevice.CreateTargetForHwnd(window, topmost: true, out IDCompositionTarget compositionTarget);
        // 以上的 CreateTargetForHwnd 參數 topmost 不是值窗口置頂,而是:如果視覺樹應顯示在由 hwnd 參數指定的窗口子元素之上,則為 TRUE;否則,視覺樹將顯示在子元素之後
        // > TRUE if the visual tree should be displayed on top of the children of the window specified by the hwnd parameter; otherwise, the visual tree is displayed behind the children.
        // https://learn.microsoft.com/en-us/windows/win32/api/dcomp/nf-dcomp-idcompositiondevice-createtargetforhwnd

        // 創建視覺對象
        RECT windowRect;
        PInvoke.GetClientRect(window, &windowRect);
        var clientSize = new SizeI(windowRect.right - windowRect.left, windowRect.bottom - windowRect.top);

        IDCompositionVisual compositionVisual = compositionDevice.CreateVisual();
        IDCompositionVirtualSurface surface = compositionDevice.CreateVirtualSurface((uint) clientSize.Width,
            (uint) clientSize.Height, Format.B8G8R8A8_UNorm, AlphaMode.Premultiplied);

        // 創建 IDCompositionSurface 有兩個方法,分別是 CreateVirtualSurface 和 CreateSurface 方法。兩者不同的是 CreateVirtualSurface 創建的是稀疏表面,而 CreateSurface 是大數組(矩陣),在調用 BeginDraw 之前 IDCompositionVirtualSurface 不會分別空間,且 IDCompositionVirtualSurface 還能 Resize 重新設置大小。而 CreateSurface 則就不能
        //var createSurfaceResult = compositionDevice.CreateSurface((uint) clientSize.Width,
        //    (uint) clientSize.Height, Format.B8G8R8A8_UNorm, AlphaMode.Premultiplied, out IDCompositionSurface? dCompositionSurface);
        //createSurfaceResult.CheckError();
        //surface = dCompositionSurface;

        compositionVisual.SetContent(surface);

        compositionTarget.SetRoot(compositionVisual);
        compositionDevice.Commit();

        Vortice.Direct2D1.ID2D1Factory1 d2DFactory = Vortice.Direct2D1.D2D1.D2D1CreateFactory<Vortice.Direct2D1.ID2D1Factory1>();

        var d2DRenderDemo = new D2DRenderDemo();

        while (!_isMainWindowClosed)
        {
            using IDXGISurface dxgiSurface = surface.BeginDraw<IDXGISurface>(null, out var updateOffset);
            _ = updateOffset;

            var renderTargetProperties = new Vortice.Direct2D1.RenderTargetProperties()
            {
                PixelFormat = new PixelFormat(Format.B8G8R8A8_UNorm, Vortice.DCommon.AlphaMode.Premultiplied),
                Type = Vortice.Direct2D1.RenderTargetType.Hardware,
            };

            using Vortice.Direct2D1.ID2D1RenderTarget d2D1RenderTarget =
                d2DFactory.CreateDxgiSurfaceRenderTarget(dxgiSurface, renderTargetProperties);

            Vortice.Direct2D1.ID2D1RenderTarget renderTarget = d2D1RenderTarget;

            renderTarget.BeginDraw();

            // 在這裏編寫繪製邏輯
            renderTarget.Clear(new Color4(0f));
            d2DRenderDemo.Draw(renderTarget, clientSize);

            renderTarget.EndDraw();
            surface.EndDraw();

            compositionDevice.Commit();
            compositionDevice.WaitForCommitCompletion();

            while (true)
            {
                var success = PInvoke.PeekMessage(out var message, window, 0, 0, PEEK_MESSAGE_REMOVE_TYPE.PM_REMOVE);
                if (!success)
                {
                    break;
                }

                PInvoke.TranslateMessage(&message);
                PInvoke.DispatchMessage(&message);
            }
        }
    }

    private bool _isMainWindowClosed;

    private unsafe HWND CreateWindow()
    {
        PInvoke.DwmIsCompositionEnabled(out var compositionEnabled);

        if (!compositionEnabled)
        {
            Console.WriteLine($"無法啓用透明窗口效果");
        }

        // [Windows 窗口樣式 什麼是 WS_EX_NOREDIRECTIONBITMAP 樣式](https://blog.lindexi.com/post/Windows-%E7%AA%97%E5%8F%A3%E6%A0%B7%E5%BC%8F-%E4%BB%80%E4%B9%88%E6%98%AF-WS_EX_NOREDIRECTIONBITMAP-%E6%A0%B7%E5%BC%8F.html )
        WINDOW_EX_STYLE exStyle = WINDOW_EX_STYLE.WS_EX_NOREDIRECTIONBITMAP;

        var style = WNDCLASS_STYLES.CS_OWNDC | WNDCLASS_STYLES.CS_HREDRAW | WNDCLASS_STYLES.CS_VREDRAW;

        var defaultCursor = PInvoke.LoadCursor(
            new HINSTANCE(IntPtr.Zero), new PCWSTR(PInvoke.IDC_ARROW.Value));

        var className = $"lindexi-{Guid.NewGuid().ToString()}";
        var title = "The Title";
        _wndProcDelegate = new WNDPROC(WndProc); // 僅用於防止 GC 回收。詳細請看 https://github.com/lindexi/lindexi_gd/pull/85
        fixed (char* pClassName = className)
        fixed (char* pTitle = title)
        {
            var wndClassEx = new WNDCLASSEXW
            {
                cbSize = (uint) Marshal.SizeOf<WNDCLASSEXW>(),
                style = style,
                lpfnWndProc = _wndProcDelegate,
                hInstance = new HINSTANCE(PInvoke.GetModuleHandle(null).DangerousGetHandle()),
                hCursor = defaultCursor,
                hbrBackground = new HBRUSH(IntPtr.Zero),
                lpszClassName = new PCWSTR(pClassName)
            };
            ushort atom = PInvoke.RegisterClassEx(in wndClassEx);

            var dwStyle = WINDOW_STYLE.WS_OVERLAPPEDWINDOW | WINDOW_STYLE.WS_VISIBLE;

            var windowHwnd = PInvoke.CreateWindowEx(
                exStyle,
                new PCWSTR((char*) atom),
                new PCWSTR(pTitle),
                dwStyle,
                0, 0, 1900, 1000,
                HWND.Null, HMENU.Null, HINSTANCE.Null, null);

            return windowHwnd;
        }
    }

    private WNDPROC? _wndProcDelegate;

    private LRESULT WndProc(HWND hwnd, uint message, WPARAM wParam, LPARAM lParam)
    {
        if (message == PInvoke.WM_CLOSE)
        {
            _isMainWindowClosed = true;
        }

        return PInvoke.DefWindowProc(hwnd, message, wParam, lParam);
    }
}

class D2DRenderDemo
{
    // 此為調試代碼,繪製一些矩形條
    private List<D2DRenderInfo>? _renderList;

    public void OnReSize()
    {
        _renderList = null;
    }

    public void Draw(D2D.ID2D1RenderTarget renderTarget, SizeI clientSize)
    {
        var rectWeight = 10;
        var rectHeight = 20;

        var margin = 5;

        if (_renderList is null)
        {
            _renderList = new List<D2DRenderInfo>();

            for (int top = margin; top < clientSize.Height - rectHeight - margin; top += rectHeight + margin)
            {
                Rect rect = new Rect(margin, top, rectWeight, rectHeight);

                var color = new Color4(Random.Shared.NextSingle(), Random.Shared.NextSingle(),
                    Random.Shared.NextSingle());
                var step = Random.Shared.Next(1, 20);

                var renderInfo = new D2DRenderInfo(rect, step, color);
                _renderList.Add(renderInfo);
            }
        }

        for (var i = 0; i < _renderList.Count; i++)
        {
            var renderInfo = _renderList[i];
            using var brush = renderTarget.CreateSolidColorBrush(renderInfo.Color);

            renderTarget.FillRectangle(renderInfo.Rect, brush);

            var nextRect = renderInfo.Rect with
            {
                Width = renderInfo.Rect.Width + renderInfo.Step
            };

            if (nextRect.Width > clientSize.Width - margin * 2)
            {
                nextRect = nextRect with
                {
                    Width = rectWeight
                };
            }

            _renderList[i] = renderInfo with
            {
                Rect = nextRect
            };
        }
    }

    private readonly record struct D2DRenderInfo(Rect Rect, int Step, Color4 Color);
}

全部代碼

本文代碼放在 github 和 gitee 上,可以使用如下命令行拉取代碼。我整個代碼倉庫比較龐大,使用以下命令行可以進行部分拉取,拉取速度比較快

先創建一個空文件夾,接着使用命令行 cd 命令進入此空文件夾,在命令行裏面輸入以下代碼,即可獲取到本文的代碼

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

以上使用的是國內的 gitee 的源,如果 gitee 不能訪問,請替換為 github 的源。請在命令行繼續輸入以下代碼,將 gitee 源換成 github 源進行拉取代碼。如果依然拉取不到代碼,可以發郵件向我要代碼

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

獲取代碼之後,進入 DirectX/DirectComposition/DijallnemrecerkuCheberewhibair 文件夾,即可獲取到源代碼

更多博客

渲染部分,關於 SharpDx 和 Vortice 的使用方法,包括入門級教程,請參閲:

  • 渲染博客導航
  • SharpDX 系列

更多關於我博客請參閲 博客導航

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.