BaseDx12(Dx12研究とフレームワーク)

【第1部】シンプルバージョン編

1203.複数のオブジェクトの描画(3)

 前項に引き続きSimplesample102です。

Scene::OnInitAssets()関数

 Scene::OnInitAssets()関数は、このサンプルで配置されるオブジェクトを初期化します。
 このサンプルでは、静止している三角形移動する三角形移動する四角形の3つのオブジェクトを描画するわけですが、それぞれクラスとして定義されています。
 それらはScene.h/cppに宣言、定義されています。
 まずSceneクラスですが、以下のように宣言されています。
    class Scene :public SceneBase {
        FixedTriangle m_FixedTriangle;
        MoveTriangle m_MoveTriangle;
        MoveSquare m_MoveSquare;
    public:
        Scene() :SceneBase() {}
        virtual ~Scene() {}
        virtual void OnInit()override;
        virtual void OnInitAssets()override;
        virtual void OnUpdate()override;
        virtual void OnDraw()override;
        virtual void OnDestroy()override;
    };
 ここで宣言されている
        FixedTriangle m_FixedTriangle;
        MoveTriangle m_MoveTriangle;
        MoveSquare m_MoveSquare;
 がそれぞれのオブジェクトです。
 それらを初期化するのがScene::OnInitAssets()関数です。
    void Scene::OnInitAssets() {
        // それぞれのオブジェクトの初期化
        {
            m_FixedTriangle.OnInit();
            m_MoveTriangle.OnInit();
            m_MoveSquare.OnInit();
        }
    }

FixedTriangleクラス

 まず、静止している三角形から説明します。  Scene.hにありますFixedTriangle静止している三角形です。以下に宣言部を紹介します。
    class FixedTriangle {
        //PositionColorの三角形メッシュ
        shared_ptr<BaseMesh> m_pcTriangleMesh;
        //PositionColor用パイプラインステート(コンスタントバッファなし)
        ComPtr<ID3D12PipelineState> m_pcPipelineState;
    public:
        FixedTriangle() {}
        ~FixedTriangle() {}
        void OnInit();
        void OnUpdate() {}
        void OnDraw();
    };
 このサンプルでは、オブジェクトごとにクラスを作成しますので、メッシュは
        shared_ptr<BaseMesh> m_pcTriangleMesh;
 という形で準備します。また、パイプラインステートも準備します。
 初期化そのものはFixedTriangle::OnInit()で行います。
    void FixedTriangle::OnInit() {
        auto baseDevice = App::GetBaseDevice();
        auto commandList = baseDevice->GetCommandList();
        auto aspectRatio = baseDevice->GetAspectRatio();

        D3D12_GRAPHICS_PIPELINE_STATE_DESC pipeLineDesc;
        //コンスタントバッファ無し
        m_pcPipelineState
        = PipelineState::CreateDefault2D<VertexPositionColor, VSPCSprite, PSPCSprite>(
            baseDevice->GetRootSignature(), pipeLineDesc
        );
        vector<VertexPositionColor> vertex =
        {
            { Float3(0.0f, 0.25f * aspectRatio, 0.0f), Float4(1.0f, 0.0f, 0.0f, 1.0f) },
            { Float3(0.25f, -0.25f * aspectRatio, 0.0f), Float4(0.0f, 1.0f, 0.0f, 1.0f) },
            { Float3(-0.25f, -0.25f * aspectRatio, 0.0f), Float4(0.0f, 0.0f, 1.0f, 1.0f) }
        };
        //三角形メッシュ作成
        m_pcTriangleMesh = BaseMesh::CreateBaseMesh<VertexPositionColor>(vertex);
    }
 このオブジェクトはSimpleSample101で紹介したものとほぼおなじです。
 シェーダも同じ内容のシェーダを使っています。

