本文將告訴大家如何使用 Vortice 底層庫從零開始,從一個控制檯專案,開始搭建一個最簡單的使用 Direct2D1 的 DirectX 應用。本文屬於入門級部落格,期望本文能讓大家瞭解 Vortice 底層庫是可以如何呼叫 DirectX 的功能,以及瞭解 DirectX 中,特別是 D2D 部分的初始化邏輯
在開始聊 Vortice 之前,必須要先聊聊 SharpDx 庫。 眾所周知,現在 SharpDx 已不維護,儘管 SharpDx 的不維護對咱開發影響很小,除非需要用到這幾年新加的功能,否則使用不維護的 SharpDx 的問題也不大。而 Vortice 是作為 SharpDx 的一個代替的存在,是從 SharpDx 的基礎上,繼續開發的一個專案。 使用 Vortice 底層庫,能讓 C# 程式碼比較方便的和 DirectX 對接
從設計上,此 Vortice 庫和 SharpDx 是對 DirectX 的低階封裝,低階封裝意味著將會讓咱在開發時,必須瞭解非常的細節,但同時也帶來了可以進行底層優化的可能
可以代替 SharpDx 的庫,除了 Vortice 之外,還有很多,詳細請看 SharpDx 的代替專案
在開始閱讀本文之前,我期望讀者已瞭解很多相關的知識,例如 Win32 的概念,以及 DirectX 是什麼,和 .NET 框架的基礎知識加 C# 的基礎語法等知識。儘管本文屬於入門級部落格,但不會涉及到過於基礎的知識
想要開始使用 D2D 繪製內容,就需要有一個用來承載繪製內容的 "畫布" 物件,在 D2D 裡面,對應的就是一個 ID2D1RenderTarget 型別的物件
為了能在螢幕上能看到繪製的內容,那最好是有一個視窗用來顯示繪製內容。當然,使用離屏渲染也可以,只是用離屏渲染的話,自然有離屏渲染的自帶的坑再加上為了能看到渲染內容而做的編碼為圖片的坑,這就讓入門部落格不友好了。本文將通過 Win32 的方式一步步建立視窗,儘可能告訴大家更多的細節
本文使用的步驟如下:
本文將使用 VisualStudio 2022 作為 IDE 工具,理論上還在使用低於 VisualStudio 2022 版本的開發者,也應該升級 IDE 了
使用非 VisualStudio 作為 IDE 的,那推薦本文看著玩就好了,不要去嘗試本文的程式碼
新建一個 dotnet 6 的控制檯專案
接下來咱將從這個控制檯專案開始,編寫 D2D 應用
本文貼出的程式碼只有部分,如果構建不通過,推薦到本文的最後獲取整個專案的程式碼。本文的所有的原始碼可在本文的最後找到下載方式
找到咱 dotnet 的慣例,在使用某個庫之前,就是使用 NuGet 安裝庫
本文需要安裝以下的 NuGet 庫:
新建的專案預設採用 SDK 風格的 csproj 專案檔案格式,可以雙擊專案,編輯 csproj 專案檔案,在專案檔案上新增如下程式碼用來快速安裝 NuGet 庫
<ItemGroup>
<PackageReference Include="Vortice.Direct2D1" Version="2.1.32" />
<PackageReference Include="Vortice.Direct3D11" Version="2.1.32" />
<PackageReference Include="Vortice.DirectX" Version="2.1.32" />
<PackageReference Include="Vortice.D3DCompiler" Version="2.1.32" />
<PackageReference Include="Vortice.Win32" Version="1.6.2" />
<PackageReference Include="Microsoft.Windows.CsWin32" PrivateAssets="all" Version="0.2.63-beta" />
</ItemGroup>
編輯之後的 csproj 專案檔案的程式碼如下
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Vortice.Direct2D1" Version="2.1.32" />
<PackageReference Include="Vortice.Direct3D11" Version="2.1.32" />
<PackageReference Include="Vortice.DirectX" Version="2.1.32" />
<PackageReference Include="Vortice.D3DCompiler" Version="2.1.32" />
<PackageReference Include="Vortice.Win32" Version="1.6.2" />
<PackageReference Include="Microsoft.Windows.CsWin32" PrivateAssets="all" Version="0.2.63-beta" />
</ItemGroup>
</Project>
為了更加方便大家靜態閱讀程式碼,我特意使用了以下參照方式,讓大家在閱讀程式碼的時候,瞭解到對應的型別是屬於哪個名稱空間下
using D3D = Vortice.Direct3D;
using D3D11 = Vortice.Direct3D11;
using DXGI = Vortice.DXGI;
using D2D = Vortice.Direct2D1;
其他的參照程式碼如下
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using Windows.Win32.Foundation;
using Windows.Win32.UI.WindowsAndMessaging;
using static Windows.Win32.PInvoke;
using static Windows.Win32.UI.WindowsAndMessaging.PEEK_MESSAGE_REMOVE_TYPE;
using static Windows.Win32.UI.WindowsAndMessaging.WNDCLASS_STYLES;
using static Windows.Win32.UI.WindowsAndMessaging.WINDOW_STYLE;
using static Windows.Win32.UI.WindowsAndMessaging.WINDOW_EX_STYLE;
using static Windows.Win32.UI.WindowsAndMessaging.SYSTEM_METRICS_INDEX;
using static Windows.Win32.UI.WindowsAndMessaging.SHOW_WINDOW_CMD;
using Vortice.DCommon;
using Vortice.Mathematics;
using AlphaMode = Vortice.DXGI.AlphaMode;
由於本文使用的專案,在 csproj 專案檔案設定了使用 ImplicitUsings 屬性,加上對 System 等名稱空間的預設參照,這裡就不需要再寫對預設名稱空間的參照
為了建立 Win32 視窗以及初始化建立 DX 物件,就需要使用一些 Win32 函數。使用 Win32 函數之前,需要對 Win32 函數進行定義。本文使用 Microsoft.Windows.CsWin32 庫來輔助編寫 Win32 函數的定義
在安裝了 Microsoft.Windows.CsWin32 庫,即可新建一個 NativeMethods.txt
的檔案,在這個檔案裡面,一行一個函數或 Win32 型別名,即可自動使用原始碼生成的方式建立定義
新建一個 NativeMethods.txt
檔案,在這個檔案裡面寫上需要使用的 Win32 函數,內容如下
GetModuleHandle
PeekMessage
TranslateMessage
DispatchMessage
GetMessage
RegisterClassExW
DefWindowProc
LoadCursor
PostQuitMessage
CreateWindowExW
DestroyWindow
ShowWindow
GetSystemMetrics
AdjustWindowRectEx
GetClientRect
GetWindowRect
IDC_ARROW
WM_KEYDOWN
WM_KEYUP
WM_SYSKEYDOWN
WM_SYSKEYUP
WM_DESTROY
WM_QUIT
WM_PAINT
WM_CLOSE
WM_ACTIVATEAPP
VIRTUAL_KEY
完成了初始化準備之後,接下來就可以開始編寫程式碼
本文使用控制檯專案,在建立 Win32 視窗,需要使用到很多 Win32 視窗建立的細節程式碼,但本文更側重如何使用 DX 的知識,因此關於 Win32 建立視窗的邏輯,大部分都會略過
在開始建立 Win32 視窗之前,先準備一些引數
設定視窗的尺寸
SizeI clientSize = new SizeI(1000, 1000);
再給視窗定義一個標題
// 視窗標題
var title = "Demo";
var windowClassName = "lindexi doubi";
設定視窗的 Win32 樣式,這個樣式的內容沒啥固定的,可以根據自己的需求來,也可以亂來,不離譜就好
// 視窗樣式,視窗樣式含義請執行參閱官方檔案,樣式只要不離譜,自己隨便寫,影響不大
WINDOW_STYLE style = WS_CAPTION |
WS_SYSMENU |
WS_MINIMIZEBOX |
WS_CLIPSIBLINGS |
WS_BORDER |
WS_DLGFRAME |
WS_THICKFRAME |
WS_GROUP |
WS_TABSTOP |
WS_SIZEBOX;
根據上面設定的視窗尺寸,嘗試根據樣式算出實際可用的尺寸
var rect = new RECT
{
right = clientSize.Width,
bottom = clientSize.Height
};
// Adjust according to window styles
AdjustWindowRectEx(&rect, style, false, WS_EX_APPWINDOW);
接著算出視窗顯示的座標和尺寸,這個邏輯非核心邏輯,也可以自己隨意寫一個座標。本文將嘗試讓視窗顯示在螢幕中間
int x = 0;
int y = 0;
int windowWidth = rect.right - rect.left;
int windowHeight = rect.bottom - rect.top;
// 隨便,放在螢幕中間好了。多個顯示器?忽略
int screenWidth = GetSystemMetrics(SM_CXSCREEN);
int screenHeight = GetSystemMetrics(SM_CYSCREEN);
x = (screenWidth - windowWidth) / 2;
y = (screenHeight - windowHeight) / 2;
準備完成,開始建立視窗
var hInstance = GetModuleHandle((string)null);
fixed (char* lpszClassName = windowClassName)
{
PCWSTR szCursorName = new((char*)IDC_ARROW);
var wndClassEx = new WNDCLASSEXW
{
cbSize = (uint)Unsafe.SizeOf<WNDCLASSEXW>(),
style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC,
// 核心邏輯,設定訊息迴圈
lpfnWndProc = new WNDPROC(WndProc),
hInstance = (HINSTANCE)hInstance.DangerousGetHandle(),
hCursor = LoadCursor((HINSTANCE)IntPtr.Zero, szCursorName),
hbrBackground = (Windows.Win32.Graphics.Gdi.HBRUSH)IntPtr.Zero,
hIcon = (HICON)IntPtr.Zero,
lpszClassName = lpszClassName
};
ushort atom = RegisterClassEx(wndClassEx);
if (atom == 0)
{
throw new InvalidOperationException(
$"Failed to register window class. Error: {Marshal.GetLastWin32Error()}"
);
}
}
// 建立視窗
var hWnd = CreateWindowEx
(
WS_EX_APPWINDOW,
windowClassName,
title,
style,
x,
y,
windowWidth,
windowHeight,
hWndParent: default,
hMenu: default,
hInstance: default,
lpParam: null
);
獲取到的 hWnd
將會在接下來被 DX 掛上
但願大家知道 hWnd
是啥意思
既然建立出了視窗了,那就顯示出來吧
// 建立完成,那就顯示
ShowWindow(hWnd, SW_NORMAL);
獲取實際的視窗大小,這將用來決定後續交換鏈的建立。什麼是交換鏈?自己去了解
RECT windowRect;
GetClientRect(hWnd, &windowRect);
clientSize = new SizeI(windowRect.right - windowRect.left, windowRect.bottom - windowRect.top);
以上程式碼就完成了建立 Win32 視窗
這一步是可選的,通過列舉 DX 提供的抽象的顯示介面卡介面,可以用來後續建立 D3D 裝置。本文這裡是給大家演示如何獲取抽象的顯示介面卡介面的方法,沒有指定顯示介面卡介面也是可以建立 D3D 裝置
顯示介面卡介面 IDXGIAdapter 是對硬體或軟體的一個抽象,可以是一個顯示卡,也可以是一個軟體渲染器。這裡獲取到的抽象的顯示介面卡介面,在大部分情況下都是和具體的顯示卡相關的,但是不代表著一定就是真實的顯示卡
下圖是從官方檔案拷貝的,一個電腦加兩個顯示卡的物件關係
先嚐試使用 IDXGIFactory6 提供的 EnumAdapterByGpuPreference 方法列舉顯示卡,這個方法的功能是可以按照給定的引數進行排序,特別方便開發時,獲取首個可用顯示卡
想要使用 EnumAdapterByGpuPreference 方法,需要先獲取 IDXGIFactory6 物件。而 IDXGIFactory6 物件可以通過工廠建立 IDXGIFactory2 物件間接獲取
接下來咱也會用到 IDXGIFactory2 提供的功能
// 開始建立工廠建立 D3D 的邏輯
var dxgiFactory2 = DXGI.DXGI.CreateDXGIFactory1<DXGI.IDXGIFactory2>();
為了讓大家方便閱讀獲取顯示卡的程式碼,將獲取顯示卡的程式碼放入到 GetHardwareAdapter 方法
private static IEnumerable<DXGI.IDXGIAdapter1> GetHardwareAdapter(DXGI.IDXGIFactory2 factory)
{
}
先嚐試從 IDXGIFactory2 物件獲取 IDXGIFactory6 物件
在 DX 的設計上,介面都是一個個版本迭代的,為了保持相容性,只是新加介面,而不是更改原來的介面定義。也就是獲取到的物件,也許有在這臺裝置上的 DX 版本,能支援到 IDXGIFactory6 版本,通用的做法是呼叫 QueryInterface*
方法,例如 QueryInterfaceOrNull
方法,嘗試獲取到更新的版本的介面物件。使用封裝的 QueryInterfaceOrNull
方法,可以在不支援時返回空,通過判斷返回值即可瞭解是否支援
DXGI.IDXGIFactory6? factory6 = factory.QueryInterfaceOrNull<DXGI.IDXGIFactory6>();
if (factory6 != null)
{
// 這個系統的 DX 支援 IDXGIFactory6 型別
}
else
{
// 不支援就不支援咯,用舊版本的方式獲取顯示介面卡介面
}
在 IDXGIFactory6 新加的 EnumAdapterByGpuPreference 方法可以支援傳入引數,通過引數按照順序返回顯示介面卡介面
傳入高效能引數開始獲取,將會按照順序獲取到 DX 認為的高效能排列的順序
// 先告訴系統,要高效能的顯示卡
for (int adapterIndex = 0;
factory6.EnumAdapterByGpuPreference(adapterIndex, DXGI.GpuPreference.HighPerformance,
out DXGI.IDXGIAdapter1? adapter).Success;
adapterIndex++)
{
if (adapter == null)
{
continue;
}
}
再扔掉使用軟渲染的,扔掉軟渲染的這一步只是為了演示如何判斷獲取到的顯示介面卡介面是採用軟渲染的
// 先告訴系統,要高效能的顯示卡
for (int adapterIndex = 0;
factory6.EnumAdapterByGpuPreference(adapterIndex, DXGI.GpuPreference.HighPerformance,
out DXGI.IDXGIAdapter1? adapter).Success;
adapterIndex++)
{
if (adapter == null)
{
continue;
}
DXGI.AdapterDescription1 desc = adapter.Description1;
if ((desc.Flags & DXGI.AdapterFlags.Software) != DXGI.AdapterFlags.None)
{
// Don't select the Basic Render Driver adapter.
adapter.Dispose();
continue;
}
}
這裡可以輸出獲取到的顯示介面卡介面的描述,可以看看在不同的裝置上的輸出
Console.WriteLine($"列舉到 {adapter.Description1.Description} 顯示卡");
所有的獲取的程式碼如下
DXGI.IDXGIFactory6? factory6 = factory.QueryInterfaceOrNull<DXGI.IDXGIFactory6>();
if (factory6 != null)
{
// 先告訴系統,要高效能的顯示卡
for (int adapterIndex = 0;
factory6.EnumAdapterByGpuPreference(adapterIndex, DXGI.GpuPreference.HighPerformance,
out DXGI.IDXGIAdapter1? adapter).Success;
adapterIndex++)
{
if (adapter == null)
{
continue;
}
DXGI.AdapterDescription1 desc = adapter.Description1;
if ((desc.Flags & DXGI.AdapterFlags.Software) != DXGI.AdapterFlags.None)
{
// Don't select the Basic Render Driver adapter.
adapter.Dispose();
continue;
}
//factory6.Dispose();
Console.WriteLine($"列舉到 {adapter.Description1.Description} 顯示卡");
yield return adapter;
}
factory6.Dispose();
}
如果獲取不到,那就使用舊的方法列舉
// 如果列舉不到,那系統返回啥都可以
for (int adapterIndex = 0;
factory.EnumAdapters1(adapterIndex, out DXGI.IDXGIAdapter1? adapter).Success;
adapterIndex++)
{
DXGI.AdapterDescription1 desc = adapter.Description1;
if ((desc.Flags & DXGI.AdapterFlags.Software) != DXGI.AdapterFlags.None)
{
// Don't select the Basic Render Driver adapter.
adapter.Dispose();
continue;
}
Console.WriteLine($"列舉到 {adapter.Description1.Description} 顯示卡");
yield return adapter;
}
為了方便偵錯,這裡就加上 ToList 讓所有程式碼都執行
var hardwareAdapter = GetHardwareAdapter(dxgiFactory2)
// 這裡 ToList 只是想列出所有的 IDXGIAdapter1 在實際程式碼裡,大部分都是獲取第一個
.ToList().FirstOrDefault();
if (hardwareAdapter == null)
{
throw new InvalidOperationException("Cannot detect D3D11 adapter");
}
以上程式碼即可獲取到顯示介面卡介面用來進行後續的初始化
在開始之前,按照 C# 從零開始寫 SharpDx 應用 聊聊功能等級 的方法,定義程式碼
// 功能等級
// [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)
D3D.FeatureLevel[] featureLevels = new[]
{
D3D.FeatureLevel.Level_11_1,
D3D.FeatureLevel.Level_11_0,
D3D.FeatureLevel.Level_10_1,
D3D.FeatureLevel.Level_10_0,
D3D.FeatureLevel.Level_9_3,
D3D.FeatureLevel.Level_9_2,
D3D.FeatureLevel.Level_9_1,
};
使用以上獲取的顯示介面卡介面建立裝置
DXGI.IDXGIAdapter1 adapter = hardwareAdapter;
D3D11.DeviceCreationFlags creationFlags = D3D11.DeviceCreationFlags.BgraSupport;
var result = D3D11.D3D11.D3D11CreateDevice
(
adapter,
D3D.DriverType.Unknown,
creationFlags,
featureLevels,
out D3D11.ID3D11Device d3D11Device, out D3D.FeatureLevel featureLevel,
out D3D11.ID3D11DeviceContext d3D11DeviceContext
);
也許使用這個顯示介面卡介面建立不出裝置,通過判斷返回值即可瞭解是否成功。建立失敗,那就不指定具體的引數,使用 WARP 的方法建立
if (result.Failure)
{
// 如果失敗了,那就不指定顯示卡,走 WARP 的方式
// http://go.microsoft.com/fwlink/?LinkId=286690
result = D3D11.D3D11.D3D11CreateDevice(
IntPtr.Zero,
D3D.DriverType.Warp,
creationFlags,
featureLevels,
out d3D11Device, out featureLevel, out d3D11DeviceContext);
// 如果失敗,就不能繼續
result.CheckError();
}
以上程式碼的 CheckError 方法,將會在失敗丟擲異常
建立成功,可以獲取到 ID3D11Device 和 ID3D11DeviceContext 型別的物件和實際的功能等級。 這裡的 ID3D11Device 就是 D3D 裝置,提供給交換鏈繫結的功能,可以繪製到交換鏈的快取裡,從而被交換鏈重新整理到螢幕上。這裡的 ID3D11DeviceContext 是包含了 D3D 裝置的環境和設定,可以用來設定渲染狀態等
由於後續期望使用的是 ID3D11Device1 介面,按照慣例,從 d3D11Device
獲取
// 大部分情況下,用的是 ID3D11Device1 和 ID3D11DeviceContext1 型別
// 從 ID3D11Device 轉換為 ID3D11Device1 型別
var d3D11Device1 = d3D11Device.QueryInterface<D3D11.ID3D11Device1>();
理論上當前能執行 dotnet 6 的 Windows 系統,都是支援 ID3D11Device1 的
同理,獲取 ID3D11DeviceContext1 介面
var d3D11DeviceContext1 = d3D11DeviceContext.QueryInterface<D3D11.ID3D11DeviceContext1>();
獲取到了新的兩個介面,就可以減少 d3D11Device
和 d3D11DeviceContext
的參照計數。呼叫 Dispose 不會釋放掉剛才申請的 D3D 資源,只是減少參照計數
d3D11Device.Dispose();
d3D11DeviceContext.Dispose();
建立裝置完成之後,接下來就是建立交換鏈和關聯視窗。建立交換鏈需要很多引數,在 DX 的設計上,將引數放入到 SwapChainDescription 型別裡面。和 DX 的介面設計一樣,也有多個 SwapChainDescription 版本
建立 SwapChainDescription1 引數的程式碼如下
// 顏色格式,如果後續準備接入 WPF 那推薦使用此格式
DXGI.Format colorFormat = DXGI.Format.B8G8R8A8_UNorm;
// 快取的數量,包括前快取。大部分應用來說,至少需要兩個快取,這個玩過遊戲的夥伴都知道
const int FrameCount = 2;
DXGI.SwapChainDescription1 swapChainDescription = new()
{
Width = clientSize.Width,
Height = clientSize.Height,
Format = colorFormat,
BufferCount = FrameCount,
BufferUsage = DXGI.Usage.RenderTargetOutput,
SampleDescription = DXGI.SampleDescription.Default,
Scaling = DXGI.Scaling.Stretch,
SwapEffect = DXGI.SwapEffect.FlipDiscard,
AlphaMode = AlphaMode.Ignore
};
引數上面的各個引數的排列組合可以實現很多不同的功能,但是 DX 有一個坑的地方在於,引數是不正交的,有些引數設定不對,將會在後續建立失敗
再設定是否進入全螢幕模式,對於現在很多遊戲和應用,都可以使用設定視窗進入最大化的全螢幕模式,這裡就設定不進入全螢幕
// 設定是否全螢幕
DXGI.SwapChainFullscreenDescription fullscreenDescription = new DXGI.SwapChainFullscreenDescription
{
Windowed = true
};
設定了引數,就可以建立交換鏈。可以通過 HWnd 視窗控制程式碼建立,也可以建立和 UWP 對接的 CreateSwapChainForCoreWindow 方式,也可以通過 DirectComposition 的 CreateSwapChainForComposition 建立。本文這裡採用 CreateSwapChainForHwnd 建立,關聯視窗
DXGI.IDXGISwapChain1 swapChain =
dxgiFactory2.CreateSwapChainForHwnd(d3D11Device1, hWnd, swapChainDescription, fullscreenDescription);
附帶禁止按下 alt+enter 進入全螢幕,這是可選的
// 不要被按下 alt+enter 進入全螢幕
dxgiFactory2.MakeWindowAssociation(hWnd, DXGI.WindowAssociationFlags.IgnoreAltEnter);
這就完成了最重要的交換鏈的建立,以上完成之後,即可讓 D3D 的內容繪製在視窗上。接下來準備再加上 D2D 的繪製
如下圖,通過 D3D 承載 D2D 的內容。以上完成了 D3D 的初始化,接下來可以通過 DXGI 輔助建立 D2D 的 ID2D1RenderTarget 畫布
如上圖的框架,想要使用 D2D 之前,需要先解決讓 D2D 繪製到哪。讓 D2D 繪製的輸出,可以是一個 IDXGISurface 物件。通過 CreateDxgiSurfaceRenderTarget 方法既可以在 IDXGISurface 建立 ID2D1RenderTarget 物件,讓繪製可以輸出。而 IDXGISurface 可以從 ID3D11Texture2D 獲取到。通過交換鏈的 GetBuffer 方法可以獲取到 ID3D11Texture2D 物件
本文將按照這個步驟,建立 ID2D1RenderTarget 畫布。除了以上步驟之外,還有其他的方法,詳細還請看官方檔案的轉換框架
按照慣例建立 D2D 需要先建立工廠
// 對接 D2D 需要建立工廠
D2D.ID2D1Factory1 d2DFactory = D2D.D2D1.D2D1CreateFactory<D2D.ID2D1Factory1>();
先從交換鏈獲取到 ID3D11Texture2D 物件,通過 IDXGISwapChain1 的 GetBuffer 獲取交換鏈的一個後臺快取
D3D11.ID3D11Texture2D backBufferTexture = swapChain.GetBuffer<D3D11.ID3D11Texture2D>(0);
接著使用 QueryInterface 將 ID3D11Texture2D 轉換為 IDXGISurface 物件
DXGI.IDXGISurface dxgiSurface = backBufferTexture.QueryInterface<DXGI.IDXGISurface>();
獲取到 IDXGISurface 即可通過 D2D 工廠建立 ID2D1RenderTarget 畫布
var renderTargetProperties = new D2D.RenderTargetProperties(PixelFormat.Premultiplied);
D2D.ID2D1RenderTarget d2D1RenderTarget =
d2DFactory.CreateDxgiSurfaceRenderTarget(dxgiSurface, renderTargetProperties);
這裡獲取到的 ID2D1RenderTarget 就是可以用來方便繪製 2D 的畫布
最簡單的繪製方式就是使用 Clear 方法修改顏色。本文只是告訴大家如何進行初始化,不會涉及到如何使用 D2D 繪製的內容
在開始呼叫 Clear 方法之前,需要先呼叫 BeginDraw 方法,告訴 DX 開始繪製。完成繪製,需要呼叫 EndDraw 方法告訴 DX 繪製完成。這裡必須明確的是,在對 ID2D1RenderTarget 呼叫各個繪製方法時,不是方法呼叫完成就渲染完成的,這些方法只是收集繪製指令,而不是立刻進行渲染
var renderTarget = d2D1RenderTarget;
// 開始繪製邏輯
renderTarget.BeginDraw();
// 隨意建立顏色
var color = new Color4((byte)Random.Shared.Next(255), (byte)Random.Shared.Next(255),
(byte)Random.Shared.Next(255));
renderTarget.Clear(color);
renderTarget.EndDraw();
以上程式碼使用隨意的顏色清理,呼叫 Clear 時,將會讓整個 ID2D1RenderTarget 使用給定的顏色清理,也就是修改顏色
在完成之後,呼叫一下交換鏈的 Present 和等待重新整理
swapChain.Present(1, DXGI.PresentFlags.None);
// 等待重新整理
d3D11DeviceContext1.Flush();
呼叫交換鏈的 Present
函數在螢幕上顯示渲染緩衝區的內容 swapChain.Present(1, PresentFlags.None);
是等待垂直同步,在重新整理完成在完成這個方法,第一個引數是同步間隔,第二個引數是演示的標誌
嘗試執行一下程式碼,就可以看到建立出了一個視窗,視窗的設定了一個詭異的顏色
這就是入門級的使用 Vortice 從零開始控制檯建立視窗,在視窗上使用 D2D 繪製的方法
在完成初始化的邏輯之後,就可以使用 D2D 繪製複雜的介面了。 在 ID2D1RenderTarget 可以方便呼叫各個方法進行繪製,如繪製矩形,畫圓等。詳細請看 C# 從零開始寫 SharpDx 應用 繪製基礎圖形
本文有部分程式碼沒有貼出,可以通過以下方法獲取本文使用的專案。如果發現自己照著寫,跑不起來,推薦使用本文的專案跑一下對比程式碼
可以通過如下方式獲取本文的原始碼,先建立一個空資料夾,接著使用命令列 cd 命令進入此空資料夾,在命令列裡面輸入以下程式碼,即可獲取到本文的程式碼
git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin 162977106065bd3cf7bfbed0a87828c992b8df3d
以上使用的是 gitee 的源,如果 gitee 不能存取,請替換為 github 的源。請在命令列繼續輸入以下程式碼
git remote remove origin
git remote add origin https://github.com/lindexi/lindexi_gd.git
git pull origin 162977106065bd3cf7bfbed0a87828c992b8df3d
獲取程式碼之後,進入 HoyebenawlerWegemnardicheba 資料夾
渲染部分,關於 SharpDx 使用,包括入門級教學,請參閱:
在 WPF 框架的渲染部分,請參閱: WPF 底層渲染_lindexi_gd的部落格-CSDN部落格
更多關於我部落格請參閱 部落格導航
部落格園部落格只做備份,部落格釋出就不再更新,如果想看最新部落格,請到 https://blog.lindexi.com/