【學習DirectX12(5)】渲染——第二部分

 

COMMANDQUEUE::GETCOMMANDLIST

GetCommandList可以返回一個指令集並直接用於發佈GPU繪製or dispatch指令。這個指令集一開始就處在記錄狀態所有開發者無需重設它就可以直接使用它。但指令集需要一個指令分配器與之相關聯。但是指令集又沒有直接的方法去查詢究竟應該是哪個指令分配器,所以一開始創建的時候就必須指定一個未經使用的指令分配器。但只要這個指令分配器不在當前這個指令隊列上工作,那麼這個指令分配器就可以立馬重用。


Microsoft::WRL::ComPtr<ID3D12GraphicsCommandList2> CommandQueue::GetCommandList()
{
    Microsoft::WRL::ComPtr<ID3D12CommandAllocator> commandAllocator;
    Microsoft::WRL::ComPtr<ID3D12GraphicsCommandList2> commandList;
if ( !m_CommandAllocatorQueue.empty() && IsFenceComplete(m_CommandAllocatorQueue.front().fenceValue))
{
    commandAllocator = m_CommandAllocatorQueue.front().commandAllocator;
    m_CommandAllocatorQueue.pop();
    ThrowIfFailed(commandAllocator->Reset());
}
else
{
    commandAllocator = CreateCommandAllocator();
}
    // Associate the command allocator with the command list so that it can be   
 // retrieved when the command list is executed.    
ThrowIfFailed(commandList->SetPrivateDataInterface(__uuidof(ID3D12CommandAllocator), commandAllocator.Get()));    
return commandList;}

使用ID3D12Object::SetPrivateDataInterface方法來將指令分配器和指令集關聯起來。當指令集執行後,指令分配器就可以回到指令分配器隊列中。而爲ID3D12Object指定COM物體時也是使用ID3D12Object物體時也是使用ID3D12Object::SetPrivateDataInterface,然後COM物體的內部計數指針會增加,除非ID3D12Object物體被銷燬,或COM被別人或空指針代替。

COMMANDQUEUE::EXECUTECOMMANDLIST

uint64_t CommandQueue::ExecuteCommandList(Microsoft::WRL::ComPtr<ID3D12GraphicsCommandList2> commandList)
{
    commandList->Close();
    ID3D12CommandAllocator* commandAllocator;
    UINT dataSize = sizeof(commandAllocator);
    ThrowIfFailed(commandList->GetPrivateData(__uuidof(ID3D12CommandAllocator), &dataSize, &commandAllocator));
    ID3D12CommandList* const ppCommandLists[] = {
        commandList.Get()
    };
    m_d3d12CommandQueue->ExecuteCommandLists(1, ppCommandLists);
    uint64_t fenceValue = Signal();
    m_CommandAllocatorQueue.emplace(CommandAllocatorEntry{ fenceValue, commandAllocator });
    m_CommandListQueue.push(commandList);
 // The ownership of the command allocator has been transferred to the ComPtr
    // in the command allocator queue. It is safe to release the reference
    // in this temporary COM pointer here.
    commandAllocator->Release();
    return fenceValue;
}

在指令集可以被執行之前,首先應該關閉,用ID3D12GraphicsCommandList::Close。然後用ID3DObject::GetPrivateData來獲取指令分配器。由於ID3DCommandQueueList::ExecuteCommandLists方法需要一個數組的ID3D12CommmandList,所以創建一個數組。當開始執行指令集之前的一瞬間,然後就用Signal把一個fence給與指令分配器。然後指令分配器和指令集將被推進至各自的隊列裏用於重新使用。

 

The Game Class

Game類是個抽象基礎層,需要如下功能

  1. 初始化DX12 Runtime,並且創建用於渲染的窗口
  2. 加載/unload指定的內容
  3. 銷燬指定的內容且釋放內存
  4. 處理窗口消息

Game Class

The Game Header

/**
 *   @brief The Game class is the abstract base class for DirecX 12 demos.
 */
