簡介
本文使用D3D繪制一個三角形,通過這個程序可以了解一下D3D的基本開發過程。

開發環境
系統:Windows 10
IDE:Visual Studio 2019
注意
本示例需要兩個幫助文件點擊下載
開發步驟
創建窗口
1、新建一個空白C++項目,文件->新建->項目,語言選擇C++,平台選擇Windows,選擇空項目,點擊下一步,真寫項目名稱和位置,點擊創建。
2、新建一個C++文件:stdafx.cpp和一個頭文件:stdafx.h。
3、解決方案資源管理器中右擊項目,選擇屬性。
常規里邊,輸出目錄改為:bin$(Configuration)\,中間目錄改為:obj$(Configuration)\。
調試里邊,工作目錄改為:$(OutDir)
點擊確定
4、右擊項目,選擇屬性。
C/C++->預處理器,預處理器定義,編輯,填入如下內容:
_DEBUG
_WINDOWS
NOMINMAX
C/C++->預編譯頭,改為使用(/Yu)。
鏈接器->輸入,附加依賴項改為:
d3d12.lib
D3D11.lib
dxgi.lib
d3dcompiler.lib
d2d1.lib
dwrite.lib
dxguid.lib
xaudio2.lib
mfcore.lib
mfplat.lib
mfreadwrite.lib
mfuuid.lib
鏈接器->系統,子系統改為:窗口 (/SUBSYSTEM:WINDOWS)
點擊確定。
5、右擊項目,管理NuGet程序包,瀏覽里邊搜索DirectXTK12,在搜索結果中選擇directxtk12_desktop_2017,安裝。
6、右擊stdafx.cpp文件,選擇屬性,預編譯頭,改為創建 (/Yc)
6、打開stdafx.h,輸入以下內容
#pragma once
#include <SDKDDKVer.h>
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <stdlib.h>
#include <malloc.h>
#include <memory.h>
#include <tchar.h>
#include <time.h>
#include <string>
#include <d3d12.h>
#include <d3d11_3.h>
#include <d3d11on12.h>
#include <dxgi1_4.h>
#include <D3Dcompiler.h>
#include <DirectXMath.h>
#include <d2d1_3.h>
#include <dwrite_3.h>
#include <wincodec.h>
#include <CommonStates.h>
#include <DDSTextureLoader.h>
#include <ResourceUploadBatch.h>
#include <DirectXHelpers.h>
#include <Effects.h>
#include <GamePad.h>
#include <GeometricPrimitive.h>
#include <GraphicsMemory.h>
#include <Keyboard.h>
#include <Model.h>
#include <Mouse.h>
#include <PostProcess.h>
#include <PrimitiveBatch.h>
#include <ScreenGrab.h>
#include <SimpleMath.h>
#include <SpriteBatch.h>
#include <SpriteFont.h>
#include <VertexTypes.h>
#include <WICTextureLoader.h>
#include <Audio.h>
#include <string>
#include <wrl.h>
#include <shellapi.h>
7、創建一個C++源文件TriangleMain.cpp,輸入以下內容:
#include "stdafx.h"
// 全局變量:
HINSTANCE hInst; // 當前實例
WCHAR szWindowClass[] = L"TriangleWindow";
// 此代碼模塊中包含的函數的前向聲明:
ATOM MyRegisterClass(HINSTANCE hInstance);
BOOL InitInstance(HINSTANCE, int);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM);
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR lpCmdLine,
_In_ int nCmdShow)
{
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
// TODO: 在此處放置代碼。
// 初始化全局字符串
MyRegisterClass(hInstance);
// 執行應用程序初始化:
if (!InitInstance(hInstance, nCmdShow))
{
return FALSE;
}
MSG msg = {};
// 主消息循環:
while (msg.message != WM_QUIT)
{
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return (int)msg.wParam;
}
//
// 函數: MyRegisterClass()
//
// 目標: 注冊窗口類。
//
ATOM MyRegisterClass(HINSTANCE hInstance)
{
WNDCLASSEXW wcex = { 0 };
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wcex.lpszClassName = szWindowClass;
return RegisterClassExW(&wcex);
}
//
// 函數: InitInstance(HINSTANCE, int)
//
// 目標: 保存實例句柄並創建主窗口
//
// 注釋:
//
// 在此函數中,我們在全局變量中保存實例句柄並
// 創建和顯示主程序窗口。
//
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
hInst = hInstance; // 將實例句柄存儲在全局變量中
HWND hWnd = CreateWindowW(szWindowClass, L"Triangle", WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, 800, 600, nullptr, nullptr, hInstance, nullptr);
if (!hWnd)
{
return FALSE;
}
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
return TRUE;
}
//
// 函數: WndProc(HWND, UINT, WPARAM, LPARAM)
//
// 目標: 處理主窗口的消息。
//
// WM_COMMAND - 處理應用程序菜單
// WM_PAINT - 繪制主窗口
// WM_DESTROY - 發送退出消息並返回
//
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_KEYDOWN:
break;
case WM_PAINT:
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
編譯后按F5運行,正常情況下會出現一個空白窗口。這個窗口用作繪圖區域。
准備3D環境
首先,我們要在程序里枚舉出支持DirectX的設備,並創建一個交換鏈用於呈現后台緩存中的圖像內容。我們將這部分代碼封裝到一個Device的類中,繪圖操作放到Scene類中。
Device類實現
這個類中需要創建兩個東西,一個設備接口實例,一個交換鏈實例,還有一個是CPU與GPU數據同步的圍欄,以及提供創建各類資源、呈現圖像、緩存管理的方法。別外,創建交互鏈需要提供窗口的句柄和尺寸。
//Device.h
#pragma once
using namespace DirectX;
using namespace Microsoft::WRL;
class Device
{
private:
HWND m_hwnd; //窗口句柄
int m_width; //窗口寬度
int m_height; //窗口高度
bool m_useWarpDevice; //是否使用模擬設備
UINT m_frameCount; //緩沖數量
int m_rtvDescriptorSize; //RTV緩存大小
ComPtr<IDXGISwapChain3> m_swapChain;
ComPtr<ID3D12Device> m_device;
ComPtr<ID3D12CommandQueue> m_commandQueue;
ComPtr<ID3D12DescriptorHeap> m_rtvHeap;
std::vector<ComPtr<ID3D12Resource>> m_renderTargets;
ComPtr<ID3D12Resource> m_depthStencil;
ComPtr<ID3D12DescriptorHeap> m_dsvHeap;
ComPtr<ID3D12Fence> m_fence;
UINT64 m_fenceValue;
HANDLE m_fenceEvent;
int m_frameIndex;
void GetHardwareAdapter(_In_ IDXGIFactory2* pFactory, _Outptr_result_maybenull_ IDXGIAdapter1** ppAdapter);
void CreateDevice();
void CreateRenderTarget();
void CreateDepthStencil();
public:
Device(HWND hwnd, int wWidth, int wHeight, int frameCount, bool useWrap);
HRESULT CreateCommandList(ComPtr<ID3D12CommandAllocator>& commandAllocator, ComPtr<ID3D12GraphicsCommandList>& commandList);
void Present();
void PostCommand(ID3D12GraphicsCommandList* commandList);
void WaitGpu();
ID3D12Device* GetDevice() { return m_device.Get(); }
ID3D12DescriptorHeap* GetDepthStencilHeap() { return m_dsvHeap.Get(); };
ComPtr<ID3D12CommandQueue> GetCommandQueue() { return m_commandQueue; }
ID3D12Resource* GetRenderTargetResource() { return m_renderTargets[m_frameIndex].Get(); }
CD3DX12_CPU_DESCRIPTOR_HANDLE GetRenderTargetHandle()
{
CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHandle(m_rtvHeap.Get()->GetCPUDescriptorHandleForHeapStart(), m_frameIndex, m_rtvDescriptorSize);
return rtvHandle;
}
};
//Device.cpp
#include "stdafx.h"
#include "Device.h"
void Device::GetHardwareAdapter(IDXGIFactory2* pFactory, IDXGIAdapter1** ppAdapter)
{
ComPtr<IDXGIAdapter1> adapter;
*ppAdapter = nullptr;
for (UINT adapterIndex = 0; DXGI_ERROR_NOT_FOUND != pFactory->EnumAdapters1(adapterIndex, &adapter); ++adapterIndex)
{
DXGI_ADAPTER_DESC1 desc;
adapter->GetDesc1(&desc);
if (desc.Flags & DXGI_ADAPTER_FLAG_SOFTWARE)
{
// Don't select the Basic Render Driver adapter.
// If you want a software adapter, pass in "/warp" on the command line.
continue;
}
// Check to see if the adapter supports Direct3D 12, but don't create the
// actual device yet.
if (SUCCEEDED(D3D12CreateDevice(adapter.Get(), D3D_FEATURE_LEVEL_11_0, _uuidof(ID3D12Device), nullptr)))
{
break;
}
}
*ppAdapter = adapter.Detach();
}
void Device::CreateDevice()
{
UINT dxgiFactoryFlags = 0;
#if defined(_DEBUG)
// Enable the debug layer (requires the Graphics Tools "optional feature").
// NOTE: Enabling the debug layer after device creation will invalidate the active device.
{
ComPtr<ID3D12Debug> debugController;
if (SUCCEEDED(D3D12GetDebugInterface(IID_PPV_ARGS(&debugController))))
{
debugController->EnableDebugLayer();
// Enable additional debug layers.
dxgiFactoryFlags |= DXGI_CREATE_FACTORY_DEBUG;
}
}
#endif
ComPtr<IDXGIFactory4> factory;
CreateDXGIFactory2(dxgiFactoryFlags, IID_PPV_ARGS(&factory));
bool useWarpDevice = true;
if (useWarpDevice)
{
ComPtr<IDXGIAdapter> warpAdapter;
factory->EnumWarpAdapter(IID_PPV_ARGS(&warpAdapter));
D3D12CreateDevice(
warpAdapter.Get(),
D3D_FEATURE_LEVEL_11_0,
IID_PPV_ARGS(&m_device)
);
}
else
{
ComPtr<IDXGIAdapter1> hardwareAdapter;
GetHardwareAdapter(factory.Get(), &hardwareAdapter);
D3D12CreateDevice(
hardwareAdapter.Get(),
D3D_FEATURE_LEVEL_11_0,
IID_PPV_ARGS(&m_device)
);
}
D3D12_COMMAND_QUEUE_DESC queueDesc = {};
queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
m_device->CreateCommandQueue(&queueDesc, IID_PPV_ARGS(&m_commandQueue));
// Describe and create the swap chain.
DXGI_SWAP_CHAIN_DESC1 swapChainDesc = {};
swapChainDesc.BufferCount = m_frameCount;
swapChainDesc.Width = m_width;
swapChainDesc.Height = m_height;
swapChainDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
swapChainDesc.SampleDesc.Count = 1;
ComPtr<IDXGISwapChain1> swapChain;
factory->CreateSwapChainForHwnd(
m_commandQueue.Get(), // Swap chain needs the queue so that it can force a flush on it.
m_hwnd,
&swapChainDesc,
nullptr,
nullptr,
&swapChain
);
// This sample does not support fullscreen transitions.
factory->MakeWindowAssociation(m_hwnd, DXGI_MWA_NO_ALT_ENTER);
swapChain.As(&m_swapChain);
m_frameIndex = m_swapChain->GetCurrentBackBufferIndex();
m_device->CreateFence(0, D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&m_fence));
m_fenceValue = 1;
// Create an event handle to use for frame synchronization.
m_fenceEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
if (m_fenceEvent == nullptr)
{
HRESULT_FROM_WIN32(GetLastError());
}
}
void Device::CreateRenderTarget()
{
D3D12_DESCRIPTOR_HEAP_DESC rtvHeapDesc = {};
rtvHeapDesc.NumDescriptors = m_frameCount;
rtvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
rtvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
m_device->CreateDescriptorHeap(&rtvHeapDesc, IID_PPV_ARGS(&m_rtvHeap));
m_rtvDescriptorSize = m_device->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
m_renderTargets.resize(m_frameCount);
CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHandle(m_rtvHeap->GetCPUDescriptorHandleForHeapStart());
// Create a RTV for each frame.
for (UINT n = 0; n < m_frameCount; n++)
{
m_swapChain->GetBuffer(n, IID_PPV_ARGS(&m_renderTargets[n]));
m_device->CreateRenderTargetView(m_renderTargets[n].Get(), nullptr, rtvHandle);
NAME_D3D12_OBJECT_INDEXED(m_renderTargets, n);
rtvHandle.Offset(1, m_rtvDescriptorSize);
}
}
void Device::CreateDepthStencil()
{
D3D12_DESCRIPTOR_HEAP_DESC dsvHeapDesc = {};
dsvHeapDesc.NumDescriptors = 1;
dsvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_DSV;
dsvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
ThrowIfFailed(m_device->CreateDescriptorHeap(&dsvHeapDesc, IID_PPV_ARGS(&m_dsvHeap)));
D3D12_DEPTH_STENCIL_VIEW_DESC depthStencilDesc = {};
depthStencilDesc.Format = DXGI_FORMAT_D32_FLOAT;
depthStencilDesc.ViewDimension = D3D12_DSV_DIMENSION_TEXTURE2D;
depthStencilDesc.Flags = D3D12_DSV_FLAG_NONE;
D3D12_CLEAR_VALUE depthOptimizedClearValue = {};
depthOptimizedClearValue.Format = DXGI_FORMAT_D32_FLOAT;
depthOptimizedClearValue.DepthStencil.Depth = 1.0f;
depthOptimizedClearValue.DepthStencil.Stencil = 0;
m_device->CreateCommittedResource(
&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT),
D3D12_HEAP_FLAG_NONE,
&CD3DX12_RESOURCE_DESC::Tex2D(DXGI_FORMAT_D32_FLOAT, m_width, m_height, 1, 0, 1, 0, D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL),
D3D12_RESOURCE_STATE_DEPTH_WRITE,
&depthOptimizedClearValue,
IID_PPV_ARGS(&m_depthStencil)
);
NAME_D3D12_OBJECT(m_depthStencil);
m_device->CreateDepthStencilView(m_depthStencil.Get(), &depthStencilDesc, m_dsvHeap->GetCPUDescriptorHandleForHeapStart());
}
Device::Device(HWND hwnd, int width, int height, int frameCount, bool useWrap):
m_hwnd(hwnd),
m_width(width),
m_height(height),
m_frameCount(frameCount),
m_useWarpDevice(useWrap)
{
CreateDevice();
CreateRenderTarget();
CreateDepthStencil();
}
HRESULT Device::CreateCommandList(ComPtr<ID3D12CommandAllocator>& commandAllocator, ComPtr<ID3D12GraphicsCommandList>& commandList)
{
HRESULT hr = E_INVALIDARG;
ComPtr<ID3D12CommandAllocator> m_commandAllocator;
hr = m_device->CreateCommandAllocator(D3D12_COMMAND_LIST_TYPE_DIRECT, IID_PPV_ARGS(&m_commandAllocator));
if (hr == S_OK) {
commandAllocator = m_commandAllocator;
NAME_D3D12_OBJECT(m_commandAllocator);
ComPtr<ID3D12GraphicsCommandList> m_commandList;
hr = m_device->CreateCommandList(0, D3D12_COMMAND_LIST_TYPE_DIRECT, m_commandAllocator.Get(), nullptr, IID_PPV_ARGS(&m_commandList));
commandList = m_commandList;
NAME_D3D12_OBJECT(m_commandList);
}
return hr;
}
void Device::Present()
{
m_swapChain->Present(1, 0);
WaitGpu();
}
void Device::PostCommand(ID3D12GraphicsCommandList* commandList)
{
ID3D12CommandList* ppCommandLists[] = { commandList };
m_commandQueue->ExecuteCommandLists(_countof(ppCommandLists), ppCommandLists);
}
void Device::WaitGpu()
{
const UINT64 fence = m_fenceValue;
m_commandQueue->Signal(m_fence.Get(), fence);
m_fenceValue++;
// Wait until the previous frame is finished.
if (m_fence->GetCompletedValue() < fence)
{
m_fence->SetEventOnCompletion(fence, m_fenceEvent);
WaitForSingleObject(m_fenceEvent, INFINITE);
}
m_frameIndex = m_swapChain->GetCurrentBackBufferIndex();
}
Scene的實現
這個類准備一個命令表,通過這個命令表傳遞GPU繪圖命令,創建相關的資源,如頂點,紋理等。最后交給命令隊列去執行,將繪圖結果放入后台緩沖,最后調用交互鏈的呈現接口,將生成的圖像呈現在屏幕上。
Scene.h
#pragma once
#include "Device.h"
using namespace DirectX;
using namespace DirectX::SimpleMath;
class Scene
{
private:
CD3DX12_VIEWPORT m_viewport;
CD3DX12_RECT m_scissorRect;
Device* m_device;
std::unique_ptr<GraphicsMemory> m_graphicsMemory;
std::unique_ptr<DirectX::CommonStates> m_states;
ComPtr<ID3D12GraphicsCommandList> m_commandList;
ID3D12CommandQueue* m_commandQueue;
ComPtr<ID3D12CommandAllocator> m_commandAllocator;
DirectX::SimpleMath::Matrix m_world;
DirectX::SimpleMath::Matrix m_view;
DirectX::SimpleMath::Matrix m_proj;
using VertexType = DirectX::VertexPositionColor;
std::unique_ptr<DirectX::BasicEffect> m_effect;
std::unique_ptr<DirectX::PrimitiveBatch<VertexType>> m_batch;
public:
HWND m_hwnd;
Scene(HWND hwnd, int width, int height);
~Scene() {};
void Render();
};
Scene.cpp
#include "stdafx.h"
#include "Scene.h"
Scene::Scene(HWND hwnd, int width, int height) :
m_hwnd(hwnd),
m_viewport(0.0f, 0.0f, static_cast<float>(width), static_cast<float>(height)),
m_scissorRect(0, 0, static_cast<LONG>(width), static_cast<LONG>(height))
{
m_device = new Device(hwnd, width, height, 3, true);
m_graphicsMemory = std::make_unique<GraphicsMemory>(m_device->GetDevice());
m_device->CreateCommandList(m_commandAllocator, m_commandList);
m_commandList->Close();
m_states = std::make_unique<CommonStates>(m_device->GetDevice());
m_batch = std::make_unique<PrimitiveBatch<VertexType>>(m_device->GetDevice());
RenderTargetState rtState(DXGI_FORMAT_R8G8B8A8_UNORM, DXGI_FORMAT_D32_FLOAT);
EffectPipelineStateDescription pd(
&VertexType::InputLayout,
CommonStates::Opaque,
CommonStates::DepthDefault,
CommonStates::CullNone,
rtState);
m_effect = std::make_unique<BasicEffect>(m_device->GetDevice(), EffectFlags::VertexColor, pd);
Matrix proj = Matrix::CreateScale(2.f / float(width),
-2.f / float(height), 1.f)
* Matrix::CreateTranslation(-1.f, 1.f, 0.f);
m_effect->SetProjection(proj);
}
void Scene::Render()
{
m_commandAllocator->Reset();
m_commandList->Reset(m_commandAllocator.Get(),nullptr);
ID3D12DescriptorHeap* heaps[] = { m_states->Heap() };
m_commandList->SetDescriptorHeaps(_countof(heaps), heaps);
m_commandList->RSSetViewports(1, &m_viewport);
m_commandList->RSSetScissorRects(1, &m_scissorRect);
ID3D12Resource* rt = m_device->GetRenderTargetResource();
m_commandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(rt, D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_RENDER_TARGET));
CD3DX12_CPU_DESCRIPTOR_HANDLE rtvHandle = m_device->GetRenderTargetHandle();
CD3DX12_CPU_DESCRIPTOR_HANDLE dsvHandle(m_device->GetDepthStencilHeap()->GetCPUDescriptorHandleForHeapStart());
m_commandList->OMSetRenderTargets(1, &rtvHandle, FALSE, &dsvHandle);
const float clearColor[] = { 0.5f, 0.2f, 0.4f, 0.5f };
m_commandList->ClearRenderTargetView(rtvHandle, clearColor, 0, nullptr);
m_commandList->ClearDepthStencilView(dsvHandle, D3D12_CLEAR_FLAG_DEPTH, 1.0f, 0, 0, nullptr);
m_effect->Apply(m_commandList.Get());
m_batch->Begin(m_commandList.Get());
VertexPositionColor v1(Vector3(400.f, 150.f, 0.f), Colors::Yellow);
VertexPositionColor v2(Vector3(600.f, 450.f, 0.f), Colors::Yellow);
VertexPositionColor v3(Vector3(200.f, 450.f, 0.f), Colors::Yellow);
m_batch->DrawTriangle(v1, v2, v3);
m_batch->End();
m_commandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(rt, D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_PRESENT));
m_commandList->Close();
m_device->PostCommand(m_commandList.Get());
m_device->Present();
}
