欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

使用纯C语言开发简单的Direct3D 12应用

程序员文章站 2022-12-10 23:51:42
由于微软官方给出的D3D12的demo都经过C++层层封装,即便是很简单的画三角形的程序都显得比较复杂。因此笔者这里就用纯C语言来改写画三角形的简单D3D12应用程序。这里面不包含...

由于微软官方给出的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;
}

全都完成之后,我们就可以编译构建应用,然后会自动弹出窗口显示一个彩色三角形了。我们还可以点击窗口放大按钮与还原按钮,这些操作都能正常显示。