#pragma once
#include <Events.h>
#include <memory> // for std::enable_shared_from_this
#include <string> // for std::wstring
class Window;
class Game : public std::enable_shared_from_this<Game>
{
public:
    /**
     * Create the DirectX demo using the specified window dimensions.
     */
    Game(const std::wstring& name, int width, int height, bool vSync);
    virtual ~Game();
/*  Initialize the DirectX Runtime.*/
virtual bool Initialize();
/* Load content required for the demo.*/
virtual bool LoadContent() = 0;
/* Unload demo specific content that was loaded in LoadContent.*/
virtual void UnloadContent() = 0;
/* Destroy any resource that are used by the game. */
virtual void Destroy();

 The vSync option specifies whether rendering should be synchronized to the vertical refresh rate of the screen or not.

Game類提供Initialize的默認實現,僅僅是創建了一個用於渲染圖形的創建並註冊了了窗口回調函數。而默認銷燬函數僅僅是銷燬窗口。而LoadContent和UnLoadContent必須由子類提供。 The default implementations for the window callback functions is to do nothing.

protected:
    friend class Window;
    /**
     *  Update the game logic.
     */
    virtual void OnUpdate(UpdateEventArgs& e);
    /**
     *  Render stuff.
     */
    virtual void OnRender(RenderEventArgs& e);
    /**
     * Invoked by the registered window when a key is pressed
     * while the window has focus.
     */
    virtual void OnKeyPressed(KeyEventArgs& e);
/**
     * Invoked when a key on the keyboard is released.
     */
    virtual void OnKeyReleased(KeyEventArgs& e);
    /**
     * Invoked when the mouse is moved over the registered window.
     */
    virtual void OnMouseMoved(MouseMotionEventArgs& e);
    /**
     * Invoked when a mouse button is pressed over the registered window.
     */
    virtual void OnMouseButtonPressed(MouseButtonEventArgs& e);
    /**
     * Invoked when a mouse button is released over the registered window.
     */
    virtual void OnMouseButtonReleased(MouseButtonEventArgs& e);
    /**
     * Invoked when the mouse wheel is scrolled while the registered window has focus.
     */
    virtual void OnMouseWheel(MouseWheelEventArgs& e);
    /*
     * Invoked when the attached window is resized.
     */
    virtual void OnResize(ResizeEventArgs& e);
    /**
     * Invoked when the registered window instance is destroyed.
     */
    virtual void OnWindowDestroy();

OnUpdate函數將會處理窗口接受到的WM_PAINT消息。然後OnUpdate函數回調將會使用OnRender方法。The Game class also provides protected access to the window instance that is created by default in the Game::Initialize method.

   std::shared_ptr<Window> m_pWindow;
private:
    std::wstring m_Name;
    int m_Width;
    int m_Height;
    bool m_vSync;
};

Game::Initialize

The purpose of the Game::Initialize method is to initialize the game specific state. GPU resources should be allocated in the LoadContent method which should be provided by the child class.

bool Game::Initialize()
{
    // Check for DirectX Math library support.
    if (!DirectX::XMVerifyCPUSupport())
    {
        MessageBoxA(NULL, "Failed to verify DirectX Math library support.", "Error", MB_OK | MB_ICONERROR);
        return false;
    }
    m_pWindow = Application::Get().CreateRenderWindow(m_Name, m_Width, m_Height, m_vSync);
    m_pWindow->RegisterCallbacks(shared_from_this());
    m_pWindow->Show();
    return true;
}

Game::Destroy

void Game::Destroy()
{
    Application::Get().DestroyWindow(m_pWindow);
    m_pWindow.reset();
}

 

 

The Tutorial Class

這個教程的Tutorial2類不僅重寫了Game類的LoadContent和UnLoadContent,還提供了一些幫助函數用於上傳頂點和索引數據到GPU並且執行資源轉換。

The Tutorial Header

