由于微软官方给出的D3D12的demo都经过C++层层封装,即便是很简单的画三角形的程序都显得比较复杂。因此笔者这里就用纯C语言来改写画三角形的简单D3D12应用程序。这里面不包含任何已被废弃的D3DX的库,所以可以直接拿来使用。
各位要做的准备工作是,先要有一部装有Windows 10的PC。然后,在上面安装Visual Studio 2015开发环境,笔者这里用的是微软免费的Visual Studio 2015 Express Edition for Desktop,可支持部分C99语法特性,这会使得C语言代码更为精简~
安装完成之后,各位能够在安装目录中看到,除了Visual Studio 2015之外,还出现了Windows Kit,这里面就已经包含了Direct3D所需要的所有头文件以及库文件。
我们首先创建一个名为Direct3D12Test的工程,然后选择Win32 Application,不需要勾选SDL选项。然后,在项目选项中C/C++一栏下的General中,Additional Included Directories一栏输入D3D12所需要的头文件路径,笔者系统下是:C:\Program Files (x86)\Windows Kits\10\Include\10.0.14393.0\um;C:\Program Files (x86)\Windows Kits\10\Include\10.0.14393.0\shared
分号左边的路径是d3d12.h所在的目录;分号右边是dxgi相关的头文件所在的目录。
随后在Linker下的Additional Library Directories中输入我们所要连接的d3d12库文件所在的路径,笔者系统下是:C:\Program Files (x86)\Windows Kits\10\Lib\10.0.14393.0\um\x64
然后在Input一栏的Additional Dependencies中添加d3d12.lib;dxguid.lib;dxgi.lib;d3dcompiler.lib;
以上这些就是我们需要连接的静态库。然后,我们将Debug旁边的x86改为x64,也就是说,我们这里将构建64位应用程序。
接下来,我们把IDE自动生成的Direct3D12Test.cpp源文件先暂时移除,然后将文件名后缀改为.c,然后再添加到工程中去。
最后,我们在项目工程选项中,找到C/C++一栏下的Precompiled Header,我们将这里面的Precompiled Header选项改为Not Using Precompiled Headers。如果开启这个选项,由于在项目之前自动构建的时候用的是C++,我们再使用C源文件就会报错。完成之后,我们就可以用以下代码来替换掉原来该源文件中的内容了:
// Direct3D12Test.c : Defines the entry point for the application.
// 这是一个C语言源文件
#include "stdafx.h"
#include "Direct3D12Test.h"
#include <d3d12.h>
#include <dxgi1_4.h>
#include <d3dcompiler.h>
#include <stdint.h>
#include <stdbool.h>
#define MAX_LOADSTRING 100
// Global Variables:
static HINSTANCE hInst; // current instance
static WCHAR szTitle[MAX_LOADSTRING]; // The title bar text
static WCHAR szWindowClass[MAX_LOADSTRING]; // the main window class name
// Forward declarations of functions included in this code module:
static ATOM MyRegisterClass(HINSTANCE hInstance);
static HWND InitInstance(HINSTANCE, int);
static LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
static INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM);
static const int s_windowWidth = 640;
static const int s_windowHeight = 480;
#define FRAME_COUNT 2
// Pipeline objects.
static D3D12_VIEWPORT s_viewport;
static D3D12_RECT s_scissorRect;
static IDXGISwapChain3 *s_swapChain;
static ID3D12Device *s_device;
static ID3D12Resource *s_renderTargets[FRAME_COUNT];
static ID3D12CommandAllocator *s_commandAllocator;
static ID3D12CommandQueue *s_commandQueue;
static ID3D12RootSignature *s_rootSignature;
static ID3D12DescriptorHeap *s_rtvHeap;
static ID3D12PipelineState *s_pipelineState;
static ID3D12GraphicsCommandList *s_commandList;
static uint32_t s_rtvDescriptorSize;
// App resources.
static ID3D12Resource *s_vertexBuffer;
static D3D12_VERTEX_BUFFER_VIEW s_vertexBufferView;
// Synchronization objects.
static uint32_t s_frameIndex;
static HANDLE s_fenceEvent;
static ID3D12Fence *s_fence;
static uint64_t s_fenceValue;
// 自己定制是否使用warp device
static const bool s_useWarpDevice = true;
/**
加载渲染流水线
*/
static bool LoadPipeline(HWND hWnd)
{
// 我们这里采用调试模式
ID3D12Debug *debugController;
if (SUCCEEDED(D3D12GetDebugInterface(&IID_IDebug, &debugController)))
debugController->lpVtbl->EnableDebugLayer(debugController);
IDXGIFactory4 *factory;
if (CreateDXGIFactory1(&IID_IDXGIFactory4, &factory) < 0)
return false;
if (s_useWarpDevice)
{
IDXGIAdapter *warpAdapter;
if (factory->lpVtbl->EnumWarpAdapter(factory, &IID_IDXGIAdapter, &warpAdapter) < 0)
return false;
if (D3D12CreateDevice((IUnknown*)warpAdapter, D3D_FEATURE_LEVEL_11_0, &IID_ID3D12Device, &s_device) < 0)
return false;
}
else
{
IDXGIAdapter1 *hardwareAdapter = NULL;
for (int i = 0; i < 3; i++)
{
factory->lpVtbl->EnumAdapters1(factory, i, &hardwareAdapter);
if (D3D12CreateDevice((IUnknown*)hardwareAdapter, D3D_FEATURE_LEVEL_11_0, &IID_ID3D12Device, &s_device) >= 0)
break;
hardwareAdapter->lpVtbl->Release(hardwareAdapter);
}
if (hardwareAdapter == NULL)
return false;
}
// Describe and create the command queue.
D3D12_COMMAND_QUEUE_DESC queueDesc = { 0 };
queueDesc.Flags = D3D12_COMMAND_QUEUE_FLAG_NONE;
queueDesc.Type = D3D12_COMMAND_LIST_TYPE_DIRECT;
if (s_device->lpVtbl->CreateCommandQueue(s_device, &queueDesc, &IID_ID3D12CommandQueue, &s_commandQueue) < 0)
return false;
// Describe and create the swap chain.
DXGI_SWAP_CHAIN_DESC swapChainDesc = { 0 };
swapChainDesc.BufferCount = FRAME_COUNT;
swapChainDesc.BufferDesc.Width = s_windowWidth;
swapChainDesc.BufferDesc.Height = s_windowHeight;
swapChainDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
swapChainDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
swapChainDesc.SwapEffect = DXGI_SWAP_EFFECT_FLIP_DISCARD;
swapChainDesc.OutputWindow = hWnd;
swapChainDesc.SampleDesc.Count = 1;
swapChainDesc.Windowed = TRUE;
IDXGISwapChain *swapChain;
if (factory->lpVtbl->CreateSwapChain(factory, (IUnknown*)s_commandQueue, &swapChainDesc, &swapChain) < 0)
return false;
s_swapChain = (IDXGISwapChain3*)swapChain;
// This sample does not support fullscreen transitions.
if (factory->lpVtbl->MakeWindowAssociation(factory, hWnd, DXGI_MWA_NO_ALT_ENTER) < 0)
return false;
s_frameIndex = s_swapChain->lpVtbl->GetCurrentBackBufferIndex(s_swapChain);
// Create descriptor heaps
// Describe and create a render target view (RTV) descriptor heap.
D3D12_DESCRIPTOR_HEAP_DESC rtvHeapDesc = { 0 };
rtvHeapDesc.NumDescriptors = FRAME_COUNT;
rtvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
rtvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
if (s_device->lpVtbl->CreateDescriptorHeap(s_device, &rtvHeapDesc, &IID_ID3D12DescriptorHeap, &s_rtvHeap) < 0)
return false;
s_rtvDescriptorSize = s_device->lpVtbl->GetDescriptorHandleIncrementSize(s_device, D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
// Create frame resources
D3D12_CPU_DESCRIPTOR_HANDLE rtvHandle;
s_rtvHeap->lpVtbl->GetCPUDescriptorHandleForHeapStart(s_rtvHeap, &rtvHandle);
// Create a RTV for each frame.
for (int n = 0; n < FRAME_COUNT; n++)
{
if (s_swapChain->lpVtbl->GetBuffer(s_swapChain, n, &IID_ID3D12Resource, &s_renderTargets[n]) < 0)
return false;
s_device->lpVtbl->CreateRenderTargetView(s_device, s_renderTargets[n], NULL, rtvHandle);
rtvHandle.ptr += s_rtvDescriptorSize;
}
if (s_device->lpVtbl->CreateCommandAllocator(s_device, D3D12_COMMAND_LIST_TYPE_DIRECT, &IID_ID3D12CommandAllocator, &s_commandAllocator) < 0)
return false;
return true;
}
/**
等待上一帧处理完成
*/
static bool WaitForPreviousFrame(void)
{
// WAITING FOR THE FRAME TO COMPLETE BEFORE CONTINUING IS NOT BEST PRACTICE.
// This is code implemented as such for simplicity. The D3D12HelloFrameBuffering
// sample illustrates how to use fences for efficient resource usage and to
// maximize GPU utilization.
// Signal and increment the fence value.
const uint64_t fence = s_fenceValue;
if (s_commandQueue->lpVtbl->Signal(s_commandQueue, s_fence, fence) < 0)
return false;
s_fenceValue++;
// Wait until the previous frame is finished.
if (s_fence->lpVtbl->GetCompletedValue(s_fence) < fence)
{
if (s_fence->lpVtbl->SetEventOnCompletion(s_fence, fence, s_fenceEvent) < 0)
return false;
WaitForSingleObject(s_fenceEvent, INFINITE);
}
s_frameIndex = s_swapChain->lpVtbl->GetCurrentBackBufferIndex(s_swapChain);
return true;
}
/**
加载本demo所需的物资
*/
static bool LoadAssets(void)
{
// Create an empty root signature.
D3D12_ROOT_SIGNATURE_DESC rootSignatureDesc = { 0, NULL, 0, NULL, D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT };
ID3DBlob *signature;
ID3DBlob *error;
if (D3D12SerializeRootSignature(&rootSignatureDesc, D3D_ROOT_SIGNATURE_VERSION_1, &signature, &error) < 0)
return false;
if (s_device->lpVtbl->CreateRootSignature(s_device, 0, signature->lpVtbl->GetBufferPointer(signature),
signature->lpVtbl->GetBufferSize(signature), &IID_ID3D12RootSignature, &s_rootSignature) < 0)
return false;
// Create the pipeline state, which includes compiling and loading shaders.
ID3DBlob *vertexShader;
ID3DBlob *pixelShader;
// Enable better shader debugging with the graphics debugging tools.
// 若不允许调试,则将compileFlags置为0即可
uint32_t compileFlags = D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION;
if (D3DCompileFromFile(L"shaders.hlsl", NULL, NULL, "VSMain", "vs_5_0", compileFlags, 0, &vertexShader, NULL) < 0)
return false;
if (D3DCompileFromFile(L"shaders.hlsl", NULL, NULL, "PSMain", "ps_5_0", compileFlags, 0, &pixelShader, NULL) < 0)
return false;
// Define the vertex input layout.
D3D12_INPUT_ELEMENT_DESC inputElementDescs[] =
{
{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
{ "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 12, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 }
};
// Describe and create the graphics pipeline state object (PSO).
D3D12_GRAPHICS_PIPELINE_STATE_DESC psoDesc = { 0 };
psoDesc.InputLayout = (D3D12_INPUT_LAYOUT_DESC){ inputElementDescs, _countof(inputElementDescs) };
psoDesc.pRootSignature = s_rootSignature;
psoDesc.VS = (D3D12_SHADER_BYTECODE) { vertexShader->lpVtbl->GetBufferPointer(vertexShader),
vertexShader->lpVtbl->GetBufferSize(vertexShader) };
psoDesc.PS = (D3D12_SHADER_BYTECODE) { pixelShader->lpVtbl->GetBufferPointer(pixelShader),
pixelShader->lpVtbl->GetBufferSize(pixelShader) };
// 使用默认的光栅化状态
psoDesc.RasterizerState = (D3D12_RASTERIZER_DESC) { D3D12_FILL_MODE_SOLID, D3D12_CULL_MODE_NONE, FALSE, 0, 0.0f, 0.0f,
TRUE, FALSE, FALSE, 0, D3D12_CONSERVATIVE_RASTERIZATION_MODE_OFF };
psoDesc.BlendState = (D3D12_BLEND_DESC) { FALSE, FALSE,
{ [0] = { FALSE, FALSE, D3D12_BLEND_SRC_ALPHA, D3D12_BLEND_INV_SRC_ALPHA, D3D12_BLEND_OP_ADD, D3D12_BLEND_ONE, D3D12_BLEND_ZERO,
D3D12_BLEND_OP_ADD, D3D12_LOGIC_OP_NOOP, D3D12_COLOR_WRITE_ENABLE_ALL } }
};
psoDesc.DepthStencilState.DepthEnable = FALSE;
psoDesc.DepthStencilState.StencilEnable = FALSE;
psoDesc.SampleMask = UINT32_MAX;
psoDesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
psoDesc.NumRenderTargets = 1;
psoDesc.RTVFormats[0] = DXGI_FORMAT_R8G8B8A8_UNORM;
psoDesc.SampleDesc.Count = 1;
if (s_device->lpVtbl->CreateGraphicsPipelineState(s_device, &psoDesc, &IID_ID3D12PipelineState, &s_pipelineState) < 0)
return false;
// Create the command list.
if (s_device->lpVtbl->CreateCommandList(s_device, 0, D3D12_COMMAND_LIST_TYPE_DIRECT, s_commandAllocator, s_pipelineState,
&IID_ID3D12GraphicsCommandList, &s_commandList) < 0)
return false;
// Command lists are created in the recording state, but there is nothing
// to record yet. The main loop expects it to be closed, so close it now.
if (s_commandList->lpVtbl->Close(s_commandList) < 0)
return false;
// Create the vertex buffer.
// Define the geometry for a triangle.
const float aspectRatio = s_viewport.Height / s_viewport.Width;
struct Vertex
{
float position[4];
float color[4];
} triangleVertices[] =
{
// Direct3D是以左手作为前面背面顶点排列的依据
{ { 0.0f, 0.75f * aspectRatio, 0.0f, 1.0f },{ 1.0f, 0.0f, 0.0f, 1.0f } }, // 中上顶点
{ { 0.5f, -0.75f * aspectRatio, 0.0f, 1.0f },{ 0.0f, 1.0f, 0.0f, 1.0f } }, // 右下顶点
{ { -0.5f, -0.75f * aspectRatio, 0.0f, 1.0f },{ 0.0f, 0.0f, 1.0f, 1.0f } } // 左下顶点
};
const size_t vertexBufferSize = sizeof(triangleVertices);
// Note: using upload heaps to transfer static data like vert buffers is not
// recommended. Every time the GPU needs it, the upload heap will be marshalled
// over. Please read up on Default Heap usage. An upload heap is used here for
// code simplicity and because there are very few verts to actually transfer.
D3D12_HEAP_PROPERTIES heapProperties = { D3D12_HEAP_TYPE_UPLOAD, D3D12_CPU_PAGE_PROPERTY_UNKNOWN, D3D12_MEMORY_POOL_UNKNOWN,
1, 1 };
D3D12_RESOURCE_DESC resourceDesc = { D3D12_RESOURCE_DIMENSION_BUFFER, 0, vertexBufferSize, 1, 1, 1, DXGI_FORMAT_UNKNOWN,
{ 1, 0 }, D3D12_TEXTURE_LAYOUT_ROW_MAJOR, D3D12_RESOURCE_FLAG_NONE };
if (s_device->lpVtbl->CreateCommittedResource(s_device, &heapProperties, D3D12_HEAP_FLAG_NONE, &resourceDesc,
D3D12_RESOURCE_STATE_GENERIC_READ, NULL, &IID_ID3D12Resource, &s_vertexBuffer) < 0)
return false;
// Copy the triangle data to the vertex buffer.
uint8_t* pVertexDataBegin;
D3D12_RANGE readRange = { 0, 0 }; // We do not intend to read from this resource on the CPU.
if (s_vertexBuffer->lpVtbl->Map(s_vertexBuffer, 0, &readRange, &pVertexDataBegin) < 0)
return false;
memcpy(pVertexDataBegin, triangleVertices, sizeof(triangleVertices));
s_vertexBuffer->lpVtbl->Unmap(s_vertexBuffer, 0, NULL);
// Initialize the vertex buffer view.
s_vertexBufferView.BufferLocation = s_vertexBuffer->lpVtbl->GetGPUVirtualAddress(s_vertexBuffer);
s_vertexBufferView.StrideInBytes = sizeof(triangleVertices[0]);
s_vertexBufferView.SizeInBytes = (uint32_t)vertexBufferSize;
// Create synchronization objects and wait until assets have been uploaded to the GPU.
if (s_device->lpVtbl->CreateFence(s_device, 0, D3D12_FENCE_FLAG_NONE, &IID_ID3D12Fence, &s_fence) < 0)
return false;
s_fenceValue = 1;
// Create an event handle to use for frame synchronization.
s_fenceEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
if (s_fenceEvent == NULL)
{
if (HRESULT_FROM_WIN32(GetLastError()) < 0)
return false;
}
// Wait for the command list to execute; we are reusing the same command
// list in our main loop but for now, we just want to wait for setup to
// complete before continuing.
WaitForPreviousFrame();
return true;
}
/**
填充命令队列
*/
static bool PopulateCommandList(void)
{
// Command list allocators can only be reset when the associated
// command lists have finished execution on the GPU; apps should use
// fences to determine GPU execution progress.
if (s_commandAllocator->lpVtbl->Reset(s_commandAllocator) < 0)
return false;
// However, when ExecuteCommandList() is called on a particular command
// list, that command list can then be reset at any time and must be before
// re-recording.
if (s_commandList->lpVtbl->Reset(s_commandList, s_commandAllocator, s_pipelineState) < 0)
return false;
// Set necessary state.
s_commandList->lpVtbl->SetGraphicsRootSignature(s_commandList, s_rootSignature);
s_commandList->lpVtbl->RSSetViewports(s_commandList, 1, &s_viewport);
s_commandList->lpVtbl->RSSetScissorRects(s_commandList, 1, &s_scissorRect);
// Indicate that the back buffer will be used as a render target.
D3D12_RESOURCE_BARRIER barrier = { D3D12_RESOURCE_BARRIER_TYPE_TRANSITION, D3D12_RESOURCE_BARRIER_FLAG_NONE,
.Transition = {s_renderTargets[s_frameIndex], D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES, D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_RENDER_TARGET} };
s_commandList->lpVtbl->ResourceBarrier(s_commandList, 1, &barrier);
D3D12_CPU_DESCRIPTOR_HANDLE rtvHandle;
s_rtvHeap->lpVtbl->GetCPUDescriptorHandleForHeapStart(s_rtvHeap, &rtvHandle);
rtvHandle.ptr += s_frameIndex * s_rtvDescriptorSize;
s_commandList->lpVtbl->OMSetRenderTargets(s_commandList, 1, &rtvHandle, FALSE, NULL);
// Record commands.
const float clearColor[] = { 0.0f, 0.2f, 0.4f, 1.0f };
s_commandList->lpVtbl->ClearRenderTargetView(s_commandList, rtvHandle, clearColor, 0, NULL);
s_commandList->lpVtbl->IASetPrimitiveTopology(s_commandList, D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
s_commandList->lpVtbl->IASetVertexBuffers(s_commandList, 0, 1, &s_vertexBufferView);
s_commandList->lpVtbl->DrawInstanced(s_commandList, 3, 1, 0, 0);
// Indicate that the back buffer will now be used to present.
D3D12_RESOURCE_BARRIER barrier2 = { D3D12_RESOURCE_BARRIER_TYPE_TRANSITION, D3D12_RESOURCE_BARRIER_FLAG_NONE,
.Transition = { s_renderTargets[s_frameIndex], D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES, D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_PRESENT } };
s_commandList->lpVtbl->ResourceBarrier(s_commandList, 1, &barrier2);
if (s_commandList->lpVtbl->Close(s_commandList) < 0)
return false;
return true;
}
/**
渲染场景
*/
static bool Render(void)
{
// Record all the commands we need to render the scene into the command list.
if (!PopulateCommandList())
return false;
// Execute the command list.
ID3D12CommandList* ppCommandLists[] = { (ID3D12CommandList*)s_commandList };
s_commandQueue->lpVtbl->ExecuteCommandLists(s_commandQueue, _countof(ppCommandLists), ppCommandLists);
// Present the frame.
if (s_swapChain->lpVtbl->Present(s_swapChain, 1, 0) < 0)
return false;
if (!WaitForPreviousFrame())
return false;
return true;
}
/**
清除资源
*/
static void CleanupResource(void)
{
if (s_fence != NULL)
s_fence->lpVtbl->Release(s_fence);
if (s_vertexBuffer != NULL)
s_vertexBuffer->lpVtbl->Release(s_vertexBuffer);
if (s_rootSignature != NULL)
s_rootSignature->lpVtbl->Release(s_rootSignature);
if (s_rtvHeap != NULL)
s_rtvHeap->lpVtbl->Release(s_rtvHeap);
for (int i = 0; i < FRAME_COUNT; i++)
{
if(s_renderTargets[i] != NULL)
s_renderTargets[i]->lpVtbl->Release(s_renderTargets[i]);
}
if (s_pipelineState != NULL)
s_pipelineState->lpVtbl->Release(s_pipelineState);
if (s_commandList != NULL)
s_commandList->lpVtbl->Release(s_commandList);
if (s_commandAllocator != NULL)
s_commandAllocator->lpVtbl->Release(s_commandAllocator);
if (s_commandQueue != NULL)
s_commandQueue->lpVtbl->Release(s_commandQueue);
if (s_swapChain != NULL)
s_swapChain->lpVtbl->Release(s_swapChain);
if (s_device != NULL)
s_device->lpVtbl->Release(s_device);
}
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR lpCmdLine,
_In_ int nCmdShow)
{
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
// TODO: Place code here.
// Initialize global strings
LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
LoadStringW(hInstance, IDC_DIRECT3D12TEST, szWindowClass, MAX_LOADSTRING);
MyRegisterClass(hInstance);
// Perform application initialization:
HWND hWnd = InitInstance(hInstance, nCmdShow);
if (hWnd == NULL)
{
return FALSE;
}
HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_DIRECT3D12TEST));
// 初始化s_viewport与s_scissorRect对象
s_viewport.Width = (float)s_windowWidth;
s_viewport.Height = (float)s_windowHeight;
s_viewport.MaxDepth = 1.0f;
s_scissorRect.right = (long)s_windowWidth;
s_scissorRect.bottom = (long)s_windowHeight;
if (!LoadPipeline(hWnd))
return FALSE;
if (!LoadAssets())
return FALSE;
if (!Render())
return FALSE;
MSG msg;
// Main message loop:
while (GetMessage(&msg, NULL, 0, 0))
{
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return (int) msg.wParam;
}
//
// FUNCTION: MyRegisterClass()
//
// PURPOSE: Registers the window class.
//
ATOM MyRegisterClass(HINSTANCE hInstance)
{
WNDCLASSEXW wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_DIRECT3D12TEST));
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wcex.lpszMenuName = MAKEINTRESOURCEW(IDC_DIRECT3D12TEST);
wcex.lpszClassName = szWindowClass;
wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));
return RegisterClassExW(&wcex);
}
//
// FUNCTION: InitInstance(HINSTANCE, int)
//
// PURPOSE: Saves instance handle and creates main window
//
// COMMENTS:
//
// In this function, we save the instance handle in a global variable and
// create and display the main program window.
//
HWND InitInstance(HINSTANCE hInstance, int nCmdShow)
{
hInst = hInstance; // Store instance handle in our global variable
HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, s_windowWidth, s_windowHeight, NULL, NULL, hInstance, NULL);
if (!hWnd)
{
return NULL;
}
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
return hWnd;
}
//
// FUNCTION: WndProc(HWND, UINT, WPARAM, LPARAM)
//
// PURPOSE: Processes messages for the main window.
//
// WM_COMMAND - process the application menu
// WM_PAINT - Paint the main window
// WM_DESTROY - post a quit message and return
//
//
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_COMMAND:
{
int wmId = LOWORD(wParam);
// Parse the menu selections:
switch (wmId)
{
case IDM_ABOUT:
DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
break;
case IDM_EXIT:
DestroyWindow(hWnd);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
}
break;
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
// TODO: Add any drawing code that uses hdc here...
// 我们这里再绘制一帧
if(s_pipelineState != NULL)
Render();
EndPaint(hWnd, &ps);
}
break;
case WM_DESTROY:
// 清除D3D相关的资源
CleanupResource();
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
// Message handler for about box.
INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
UNREFERENCED_PARAMETER(lParam);
switch (message)
{
case WM_INITDIALOG:
return (INT_PTR)TRUE;
case WM_COMMAND:
if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
{
EndDialog(hDlg, LOWORD(wParam));
return (INT_PTR)TRUE;
}
break;
}
return (INT_PTR)FALSE;
}
输入完成之后,我们可以在菜单栏File下面找到Advanced Save Options...,可以将Encoding改为Unicode(UTF-8 without Signature),这样我们就可以在所有操作系统上看到正常的中文汉字了。否则在不少系统上不支持GBK或GB2312,会导致汉字部分出现乱码。
下面这个文件就是绘制三角形时所需要的HLSL文件,我们不能直接将它们添加到工程中,因为IDE会自动编译它们,然后导致找不到main入口而出现连接错误。所以我们将这个文件存放到与上述源文件同一目录下即可,然后再将它复制到与我们最后生成的exe可执行文件的同一目录下,这样就可以直接点击加载应用了,而不需要通过Visual Studio来启动。该文件名为shaders.hlsl。
//*********************************************************
//
// Copyright (c) Microsoft. All rights reserved.
// This code is licensed under the MIT License (MIT).
// THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
// IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
// PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************
struct PSInput
{
float4 position : SV_POSITION;
float4 color : COLOR;
};
PSInput VSMain(float4 position : POSITION, float4 color : COLOR)
{
PSInput result;
result.position = position;
result.color = color;
return result;
}
float4 PSMain(PSInput input) : SV_TARGET
{
return input.color;
}
全都完成之后,我们就可以编译构建应用,然后会自动弹出窗口显示一个彩色三角形了。我们还可以点击窗口放大按钮与还原按钮,这些操作都能正常显示。