MoveTriangleクラス

 MoveTriangleクラスは左から右に流れるように動く三角形です。
 以下が宣言部です。
    class MoveTriangle {
        //PositionColorの三角形メッシュ
        shared_ptr<BaseMesh> m_pcTriangleMesh;
        //PositionColor用パイプラインステート(コンスタントバッファあり)
        ComPtr<ID3D12PipelineState> m_pcConstPipelineState;
        //コンスタントバッファ構造体の実体
        SceneConstantBuffer m_constantBufferData;
        //コンスタントバッファ
        shared_ptr<ConstantBuffer> m_ConstantBuffer;
        //コンスタントバッファのインデックス
        UINT m_constBuffIndex;
    public:
        MoveTriangle(){}
        ~MoveTriangle() {}
        void OnInit();
        void OnUpdate();
        void OnDraw();
    };
 赤くなっている部分が、今回注目してほしい部分です。
 SimpleSample101では、オブジェクトは変化しませんでしたが、MoveTriangleは移動します。
 移動を表現するためには、シェーダーに対して描画する場所を変化させなければなりません。
 シェーダに対して、そういうパラメータを渡す領域がコンスタントバッファです。
 コンスタントバッファについては1201.複数のオブジェクトの描画(1)で説明したCbvSrvUavデスクプリタヒープを思い出してください。BaeDx12ではCbvSrvUavデスクプリタヒープはデフォルトで1024個作成する、と説明しました。
 つまり、GameDevice::LoadPipeline()ですでに、この数のデスクプリタヒープは作成済みです。
 ですので、MoveTriangleの初期化のときは、ここから、このオブジェクトで使用するCbvSrvUavデスクプリタヒープを確定させます。
 それはMoveTriangle::OnInit()関数で行います。
    void MoveTriangle::OnInit() {
        auto baseDevice = App::GetBaseDevice();
        auto commandList = baseDevice->GetCommandList();
        auto aspectRatio = baseDevice->GetAspectRatio();
        D3D12_GRAPHICS_PIPELINE_STATE_DESC pipeLineDesc;
        m_pcConstPipelineState
        = PipelineState::CreateDefault2D<VertexPositionColor, VSPCConstSprite, PSPCConstSprite>(
                baseDevice->GetRootSignature(), 
                pipeLineDesc
        );
        vector<VertexPositionColor> vertex =
        {
            { Float3(0.0f, 0.25f * aspectRatio, 0.0f), Float4(0.0f, 1.0f, 0.0f, 1.0f) },
            { Float3(0.25f, -0.25f * aspectRatio, 0.0f), Float4(0.0f, 0.0f, 1.0f, 1.0f) },
            { Float3(-0.25f, -0.25f * aspectRatio, 0.0f), Float4(1.0f, 0.0f, 0.0f, 1.0f) }
        };
        //三角形メッシュ作成
        m_pcTriangleMesh = BaseMesh::CreateBaseMesh<VertexPositionColor>(vertex);
        //コンスタントバッファハンドルを作成
        m_constBuffIndex = baseDevice->GetCbvSrvUavNextIndex();
        CD3DX12_CPU_DESCRIPTOR_HANDLE Handle(
            baseDevice->GetCbvSrvUavDescriptorHeap()->GetCPUDescriptorHandleForHeapStart(),
            m_constBuffIndex,
            baseDevice->GetCbvSrvUavDescriptorHandleIncrementSize()
        );
        m_ConstantBuffer = ConstantBuffer::CreateDirect(Handle, m_constantBufferData);
    }
 三角形メッシュの作成まではFixedTriangleクラスと同じです。赤くなっている部分がコンスタントバッファい関連する部分です。
 まず
        m_constBuffIndex = baseDevice->GetCbvSrvUavNextIndex();
 でCbvSrvUavデスクプリタヒープ次のインデックスを取得します。この関数はCbvSrvUavデスクプリタヒープの中から、使ってない場所のインデックスを返します。最初は0です。2個目は13個目は2が帰ってきます。
 次のインデックスを取得したら
        CD3DX12_CPU_DESCRIPTOR_HANDLE Handle(
            baseDevice->GetCbvSrvUavDescriptorHeap()->GetCPUDescriptorHandleForHeapStart(),
            m_constBuffIndex,
            baseDevice->GetCbvSrvUavDescriptorHandleIncrementSize()
        );
 で、そのハンドル(ディスクプリタハンドル)を作成します。上記の設定で、このオブジェクトが使用するハンドルを作れます。
 そのご、そのハンドルをもとに
        m_ConstantBuffer = ConstantBuffer::CreateDirect(Handle, m_constantBufferData);
 とコンスタントバッファを作成します。
 ConstantBuffer::CreateDirect()関数はテンプレート関数になっていて、以下のような形です。
    template<typename T>
    static inline shared_ptr<ConstantBuffer> CreateDirect(
        const CD3DX12_CPU_DESCRIPTOR_HANDLE& descHandle,
        const T& src
    ) {
        //デバイスの取得
        auto device = App::GetID3D12Device();
        shared_ptr<ConstantBuffer> ptrConst = shared_ptr<ConstantBuffer>(new ConstantBuffer());
        // コンスタントバッファのサイズは256バイト境界ごとに作成する
        UINT constsize = (sizeof(T) + 255) & ~255;
        ThrowIfFailed(device->CreateCommittedResource(
            &CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD),
            D3D12_HEAP_FLAG_NONE,
            &CD3DX12_RESOURCE_DESC::Buffer(constsize),
            D3D12_RESOURCE_STATE_GENERIC_READ,
            nullptr,
            IID_PPV_ARGS(&ptrConst->m_constantBuffer)));

        // コンスタントバッファビューの作成
        D3D12_CONSTANT_BUFFER_VIEW_DESC cbvDesc = {};
        cbvDesc.BufferLocation = ptrConst->m_constantBuffer->GetGPUVirtualAddress();
        cbvDesc.SizeInBytes = constsize;    
        device->CreateConstantBufferView(&cbvDesc, descHandle);

        // アプリケーションが動作しているあいだ中、Map状態でも問題ない
        CD3DX12_RANGE readRange(0, 0); 
        ThrowIfFailed(ptrConst->m_constantBuffer->Map(
            0,
            &readRange,
            reinterpret_cast<void**>(&ptrConst->m_pCbvDataBegin))
        );
        memcpy(ptrConst->m_pCbvDataBegin, &src, sizeof(src));
        return ptrConst;
    }
 テンプレートであるtypename Tは、実引数ではconst T& srcになります。
 今回は
        m_ConstantBuffer = ConstantBuffer::CreateDirect(Handle, m_constantBufferData);
 と呼びだしているので、m_constantBufferDataの型、ということになります。
 この型はScene.hに宣言されている構造体で、
    struct SceneConstantBuffer
    {
        XMFLOAT4 offset;
        SceneConstantBuffer() :
            offset(0.0f, 0, 0, 0)
        {}
    };
 となっています。XMFLOAT4DirectXMathで宣言されている型です。