#pragma once
#include <Game.h>
#include <Window.h>
#include <DirectXMath.h>
class Tutorial2 : public Game
{
public:
    using super = Game;
    Tutorial2(const std::wstring& name, int width, int height, bool vSync = false);
    /* Load content required for the demo.*/
    virtual bool LoadContent() override;
    /* Unload demo specific content that was loaded in LoadContent. */
    virtual void UnloadContent() override;
protected:  
  /**     *  Update the game logic.     */   
 virtual void OnUpdate(UpdateEventArgs& e) override;  
  /**     *  Render stuff.     */   
 virtual void OnRender(RenderEventArgs& e) override; 
   /**     * Invoked by the registered window when a key is pressed     * while the window has focus.     */  
  virtual void OnKeyPressed(KeyEventArgs& e) override;   
 /**     * Invoked when the mouse wheel is scrolled while the registered window has focus.     */ 
   virtual void OnMouseWheel(MouseWheelEventArgs& e) override;   
 virtual void OnResize(ResizeEventArgs& e) override; 
private:
    // Helper functions
    // Transition a resource
    void TransitionResource(Microsoft::WRL::ComPtr<ID3D12GraphicsCommandList2> commandList,
        Microsoft::WRL::ComPtr<ID3D12Resource> resource,
        D3D12_RESOURCE_STATES beforeState, D3D12_RESOURCE_STATES afterState);
    // Clear a render target view.
    void ClearRTV(Microsoft::WRL::ComPtr<ID3D12GraphicsCommandList2> commandList,
        D3D12_CPU_DESCRIPTOR_HANDLE rtv, FLOAT* clearColor);
    // Clear the depth of a depth-stencil view.
    void ClearDepth(Microsoft::WRL::ComPtr<ID3D12GraphicsCommandList2> commandList,
        D3D12_CPU_DESCRIPTOR_HANDLE dsv, FLOAT depth = 1.0f );
    // Create a GPU buffer.
    void UpdateBufferResource(Microsoft::WRL::ComPtr<ID3D12GraphicsCommandList2> commandList,
        ID3D12Resource** pDestinationResource, ID3D12Resource** pIntermediateResource,
        size_t numElements, size_t elementSize, const void* bufferData,
        D3D12_RESOURCE_FLAGS flags = D3D12_RESOURCE_FLAG_NONE );
    // Resize the depth buffer to match the size of the client area.
    void ResizeDepthBuffer(int width, int height);

windows.h是WINAPI,而DirectXMath儘管是WIN 10 SDK的,但仍然被當作DirectX的一部分。 The vSync option determines if the renderer should synchronize with vertical refresh rate of the screen.當遊戲邏輯部分需要更新時使用OnUpdate,而當屏幕渲染需要更新時使用OnRender。

TransitionResouce和ClearRTV方法在之前的課程已經說過了,而ClearDepth方法與ClearRTV方法非常類似,但是這是清除depth-stencil view的。而UpdateBufferResouce方法將同時創建一個足夠大的資源來存儲緩存數據,但是也將創建一個intermediate upload buffer用於把緩存數據從CPU傳回到GPU,但要在圖形指令集完成把資源加載到GPU之後。因此除非全部加載完成,否則這個函數的指針不能被銷燬。

depth buffer儲存像素的深度值,遠處爲1,近處爲0。

 

uint64_t m_FenceValues[Window::BufferCount] = {};
// Vertex buffer for the cube.
Microsoft::WRL::ComPtr<ID3D12Resource> m_VertexBuffer;
D3D12_VERTEX_BUFFER_VIEW m_VertexBufferView;
// Index buffer for the cube.
Microsoft::WRL::ComPtr<ID3D12Resource> m_IndexBuffer;
D3D12_INDEX_BUFFER_VIEW m_IndexBufferView;
// Depth buffer.
Microsoft::WRL::ComPtr<ID3D12Resource> m_DepthBuffer;
// Descriptor heap for depth buffer.
Microsoft::WRL::ComPtr<ID3D12DescriptorHeap> m_DSVHeap;
// Root signature
Microsoft::WRL::ComPtr<ID3D12RootSignature> m_RootSignature;
// Pipeline state object.
Microsoft::WRL::ComPtr<ID3D12PipelineState> m_PipelineState;
D3D12_VIEWPORT m_Viewport;
D3D12_RECT m_ScissorRect;
float m_FoV;
DirectX::XMMATRIX m_ModelMatrix;
DirectX::XMMATRIX m_ViewMatrix;
DirectX::XMMATRIX m_ProjectionMatrix;
bool m_ContentLoaded;
};

 The number of frames that need to be tracked is stored in the Window::BufferCount static constant.In order to demonstrate a basic rendering pipeline, a cube is rendered using a vertex buffer and index buffer. For each of these resources, a view is created which describes the buffer resources to the rendering pipeline.The depth buffer is stored in the m_DepthBuffer member variable. Similar to the render target views for the swap chain, the depth buffer requires a depth-stencil view. The depth-stencil view is created in a descriptor heap.

