由於微軟官方給出的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#include #include #include #include #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; }
全都完成之後,我們就可以編譯構建應用,然後會自動彈出窗口顯示一個彩色三角形了。我們還可以點擊窗口放大按鈕與還原按鈕,這些操作都能正常顯示。