在 DirectComposition 裏面提供了 Commit 機制,一次 Commit 的所有內容都能在相同的一幀在屏幕顯示出來,如此可以非常方便地完成渲染對齊任務
通過 WaitForCommitCompletion 方法可以等待 Commit 內容完成渲染,此方法作用相當於等待交換鏈寫法的等待垂直同步實現
在 上一篇博客 中,採用了傳統的 DXGI 交換鏈與 DirectComposition 對接
在本文這裏將去掉交換鏈,可以很大簡化對接渲染的邏輯。採用 DXGI 交換鏈對接的方式,可以比較方便對接原有的程序,且可以實現更高幀率的控制。而採用 DirectComposition 的 Commit 寫法,可以更好利用 DirectComposition 機制,實現多表面合成以及更加實時的合成器動畫
本文將給出最簡實現對接的代碼邏輯,其步驟如下
- 創建 Win32 窗口
- 創建 DirectComposition 設備和關聯窗口,獲取渲染表面
- 執行渲染邏輯
為了保持本文簡潔,我將不在正文部分貼出非關鍵部分的代碼,在本文末尾給出全部核心代碼。本文的全部核心代碼部分不到 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 系列
更多關於我博客請參閲 博客導航