The root signature describes the parameters passed to the various stages of the rendering pipeline and the pipeline state object describes the rendering pipeline itself.The viewport and scissor rect variables are used to initialize the rasterizer stage of the rendering pipeline.The m_FoV variable is used to store the current field of view of the camera. For this demo, the middle mouse wheel is used to “zoom-in” to the cube in the center of the scene.

The modelview and projection matrices are used to compute the MVP matrix that is used in the vertex shader to rotate the cube, position the camera, and project the vertices into clip-space.The m_ContentLoaded is used to indicate when the game content has been loaded.

Tutorial2 Preamble

首先包含頭文件

#include <Tutorial2.h>
#include <Application.h>
#include <CommandQueue.h>
#include <Helpers.h>
#include <Window.h>
#include <wrl.h>
using namespace Microsoft::WRL;
#include <d3dx12.h>
#include <d3dcompiler.h>
#include <algorithm> // For std::min and std::max.
#if defined(min)
#undef min
#endif
#if defined(max)
#undef max
#endif
using namespace DirectX;

The CommandQueue.h header file contains the definition of the CommandQueue class that was described in detail in the section titled Command Queue.The Helpers.h header file contains helper functions and macros that are used throughout the demo source code.The Window.h header file contains the Window class definition (not to be mistaken with the Windows.h WIN32 API header file).The wrl.h header file contains the Windows Runtime C++ Template Library. It is required for the ComPtr template class.The d3dx12.h header file included on line 11 is not considered to be part of the standard DirectX 12 API and must be downloaded separately from the Microsoft GitHub page (https://github.com/Microsoft/DirectX-Graphics-Samples/blob/master/Libraries/D3DX12/d3dx12.h).The d3dcompiler.h header file contains functions that are required to load (precompiled) shaders from disc.When using runtime compiled HLSL shaders using any of the D3DCompiler functions, do not forget to link against the D3Dcompiler_47.lib library and copy the D3dcompiler_47.dll to the same folder as the binary executable when distributing your project.A redistributable version of the D3dcompiler_47.dll file can be found in the Windows 10 SDK installation folder at C:\Program Files (x86)\Windows Kits\10\Redist\D3D\.For more information, refer to the MSDN blog post at: https://blogs.msdn.microsoft.com/chuckw/2012/05/07/hlsl-fxc-and-d3dcompile/

In order to avoid polluting the global namespace, all of the DirectX Math types are declared in the DirectX namespace. To avoid having to type “DirectX::” for all DirectX math types, the DirectX namespace is imported into the current source file on line 23.Avoid importing namespaces in header files. Importing namespaces in your own C++ source files is okay because then you are only polluting your own compilation units. If you import namespaces in your header files, you are polluting everybody’s (who is dependent on your header files) compilation units.

Starting with the C++17 standard, the algorithm header file also contains the clamp function. Until C++17 standard is fully implemented in Visual Studio, an implementation of the clamp function will need to be provided manually 😭.

// Clamp a value between a min and max range.
template<typename T>
constexpr const T& clamp(const T& val, const T& min, const T& max)
{
    return val < min ? min : val > max ? max : val;
}
// Vertex data for a colored cube.
struct VertexPosColor
{
    XMFLOAT3 Position;
    XMFLOAT3 Color;
};
static VertexPosColor g_Vertices[8] = {
    { XMFLOAT3(-1.0f, -1.0f, -1.0f), XMFLOAT3(0.0f, 0.0f, 0.0f) }, // 0
    { XMFLOAT3(-1.0f,  1.0f, -1.0f), XMFLOAT3(0.0f, 1.0f, 0.0f) }, // 1
    { XMFLOAT3( 1.0f,  1.0f, -1.0f), XMFLOAT3(1.0f, 1.0f, 0.0f) }, // 2
    { XMFLOAT3( 1.0f, -1.0f, -1.0f), XMFLOAT3(1.0f, 0.0f, 0.0f) }, // 3
    { XMFLOAT3(-1.0f, -1.0f,  1.0f), XMFLOAT3(0.0f, 0.0f, 1.0f) }, // 4
    { XMFLOAT3(-1.0f,  1.0f,  1.0f), XMFLOAT3(0.0f, 1.0f, 1.0f) }, // 5
    { XMFLOAT3( 1.0f,  1.0f,  1.0f), XMFLOAT3(1.0f, 1.0f, 1.0f) }, // 6
    { XMFLOAT3( 1.0f, -1.0f,  1.0f), XMFLOAT3(1.0f, 0.0f, 1.0f) }  // 7
};

之前定義的頂點是不能字節繪製幾何網格的。而 Input Assembler 可以渲染一組點,支持如下方式,但是還不能生成幾何體。

Primitive Topologies

The D3D_PRIMITIVE_TOPOLOGY enumeration specifies how the Input Assembler stage interprets the vertex data that is bound to the rendering pipeline.

  • D3D_PRIMITIVE_TOPOLOGY_POINTLIST: A disconnected list of points.
  • D3D_PRIMITIVE_TOPOLOGY_LINELIST: A list of disconnected lines.
  • D3D_PRIMITIVE_TOPOLOGY_LINESTRIP: A list of connected lines.
  • D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST: A list of disconnected triangles.
  • D3D_PRIMITIVE_TOPOLOGY_TRIANGLESTRIP: A list of connected triangles. Vertex [Math Processing Error]n and vertex [Math Processing Error]n−2,∀n>1 are implicitly connected to form the triangles.
  • D3D_PRIMITIVE_TOPOLOGY_LINELIST_ADJ: A list of disconnected lines. Additional adjacency information is available to the Geometry shader stage.
  • D3D_PRIMITIVE_TOPOLOGY_LINESTRIP_ADJ: A list of connected lines. Additional adjacency information is available to the Geometry shader stage.
  • D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST_ADJ: A list of disconnected triangles. Additional adjacency information is available to the Geometry shader stage.
  •  
  • D3D_PRIMITIVE_TOPOLOGY_TRIANGRIP_ADJ: A list of connected triangles. Additional adjacency information is available to the Geometry shader stage.

 

之前說過,經過Input Assembler後,這些頂點變成了點和線,但是還不能生成網格。只能由三角形生成網格,於是上頂點索引。

static WORD g_Indicies[36] =
{
    0, 1, 2, 0, 2, 3,
    4, 6, 5, 4, 7, 6,
    4, 5, 1, 4, 1, 0,
    3, 2, 6, 3, 6, 7,
    1, 5, 6, 1, 6, 2,
    4, 0, 3, 4, 3, 7
};

如上圖所示,在DX12中,三角形分爲前向和後向的,也就是front-face和back-face。前向則是正對着攝像機的,後向就算願你攝像機的。如果一個多邊形是後向的,那麼就需要告訴Rasterizer階段去cull。在DX12中,前向的多邊形的索引是順時針的,而後向是逆時針的。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章