struct XMFLOAT4
{
    float x;
    float y;
    float z;
    float w;

    XMFLOAT4() = default;

    XMFLOAT4(const XMFLOAT4&) = default;
    XMFLOAT4& operator=(const XMFLOAT4&) = default;

    XMFLOAT4(XMFLOAT4&&) = default;
    XMFLOAT4& operator=(XMFLOAT4&&) = default;

    XM_CONSTEXPR XMFLOAT4(float _x, float _y, float _z, float _w) :
             x(_x), y(_y), z(_z), w(_w) {}
    explicit XMFLOAT4(_In_reads_(4) const float *pArray) :
             x(pArray[0]), y(pArray[1]), z(pArray[2]), w(pArray[3]) {}
};
 こんな感じになっています。シェーダーの入力と互換が取れているので、この型になっています。
 ConstantBufferクラスです。CreateDirect()static関数によってインスタンスを作成し、必要なリソースを作成します。
        UINT constsize = (sizeof(T) + 255) & ~255;
        ThrowIfFailed(device->CreateCommittedResource(
            &CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_UPLOAD),
            D3D12_HEAP_FLAG_NONE,
            &CD3DX12_RESOURCE_DESC::Buffer(constsize),
            D3D12_RESOURCE_STATE_GENERIC_READ,
            nullptr,
            IID_PPV_ARGS(&ptrConst->m_constantBuffer)));
 と作成するわけですが、作成するリソースのサイズをconstsizeに設定しますが、その際
        UINT constsize = (sizeof(T) + 255) & ~255;
 とします。これはコンスタントバッファは256バイト単位で作成しなければならないからです。
 その後CreateCommittedResource()関数でリソースを作成します。それはConstantBufferクラスのm_constantBufferに設定されます。
 続いてコンスタントバッファビューを作成します。
        // コンスタントバッファビューの作成
        D3D12_CONSTANT_BUFFER_VIEW_DESC cbvDesc = {};
        cbvDesc.BufferLocation = ptrConst->m_constantBuffer->GetGPUVirtualAddress();
        cbvDesc.SizeInBytes = constsize;    
        device->CreateConstantBufferView(&cbvDesc, descHandle);
 ここで、先ほど作成したリソースGPU側仮想アドレスと、descHandleを結びつけます。
 これでディスクリプタヒープ(ハンドル経由)、リソースが関連づけられコンスタントバッファビューが作成されます。
 最後に、コンスタントバッファと、CPU側のポインタptrConst->m_pCbvDataBeginをマップし、データの内容をコピーします。
        // アプリケーションが動作しているあいだ中、Map状態でも問題ない
        CD3DX12_RANGE readRange(0, 0); 
        ThrowIfFailed(ptrConst->m_constantBuffer->Map(
            0,
            &readRange,
            reinterpret_cast<void**>(&ptrConst->m_pCbvDataBegin))
        );
        memcpy(ptrConst->m_pCbvDataBegin, &src, sizeof(src));
 ここで、コメントにあるように、マップした領域は、アプリケーションが終了するまでマップしたままで問題ありません。
 これでMoveTriangle::OnInit()は、終わりです。