BRE Architecture Series Part 7 – Geometry Pass

In this opportunity, we are going to talk about the most complex pass in BRE (at the time of writing this article). It is the Geometry Pass. It is the first pass to be executed at the beginning of each frame and its next pass is the AmbientOcclusionPass.

Remember that BRE implements deferred shading. Thus, we have several geometry buffers that store material and geometry information. In the following image, we can see the different geometry buffers we have in BRE

geometry_buffers

It is important to mention that we do not create an additional buffer for the depth but we reuse the depth buffer.

As we already explained in BRE Architecture Series Part 2- Managers, there is a class named ModelManager that is responsible for loading models. But we did not explain what is the output of that class. Once ModelManager loads a model from disk, it creates an instance of Model class. That class has a list of meshes represented with the class Mesh. This class has the vertex and index buffers of that particular mesh. Its implementation is the following

Model.h

#pragma once

#include <vector>
#include <wrl.h>

#include <GeometryGenerator/GeometryGenerator.h>
#include <ModelManager/Mesh.h>

namespace BRE {
///
/// @brief Represents a model that can be loaded from a file
///
class Model {
public:
    ~Model() = default;
    Model(const Model&) = delete;
    const Model& operator=(const Model&) = delete;
    Model(Model&&) = delete;
    Model& operator=(Model&&) = delete;

    ///
    /// @brief Model constructor
    /// @param modelFilename Model filename. Must not be nullptr.
    /// @param commandList Command list used to upload buffers content to GPU.
    /// It must be executed after this function call to upload buffers content to GPU.
    /// It must be in recording state before calling this method.
    /// @param uploadVertexBuffer Upload buffer to upload the vertex buffer content.
    /// It has to be kept alive after the function call because
    /// the command list has not been executed yet that performs the actual copy.
    /// The caller can Release the uploadVertexBuffer after it knows the copy has been executed.
    /// @param uploadIndexBuffer Upload buffer to upload the index buffer content.
    /// It has to be kept alive after the function call because
    /// the command list has not been executed yet that performs the actual copy.
    /// The caller can Release the uploadIndexBuffer after it knows the copy has been executed.
    ///
    explicit Model(const char* modelFilename,
                   ID3D12GraphicsCommandList& commandList,
                   Microsoft::WRL::ComPtr<ID3D12Resource>& uploadVertexBuffer,
                   Microsoft::WRL::ComPtr<ID3D12Resource>& uploadIndexBuffer);

    ///
    /// @brief Model constructor
    /// @param meshData Mesh data.
    /// @param commandList Command list used to upload buffers content to GPU.
    /// It must be executed after this function call to upload buffers content to GPU.
    /// It must be in recording state before calling this method.
    /// @param uploadVertexBuffer Upload buffer to upload the vertex buffer content.
    /// It has to be kept alive after the function call because
    /// the command list has not been executed yet that performs the actual copy.
    /// The caller can Release the uploadVertexBuffer after it knows the copy has been executed.
    /// @param uploadIndexBuffer Upload buffer to upload the index buffer content.
    /// It has to be kept alive after the function call because
    /// the command list has not been executed yet that performs the actual copy.
    /// The caller can Release the uploadIndexBuffer after it knows the copy has been executed.
    ///
    explicit Model(const GeometryGenerator::MeshData& meshData,
                   ID3D12GraphicsCommandList& commandList,
                   Microsoft::WRL::ComPtr<ID3D12Resource>& uploadVertexBuffer,
                   Microsoft::WRL::ComPtr<ID3D12Resource>& uploadIndexBuffer);

    ///
    /// @brief Checks if there are meshes or not
    /// @return True if there are meshes. Otherwise, false.
    ///
    __forceinline bool HasMeshes() const noexcept
    {
        return (mMeshes.size() > 0UL);
    }

    ///
    /// @brief Get meshes
    /// @return List of meshes
    ///
    __forceinline const std::vector<Mesh>& GetMeshes() const noexcept
    {
        return mMeshes;
    }

private:
    std::vector<Mesh> mMeshes;
};
}

Model.cpp

#include "Model.h"

#include <assimp/Importer.hpp>
#include <assimp/postprocess.h>
#include <assimp/scene.h>

#include <Utils/DebugUtils.h>

namespace BRE {
Model::Model(const char* modelFilename,
             ID3D12GraphicsCommandList& commandList,
             Microsoft::WRL::ComPtr<ID3D12Resource>& uploadVertexBuffer,
             Microsoft::WRL::ComPtr<ID3D12Resource>& uploadIndexBuffer)
{
    BRE_ASSERT(modelFilename != nullptr);
    const std::string filePath(modelFilename);

    Assimp::Importer importer;
    const std::uint32_t flags{ aiProcessPreset_TargetRealtime_Fast | aiProcess_ConvertToLeftHanded };
    const aiScene* scene{ importer.ReadFile(filePath.c_str(), flags) };
    BRE_ASSERT_MSG(scene != nullptr, StringUtils::AnsiToWideString(importer.GetErrorString()).c_str());

    BRE_ASSERT(scene->HasMeshes());

    for (std::uint32_t i = 0U; i < scene->mNumMeshes; ++i) {
        aiMesh* mesh{ scene->mMeshes[i] };
        BRE_ASSERT(mesh != nullptr);
        mMeshes.push_back(Mesh(*mesh,
                               commandList,
                               uploadVertexBuffer,
                               uploadIndexBuffer));
    }
}

Model::Model(const GeometryGenerator::MeshData& meshData,
             ID3D12GraphicsCommandList& commandList,
             Microsoft::WRL::ComPtr<ID3D12Resource>& uploadVertexBuffer,
             Microsoft::WRL::ComPtr<ID3D12Resource>& uploadIndexBuffer)
{
    mMeshes.push_back(Mesh(meshData,
                           commandList,
                           uploadVertexBuffer,
                           uploadIndexBuffer));
}
}

Mesh.h

#pragma once

#include <cstdint>

#include <GeometryGenerator/GeometryGenerator.h>
#include <ResourceManager\VertexAndIndexBufferCreator.h>
#include <Utils/DebugUtils.h>

struct aiMesh;

namespace BRE {
class Model;

///
/// @brief Stores model's mesh vertex and index data.
///
class Mesh {
    friend class Model;

public:
    ~Mesh() = default;
    Mesh(const Mesh&) = delete;
    const Mesh& operator=(const Mesh&) = delete;
    Mesh(Mesh&&) = default;
    Mesh& operator=(Mesh&&) = delete;

    ///
    /// @brief Get vertex buffer data
    ///
    /// Data must be valid
    ///
    /// @return Vertex buffer data
    ///
    __forceinline const VertexAndIndexBufferCreator::VertexBufferData& GetVertexBufferData() const noexcept
    {
        BRE_ASSERT(mVertexBufferData.IsDataValid());
        return mVertexBufferData;
    }

    ///
    /// @brief Get index buffer data
    ///
    /// Data must be valid
    ///
    /// @return Index buffer data
    ///
    __forceinline const VertexAndIndexBufferCreator::IndexBufferData& GetIndexBufferData() const noexcept
    {
        BRE_ASSERT(mIndexBufferData.IsDataValid());
        return mIndexBufferData;
    }

private:
    ///
    /// @brief Mesh constructor
    /// @param mesh Assimp mesh
    /// @param commandList Command list used to upload buffers content to GPU.
    /// It must be executed after this function call to upload buffers content to GPU.
    /// It must be in recording state before calling this method.
    /// @param uploadVertexBuffer Upload buffer to upload the vertex buffer content.
    /// It has to be kept alive after the function call because
    /// the command list has not been executed yet that performs the actual copy.
    /// The caller can Release the uploadVertexBuffer after it knows the copy has been executed.
    /// @param uploadIndexBuffer Upload buffer to upload the index buffer content.
    /// It has to be kept alive after the function call because
    /// the command list has not been executed yet that performs the actual copy.
    /// The caller can Release the uploadIndexBuffer after it knows the copy has been executed.
    ///
    explicit Mesh(const aiMesh& mesh,
                  ID3D12GraphicsCommandList& commandList,
                  Microsoft::WRL::ComPtr<ID3D12Resource>& uploadVertexBuffer,
                  Microsoft::WRL::ComPtr<ID3D12Resource>& uploadIndexBuffer);

    ///
    /// @brief Mesh constructor
    /// @param meshData Mesh data where we extract vertex and indices
    /// @param commandList Command list used to upload buffers content to GPU.
    /// It must be executed after this function call to upload buffers content to GPU.
    /// It must be in recording state before calling this method.
    /// @param uploadVertexBuffer Upload buffer to upload the vertex buffer content.
    /// It has to be kept alive after the function call because
    /// the command list has not been executed yet that performs the actual copy.
    /// The caller can Release the uploadVertexBuffer after it knows the copy has been executed.
    /// @param uploadIndexBuffer Upload buffer to upload the index buffer content.
    /// It has to be kept alive after the function call because
    /// the command list has not been executed yet that performs the actual copy.
    /// The caller can Release the uploadIndexBuffer after it knows the copy has been executed.
    ///
    explicit Mesh(const GeometryGenerator::MeshData& meshData,
                  ID3D12GraphicsCommandList& commandList,
                  Microsoft::WRL::ComPtr<ID3D12Resource>& uploadVertexBuffer,
                  Microsoft::WRL::ComPtr<ID3D12Resource>& uploadIndexBuffer);

    VertexAndIndexBufferCreator::VertexBufferData mVertexBufferData;
    VertexAndIndexBufferCreator::IndexBufferData mIndexBufferData;
};
}

Mesh.cpp

#include "Mesh.h"

#include <assimp/scene.h>

#include <Utils/DebugUtils.h>

using namespace DirectX;

namespace BRE {
namespace {
///
/// @brief Creates vertex and index buffer data
/// @param vertexBufferData Vertex buffer data
/// @param indexBufferData Index buffer data
/// @param meshData Mesh data to get vertices and indices
/// @param commandList Command list used to upload buffers content to GPU.
/// It must be executed after this function call to upload buffers content to GPU.
/// @param uploadVertexBuffer Upload buffer to create the buffer.
/// It has to be kept alive after the function call because
/// the command list has not been executed yet that performs the actual copy.
/// The caller can Release the uploadBuffer after it knows the copy has been executed.
/// @param uploadIndexBuffer Upload buffer to create the buffer.
/// It has to be kept alive after the function call because
/// the command list has not been executed yet that performs the actual copy.
/// The caller can Release the uploadBuffer after it knows the copy has been executed.
///
void CreateVertexAndIndexBufferData(VertexAndIndexBufferCreator::VertexBufferData& vertexBufferData,
                                    VertexAndIndexBufferCreator::IndexBufferData& indexBufferData,
                                    const GeometryGenerator::MeshData& meshData,
                                    ID3D12GraphicsCommandList& commandList,
                                    Microsoft::WRL::ComPtr<ID3D12Resource>& uploadVertexBuffer,
                                    Microsoft::WRL::ComPtr<ID3D12Resource>& uploadIndexBuffer) noexcept
{
    BRE_ASSERT(vertexBufferData.IsDataValid() == false);
    BRE_ASSERT(indexBufferData.IsDataValid() == false);

    // Create vertex buffer
    VertexAndIndexBufferCreator::BufferCreationData vertexBufferParams(meshData.mVertices.data(),
                                                                       static_cast<std::uint32_t>(meshData.mVertices.size()),
                                                                       sizeof(GeometryGenerator::Vertex));

    VertexAndIndexBufferCreator::CreateVertexBuffer(vertexBufferParams,
                                                    vertexBufferData,
                                                    commandList,
                                                    uploadVertexBuffer);

    // Create index buffer
    VertexAndIndexBufferCreator::BufferCreationData indexBufferParams(meshData.mIndices32.data(),
                                                                      static_cast<std::uint32_t>(meshData.mIndices32.size()),
                                                                      sizeof(std::uint32_t));

    VertexAndIndexBufferCreator::CreateIndexBuffer(indexBufferParams,
                                                   indexBufferData,
                                                   commandList,
                                                   uploadIndexBuffer);

    BRE_ASSERT(vertexBufferData.IsDataValid());
    BRE_ASSERT(indexBufferData.IsDataValid());
}
}

Mesh::Mesh(const aiMesh& mesh,
           ID3D12GraphicsCommandList& commandList,
           Microsoft::WRL::ComPtr<ID3D12Resource>& uploadVertexBuffer,
           Microsoft::WRL::ComPtr<ID3D12Resource>& uploadIndexBuffer)
{
    GeometryGenerator::MeshData meshData;

    // Positions and Normals
    const std::size_t numVertices{ mesh.mNumVertices };
    BRE_ASSERT(numVertices > 0U);
    BRE_ASSERT(mesh.HasNormals());
    meshData.mVertices.resize(numVertices);
    for (std::uint32_t i = 0U; i < numVertices; ++i) {
        meshData.mVertices[i].mPosition = XMFLOAT3(reinterpret_cast<const float*>(&mesh.mVertices[i]));
        meshData.mVertices[i].mNormal = XMFLOAT3(reinterpret_cast<const float*>(&mesh.mNormals[i]));
    }

    // Texture Coordinates (if any)
    if (mesh.HasTextureCoords(0U)) {
        BRE_ASSERT(mesh.GetNumUVChannels() == 1U);
        const aiVector3D* aiTextureCoordinates{ mesh.mTextureCoords[0U] };
        BRE_ASSERT(aiTextureCoordinates != nullptr);
        for (std::uint32_t i = 0U; i < numVertices; i++) {
            meshData.mVertices[i].mUV = XMFLOAT2(reinterpret_cast<const float*>(&aiTextureCoordinates[i]));
        }
    }

    // Indices
    BRE_ASSERT(mesh.HasFaces());
    const std::uint32_t numFaces{ mesh.mNumFaces };
    for (std::uint32_t i = 0U; i < numFaces; ++i) {         const aiFace* face = &mesh.mFaces[i];         BRE_ASSERT(face != nullptr);         // We only allow triangles         BRE_ASSERT(face->mNumIndices == 3U);

        meshData.mIndices32.push_back(face->mIndices[0U]);
        meshData.mIndices32.push_back(face->mIndices[1U]);
        meshData.mIndices32.push_back(face->mIndices[2U]);
    }

    // Tangents
    if (mesh.HasTangentsAndBitangents()) {
        for (std::uint32_t i = 0U; i < numVertices; ++i) {
            meshData.mVertices[i].mTangent = XMFLOAT3(reinterpret_cast<const float*>(&mesh.mTangents[i]));
        }
    }

    CreateVertexAndIndexBufferData(mVertexBufferData,
                                   mIndexBufferData,
                                   meshData,
                                   commandList,
                                   uploadVertexBuffer,
                                   uploadIndexBuffer);

    BRE_ASSERT(mVertexBufferData.IsDataValid());
    BRE_ASSERT(mIndexBufferData.IsDataValid());
}

Mesh::Mesh(const GeometryGenerator::MeshData& meshData,
           ID3D12GraphicsCommandList& commandList,
           Microsoft::WRL::ComPtr<ID3D12Resource>& uploadVertexBuffer,
           Microsoft::WRL::ComPtr<ID3D12Resource>& uploadIndexBuffer)
{
    CreateVertexAndIndexBufferData(mVertexBufferData,
                                   mIndexBufferData,
                                   meshData,
                                   commandList,
                                   uploadVertexBuffer,
                                   uploadIndexBuffer);

    BRE_ASSERT(mVertexBufferData.IsDataValid());
    BRE_ASSERT(mIndexBufferData.IsDataValid());
}
}

At the time of writing this article, BRE supports texture mapping, normal mapping, and height mapping. To be able to implement these techniques, BRE has a base class called GeometryCommandListRecorder that has a common interface for all the techniques. For example, all of them need to write the geometry buffers, that are going to be used by the next passes. Its implementation is the following

GeometryCommandListRecorder.h

#pragma once

#include <d3d12.h>
#include <DirectXMath.h>
#include <memory>
#include <vector>

#include <CommandManager\CommandListPerFrame.h>
#include <ResourceManager\FrameUploadCBufferPerFrame.h>
#include <ResourceManager/VertexAndIndexBufferCreator.h>

namespace BRE {
struct FrameCBuffer;

///
/// @brief Responsible to record command lists for deferred shading geometry pass
///
/// Steps:
/// - Inherit from it and reimplement RecordAndPushCommandLists() method
/// - Call RecordAndPushCommandLists() to create command lists to execute in the GPU
///
class GeometryCommandListRecorder {
public:
    struct GeometryData {
        GeometryData() = default;

        VertexAndIndexBufferCreator::VertexBufferData mVertexBufferData;
        VertexAndIndexBufferCreator::IndexBufferData mIndexBufferData;
        std::vector<DirectX::XMFLOAT4X4> mWorldMatrices;
        std::vector<DirectX::XMFLOAT4X4> mInverseTransposeWorldMatrices;
        std::vector<float> mTextureScales;
    };

    GeometryCommandListRecorder() = default;
    virtual ~GeometryCommandListRecorder()
    {}

    GeometryCommandListRecorder(const GeometryCommandListRecorder&) = delete;
    const GeometryCommandListRecorder& operator=(const GeometryCommandListRecorder&) = delete;
    GeometryCommandListRecorder(GeometryCommandListRecorder&&) = default;
    GeometryCommandListRecorder& operator=(GeometryCommandListRecorder&&) = default;

    ///
    /// @brief Initializes the command list recorder
    /// @param geometryBufferRenderTargetViews Geometry buffers render target views. Must not be nullptr
    /// @param geometryBufferRenderTargetViewCount Geometry buffer render target views count. Must be greater than zero
    /// @param depthBufferView Depth buffer view
    ///
    void Init(const D3D12_CPU_DESCRIPTOR_HANDLE* geometryBufferRenderTargetViews,
              const std::uint32_t geometryBufferRenderTargetViewCount,
              const D3D12_CPU_DESCRIPTOR_HANDLE& depthBufferView) noexcept;

    ///
    /// @brief Records and pushes command lists to CommandListExecutor
    ///
    /// Init() must be called first
    ///
    /// @param frameCBuffer Constant buffer per frame, for current frame
    /// @return The number of pushed command lists
    ///
    virtual std::uint32_t RecordAndPushCommandLists(const FrameCBuffer& frameCBuffer) noexcept = 0;

    ///
    /// @brief Checks if internal data is valid. Typically, used for assertions
    /// @return True if valid. Otherwise, false
    ///
    virtual bool IsDataValid() const noexcept;

protected:
    CommandListPerFrame mCommandListPerFrame;

    // Base command data. Once you inherits from this class, you should add
    // more class members that represent the extra information you need (like resources, for example)

    std::vector<GeometryData> mGeometryDataVec;

    FrameUploadCBufferPerFrame mFrameUploadCBufferPerFrame;

    UploadBuffer* mObjectUploadCBuffers{ nullptr };
    D3D12_GPU_DESCRIPTOR_HANDLE mObjectCBufferViewsBegin{ 0U };

    const D3D12_CPU_DESCRIPTOR_HANDLE* mGeometryBufferRenderTargetViews{ nullptr };
    std::uint32_t mGeometryBufferRenderTargetViewCount{ 0U };

    D3D12_CPU_DESCRIPTOR_HANDLE mDepthBufferView{ 0UL };
};

using GeometryCommandListRecorders = std::vector<std::unique_ptr<GeometryCommandListRecorder>>;
}

GeometryCommandListRecorder.cpp

#include "GeometryCommandListRecorder.h"

#include <Utils/DebugUtils.h>

namespace BRE {
bool
GeometryCommandListRecorder::IsDataValid() const noexcept
{
    const std::size_t geometryDataCount{ mGeometryDataVec.size() };
    for (std::size_t i = 0UL; i < geometryDataCount; ++i) {
        const std::size_t numMatrices{ mGeometryDataVec[i].mWorldMatrices.size() };
        if (numMatrices == 0UL) {
            return false;
        }
    }

    return
        mObjectUploadCBuffers != nullptr &&
        mObjectCBufferViewsBegin.ptr != 0UL &&
        geometryDataCount != 0UL;
}

void
GeometryCommandListRecorder::Init(const D3D12_CPU_DESCRIPTOR_HANDLE* geometryBufferRenderTargetViews,
                                  const std::uint32_t geometryBufferRenderTargetViewCount,
                                  const D3D12_CPU_DESCRIPTOR_HANDLE& depthBufferView) noexcept
{
    BRE_ASSERT(geometryBufferRenderTargetViews != nullptr);
    BRE_ASSERT(geometryBufferRenderTargetViewCount != 0U);
    BRE_ASSERT(depthBufferView.ptr != 0UL);

    mGeometryBufferRenderTargetViews = geometryBufferRenderTargetViews;
    mGeometryBufferRenderTargetViewCount = geometryBufferRenderTargetViewCount;
    mDepthBufferView = depthBufferView;
}
}

As we can see, this class has an internal class named GeometryData which contains the vertex buffer, index buffer, and world matrices per geometry (in this case, per mesh). The source code is the following

GeometryData

    struct GeometryData {
        GeometryData() = default;

        VertexAndIndexBufferCreator::VertexBufferData mVertexBufferData;
        VertexAndIndexBufferCreator::IndexBufferData mIndexBufferData;
        std::vector<DirectX::XMFLOAT4X4> mWorldMatrices;
        std::vector<DirectX::XMFLOAT4X4> mInverseTransposeWorldMatrices;
    };

We are not going to explain the implementation of each technique, like normal mapping, because there are a lot of resources to learn that, but in the next sections, we will explain all the command list recorders that implement them.

How do we implement texture mapping?

In BRE, we have a type of command list recorder named TextureMappingCommandListRecorder that inherits from GeometryCommandListRecorder and generates a command list that implements texture mapping and pushes it to the CommandListExecutor to be executed. Its inputs are the geometry buffers, a list of GeometryData, and a list of diffuse textures per world matrix per GeometryData. It writes to the geometry buffers and the depth buffer. Its implementation and shaders are the following

TextureMappingCommandListRecorder.h

#pragma once

#include <GeometryPass/GeometryCommandListRecorder.h>

namespace BRE {
///
/// @brief Responsible to record command lists that implement texture mapping
///
class TextureMappingCommandListRecorder : public GeometryCommandListRecorder {
public:
    TextureMappingCommandListRecorder() = default;
    ~TextureMappingCommandListRecorder() = default;
    TextureMappingCommandListRecorder(const TextureMappingCommandListRecorder&) = delete;
    const TextureMappingCommandListRecorder& operator=(const TextureMappingCommandListRecorder&) = delete;
    TextureMappingCommandListRecorder(TextureMappingCommandListRecorder&&) = default;
    TextureMappingCommandListRecorder& operator=(TextureMappingCommandListRecorder&&) = default;

    ///
    /// @brief Initializes pipeline state object and root signature
    /// @param geometryBufferFormats List of geometry buffers formats. It must be not nullptr
    /// @param geometryBufferCount Number of geometry buffers formats in @p geometryBufferFormats
    ///
    static void InitSharedPSOAndRootSignature(const DXGI_FORMAT* geometryBufferFormats,
                                              const std::uint32_t geometryBufferCount) noexcept;

    ///
    /// @brief Initializes the recorder
    ///
    /// InitSharedPSOAndRootSignature() must be called first
    ///
    /// @param geometryDataVector List of geometry data. Must not be empty
    /// @param baseColorTextures List of base color textures. Must not be empty.
    /// @param metalnessTextures List of metalness textures. Must not be empty.
    /// @param roughnessTextures List of rougness textures. Must not be empty.
    ///
    void Init(const std::vector<GeometryData>& geometryDataVector,
              const std::vector<ID3D12Resource*>& baseColorTextures,
              const std::vector<ID3D12Resource*>& metalnessTextures,
              const std::vector<ID3D12Resource*>& roughnessTextures) noexcept;

    ///
    /// @brief Records and push command lists to CommandListExecutor
    ///
    /// Init() must be called first
    ///
    /// @param frameCBuffer Constant buffer per frame, for current frame
    /// @return The number of pushed command lists
    ///
    std::uint32_t RecordAndPushCommandLists(const FrameCBuffer& frameCBuffer) noexcept final override;

    ///
    /// @brief Checks if internal data is valid. Typically, used for assertions
    /// @return True if valid. Otherwise, false
    ///
    bool IsDataValid() const noexcept final override;

private:
    ///
    /// @brief Initializes the constant buffers and views
    /// @param baseColorTextures List of base color textures. Must not be empty.
    /// @param metalnessTextures List of metalness textures. Must not be empty.
    /// @param roughnessTextures List of rougness textures. Must not be empty.
    ///
    void InitCBuffersAndViews(const std::vector<ID3D12Resource*>& baseColorTextures,
                              const std::vector<ID3D12Resource*>& metalnessTextures,
                              const std::vector<ID3D12Resource*>& roughnessTextures) noexcept;

    // First descriptor in the list. All the others are contiguous
    D3D12_GPU_DESCRIPTOR_HANDLE mBaseColorTextureRenderTargetViewsBegin{ 0U };

    // First descriptor in the list. All the others are contiguous
    D3D12_GPU_DESCRIPTOR_HANDLE mMetalnessTextureRenderTargetViewsBegin{ 0U };

    // First descriptor in the list. All the others are contiguous
    D3D12_GPU_DESCRIPTOR_HANDLE mRoughnessTextureRenderTargetViewsBegin{ 0U };
};
}

TextureMappingCommandListRecorder.cpp

#include "TextureMappingCommandListRecorder.h"

#include <DirectXMath.h>

#include <CommandListExecutor\CommandListExecutor.h>
#include <DescriptorManager\CbvSrvUavDescriptorManager.h>
#include <DirectXManager\DirectXManager.h>
#include <MathUtils/MathUtils.h>
#include <PSOManager/PSOManager.h>
#include <ResourceManager/UploadBufferManager.h>
#include <RootSignatureManager\RootSignatureManager.h>
#include <ShaderManager\ShaderManager.h>
#include <ShaderUtils\CBuffers.h>
#include <Utils/DebugUtils.h>

namespace BRE {
// Root Signature:
// "DescriptorTable(CBV(b0), visibility = SHADER_VISIBILITY_VERTEX), " \ 0 -> Object CBuffers
// "CBV(b0, visibility = SHADER_VISIBILITY_VERTEX), " \ 1 -> Frame CBuffer
// "CBV(b0, visibility = SHADER_VISIBILITY_PIXEL), " \ 2 -> Frame CBuffer
// "DescriptorTable(SRV(t0), visibility = SHADER_VISIBILITY_PIXEL), " \ 3 -> Base Color Texture
// "DescriptorTable(SRV(t1), visibility = SHADER_VISIBILITY_PIXEL), " \ 4 -> Metalness Texture
// "DescriptorTable(SRV(t2), visibility = SHADER_VISIBILITY_PIXEL), " \ 5 -> Roughness Texture

namespace {
ID3D12PipelineState* sPSO{ nullptr };
ID3D12RootSignature* sRootSignature{ nullptr };
}

void
TextureMappingCommandListRecorder::InitSharedPSOAndRootSignature(const DXGI_FORMAT* geometryBufferFormats,
                                                                 const std::uint32_t geometryBufferCount) noexcept
{
    BRE_ASSERT(geometryBufferFormats != nullptr);
    BRE_ASSERT(geometryBufferCount > 0U);
    BRE_ASSERT(sPSO == nullptr);
    BRE_ASSERT(sRootSignature == nullptr);

    // Build pso and root signature
    PSOManager::PSOCreationData psoData{};
    psoData.mInputLayoutDescriptors = D3DFactory::GetPositionNormalTangentTexCoordInputLayout();

    psoData.mPixelShaderBytecode = ShaderManager::LoadShaderFileAndGetBytecode("GeometryPass/Shaders/TextureMapping/PS.cso");
    psoData.mVertexShaderBytecode = ShaderManager::LoadShaderFileAndGetBytecode("GeometryPass/Shaders/TextureMapping/VS.cso");

    ID3DBlob* rootSignatureBlob = &ShaderManager::LoadShaderFileAndGetBlob("GeometryPass/Shaders/TextureMapping/RS.cso");
    psoData.mRootSignature = &RootSignatureManager::CreateRootSignatureFromBlob(*rootSignatureBlob);
    sRootSignature = psoData.mRootSignature;

    psoData.mNumRenderTargets = geometryBufferCount;
    memcpy(psoData.mRenderTargetFormats, geometryBufferFormats, sizeof(DXGI_FORMAT) * psoData.mNumRenderTargets);
    sPSO = &PSOManager::CreateGraphicsPSO(psoData);

    BRE_ASSERT(sPSO != nullptr);
    BRE_ASSERT(sRootSignature != nullptr);
}

void
TextureMappingCommandListRecorder::Init(const std::vector<GeometryData>& geometryDataVector,
                                        const std::vector<ID3D12Resource*>& baseColorTextures,
                                        const std::vector<ID3D12Resource*>& metalnessTextures,
                                        const std::vector<ID3D12Resource*>& roughnessTextures) noexcept
{
    BRE_ASSERT(geometryDataVector.empty() == false);
    BRE_ASSERT(baseColorTextures.empty() == false);
    BRE_ASSERT(baseColorTextures.size() == metalnessTextures.size());
    BRE_ASSERT(metalnessTextures.size() == roughnessTextures.size());
    BRE_ASSERT(IsDataValid() == false);

    const std::size_t numResources = baseColorTextures.size();
    const std::size_t geometryDataCount = geometryDataVector.size();

    // Check that the total number of matrices (geometry to be drawn) will be equal to available materials
#ifdef _DEBUG
    std::size_t totalNumMatrices{ 0UL };
    for (std::size_t i = 0UL; i < geometryDataCount; ++i) {
        const std::size_t numMatrices{ geometryDataVector[i].mWorldMatrices.size() };
        totalNumMatrices += numMatrices;
        BRE_ASSERT(numMatrices != 0UL);
    }
    BRE_ASSERT(totalNumMatrices == numResources);
#endif
    mGeometryDataVec.reserve(geometryDataCount);
    for (std::uint32_t i = 0U; i < geometryDataCount; ++i) {
        mGeometryDataVec.push_back(geometryDataVector[i]);
    }

    InitCBuffersAndViews(baseColorTextures,
                         metalnessTextures,
                         roughnessTextures);

    BRE_ASSERT(IsDataValid());
}

std::uint32_t
TextureMappingCommandListRecorder::RecordAndPushCommandLists(const FrameCBuffer& frameCBuffer) noexcept
{
    BRE_ASSERT(IsDataValid());
    BRE_ASSERT(sPSO != nullptr);
    BRE_ASSERT(sRootSignature != nullptr);
    BRE_ASSERT(mGeometryBufferRenderTargetViews != nullptr);
    BRE_ASSERT(mGeometryBufferRenderTargetViewCount != 0U);
    BRE_ASSERT(mDepthBufferView.ptr != 0U);

    // Update frame constants
    UploadBuffer& uploadFrameCBuffer(mFrameUploadCBufferPerFrame.GetNextFrameCBuffer());
    uploadFrameCBuffer.CopyData(0U, &frameCBuffer, sizeof(frameCBuffer));

    ID3D12GraphicsCommandList& commandList = mCommandListPerFrame.ResetCommandListWithNextCommandAllocator(sPSO);

    commandList.RSSetViewports(1U, &ApplicationSettings::sScreenViewport);
    commandList.RSSetScissorRects(1U, &ApplicationSettings::sScissorRect);
    commandList.OMSetRenderTargets(mGeometryBufferRenderTargetViewCount,
                                   mGeometryBufferRenderTargetViews,
                                   false,
                                   &mDepthBufferView);

    ID3D12DescriptorHeap* heaps[] = { &CbvSrvUavDescriptorManager::GetDescriptorHeap() };
    commandList.SetDescriptorHeaps(_countof(heaps), heaps);
    commandList.SetGraphicsRootSignature(sRootSignature);

    const std::size_t descHandleIncSize{ DirectXManager::GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV) };
    D3D12_GPU_DESCRIPTOR_HANDLE objectCBufferView(mObjectCBufferViewsBegin);
    D3D12_GPU_DESCRIPTOR_HANDLE baseColorTextureRenderTargetView(mBaseColorTextureRenderTargetViewsBegin);
    D3D12_GPU_DESCRIPTOR_HANDLE metalnessTextureRenderTargetView(mMetalnessTextureRenderTargetViewsBegin);
    D3D12_GPU_DESCRIPTOR_HANDLE roughnessTextureRenderTargetView(mRoughnessTextureRenderTargetViewsBegin);

    commandList.IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

    // Set frame constants root parameters
    D3D12_GPU_VIRTUAL_ADDRESS frameCBufferGpuVAddress(uploadFrameCBuffer.GetResource().GetGPUVirtualAddress());
    commandList.SetGraphicsRootConstantBufferView(1U, frameCBufferGpuVAddress);
    commandList.SetGraphicsRootConstantBufferView(2U, frameCBufferGpuVAddress);

    // Draw objects
    const std::size_t geomCount{ mGeometryDataVec.size() };
    for (std::size_t i = 0UL; i < geomCount; ++i) {
        GeometryData& geomData{ mGeometryDataVec[i] };
        commandList.IASetVertexBuffers(0U, 1U, &geomData.mVertexBufferData.mBufferView);
        commandList.IASetIndexBuffer(&geomData.mIndexBufferData.mBufferView);
        const std::size_t worldMatsCount{ geomData.mWorldMatrices.size() };
        for (std::size_t j = 0UL; j < worldMatsCount; ++j) {
            commandList.SetGraphicsRootDescriptorTable(0U, objectCBufferView);
            objectCBufferView.ptr += descHandleIncSize;

            commandList.SetGraphicsRootDescriptorTable(3U, baseColorTextureRenderTargetView);
            baseColorTextureRenderTargetView.ptr += descHandleIncSize;

            commandList.SetGraphicsRootDescriptorTable(4U, metalnessTextureRenderTargetView);
            metalnessTextureRenderTargetView.ptr += descHandleIncSize;

            commandList.SetGraphicsRootDescriptorTable(5U, roughnessTextureRenderTargetView);
            roughnessTextureRenderTargetView.ptr += descHandleIncSize;

            commandList.DrawIndexedInstanced(geomData.mIndexBufferData.mElementCount, 1U, 0U, 0U, 0U);
        }
    }

    commandList.Close();
    CommandListExecutor::Get().PushCommandList(commandList);

    return 1U;
}

bool
TextureMappingCommandListRecorder::IsDataValid() const noexcept
{
    const std::size_t geometryDataCount{ mGeometryDataVec.size() };
    for (std::size_t i = 0UL; i < geometryDataCount; ++i) {
        const std::size_t numMatrices{ mGeometryDataVec[i].mWorldMatrices.size() };
        if (numMatrices == 0UL) {
            return false;
        }
    }

    const bool result =
        GeometryCommandListRecorder::IsDataValid() &&
        mBaseColorTextureRenderTargetViewsBegin.ptr != 0UL &&
        mMetalnessTextureRenderTargetViewsBegin.ptr != 0UL &&
        mRoughnessTextureRenderTargetViewsBegin.ptr != 0UL;

    return result;
}

void
TextureMappingCommandListRecorder::InitCBuffersAndViews(const std::vector<ID3D12Resource*>& baseColorTextures,
                                                        const std::vector<ID3D12Resource*>& metalnessTextures,
                                                        const std::vector<ID3D12Resource*>& roughnessTextures) noexcept
{
    BRE_ASSERT(baseColorTextures.empty() == false);
    BRE_ASSERT(baseColorTextures.size() == metalnessTextures.size());
    BRE_ASSERT(metalnessTextures.size() == roughnessTextures.size());
    BRE_ASSERT(mObjectUploadCBuffers == nullptr);

    const std::uint32_t numResources = static_cast<std::uint32_t>(baseColorTextures.size());

    // Create object cbuffer and fill it
    const std::size_t objCBufferElemSize{ UploadBuffer::GetRoundedConstantBufferSizeInBytes(sizeof(ObjectCBuffer)) };
    mObjectUploadCBuffers = &UploadBufferManager::CreateUploadBuffer(objCBufferElemSize, numResources);
    std::uint32_t k = 0U;
    const std::size_t geometryDataCount{ mGeometryDataVec.size() };
    ObjectCBuffer objCBuffer;
    for (std::size_t i = 0UL; i < geometryDataCount; ++i) {
        GeometryData& geomData{ mGeometryDataVec[i] };
        const std::uint32_t worldMatsCount{ static_cast<std::uint32_t>(geomData.mWorldMatrices.size()) };
        for (std::uint32_t j = 0UL; j < worldMatsCount; ++j) {             MathUtils::StoreTransposeMatrix(geomData.mWorldMatrices[j],                                             objCBuffer.mWorldMatrix);             MathUtils::StoreTransposeMatrix(geomData.mInverseTransposeWorldMatrices[j],                                             objCBuffer.mInverseTransposeWorldMatrix);             objCBuffer.mTextureScale = geomData.mTextureScales[j];             mObjectUploadCBuffers->CopyData(k + j,
                                            &objCBuffer,
                                            sizeof(objCBuffer));
        }

        k += worldMatsCount;
    }

    D3D12_GPU_VIRTUAL_ADDRESS objCBufferGpuAddress{ mObjectUploadCBuffers->GetResource().GetGPUVirtualAddress() };

    // Create object cbuffer descriptor descriptors
    // Create textures SRV descriptors
    std::vector<D3D12_CONSTANT_BUFFER_VIEW_DESC> objectCbufferViewDescVec;
    objectCbufferViewDescVec.reserve(numResources);

    std::vector<ID3D12Resource*> resVec;
    resVec.reserve(numResources);
    std::vector<D3D12_SHADER_RESOURCE_VIEW_DESC> srvDescVec;
    srvDescVec.reserve(numResources);

    std::vector<ID3D12Resource*> metalnessResVec;
    metalnessResVec.reserve(numResources);
    std::vector<D3D12_SHADER_RESOURCE_VIEW_DESC> metalnessSrvDescVec;
    metalnessSrvDescVec.reserve(numResources);

    std::vector<ID3D12Resource*> roughnessResVec;
    roughnessResVec.reserve(numResources);
    std::vector<D3D12_SHADER_RESOURCE_VIEW_DESC> roughnessSrvDescVec;
    roughnessSrvDescVec.reserve(numResources);
    for (std::size_t i = 0UL; i < numResources; ++i) {
        // Object cbuffer desc
        D3D12_CONSTANT_BUFFER_VIEW_DESC cBufferDesc{};
        cBufferDesc.BufferLocation = objCBufferGpuAddress + i * objCBufferElemSize;
        cBufferDesc.SizeInBytes = static_cast<std::uint32_t>(objCBufferElemSize);
        objectCbufferViewDescVec.push_back(cBufferDesc);

        // Texture descriptor
        resVec.push_back(baseColorTextures[i]);

        D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc{};
        srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
        srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
        srvDesc.Texture2D.MostDetailedMip = 0;
        srvDesc.Texture2D.ResourceMinLODClamp = 0.0f;
        srvDesc.Format = resVec.back()->GetDesc().Format;
        srvDesc.Texture2D.MipLevels = resVec.back()->GetDesc().MipLevels;
        srvDescVec.push_back(srvDesc);

        // Metalness descriptor
        metalnessResVec.push_back(metalnessTextures[i]);

        srvDesc = D3D12_SHADER_RESOURCE_VIEW_DESC{};
        srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
        srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
        srvDesc.Texture2D.MostDetailedMip = 0;
        srvDesc.Texture2D.ResourceMinLODClamp = 0.0f;
        srvDesc.Format = metalnessResVec.back()->GetDesc().Format;
        srvDesc.Texture2D.MipLevels = metalnessResVec.back()->GetDesc().MipLevels;
        metalnessSrvDescVec.push_back(srvDesc);

        // Roughness descriptor
        roughnessResVec.push_back(roughnessTextures[i]);

        srvDesc = D3D12_SHADER_RESOURCE_VIEW_DESC{};
        srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
        srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
        srvDesc.Texture2D.MostDetailedMip = 0;
        srvDesc.Texture2D.ResourceMinLODClamp = 0.0f;
        srvDesc.Format = roughnessResVec.back()->GetDesc().Format;
        srvDesc.Texture2D.MipLevels = roughnessResVec.back()->GetDesc().MipLevels;
        roughnessSrvDescVec.push_back(srvDesc);
    }

    mObjectCBufferViewsBegin =
        CbvSrvUavDescriptorManager::CreateConstantBufferViews(objectCbufferViewDescVec.data(),
                                                              static_cast<std::uint32_t>(objectCbufferViewDescVec.size()));

    mBaseColorTextureRenderTargetViewsBegin =
        CbvSrvUavDescriptorManager::CreateShaderResourceViews(resVec.data(),
                                                              srvDescVec.data(),
                                                              static_cast<std::uint32_t>(srvDescVec.size()));

    mMetalnessTextureRenderTargetViewsBegin =
        CbvSrvUavDescriptorManager::CreateShaderResourceViews(metalnessResVec.data(),
                                                              metalnessSrvDescVec.data(),
                                                              static_cast<std::uint32_t>(metalnessSrvDescVec.size()));

    mRoughnessTextureRenderTargetViewsBegin =
        CbvSrvUavDescriptorManager::CreateShaderResourceViews(roughnessResVec.data(),
                                                              roughnessSrvDescVec.data(),
                                                              static_cast<std::uint32_t>(roughnessSrvDescVec.size()));

}
}

VertexShader

#include <ShaderUtils/CBuffers.hlsli>

#include "RS.hlsl"

struct Input {
    float3 mPositionObjectSpace : POSITION;
    float3 mNormalObjectSpace : NORMAL;
    float3 mTangentObjectSpace : TANGENT;
    float2 mUV : TEXCOORD;
};

ConstantBuffer<ObjectCBuffer> gObjCBuffer : register(b0);
ConstantBuffer<FrameCBuffer> gFrameCBuffer : register(b1);

struct Output {
    float4 mPositionClipSpace : SV_POSITION;
    float3 mPositionWorldSpace : POS_WORLD;
    float3 mPositionViewSpace : POS_VIEW;
    float3 mNormalWorldSpace : NORMAL_WORLD;
    float3 mNormalViewSpace : NORMAL_VIEW;
    float2 mUV : TEXCOORD;
};

[RootSignature(RS)]
Output main(in const Input input)
{
    Output output;
    output.mPositionWorldSpace = mul(float4(input.mPositionObjectSpace, 1.0f),
                                     gObjCBuffer.mWorldMatrix).xyz;
    output.mPositionViewSpace = mul(float4(output.mPositionWorldSpace, 1.0f),
                                    gFrameCBuffer.mViewMatrix).xyz;

    output.mNormalWorldSpace = mul(float4(input.mNormalObjectSpace, 0.0f),
                                   gObjCBuffer.mInverseTransposeWorldMatrix).xyz;
    output.mNormalViewSpace = mul(float4(output.mNormalWorldSpace, 0.0f),
                                  gFrameCBuffer.mViewMatrix).xyz;

    output.mPositionClipSpace = mul(float4(output.mPositionViewSpace, 1.0f),
                                    gFrameCBuffer.mProjectionMatrix);

    output.mUV = gObjCBuffer.mTextureScale * input.mUV;

    return output;
}

PixelShader

#include <ShaderUtils/CBuffers.hlsli>
#include <ShaderUtils/Utils.hlsli>

#include "RS.hlsl"

struct Input {
    float4 mPositionClipSpace : SV_POSITION;
    float3 mPositionWorldSpace : POS_WORLD;
    float3 mPositionViewSpace : POS_VIEW;
    float3 mNormalWorldSpace : NORMAL_WORLD;
    float3 mNormalViewSpace : NORMAL_VIEW;
    float2 mUV : TEXCOORD;
};

ConstantBuffer<FrameCBuffer> gFrameCBuffer : register(b0);

SamplerState TextureSampler : register (s0);
Texture2D BaseColorTexture : register (t0);
Texture2D MetalnessTexture : register (t1);
Texture2D RoughnessTexture : register (t2);

struct Output {
    float4 mNormal_Roughness : SV_Target0;
    float4 mBaseColor_Metalness : SV_Target1;
};

[RootSignature(RS)]
Output main(const in Input input)
{
    Output output = (Output)0;

    // Normal (encoded in view space)
    const float3 normalViewSpace = normalize(input.mNormalViewSpace);
    output.mNormal_Roughness.xy = Encode(normalViewSpace);

    // Base color and metalness
    const float3 baseColor = BaseColorTexture.Sample(TextureSampler,
                                                     input.mUV).rgb;

    const float metalness = MetalnessTexture.Sample(TextureSampler,
                                                    input.mUV).r;
    output.mBaseColor_Metalness = float4(baseColor,
                                         metalness);

    // Roughness
    output.mNormal_Roughness.z = RoughnessTexture.Sample(TextureSampler,
                                                         input.mUV).r;

    return output;
}

You can see a screenshot from BRE in the following image

texture_mapping

How do we implement normal mapping?

In BRE, we have a type of command list recorder named NormalMappingCommandListRecorder that inherits from GeometryCommandListRecorder and generates a command list that implements normal mapping and pushes it to the CommandListExecutor to be executed. Its inputs are the geometry buffers, a list of GeometryData, and a list of diffuse textures, and normal textures per world matrix per GeometryData. It writes to the geometry buffers and the depth buffer. Its implementation and shaders are the following

NormalMappingCommandListRecorder.h

#pragma once

#include <GeometryPass/GeometryCommandListRecorder.h>

namespace BRE {
///
/// @brief Responsible to record command lists that implement normal mapping
///
class NormalMappingCommandListRecorder : public GeometryCommandListRecorder {
public:
    NormalMappingCommandListRecorder() = default;
    ~NormalMappingCommandListRecorder() = default;
    NormalMappingCommandListRecorder(const NormalMappingCommandListRecorder&) = delete;
    const NormalMappingCommandListRecorder& operator=(const NormalMappingCommandListRecorder&) = delete;
    NormalMappingCommandListRecorder(NormalMappingCommandListRecorder&&) = default;
    NormalMappingCommandListRecorder& operator=(NormalMappingCommandListRecorder&&) = default;

    ///
    /// @brief Initializes pipeline state object and root signature
    /// @param geometryBufferFormats List of geometry buffers formats. It must be not nullptr
    /// @param geometryBufferCount Number of geometry buffers formats in @p geometryBufferFormats
    ///
    static void InitSharedPSOAndRootSignature(const DXGI_FORMAT* geometryBufferFormats,
                                              const std::uint32_t geometryBufferCount) noexcept;

    ///
    /// @brief Initializes the recorder
    ///
    /// InitSharedPSOAndRootSignature() must be called first
    ///
    /// @param geometryDataVector List of geometry data. Must not be empty
    /// @param baseColorTextures List of base color textures. Must not be empty.
    /// @param metalnessTextures List of metalness textures. Must not be empty.
    /// @param roughnessTextures List of rougness textures. Must not be empty.
    /// @param normalTextures List of normal textures. Must not be empty.
    ///
    void Init(const std::vector<GeometryData>& geometryDataVector,
              const std::vector<ID3D12Resource*>& baseColorTextures,
              const std::vector<ID3D12Resource*>& metalnessTextures,
              const std::vector<ID3D12Resource*>& roughnessTextures,
              const std::vector<ID3D12Resource*>& normalTextures) noexcept;

    ///
    /// @brief Records and push command lists to CommandListExecutor
    ///
    /// Init() must be called first
    ///
    /// @param frameCBuffer Constant buffer per frame, for current frame
    /// @return The number of pushed command lists
    ///
    std::uint32_t RecordAndPushCommandLists(const FrameCBuffer& frameCBuffer) noexcept final override;

    ///
    /// @brief Checks if internal data is valid. Typically, used for assertions
    /// @return True if valid. Otherwise, false
    ///
    bool IsDataValid() const noexcept final override;

private:
    ///
    /// @brief Initializes the constant buffers and views
    /// @param baseColorTextures List of base color textures. Must not be empty.
    /// @param metalnessTextures List of metalness textures. Must not be empty.
    /// @param roughnessTextures List of rougness textures. Must not be empty.
    /// @param normalTextures List of normal textures. Must not be empty.
    ///
    void InitCBuffersAndViews(const std::vector<ID3D12Resource*>& baseColorTextures,
                              const std::vector<ID3D12Resource*>& metalnessTextures,
                              const std::vector<ID3D12Resource*>& roughnessTextures,
                              const std::vector<ID3D12Resource*>& normalTextures) noexcept;

    // First descriptor in the list. All the others are contiguous
    D3D12_GPU_DESCRIPTOR_HANDLE mBaseColorTextureRenderTargetViewsBegin{ 0U };

    // First descriptor in the list. All the others are contiguous
    D3D12_GPU_DESCRIPTOR_HANDLE mMetalnessTextureRenderTargetViewsBegin{ 0U };

    // First descriptor in the list. All the others are contiguous
    D3D12_GPU_DESCRIPTOR_HANDLE mRoughnessTextureRenderTargetViewsBegin{ 0U };

    // First descriptor in the list. All the others are contiguous
    D3D12_GPU_DESCRIPTOR_HANDLE mNormalTextureRenderTargetViewsBegin{ 0U };
};
}

NormalMappingCommandListRecorder.cpp

#include "NormalMappingCommandListRecorder.h"

#include <DirectXMath.h>

#include <CommandListExecutor\CommandListExecutor.h>
#include <DescriptorManager\CbvSrvUavDescriptorManager.h>
#include <DirectXManager\DirectXManager.h>
#include <MathUtils/MathUtils.h>
#include <PSOManager/PSOManager.h>
#include <ResourceManager/UploadBufferManager.h>
#include <RootSignatureManager\RootSignatureManager.h>
#include <ShaderManager\ShaderManager.h>
#include <ShaderUtils\CBuffers.h>
#include <Utils/DebugUtils.h>

namespace BRE {
// Root Signature:
// "DescriptorTable(CBV(b0), visibility = SHADER_VISIBILITY_VERTEX), " \ 0 -> Object CBuffers
// "CBV(b1, visibility = SHADER_VISIBILITY_VERTEX), " \ 1 -> Frame CBuffers
// "CBV(b0, visibility = SHADER_VISIBILITY_PIXEL), " \ 2 -> Frame CBuffer
// "DescriptorTable(SRV(t0), visibility = SHADER_VISIBILITY_PIXEL), " \ 3 -> Base Color Texture
// "DescriptorTable(SRV(t1), visibility = SHADER_VISIBILITY_PIXEL), " \ 4 -> Metalness Texture
// "DescriptorTable(SRV(t2), visibility = SHADER_VISIBILITY_PIXEL), " \ 5 -> Roughness Texture
// "DescriptorTable(SRV(t3), visibility = SHADER_VISIBILITY_PIXEL), " \ 6 -> Normal Texture

namespace {
ID3D12PipelineState* sPSO{ nullptr };
ID3D12RootSignature* sRootSignature{ nullptr };
}

void
NormalMappingCommandListRecorder::InitSharedPSOAndRootSignature(const DXGI_FORMAT* geometryBufferFormats,
                                                                const std::uint32_t geometryBufferCount) noexcept
{
    BRE_ASSERT(geometryBufferFormats != nullptr);
    BRE_ASSERT(geometryBufferCount > 0U);
    BRE_ASSERT(sPSO == nullptr);
    BRE_ASSERT(sRootSignature == nullptr);

    // Build pso and root signature
    PSOManager::PSOCreationData psoData{};
    psoData.mInputLayoutDescriptors = D3DFactory::GetPositionNormalTangentTexCoordInputLayout();
    psoData.mPixelShaderBytecode = ShaderManager::LoadShaderFileAndGetBytecode("GeometryPass/Shaders/NormalMapping/PS.cso");
    psoData.mVertexShaderBytecode = ShaderManager::LoadShaderFileAndGetBytecode("GeometryPass/Shaders/NormalMapping/VS.cso");

    ID3DBlob* rootSignatureBlob = &ShaderManager::LoadShaderFileAndGetBlob("GeometryPass/Shaders/NormalMapping/RS.cso");
    psoData.mRootSignature = &RootSignatureManager::CreateRootSignatureFromBlob(*rootSignatureBlob);
    sRootSignature = psoData.mRootSignature;

    psoData.mNumRenderTargets = geometryBufferCount;
    memcpy(psoData.mRenderTargetFormats, geometryBufferFormats, sizeof(DXGI_FORMAT) * psoData.mNumRenderTargets);
    sPSO = &PSOManager::CreateGraphicsPSO(psoData);

    BRE_ASSERT(sPSO != nullptr);
    BRE_ASSERT(sRootSignature != nullptr);
}

void
NormalMappingCommandListRecorder::Init(const std::vector<GeometryData>& geometryDataVector,
                                       const std::vector<ID3D12Resource*>& baseColorTextures,
                                       const std::vector<ID3D12Resource*>& metalnessTextures,
                                       const std::vector<ID3D12Resource*>& roughnessTextures,
                                       const std::vector<ID3D12Resource*>& normalTextures) noexcept
{
    BRE_ASSERT(IsDataValid() == false);
    BRE_ASSERT(geometryDataVector.empty() == false);
    BRE_ASSERT(baseColorTextures.empty() == false);
    BRE_ASSERT(baseColorTextures.size() == metalnessTextures.size());
    BRE_ASSERT(metalnessTextures.size() == roughnessTextures.size());
    BRE_ASSERT(roughnessTextures.size() == normalTextures.size());

    const std::size_t numResources = baseColorTextures.size();
    const std::size_t geometryDataCount = geometryDataVector.size();

    // Check that the total number of matrices (geometry to be drawn) will be equal to available materials
#ifdef _DEBUG
    std::size_t totalNumMatrices{ 0UL };
    for (std::size_t i = 0UL; i < geometryDataCount; ++i) {
        const std::size_t numMatrices{ geometryDataVector[i].mWorldMatrices.size() };
        totalNumMatrices += numMatrices;
        BRE_ASSERT(numMatrices != 0UL);
    }
    BRE_ASSERT(totalNumMatrices == numResources);
#endif
    mGeometryDataVec.reserve(geometryDataCount);
    for (std::uint32_t i = 0U; i < geometryDataCount; ++i) {
        mGeometryDataVec.push_back(geometryDataVector[i]);
    }

    InitCBuffersAndViews(baseColorTextures,
                         metalnessTextures,
                         roughnessTextures,
                         normalTextures);

    BRE_ASSERT(IsDataValid());
}

std::uint32_t
NormalMappingCommandListRecorder::RecordAndPushCommandLists(const FrameCBuffer& frameCBuffer) noexcept
{
    BRE_ASSERT(IsDataValid());
    BRE_ASSERT(sPSO != nullptr);
    BRE_ASSERT(sRootSignature != nullptr);
    BRE_ASSERT(mGeometryBufferRenderTargetViews != nullptr);
    BRE_ASSERT(mGeometryBufferRenderTargetViewCount != 0U);
    BRE_ASSERT(mDepthBufferView.ptr != 0U);

    // Update frame constants
    UploadBuffer& uploadFrameCBuffer(mFrameUploadCBufferPerFrame.GetNextFrameCBuffer());
    uploadFrameCBuffer.CopyData(0U, &frameCBuffer, sizeof(frameCBuffer));

    ID3D12GraphicsCommandList& commandList = mCommandListPerFrame.ResetCommandListWithNextCommandAllocator(sPSO);

    commandList.RSSetViewports(1U, &ApplicationSettings::sScreenViewport);
    commandList.RSSetScissorRects(1U, &ApplicationSettings::sScissorRect);
    commandList.OMSetRenderTargets(mGeometryBufferRenderTargetViewCount,
                                   mGeometryBufferRenderTargetViews,
                                   false,
                                   &mDepthBufferView);

    ID3D12DescriptorHeap* heaps[] = { &CbvSrvUavDescriptorManager::GetDescriptorHeap() };
    commandList.SetDescriptorHeaps(_countof(heaps), heaps);
    commandList.SetGraphicsRootSignature(sRootSignature);

    const std::size_t descHandleIncSize{ DirectXManager::GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV) };
    D3D12_GPU_DESCRIPTOR_HANDLE objectCBufferView(mObjectCBufferViewsBegin);
    D3D12_GPU_DESCRIPTOR_HANDLE baseColorTextureRenderTargetView(mBaseColorTextureRenderTargetViewsBegin);
    D3D12_GPU_DESCRIPTOR_HANDLE metalnessTextureRenderTargetView(mMetalnessTextureRenderTargetViewsBegin);
    D3D12_GPU_DESCRIPTOR_HANDLE roughnessTextureRenderTargetView(mRoughnessTextureRenderTargetViewsBegin);
    D3D12_GPU_DESCRIPTOR_HANDLE normalTextureRenderTargetView(mNormalTextureRenderTargetViewsBegin);

    commandList.IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

    // Set frame constants root parameters
    D3D12_GPU_VIRTUAL_ADDRESS frameCBufferGpuVAddress(uploadFrameCBuffer.GetResource().GetGPUVirtualAddress());
    commandList.SetGraphicsRootConstantBufferView(1U, frameCBufferGpuVAddress);
    commandList.SetGraphicsRootConstantBufferView(2U, frameCBufferGpuVAddress);

    // Draw objects
    const std::size_t geomCount{ mGeometryDataVec.size() };
    for (std::size_t i = 0UL; i < geomCount; ++i) {
        GeometryData& geomData{ mGeometryDataVec[i] };
        commandList.IASetVertexBuffers(0U, 1U, &geomData.mVertexBufferData.mBufferView);
        commandList.IASetIndexBuffer(&geomData.mIndexBufferData.mBufferView);
        const std::size_t worldMatsCount{ geomData.mWorldMatrices.size() };
        for (std::size_t j = 0UL; j < worldMatsCount; ++j) {
            commandList.SetGraphicsRootDescriptorTable(0U, objectCBufferView);
            objectCBufferView.ptr += descHandleIncSize;

            commandList.SetGraphicsRootDescriptorTable(3U, baseColorTextureRenderTargetView);
            baseColorTextureRenderTargetView.ptr += descHandleIncSize;

            commandList.SetGraphicsRootDescriptorTable(4U, metalnessTextureRenderTargetView);
            metalnessTextureRenderTargetView.ptr += descHandleIncSize;

            commandList.SetGraphicsRootDescriptorTable(5U, roughnessTextureRenderTargetView);
            roughnessTextureRenderTargetView.ptr += descHandleIncSize;

            commandList.SetGraphicsRootDescriptorTable(6U, normalTextureRenderTargetView);
            normalTextureRenderTargetView.ptr += descHandleIncSize;

            commandList.DrawIndexedInstanced(geomData.mIndexBufferData.mElementCount, 1U, 0U, 0U, 0U);
        }
    }

    commandList.Close();
    CommandListExecutor::Get().PushCommandList(commandList);

    return 1U;
}

bool
NormalMappingCommandListRecorder::IsDataValid() const noexcept
{
    const bool result =
        GeometryCommandListRecorder::IsDataValid() &&
        mBaseColorTextureRenderTargetViewsBegin.ptr != 0UL &&
        mMetalnessTextureRenderTargetViewsBegin.ptr != 0UL &&
        mRoughnessTextureRenderTargetViewsBegin.ptr != 0UL &&
        mNormalTextureRenderTargetViewsBegin.ptr != 0UL;

    return result;
}

void
NormalMappingCommandListRecorder::InitCBuffersAndViews(const std::vector<ID3D12Resource*>& baseColorTextures,
                                                       const std::vector<ID3D12Resource*>& metalnessTextures,
                                                       const std::vector<ID3D12Resource*>& roughnessTextures,
                                                       const std::vector<ID3D12Resource*>& normalTextures) noexcept
{
    BRE_ASSERT(baseColorTextures.empty() == false);
    BRE_ASSERT(baseColorTextures.size() == metalnessTextures.size());
    BRE_ASSERT(metalnessTextures.size() == roughnessTextures.size());
    BRE_ASSERT(roughnessTextures.size() == normalTextures.size());
    BRE_ASSERT(mObjectUploadCBuffers == nullptr);

    const std::uint32_t numResources = static_cast<std::uint32_t>(baseColorTextures.size());

    // Create object cbuffer and fill it
    const std::size_t objCBufferElemSize{ UploadBuffer::GetRoundedConstantBufferSizeInBytes(sizeof(ObjectCBuffer)) };
    mObjectUploadCBuffers = &UploadBufferManager::CreateUploadBuffer(objCBufferElemSize, numResources);
    std::uint32_t k = 0U;
    const std::size_t geometryDataCount{ mGeometryDataVec.size() };
    ObjectCBuffer objCBuffer;
    for (std::size_t i = 0UL; i < geometryDataCount; ++i) {
        GeometryData& geomData{ mGeometryDataVec[i] };
        const std::uint32_t worldMatsCount{ static_cast<std::uint32_t>(geomData.mWorldMatrices.size()) };
        for (std::uint32_t j = 0UL; j < worldMatsCount; ++j) {             MathUtils::StoreTransposeMatrix(geomData.mWorldMatrices[j],                                             objCBuffer.mWorldMatrix);             MathUtils::StoreTransposeMatrix(geomData.mInverseTransposeWorldMatrices[j],                                             objCBuffer.mInverseTransposeWorldMatrix);             objCBuffer.mTextureScale = geomData.mTextureScales[j];             mObjectUploadCBuffers->CopyData(k + j, &objCBuffer, sizeof(objCBuffer));
        }

        k += worldMatsCount;
    }

    D3D12_GPU_VIRTUAL_ADDRESS objCBufferGpuAddress{ mObjectUploadCBuffers->GetResource().GetGPUVirtualAddress() };

    // Create object / materials cbuffers descriptors
    // Create textures SRV descriptors
    std::vector<D3D12_CONSTANT_BUFFER_VIEW_DESC> objectCbufferViewDescVec;
    objectCbufferViewDescVec.reserve(numResources);

    std::vector<D3D12_CONSTANT_BUFFER_VIEW_DESC> materialCbufferViewDescVec;
    materialCbufferViewDescVec.reserve(numResources);

    std::vector<ID3D12Resource*> textureResVec;
    textureResVec.reserve(numResources);
    std::vector<D3D12_SHADER_RESOURCE_VIEW_DESC> textureSrvDescVec;
    textureSrvDescVec.reserve(numResources);

    std::vector<ID3D12Resource*> metalnessResVec;
    metalnessResVec.reserve(numResources);
    std::vector<D3D12_SHADER_RESOURCE_VIEW_DESC> metalnessSrvDescVec;
    metalnessSrvDescVec.reserve(numResources);

    std::vector<ID3D12Resource*> roughnessResVec;
    roughnessResVec.reserve(numResources);
    std::vector<D3D12_SHADER_RESOURCE_VIEW_DESC> roughnessSrvDescVec;
    roughnessSrvDescVec.reserve(numResources);

    std::vector<ID3D12Resource*> normalResVec;
    normalResVec.reserve(numResources);
    std::vector<D3D12_SHADER_RESOURCE_VIEW_DESC> normalSrvDescVec;
    normalSrvDescVec.reserve(numResources);
    for (std::size_t i = 0UL; i < numResources; ++i) {
        // Object cbuffer desc
        D3D12_CONSTANT_BUFFER_VIEW_DESC cBufferDesc{};
        cBufferDesc.BufferLocation = objCBufferGpuAddress + i * objCBufferElemSize;
        cBufferDesc.SizeInBytes = static_cast<std::uint32_t>(objCBufferElemSize);
        objectCbufferViewDescVec.push_back(cBufferDesc);

        // Texture descriptor
        textureResVec.push_back(baseColorTextures[i]);

        D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc{};
        srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
        srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
        srvDesc.Texture2D.MostDetailedMip = 0;
        srvDesc.Texture2D.ResourceMinLODClamp = 0.0f;
        srvDesc.Format = textureResVec.back()->GetDesc().Format;
        srvDesc.Texture2D.MipLevels = textureResVec.back()->GetDesc().MipLevels;
        textureSrvDescVec.push_back(srvDesc);

        // Metalness descriptor
        metalnessResVec.push_back(metalnessTextures[i]);

        srvDesc = D3D12_SHADER_RESOURCE_VIEW_DESC{};
        srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
        srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
        srvDesc.Texture2D.MostDetailedMip = 0;
        srvDesc.Texture2D.ResourceMinLODClamp = 0.0f;
        srvDesc.Format = metalnessResVec.back()->GetDesc().Format;
        srvDesc.Texture2D.MipLevels = metalnessResVec.back()->GetDesc().MipLevels;
        metalnessSrvDescVec.push_back(srvDesc);

        // Roughness descriptor
        roughnessResVec.push_back(roughnessTextures[i]);

        srvDesc = D3D12_SHADER_RESOURCE_VIEW_DESC{};
        srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
        srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
        srvDesc.Texture2D.MostDetailedMip = 0;
        srvDesc.Texture2D.ResourceMinLODClamp = 0.0f;
        srvDesc.Format = roughnessResVec.back()->GetDesc().Format;
        srvDesc.Texture2D.MipLevels = roughnessResVec.back()->GetDesc().MipLevels;
        roughnessSrvDescVec.push_back(srvDesc);

        // Normal descriptor
        normalResVec.push_back(normalTextures[i]);

        srvDesc = D3D12_SHADER_RESOURCE_VIEW_DESC{};
        srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
        srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
        srvDesc.Texture2D.MostDetailedMip = 0;
        srvDesc.Texture2D.ResourceMinLODClamp = 0.0f;
        srvDesc.Format = normalResVec.back()->GetDesc().Format;
        srvDesc.Texture2D.MipLevels = normalResVec.back()->GetDesc().MipLevels;
        normalSrvDescVec.push_back(srvDesc);
    }

    mObjectCBufferViewsBegin =
        CbvSrvUavDescriptorManager::CreateConstantBufferViews(objectCbufferViewDescVec.data(),
                                                              static_cast<std::uint32_t>(objectCbufferViewDescVec.size()));

    mBaseColorTextureRenderTargetViewsBegin =
        CbvSrvUavDescriptorManager::CreateShaderResourceViews(textureResVec.data(),
                                                              textureSrvDescVec.data(),
                                                              static_cast<std::uint32_t>(textureSrvDescVec.size()));

    mMetalnessTextureRenderTargetViewsBegin =
        CbvSrvUavDescriptorManager::CreateShaderResourceViews(metalnessResVec.data(),
                                                              metalnessSrvDescVec.data(),
                                                              static_cast<std::uint32_t>(metalnessSrvDescVec.size()));

    mRoughnessTextureRenderTargetViewsBegin =
        CbvSrvUavDescriptorManager::CreateShaderResourceViews(roughnessResVec.data(),
                                                              roughnessSrvDescVec.data(),
                                                              static_cast<std::uint32_t>(roughnessSrvDescVec.size()));

    mNormalTextureRenderTargetViewsBegin =
        CbvSrvUavDescriptorManager::CreateShaderResourceViews(normalResVec.data(),
                                                              normalSrvDescVec.data(),
                                                              static_cast<std::uint32_t>(normalSrvDescVec.size()));
}
}

VertexShader

#include <ShaderUtils/CBuffers.hlsli>

#include "RS.hlsl"

struct Input {
    float3 mPositionObjectSpace : POSITION;
    float3 mNormalObjectSpace : NORMAL;
    float3 mTangentObjectSpace : TANGENT;
    float2 mUV : TEXCOORD;
};

ConstantBuffer<ObjectCBuffer> gObjCBuffer : register(b0);
ConstantBuffer<FrameCBuffer> gFrameCBuffer : register(b1);

struct Output {
    float4 mPositionClipSpace : SV_POSITION;
    float3 mPositionWorldSpace : POS_WORLD;
    float3 mPositionViewSpace : POS_VIEW;
    float3 mNormalWorldSpace : NORMAL_WORLD;
    float3 mNormalViewSpace : NORMAL_VIEW;
    float3 mTangentWorldSpace : TANGENT_WORLD;
    float3 mTangentViewSpace : TANGENT_VIEW;
    float3 mBinormalWorldSpace : BINORMAL_WORLD;
    float3 mBinormalViewSpace : BINORMAL_VIEW;
    float2 mUV : TEXCOORD;
};

[RootSignature(RS)]
Output main(in const Input input)
{
    Output output;
    output.mPositionWorldSpace = mul(float4(input.mPositionObjectSpace, 1.0f),
                                     gObjCBuffer.mWorldMatrix).xyz;
    output.mPositionViewSpace = mul(float4(output.mPositionWorldSpace, 1.0f),
                                    gFrameCBuffer.mViewMatrix).xyz;
    output.mPositionClipSpace = mul(float4(output.mPositionViewSpace, 1.0f),
                                    gFrameCBuffer.mProjectionMatrix);

    output.mUV = gObjCBuffer.mTextureScale * input.mUV;

    output.mNormalWorldSpace = mul(float4(input.mNormalObjectSpace, 0.0f),
                                   gObjCBuffer.mWorldMatrix).xyz;
    output.mNormalViewSpace = mul(float4(output.mNormalWorldSpace, 0.0f),
                                  gFrameCBuffer.mViewMatrix).xyz;

    output.mTangentWorldSpace = mul(float4(input.mTangentObjectSpace, 0.0f),
                                    gObjCBuffer.mWorldMatrix).xyz;
    output.mTangentViewSpace = mul(float4(output.mTangentWorldSpace, 0.0f),
                                   gFrameCBuffer.mViewMatrix).xyz;

    output.mBinormalWorldSpace = normalize(cross(output.mNormalWorldSpace,
                                                 output.mTangentWorldSpace));
    output.mBinormalViewSpace = normalize(cross(output.mNormalViewSpace,
                                                output.mTangentViewSpace));

    return output;
}

PixelShader

#include <ShaderUtils/CBuffers.hlsli>
#include <ShaderUtils/Utils.hlsli>

#include "RS.hlsl"

struct Input {
    float4 mPositionClipSpace : SV_POSITION;
    float3 mPositionWorldSpace : POS_WORLD;
    float3 mPositionViewSpace : POS_VIEW;
    float3 mNormalWorldSpace : NORMAL_WORLD;
    float3 mNormalViewSpace : NORMAL_VIEW;
    float3 mTangentWorldSpace : TANGENT_WORLD;
    float3 mTangentViewSpace : TANGENT_VIEW;
    float3 mBinormalWorldSpace : BINORMAL_WORLD;
    float3 mBinormalViewSpace : BINORMAL_VIEW;
    float2 mUV : TEXCOORD;
};

ConstantBuffer<FrameCBuffer> gFrameCBuffer : register(b0);

SamplerState TextureSampler : register (s0);
Texture2D BaseColorTexture : register (t0);
Texture2D MetalnessTexture : register (t1);
Texture2D RoughnessTexture : register (t2);
Texture2D NormalTexture : register (t3);

struct Output {
    float4 mNormal_Roughness : SV_Target0;
    float4 mBaseColor_Metalness : SV_Target1;
};

[RootSignature(RS)]
Output main(const in Input input)
{
    Output output = (Output)0;

    // Normal (encoded in view space)
    const float3 normalObjectSpace = normalize(NormalTexture.Sample(TextureSampler,
                                                                    input.mUV).xyz * 2.0f - 1.0f);
    const float3x3 tbnWorldSpace = float3x3(normalize(input.mTangentWorldSpace),
                                            normalize(input.mBinormalWorldSpace),
                                            normalize(input.mNormalWorldSpace));
    const float3 normalWorldSpace = normalize(mul(normalObjectSpace, tbnWorldSpace));
    const float3x3 tbnViewSpace = float3x3(normalize(input.mTangentViewSpace),
                                           normalize(input.mBinormalViewSpace),
                                           normalize(input.mNormalViewSpace));
    output.mNormal_Roughness.xy = Encode(normalize(mul(normalObjectSpace,
                                                       tbnViewSpace)));

    // Base color and metalness
    const float3 baseColor = BaseColorTexture.Sample(TextureSampler,
                                                     input.mUV).rgb;
    const float metalness = MetalnessTexture.Sample(TextureSampler,
                                                    input.mUV).r;
    output.mBaseColor_Metalness = float4(baseColor,
                                         metalness);

    // Roughness
    output.mNormal_Roughness.z = RoughnessTexture.Sample(TextureSampler,
                                                         input.mUV).r;

    return output;
}

You can see a screenshot from BRE in the following image

normal_mapping.jpg

How do we implement height mapping?

In BRE, we have a type of command list recorder named HeightMappingCommandListRecorder that inherits from GeometryCommandListRecorder and generates a command list that implements normal mapping and pushes it to the CommandListExecutor to be executed. Its inputs are the geometry buffers, a list of GeometryData, and a list of diffuse textures, normal textures, and height textures per world matrix per GeometryData. It writes to the geometry buffers and the depth buffer. Its implementation and shaders are the following

HeightMappingCommandListRecorder.h

#pragma once

#include <GeometryPass/GeometryCommandListRecorder.h>
#include <ResourceManager\UploadBuffer.h>

namespace BRE {
///
/// @brief Responsible to record command lists that implement height mapping
///
class HeightMappingCommandListRecorder : public GeometryCommandListRecorder {
public:
    HeightMappingCommandListRecorder() = default;
    ~HeightMappingCommandListRecorder() = default;
    HeightMappingCommandListRecorder(const HeightMappingCommandListRecorder&) = delete;
    const HeightMappingCommandListRecorder& operator=(const HeightMappingCommandListRecorder&) = delete;
    HeightMappingCommandListRecorder(HeightMappingCommandListRecorder&&) = default;
    HeightMappingCommandListRecorder& operator=(HeightMappingCommandListRecorder&&) = default;

    ///
    /// @brief Initializes pipeline state object and root signature
    /// @param geometryBufferFormats List of geometry buffers formats. It must be not nullptr
    /// @param geometryBufferCount Number of geometry buffers formats in @p geometryBufferFormats
    ///
    static void InitSharedPSOAndRootSignature(const DXGI_FORMAT* geometryBufferFormats,
                                              const std::uint32_t geometryBufferCount) noexcept;

    ///
    /// @brief Initializes the recorder
    ///
    /// InitSharedPSOAndRootSignature() must be called first
    ///
    /// @param geometryDataVector List of geometry data. Must not be empty
    /// @param baseColorTextures List of base color textures. Must not be empty.
    /// @param metalnessTextures List of metalness textures. Must not be empty.
    /// @param roughnessTextures List of rougness textures. Must not be empty.
    /// @param normalTextures List of normal textures. Must not be empty.
    /// @param heightTextures List of height textures. Must not be empty.
    ///
    void Init(const std::vector<GeometryData>& geometryDataVector,
              const std::vector<ID3D12Resource*>& baseColorTextures,
              const std::vector<ID3D12Resource*>& metalnessTextures,
              const std::vector<ID3D12Resource*>& roughnessTextures,
              const std::vector<ID3D12Resource*>& normalTextures,
              const std::vector<ID3D12Resource*>& heightTextures) noexcept;

    ///
    /// @brief Records and push command lists to CommandListExecutor
    ///
    /// Init() must be called first
    ///
    /// @param frameCBuffer Constant buffer per frame, for current frame
    /// @return The number of pushed command lists
    ///
    std::uint32_t RecordAndPushCommandLists(const FrameCBuffer& frameCBuffer) noexcept final override;

    ///
    /// @brief Checks if internal data is valid. Typically, used for assertions
    /// @return True if valid. Otherwise, false
    ///
    bool IsDataValid() const noexcept final override;

private:
    ///
    /// @brief Initializes the constant buffers and views
    /// @param baseColorTextures List of base color textures. Must not be empty.
    /// @param metalnessTextures List of metalness textures. Must not be empty.
    /// @param roughnessTextures List of rougness textures. Must not be empty.
    /// @param normalTextures List of normal textures. Must not be empty.
    /// @param heightTextures List of height textures. Must not be empty.
    ///
    void InitCBuffersAndViews(const std::vector<ID3D12Resource*>& baseColorTextures,
                              const std::vector<ID3D12Resource*>& metalnessTextures,
                              const std::vector<ID3D12Resource*>& roughnessTextures,
                              const std::vector<ID3D12Resource*>& normalTextures,
                              const std::vector<ID3D12Resource*>& heightTextures) noexcept;

    // First descriptor in the list. All the others are contiguous
    D3D12_GPU_DESCRIPTOR_HANDLE mBaseColorTextureRenderTargetViewsBegin{ 0U };

    // First descriptor in the list. All the others are contiguous
    D3D12_GPU_DESCRIPTOR_HANDLE mMetalnessTextureRenderTargetViewsBegin{ 0U };

    // First descriptor in the list. All the others are contiguous
    D3D12_GPU_DESCRIPTOR_HANDLE mRoughnessTextureRenderTargetViewsBegin{ 0U };

    // First descriptor in the list. All the others are contiguous
    D3D12_GPU_DESCRIPTOR_HANDLE mNormalTextureRenderTargetViewsBegin{ 0U };

    // First descriptor in the list. All the others are contiguous
    D3D12_GPU_DESCRIPTOR_HANDLE mHeightTextureRenderTargetViewsBegin{ 0U };

    UploadBuffer* mHeightMappingUploadCBuffer{ nullptr };
};
}

HeightMappingCommandListRecorder.cpp

#include "HeightMappingCommandListRecorder.h"

#include <DirectXMath.h>

#include <CommandListExecutor\CommandListExecutor.h>
#include <DescriptorManager\CbvSrvUavDescriptorManager.h>
#include <DirectXManager\DirectXManager.h>
#include <GeometryPass\GeometrySettings.h>
#include <GeometryPass\Shaders\HeightMappingCBuffer.h>
#include <MathUtils/MathUtils.h>
#include <PSOManager/PSOManager.h>
#include <ResourceManager/UploadBufferManager.h>
#include <RootSignatureManager\RootSignatureManager.h>
#include <ShaderManager\ShaderManager.h>
#include <ShaderUtils\CBuffers.h>
#include <Utils/DebugUtils.h>

namespace BRE {
// Root signature:
// "DescriptorTable(CBV(b0), visibility = SHADER_VISIBILITY_VERTEX), " \ 0 -> Object CBuffers
// "CBV(b1, visibility = SHADER_VISIBILITY_VERTEX), " \ 1 -> Frame CBuffer
// "CBV(b2, visibility = SHADER_VISIBILITY_VERTEX), " \ 2 -> Height Mapping CBuffer
// "CBV(b0, visibility = SHADER_VISIBILITY_DOMAIN), " \ 3 -> Frame CBuffer
// "CBV(b1, visibility = SHADER_VISIBILITY_DOMAIN), " \ 4 -> Height Mapping CBuffer
// "DescriptorTable(SRV(t0), visibility = SHADER_VISIBILITY_DOMAIN), " \ 5 -> Height Texture
// "CBV(b0, visibility = SHADER_VISIBILITY_PIXEL), " \ 6 -> Frame CBuffer
// "DescriptorTable(SRV(t0), visibility = SHADER_VISIBILITY_PIXEL), " \ 7 -> Base Color Texture
// "DescriptorTable(SRV(t1), visibility = SHADER_VISIBILITY_PIXEL), " \ 8 -> Metalness Texture
// "DescriptorTable(SRV(t2), visibility = SHADER_VISIBILITY_PIXEL), " \ 9 -> Roughness Texture
// "DescriptorTable(SRV(t3), visibility = SHADER_VISIBILITY_PIXEL), " \ 10 -> Normal Texture

namespace {
ID3D12PipelineState* sPSO{ nullptr };
ID3D12RootSignature* sRootSignature{ nullptr };
}

void
HeightMappingCommandListRecorder::InitSharedPSOAndRootSignature(const DXGI_FORMAT* geometryBufferFormats,
                                                                const std::uint32_t geometryBufferCount) noexcept
{
    BRE_ASSERT(geometryBufferFormats != nullptr);
    BRE_ASSERT(geometryBufferCount > 0U);
    BRE_ASSERT(sPSO == nullptr);
    BRE_ASSERT(sRootSignature == nullptr);

    // Build pso and root signature
    PSOManager::PSOCreationData psoData{};
    psoData.mInputLayoutDescriptors = D3DFactory::GetPositionNormalTangentTexCoordInputLayout();

    psoData.mDomainShaderBytecode = ShaderManager::LoadShaderFileAndGetBytecode("GeometryPass/Shaders/HeightMapping/DS.cso");
    psoData.mHullShaderBytecode = ShaderManager::LoadShaderFileAndGetBytecode("GeometryPass/Shaders/HeightMapping/HS.cso");
    psoData.mPixelShaderBytecode = ShaderManager::LoadShaderFileAndGetBytecode("GeometryPass/Shaders/HeightMapping/PS.cso");
    psoData.mVertexShaderBytecode = ShaderManager::LoadShaderFileAndGetBytecode("GeometryPass/Shaders/HeightMapping/VS.cso");

    ID3DBlob* rootSignatureBlob = &ShaderManager::LoadShaderFileAndGetBlob("GeometryPass/Shaders/HeightMapping/RS.cso");
    psoData.mRootSignature = &RootSignatureManager::CreateRootSignatureFromBlob(*rootSignatureBlob);
    sRootSignature = psoData.mRootSignature;

    psoData.mPrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_PATCH;
    psoData.mNumRenderTargets = geometryBufferCount;
    memcpy(psoData.mRenderTargetFormats, geometryBufferFormats, sizeof(DXGI_FORMAT) * psoData.mNumRenderTargets);
    sPSO = &PSOManager::CreateGraphicsPSO(psoData);

    BRE_ASSERT(sPSO != nullptr);
    BRE_ASSERT(sRootSignature != nullptr);
}

void
HeightMappingCommandListRecorder::Init(const std::vector<GeometryData>& geometryDataVector,
                                       const std::vector<ID3D12Resource*>& baseColorTextures,
                                       const std::vector<ID3D12Resource*>& metalnessTextures,
                                       const std::vector<ID3D12Resource*>& roughnessTextures,
                                       const std::vector<ID3D12Resource*>& normalTextures,
                                       const std::vector<ID3D12Resource*>& heightTextures) noexcept
{
    BRE_ASSERT(IsDataValid() == false);
    BRE_ASSERT(geometryDataVector.empty() == false);
    BRE_ASSERT(baseColorTextures.empty() == false);
    BRE_ASSERT(baseColorTextures.size() == metalnessTextures.size());
    BRE_ASSERT(metalnessTextures.size() == roughnessTextures.size());
    BRE_ASSERT(roughnessTextures.size() == normalTextures.size());
    BRE_ASSERT(normalTextures.size() == heightTextures.size());

    const std::size_t numResources = baseColorTextures.size();
    const std::size_t geometryDataCount = geometryDataVector.size();

    // Check that the total number of matrices (geometry to be drawn) will be equal to available materials
#ifdef _DEBUG
    std::size_t totalNumMatrices{ 0UL };
    for (std::size_t i = 0UL; i < geometryDataCount; ++i) {
        const std::size_t numMatrices{ geometryDataVector[i].mWorldMatrices.size() };
        totalNumMatrices += numMatrices;
        BRE_ASSERT(numMatrices != 0UL);
    }
    BRE_ASSERT(totalNumMatrices == numResources);
#endif
    mGeometryDataVec.reserve(geometryDataCount);
    for (std::uint32_t i = 0U; i < geometryDataCount; ++i) {         mGeometryDataVec.push_back(geometryDataVector[i]);     }     InitCBuffersAndViews(baseColorTextures,                          metalnessTextures,                          roughnessTextures,                          normalTextures,                          heightTextures);     BRE_ASSERT(IsDataValid()); } std::uint32_t HeightMappingCommandListRecorder::RecordAndPushCommandLists(const FrameCBuffer& frameCBuffer) noexcept {     BRE_ASSERT(IsDataValid());     BRE_ASSERT(sPSO != nullptr);     BRE_ASSERT(sRootSignature != nullptr);     BRE_ASSERT(mGeometryBufferRenderTargetViews != nullptr);     BRE_ASSERT(mGeometryBufferRenderTargetViewCount != 0U);     BRE_ASSERT(mDepthBufferView.ptr != 0U);     // Update frame constants     UploadBuffer& uploadFrameCBuffer(mFrameUploadCBufferPerFrame.GetNextFrameCBuffer());     uploadFrameCBuffer.CopyData(0U, &frameCBuffer, sizeof(frameCBuffer));     ID3D12GraphicsCommandList& commandList = mCommandListPerFrame.ResetCommandListWithNextCommandAllocator(sPSO);     commandList.RSSetViewports(1U, &ApplicationSettings::sScreenViewport);     commandList.RSSetScissorRects(1U, &ApplicationSettings::sScissorRect);     commandList.OMSetRenderTargets(mGeometryBufferRenderTargetViewCount,                                    mGeometryBufferRenderTargetViews,                                    false,                                    &mDepthBufferView);     ID3D12DescriptorHeap* heaps[] = { &CbvSrvUavDescriptorManager::GetDescriptorHeap() };     commandList.SetDescriptorHeaps(_countof(heaps), heaps);     commandList.SetGraphicsRootSignature(sRootSignature);     const std::size_t descHandleIncSize{ DirectXManager::GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV) };     D3D12_GPU_DESCRIPTOR_HANDLE objectCBufferView(mObjectCBufferViewsBegin);     D3D12_GPU_DESCRIPTOR_HANDLE baseColorTextureRenderTargetView(mBaseColorTextureRenderTargetViewsBegin);     D3D12_GPU_DESCRIPTOR_HANDLE metalnessTextureRenderTargetView(mMetalnessTextureRenderTargetViewsBegin);     D3D12_GPU_DESCRIPTOR_HANDLE roughnessTextureRenderTargetView(mRoughnessTextureRenderTargetViewsBegin);     D3D12_GPU_DESCRIPTOR_HANDLE normalTextureRenderTargetView(mNormalTextureRenderTargetViewsBegin);     D3D12_GPU_DESCRIPTOR_HANDLE heightTextureRenderTargetView(mHeightTextureRenderTargetViewsBegin);     commandList.IASetPrimitiveTopology(D3D_PRIMITIVE_TOPOLOGY_3_CONTROL_POINT_PATCHLIST);     // Set frame constants root parameters     D3D12_GPU_VIRTUAL_ADDRESS frameCBufferGpuVAddress(         uploadFrameCBuffer.GetResource().GetGPUVirtualAddress());     const D3D12_GPU_VIRTUAL_ADDRESS heightMappingCBufferGpuVAddress(         mHeightMappingUploadCBuffer->GetResource().GetGPUVirtualAddress());
    commandList.SetGraphicsRootConstantBufferView(1U, frameCBufferGpuVAddress);
    commandList.SetGraphicsRootConstantBufferView(2U, heightMappingCBufferGpuVAddress);
    commandList.SetGraphicsRootConstantBufferView(3U, frameCBufferGpuVAddress);
    commandList.SetGraphicsRootConstantBufferView(4U, heightMappingCBufferGpuVAddress);
    commandList.SetGraphicsRootConstantBufferView(6U, frameCBufferGpuVAddress);

    // Draw objects
    const std::size_t geomCount{ mGeometryDataVec.size() };
    for (std::size_t i = 0UL; i < geomCount; ++i) {
        GeometryData& geomData{ mGeometryDataVec[i] };
        commandList.IASetVertexBuffers(0U, 1U, &geomData.mVertexBufferData.mBufferView);
        commandList.IASetIndexBuffer(&geomData.mIndexBufferData.mBufferView);
        const std::size_t worldMatsCount{ geomData.mWorldMatrices.size() };
        for (std::size_t j = 0UL; j < worldMatsCount; ++j) {
            commandList.SetGraphicsRootDescriptorTable(0U, objectCBufferView);
            objectCBufferView.ptr += descHandleIncSize;

            commandList.SetGraphicsRootDescriptorTable(5U, heightTextureRenderTargetView);
            heightTextureRenderTargetView.ptr += descHandleIncSize;

            commandList.SetGraphicsRootDescriptorTable(7U, baseColorTextureRenderTargetView);
            baseColorTextureRenderTargetView.ptr += descHandleIncSize;

            commandList.SetGraphicsRootDescriptorTable(8U, metalnessTextureRenderTargetView);
            metalnessTextureRenderTargetView.ptr += descHandleIncSize;

            commandList.SetGraphicsRootDescriptorTable(9U, roughnessTextureRenderTargetView);
            roughnessTextureRenderTargetView.ptr += descHandleIncSize;

            commandList.SetGraphicsRootDescriptorTable(10U, normalTextureRenderTargetView);
            normalTextureRenderTargetView.ptr += descHandleIncSize;

            commandList.DrawIndexedInstanced(geomData.mIndexBufferData.mElementCount, 1U, 0U, 0U, 0U);
        }
    }

    commandList.Close();
    CommandListExecutor::Get().PushCommandList(commandList);

    return 1U;
}

bool
HeightMappingCommandListRecorder::IsDataValid() const noexcept
{
    const bool result =
        GeometryCommandListRecorder::IsDataValid() &&
        mBaseColorTextureRenderTargetViewsBegin.ptr != 0UL &&
        mMetalnessTextureRenderTargetViewsBegin.ptr != 0UL &&
        mRoughnessTextureRenderTargetViewsBegin.ptr != 0UL &&
        mNormalTextureRenderTargetViewsBegin.ptr != 0UL &&
        mHeightTextureRenderTargetViewsBegin.ptr != 0UL &&
        mHeightMappingUploadCBuffer != nullptr;

    return result;
}

void
HeightMappingCommandListRecorder::InitCBuffersAndViews(const std::vector<ID3D12Resource*>& baseColorTextures,
                                                       const std::vector<ID3D12Resource*>& metalnessTextures,
                                                       const std::vector<ID3D12Resource*>& roughnessTextures,
                                                       const std::vector<ID3D12Resource*>& normalTextures,
                                                       const std::vector<ID3D12Resource*>& heightTextures) noexcept
{
    BRE_ASSERT(baseColorTextures.empty() == false);
    BRE_ASSERT(baseColorTextures.size() == metalnessTextures.size());
    BRE_ASSERT(metalnessTextures.size() == roughnessTextures.size());
    BRE_ASSERT(roughnessTextures.size() == normalTextures.size());
    BRE_ASSERT(normalTextures.size() == heightTextures.size());
    BRE_ASSERT(mObjectUploadCBuffers == nullptr);

    const std::uint32_t numResources = static_cast<std::uint32_t>(baseColorTextures.size());

    // Create object cbuffer and fill it
    const std::size_t objCBufferElemSize{ UploadBuffer::GetRoundedConstantBufferSizeInBytes(sizeof(ObjectCBuffer)) };
    mObjectUploadCBuffers = &UploadBufferManager::CreateUploadBuffer(objCBufferElemSize, numResources);
    std::uint32_t k = 0U;
    const std::size_t geometryDataCount{ mGeometryDataVec.size() };
    ObjectCBuffer objCBuffer;
    for (std::size_t i = 0UL; i < geometryDataCount; ++i) {
        GeometryData& geomData{ mGeometryDataVec[i] };
        const std::uint32_t worldMatsCount{ static_cast<std::uint32_t>(geomData.mWorldMatrices.size()) };
        for (std::uint32_t j = 0UL; j < worldMatsCount; ++j) {             MathUtils::StoreTransposeMatrix(geomData.mWorldMatrices[j],                                             objCBuffer.mWorldMatrix);             MathUtils::StoreTransposeMatrix(geomData.mInverseTransposeWorldMatrices[j],                                             objCBuffer.mInverseTransposeWorldMatrix);             objCBuffer.mTextureScale = geomData.mTextureScales[j];             mObjectUploadCBuffers->CopyData(k + j, &objCBuffer, sizeof(objCBuffer));
        }

        k += worldMatsCount;
    }

    D3D12_GPU_VIRTUAL_ADDRESS objCBufferGpuAddress{ mObjectUploadCBuffers->GetResource().GetGPUVirtualAddress() };

    // Create object / materials cbuffers descriptors
    // Create textures SRV descriptors
    std::vector<D3D12_CONSTANT_BUFFER_VIEW_DESC> objectCbufferViewDescVec;
    objectCbufferViewDescVec.reserve(numResources);

    std::vector<D3D12_CONSTANT_BUFFER_VIEW_DESC> materialCbufferViewDescVec;
    materialCbufferViewDescVec.reserve(numResources);

    std::vector<ID3D12Resource*> textureResVec;
    textureResVec.reserve(numResources);
    std::vector<D3D12_SHADER_RESOURCE_VIEW_DESC> textureSrvDescVec;
    textureSrvDescVec.reserve(numResources);

    std::vector<ID3D12Resource*> metalnessResVec;
    metalnessResVec.reserve(numResources);
    std::vector<D3D12_SHADER_RESOURCE_VIEW_DESC> metalnessSrvDescVec;
    metalnessSrvDescVec.reserve(numResources);

    std::vector<ID3D12Resource*> roughnessResVec;
    roughnessResVec.reserve(numResources);
    std::vector<D3D12_SHADER_RESOURCE_VIEW_DESC> roughnessSrvDescVec;
    roughnessSrvDescVec.reserve(numResources);

    std::vector<ID3D12Resource*> normalResVec;
    normalResVec.reserve(numResources);
    std::vector<D3D12_SHADER_RESOURCE_VIEW_DESC> normalSrvDescVec;
    normalSrvDescVec.reserve(numResources);

    std::vector<ID3D12Resource*> heightResVec;
    heightResVec.reserve(numResources);
    std::vector<D3D12_SHADER_RESOURCE_VIEW_DESC> heightSrvDescVec;
    heightSrvDescVec.reserve(numResources);
    for (std::size_t i = 0UL; i < numResources; ++i) {
        // Object cbuffer desc
        D3D12_CONSTANT_BUFFER_VIEW_DESC cBufferDesc{};
        cBufferDesc.BufferLocation = objCBufferGpuAddress + i * objCBufferElemSize;
        cBufferDesc.SizeInBytes = static_cast<std::uint32_t>(objCBufferElemSize);
        objectCbufferViewDescVec.push_back(cBufferDesc);

        // Texture descriptor
        textureResVec.push_back(baseColorTextures[i]);
        D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc{};
        srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
        srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
        srvDesc.Texture2D.MostDetailedMip = 0;
        srvDesc.Texture2D.ResourceMinLODClamp = 0.0f;
        srvDesc.Format = textureResVec.back()->GetDesc().Format;
        srvDesc.Texture2D.MipLevels = textureResVec.back()->GetDesc().MipLevels;
        textureSrvDescVec.push_back(srvDesc);

        // Metalness descriptor
        metalnessResVec.push_back(metalnessTextures[i]);

        srvDesc = D3D12_SHADER_RESOURCE_VIEW_DESC{};
        srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
        srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
        srvDesc.Texture2D.MostDetailedMip = 0;
        srvDesc.Texture2D.ResourceMinLODClamp = 0.0f;
        srvDesc.Format = metalnessResVec.back()->GetDesc().Format;
        srvDesc.Texture2D.MipLevels = metalnessResVec.back()->GetDesc().MipLevels;
        metalnessSrvDescVec.push_back(srvDesc);

        // Roughness descriptor
        roughnessResVec.push_back(roughnessTextures[i]);

        srvDesc = D3D12_SHADER_RESOURCE_VIEW_DESC{};
        srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
        srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
        srvDesc.Texture2D.MostDetailedMip = 0;
        srvDesc.Texture2D.ResourceMinLODClamp = 0.0f;
        srvDesc.Format = roughnessResVec.back()->GetDesc().Format;
        srvDesc.Texture2D.MipLevels = roughnessResVec.back()->GetDesc().MipLevels;
        roughnessSrvDescVec.push_back(srvDesc);

        // Normal descriptor
        normalResVec.push_back(normalTextures[i]);
        srvDesc = D3D12_SHADER_RESOURCE_VIEW_DESC{};
        srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
        srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
        srvDesc.Texture2D.MostDetailedMip = 0;
        srvDesc.Texture2D.ResourceMinLODClamp = 0.0f;
        srvDesc.Format = normalResVec.back()->GetDesc().Format;
        srvDesc.Texture2D.MipLevels = normalResVec.back()->GetDesc().MipLevels;
        normalSrvDescVec.push_back(srvDesc);

        // Height descriptor
        heightResVec.push_back(heightTextures[i]);
        srvDesc = D3D12_SHADER_RESOURCE_VIEW_DESC{};
        srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
        srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
        srvDesc.Texture2D.MostDetailedMip = 0;
        srvDesc.Texture2D.ResourceMinLODClamp = 0.0f;
        srvDesc.Format = heightResVec.back()->GetDesc().Format;
        srvDesc.Texture2D.MipLevels = heightResVec.back()->GetDesc().MipLevels;
        heightSrvDescVec.push_back(srvDesc);
    }

    mObjectCBufferViewsBegin =
        CbvSrvUavDescriptorManager::CreateConstantBufferViews(objectCbufferViewDescVec.data(),
                                                              static_cast<std::uint32_t>(objectCbufferViewDescVec.size()));

    mBaseColorTextureRenderTargetViewsBegin =
        CbvSrvUavDescriptorManager::CreateShaderResourceViews(textureResVec.data(),
                                                              textureSrvDescVec.data(),
                                                              static_cast<std::uint32_t>(textureSrvDescVec.size()));

    mMetalnessTextureRenderTargetViewsBegin =
        CbvSrvUavDescriptorManager::CreateShaderResourceViews(metalnessResVec.data(),
                                                              metalnessSrvDescVec.data(),
                                                              static_cast<std::uint32_t>(metalnessSrvDescVec.size()));

    mRoughnessTextureRenderTargetViewsBegin =
        CbvSrvUavDescriptorManager::CreateShaderResourceViews(roughnessResVec.data(),
                                                              roughnessSrvDescVec.data(),
                                                              static_cast<std::uint32_t>(roughnessSrvDescVec.size()));

    mNormalTextureRenderTargetViewsBegin =
        CbvSrvUavDescriptorManager::CreateShaderResourceViews(normalResVec.data(),
                                                              normalSrvDescVec.data(),
                                                              static_cast<std::uint32_t>(normalSrvDescVec.size()));

    mHeightTextureRenderTargetViewsBegin =
        CbvSrvUavDescriptorManager::CreateShaderResourceViews(heightResVec.data(),
                                                              heightSrvDescVec.data(),
                                                              static_cast<std::uint32_t>(heightSrvDescVec.size()));

    // Height mapping constant buffer
    const std::size_t heightMappingUploadCBufferElemSize =
        UploadBuffer::GetRoundedConstantBufferSizeInBytes(sizeof(HeightMappingCBuffer));

    mHeightMappingUploadCBuffer = &UploadBufferManager::CreateUploadBuffer(heightMappingUploadCBufferElemSize,
                                                                           1U);
    HeightMappingCBuffer heightMappingCBuffer(GeometrySettings::sMinTessellationDistance,
                                              GeometrySettings::sMaxTessellationDistance,
                                              GeometrySettings::sMinTessellationFactor,
                                              GeometrySettings::sMaxTessellationFactor,
                                              GeometrySettings::sHeightScale);

    mHeightMappingUploadCBuffer->CopyData(0U, &heightMappingCBuffer, sizeof(HeightMappingCBuffer));
}
}

VertexShader

#include <GeometryPass/Shaders/HeightMappingCBuffer.hlsli>
#include <ShaderUtils/CBuffers.hlsli>

#include "RS.hlsl"

struct Input {
    float3 mPositionObjectSpace : POSITION;
    float3 mNormalObjectSpace : NORMAL;
    float3 mTangentObjectSpace : TANGENT;
    float2 mUV : TEXCOORD;
};

ConstantBuffer<ObjectCBuffer> gObjCBuffer : register(b0);
ConstantBuffer<FrameCBuffer> gFrameCBuffer : register(b1);
ConstantBuffer<HeightMappingCBuffer> gHeightMappingCBuffer : register(b2);

struct Output {
    float3 mPositionWorldSpace : POS_WORLD;
    float3 mNormalWorldSpace : NORMAL_WORLD;
    float3 mTangentWorldSpace : TANGENT_WORLD;
    float2 mUV : TEXCOORD0;
    float mTessellationFactor : TESS;
};

[RootSignature(RS)]
Output main(in const Input input)
{
    Output output;

    output.mPositionWorldSpace = mul(float4(input.mPositionObjectSpace, 1.0f),
                                     gObjCBuffer.mWorldMatrix).xyz;

    output.mNormalWorldSpace = mul(float4(input.mNormalObjectSpace, 0.0f),
                                   gObjCBuffer.mInverseTransposeWorldMatrix).xyz;

    output.mTangentWorldSpace = mul(float4(input.mTangentObjectSpace, 0.0f),
                                    gObjCBuffer.mWorldMatrix).xyz;

    output.mUV = gObjCBuffer.mTextureScale * input.mUV;

    // Normalized tessellation factor.
    // The tessellation is
    //   0 if d >= min tessellation distance and
    //   1 if d <= max tessellation distance.       const float distance = length(output.mPositionWorldSpace - gFrameCBuffer.mEyePositionWorldSpace.xyz);     const float tessellationFactor = saturate((gHeightMappingCBuffer.mMinTessellationDistance - distance)                                               / (gHeightMappingCBuffer.mMinTessellationDistance                                                  - gHeightMappingCBuffer.mMaxTessellationDistance));     // Rescale [0,1] --> [min tessellation factor, max tessellation factor].
    output.mTessellationFactor = gHeightMappingCBuffer.mMinTessellationFactor
        + tessellationFactor * (gHeightMappingCBuffer.mMaxTessellationFactor - gHeightMappingCBuffer.mMinTessellationFactor);

    return output;
}

HullShader

#include "RS.hlsl"

#define NUM_PATCH_POINTS 3

struct Input {
    float3 mPositionWorldSpace : POS_WORLD;
    float3 mNormalWorldSpace : NORMAL_WORLD;
    float3 mTangentWorldSpace : TANGENT_WORLD;
    float2 mUV : TEXCOORD0;
    float mTessellationFactor : TESS;
};

struct HullShaderConstantOutput {
    float mEdgeFactors[3] : SV_TessFactor;
    float mInsideFactors : SV_InsideTessFactor;
};

struct Output {
    float3 mPositionWorldSpace : POS_WORLD;
    float3 mNormalWorldSpace : NORMAL_WORLD;
    float3 mTangentWorldSpace : TANGENT_WORLD;
    float2 mUV : TEXCOORD0;
};

HullShaderConstantOutput constant_hull_shader(const InputPatch<Input, NUM_PATCH_POINTS> patch,
                                              const uint patchID : SV_PrimitiveID)
{
    // Average tess factors along edges, and pick an edge tess factor for
    // the interior tessellation. It is important to do the tess factor
    // calculation based on the edge properties so that edges shared by
    // more than one triangle will have the same tessellation factor.
    // Otherwise, gaps can appear.
    HullShaderConstantOutput output = (HullShaderConstantOutput)0;
    output.mEdgeFactors[0] = 0.5f * (patch[1].mTessellationFactor + patch[2].mTessellationFactor);
    output.mEdgeFactors[1] = 0.5f * (patch[2].mTessellationFactor + patch[0].mTessellationFactor);
    output.mEdgeFactors[2] = 0.5f * (patch[0].mTessellationFactor + patch[1].mTessellationFactor);
    output.mInsideFactors = output.mEdgeFactors[0];

    return output;
}

[RootSignature(RS)]
[domain("tri")]
[partitioning("fractional_odd")]
[outputtopology("triangle_cw")]
[outputcontrolpoints(NUM_PATCH_POINTS)]
[patchconstantfunc("constant_hull_shader")]
Output main(const InputPatch <Input, NUM_PATCH_POINTS> patch,
            const uint controlPointID : SV_OutputControlPointID,
            const uint patchId : SV_PrimitiveID)
{
    Output output = (Output)0;
    output.mPositionWorldSpace = patch[controlPointID].mPositionWorldSpace;
    output.mNormalWorldSpace = patch[controlPointID].mNormalWorldSpace;
    output.mTangentWorldSpace = patch[controlPointID].mTangentWorldSpace;
    output.mUV = patch[controlPointID].mUV;

    return output;
}

DomainShader

#include <GeometryPass/Shaders/HeightMappingCBuffer.hlsli>
#include <ShaderUtils/CBuffers.hlsli>

#include "RS.hlsl"

#define NUM_PATCH_POINTS 3

struct HullShaderConstantOutput {
    float mEdgeFactors[3] : SV_TessFactor;
    float mInsideFactors : SV_InsideTessFactor;
};

struct Input {
    float3 mPositionWorldSpace : POS_WORLD;
    float3 mNormalWorldSpace : NORMAL_WORLD;
    float3 mTangentWorldSpace : TANGENT_WORLD;
    float2 mUV : TEXCOORD0;
};

ConstantBuffer<FrameCBuffer> gFrameCBuffer : register(b0);
ConstantBuffer<HeightMappingCBuffer> gHeightMappingCBuffer : register(b1);

SamplerState TextureSampler : register (s0);
Texture2D HeightTexture : register (t0);

struct Output {
    float4 mPositionClipSpace : SV_Position;
    float3 mPositionWorldSpace : POS_WORLD;
    float3 mPositionViewSpace : POS_VIEW;
    float3 mNormalWorldSpace : NORMAL_WORLD;
    float3 mNormalViewSpace : NORMAL_VIEW;
    float3 mTangentWorldSpace : TANGENT_WORLD;
    float3 mTangentViewSpace : TANGENT_VIEW;
    float3 mBinormalWorldSpace : BINORMAL_WORLD;
    float3 mBinormalViewSpace : BINORMAL_VIEW;
    float2 mUV : TEXCOORD0;
};

[RootSignature(RS)]
[domain("tri")]
Output main(const HullShaderConstantOutput HSConstantOutput,
            const float3 uvw : SV_DomainLocation,
            const OutputPatch <Input, NUM_PATCH_POINTS> patch)
{
    Output output = (Output)0;

    // Get texture coordinates
    output.mUV = uvw.x * patch[0].mUV + uvw.y * patch[1].mUV + uvw.z * patch[2].mUV;

    // Get normal
    const float3 normalWorldSpace =
        normalize(uvw.x * patch[0].mNormalWorldSpace + uvw.y * patch[1].mNormalWorldSpace + uvw.z * patch[2].mNormalWorldSpace);
    output.mNormalWorldSpace = normalize(normalWorldSpace);
    output.mNormalViewSpace = normalize(mul(float4(output.mNormalWorldSpace, 0.0f),
                                            gFrameCBuffer.mViewMatrix).xyz);

    // Get position
    float3 positionWorldSpace =
        uvw.x * patch[0].mPositionWorldSpace + uvw.y * patch[1].mPositionWorldSpace + uvw.z * patch[2].mPositionWorldSpace;
    float3 positionViewSpace = mul(float4(positionWorldSpace, 1.0f),
                                   gFrameCBuffer.mViewMatrix).xyz;

    const float height = HeightTexture.SampleLevel(TextureSampler,
                                                   output.mUV,
                                                   0).x;
    const float displacement = (gHeightMappingCBuffer.mHeightScale * (height - 1));

    // Offset vertex along normal
    positionWorldSpace += output.mNormalWorldSpace * displacement;
    positionViewSpace += output.mNormalViewSpace * displacement;

    // Get tangent
    output.mTangentWorldSpace =
        normalize(uvw.x * patch[0].mTangentWorldSpace + uvw.y * patch[1].mTangentWorldSpace + uvw.z * patch[2].mTangentWorldSpace);
    output.mTangentViewSpace = normalize(mul(float4(output.mTangentWorldSpace, 0.0f),
                                             gFrameCBuffer.mViewMatrix)).xyz;

    // Get binormal
    output.mBinormalWorldSpace = normalize(cross(output.mNormalWorldSpace,
                                                 output.mTangentWorldSpace));
    output.mBinormalViewSpace = normalize(cross(output.mNormalViewSpace,
                                                output.mTangentViewSpace));

    output.mPositionWorldSpace = positionWorldSpace;
    output.mPositionViewSpace = positionViewSpace;
    output.mPositionClipSpace = mul(float4(positionViewSpace, 1.0f),
                                    gFrameCBuffer.mProjectionMatrix);

    return output;
}

PixelShader

#include <ShaderUtils/CBuffers.hlsli>
#include <ShaderUtils/Utils.hlsli>

#include "RS.hlsl"

struct Input {
    float4 mPositionClipSpace : SV_Position;
    float3 mPositionWorldSpace : POS_WORLD;
    float3 mPositionViewSpace : POS_VIEW;
    float3 mNormalWorldSpace : NORMAL_WORLD;
    float3 mNormalViewSpace : NORMAL_VIEW;
    float3 mTangentWorldSpace : TANGENT_WORLD;
    float3 mTangentViewSpace : TANGENT_VIEW;
    float3 mBinormalWorldSpace : BINORMAL_WORLD;
    float3 mBinormalViewSpace : BINORMAL_VIEW;
    float2 mUV : TEXCOORD0;
};

ConstantBuffer<FrameCBuffer> gFrameCBuffer : register(b0);

SamplerState TextureSampler : register (s0);
Texture2D BaseColorTexture : register (t0);
Texture2D MetalnessTexture : register (t1);
Texture2D RoughnessTexture : register (t2);
Texture2D NormalTexture : register (t3);

struct Output {
    float4 mNormal_Roughness : SV_Target0;
    float4 mBaseColor_Metalness : SV_Target1;
};

[RootSignature(RS)]
Output main(const in Input input)
{
    Output output = (Output)0;

    // Normal (encoded in view space)
    const float3 normalObjectSpace = normalize(NormalTexture.Sample(TextureSampler,
                                                                    input.mUV).xyz * 2.0f - 1.0f);
    const float3x3 tbnWorldSpace = float3x3(normalize(input.mTangentWorldSpace),
                                            normalize(input.mBinormalWorldSpace),
                                            normalize(input.mNormalWorldSpace));
    const float3 normalWorldSpace = mul(normalObjectSpace, tbnWorldSpace);
    const float3x3 tbnViewSpace = float3x3(normalize(input.mTangentViewSpace),
                                           normalize(input.mBinormalViewSpace),
                                           normalize(input.mNormalViewSpace));
    output.mNormal_Roughness.xy = Encode(normalize(mul(normalObjectSpace, tbnViewSpace)));

    // Base color and metalness
    const float3 baseColor = BaseColorTexture.Sample(TextureSampler,
                                                     input.mUV).rgb;
    const float metalness = MetalnessTexture.Sample(TextureSampler,
                                                    input.mUV).r;
    output.mBaseColor_Metalness = float4(baseColor,
                                         metalness);

    // Roughness
    output.mNormal_Roughness.z = RoughnessTexture.Sample(TextureSampler,
                                                         input.mUV).r;

    return output;
}

This pass also has its own constant buffers. It has properties like displacement min and max distances, displacement min and max factors, etc. The code is the following

HeightMappingCBuffer.h

#pragma once

#include <GeometryPass\GeometrySettings.h>

namespace BRE {
///
/// @brief Height mapping constant buffer
///
struct HeightMappingCBuffer {
    HeightMappingCBuffer() = default;

    HeightMappingCBuffer(const float minTessellationDistance,
                         const float maxTessellationDistance,
                         const float minTessellationFactor,
                         const float maxTessellationFactor,
                         const float heightScale)
        : mMinTessellationDistance(minTessellationDistance)
        , mMaxTessellationDistance(maxTessellationDistance)
        , mMinTessellationFactor(minTessellationFactor)
        , mMaxTessellationFactor(maxTessellationFactor)
        , mHeightScale(heightScale)
    {

    }

    ~HeightMappingCBuffer() = default;
    HeightMappingCBuffer(const HeightMappingCBuffer&) = default;
    HeightMappingCBuffer(HeightMappingCBuffer&&) = default;
    HeightMappingCBuffer& operator=(HeightMappingCBuffer&&) = default;

    float mMinTessellationDistance{ GeometrySettings::sMinTessellationDistance };
    float mMaxTessellationDistance{ GeometrySettings::sMaxTessellationDistance };
    float mMinTessellationFactor{ GeometrySettings::sMinTessellationFactor };
    float mMaxTessellationFactor{ GeometrySettings::sMaxTessellationFactor };
    float mHeightScale{ GeometrySettings::sHeightScale };
};

}

HeightMappingCBuffer.hlsli

#ifndef HEIGHT_MAPPING_CBUFFER
#define HEIGHT_MAPPING_CBUFFER

// Height mapping constant buffer
struct HeightMappingCBuffer {
    float mMinTessellationDistance;
    float mMaxTessellationDistance;
    float mMinTessellationFactor;
    float mMaxTessellationFactor;
    float mHeightScale;
};

#endif

You can see a screenshot from BRE in the following image

height_mapping.jpg

What is the pass that executes all these command list recorders?

As we described in BRE Architecture Series Part 5 – Scene Generation, the SceneLoader is responsible for reading the scene file and generate the GeometryCommandListRecorders. Also, we described that they are stored in an instance of the class Scene.
The pass responsible for executing all the GeometryCommandListRecorders is GeometryPass, and its implementation is the following

GeometryPass.h

#pragma once

#include <memory>
#include <vector>

#include <CommandManager\CommandListPerFrame.h>
#include <GeometryPass\GeometryCommandListRecorder.h>

namespace BRE {
struct FrameCBuffer;

///
/// @brief Responsible to execute command list recorders related with deferred shading geometry pass
///
class GeometryPass {
public:
    // Geometry buffers
    enum BufferType {
        NORMAL_ROUGHNESS = 0U, // 2 encoded normals based on octahedron encoding + 1 roughness
        BASECOLOR_METALNESS, // 3 base color + 1 metalness
        BUFFERS_COUNT
    };

    GeometryPass(GeometryCommandListRecorders& geometryPassCommandListRecorders);
    ~GeometryPass() = default;
    GeometryPass(const GeometryPass&) = delete;
    const GeometryPass& operator=(const GeometryPass&) = delete;
    GeometryPass(GeometryPass&&) = delete;
    GeometryPass& operator=(GeometryPass&&) = delete;

    ///
    /// @brief Initializes geometry pass
    /// @param depthBufferView Depth buffer view
    ///
    void Init(const D3D12_CPU_DESCRIPTOR_HANDLE& depthBufferView) noexcept;

    ///
    /// @brief Get geometry buffer by type
    /// @param bufferType Buffer type
    /// @return Geometry buffer
    ///
    __forceinline ID3D12Resource& GetGeometryBuffer(const BufferType bufferType) noexcept
    {
        return *mGeometryBuffers[bufferType];
    }

    ///
    /// @brief Get the shader resource view to the beginning of the contiguos list
    /// of shader resource views of the geometry buffers.
    /// @return The shader resource view to the beginning of the contiguos list
    /// where all the shader resource views for all the geometry buffers are stored.
    ///
    __forceinline D3D12_GPU_DESCRIPTOR_HANDLE GetGeometryBufferShaderResourceViews() noexcept
    {
        return mGeometryBufferShaderResourceViews[0U];
    }

    ///
    /// @brief Get a shader resource view to a specific geometry buffer
    /// @return Shader resource view
    ///
    __forceinline D3D12_GPU_DESCRIPTOR_HANDLE GetGeometryBufferShaderResourceView(const BufferType bufferType) noexcept
    {
        return mGeometryBufferShaderResourceViews[bufferType];
    }

    ///
    /// @brief Executes the pass
    ///
    /// Init() must be called first. This method can record and
    /// push command lists to the CommandListExecutor.
    ///
    /// @param frameCBuffer Constant buffer per frame, for current frame
    /// @return The number of recorded command lists.
    ///
    std::uint32_t Execute(const FrameCBuffer& frameCBuffer) noexcept;

private:
    ///
    /// @brief Checks if internal data is valid. Typically, used for assertions
    /// @return True if valid. Otherwise, false
    ///
    bool IsDataValid() const noexcept;

    ///
    /// @brief Records pre pass command lists and pushes them to
    /// the CommandListExecutor.
    /// @return The number of recorded command lists
    ///
    std::uint32_t RecordAndPushPrePassCommandLists() noexcept;

    ///
    /// @brief Initializes shader resource views
    ///
    void InitShaderResourceViews() noexcept;

    CommandListPerFrame mPrePassCommandListPerFrame;

    // Geometry buffers data
    ID3D12Resource* mGeometryBuffers[BUFFERS_COUNT]{ nullptr };
    D3D12_GPU_DESCRIPTOR_HANDLE mGeometryBufferShaderResourceViews[BUFFERS_COUNT]{ 0UL };
    D3D12_CPU_DESCRIPTOR_HANDLE mGeometryBufferRenderTargetViews[BUFFERS_COUNT]{ 0UL };

    GeometryCommandListRecorders& mGeometryCommandListRecorders;
};
}

GeometryPass.cpp

#include "GeometryPass.h"

#include <d3d12.h>
#include <DirectXColors.h>
#include <tbb/parallel_for.h>

#include <CommandListExecutor/CommandListExecutor.h>
#include <DescriptorManager\CbvSrvUavDescriptorManager.h>
#include <DescriptorManager\RenderTargetDescriptorManager.h>
#include <DirectXManager\DirectXManager.h>
#include <DXUtils/D3DFactory.h>
#include <GeometryPass\Recorders\HeightMappingCommandListRecorder.h>
#include <GeometryPass\Recorders\NormalMappingCommandListRecorder.h>
#include <GeometryPass\Recorders\TextureMappingCommandListRecorder.h>
#include <ResourceManager\ResourceManager.h>
#include <ResourceStateManager\ResourceStateManager.h>
#include <ShaderUtils\CBuffers.h>
#include <Utils\DebugUtils.h>

using namespace DirectX;

namespace BRE {
namespace {
// Geometry buffer formats
const DXGI_FORMAT sGeometryBufferFormats[D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT]{
    DXGI_FORMAT_R16G16B16A16_FLOAT,
    DXGI_FORMAT_R8G8B8A8_UNORM,
    DXGI_FORMAT_UNKNOWN,
    DXGI_FORMAT_UNKNOWN,
    DXGI_FORMAT_UNKNOWN,
    DXGI_FORMAT_UNKNOWN,
    DXGI_FORMAT_UNKNOWN,
    DXGI_FORMAT_UNKNOWN
};

///
/// @brief Create geometry buffers and render target views
/// @param buffers Output list of geometry buffers
/// @param bufferRenderTargetViews Output geometry buffers render target views
///
void
CreateGeometryBuffersAndRenderTargetViews(ID3D12Resource* buffers[GeometryPass::BUFFERS_COUNT],
                                          D3D12_CPU_DESCRIPTOR_HANDLE bufferRenderTargetViews[GeometryPass::BUFFERS_COUNT]) noexcept
{
    // Set shared buffers properties
    D3D12_RESOURCE_DESC resourceDescriptor = D3DFactory::GetResourceDescriptor(ApplicationSettings::sWindowWidth,
                                                                               ApplicationSettings::sWindowHeight,
                                                                               DXGI_FORMAT_UNKNOWN,
                                                                               D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET,
                                                                               D3D12_RESOURCE_DIMENSION_TEXTURE2D,
                                                                               D3D12_TEXTURE_LAYOUT_UNKNOWN,
                                                                               0U);

    D3D12_CLEAR_VALUE clearValue[]
    {
        { DXGI_FORMAT_UNKNOWN, 0.0f, 0.0f, 0.0f, 1.0f },
        { DXGI_FORMAT_UNKNOWN, 0.0f, 0.0f, 0.0f, 0.0f },
    };
    BRE_ASSERT(_countof(clearValue) == GeometryPass::BUFFERS_COUNT);

    const D3D12_HEAP_PROPERTIES heapProperties = D3DFactory::GetHeapProperties();

    // Create and store render target views
    const wchar_t* resourceNames[GeometryPass::BUFFERS_COUNT] =
    {
        L"Normal_RoughnessTexture Buffer",
        L"BaseColor_MetalnessTexture Buffer"
    };
    for (std::uint32_t i = 0U; i < GeometryPass::BUFFERS_COUNT; ++i) {         resourceDescriptor.Format = sGeometryBufferFormats[i];         clearValue[i].Format = resourceDescriptor.Format;         D3D12_RENDER_TARGET_VIEW_DESC rtvDescriptor{};         rtvDescriptor.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2D;         rtvDescriptor.Format = resourceDescriptor.Format;         resourceDescriptor.MipLevels = 1U;         buffers[i] = &ResourceManager::CreateCommittedResource(heapProperties,                                                                D3D12_HEAP_FLAG_NONE,                                                                resourceDescriptor,                                                                D3D12_RESOURCE_STATE_RENDER_TARGET,                                                                &clearValue[i],                                                                resourceNames[i],                                                                ResourceManager::ResourceStateTrackingType::FULL_TRACKING);         RenderTargetDescriptorManager::CreateRenderTargetView(*buffers[i],                                                               rtvDescriptor,                                                               &bufferRenderTargetViews[i]);     } } } GeometryPass::GeometryPass(GeometryCommandListRecorders& geometryPassCommandListRecorders)     : mGeometryCommandListRecorders(geometryPassCommandListRecorders) {} void GeometryPass::Init(const D3D12_CPU_DESCRIPTOR_HANDLE& depthBufferView) noexcept {     BRE_ASSERT(IsDataValid() == false);     BRE_ASSERT(mGeometryCommandListRecorders.empty() == false);     CreateGeometryBuffersAndRenderTargetViews(mGeometryBuffers, mGeometryBufferRenderTargetViews);     HeightMappingCommandListRecorder::InitSharedPSOAndRootSignature(sGeometryBufferFormats, BUFFERS_COUNT);     NormalMappingCommandListRecorder::InitSharedPSOAndRootSignature(sGeometryBufferFormats, BUFFERS_COUNT);     TextureMappingCommandListRecorder::InitSharedPSOAndRootSignature(sGeometryBufferFormats, BUFFERS_COUNT);     InitShaderResourceViews();     // Init geometry command list recorders     for (GeometryCommandListRecorders::value_type& recorder : mGeometryCommandListRecorders) {         BRE_ASSERT(recorder.get() != nullptr);         recorder->Init(mGeometryBufferRenderTargetViews,
                       BUFFERS_COUNT,
                       depthBufferView);
    }

    BRE_ASSERT(IsDataValid());
}

std::uint32_t
GeometryPass::Execute(const FrameCBuffer& frameCBuffer) noexcept
{
    BRE_ASSERT(IsDataValid());

    const std::uint32_t geometryPassCommandListCount = static_cast<std::uint32_t>(mGeometryCommandListRecorders.size());
    std::uint32_t commandListCount = geometryPassCommandListCount;

    commandListCount += RecordAndPushPrePassCommandLists();

    // Execute tasks
    std::uint32_t grainSize{ max(1U, (geometryPassCommandListCount) / ApplicationSettings::sCpuProcessorCount) };
    tbb::parallel_for(tbb::blocked_range<std::size_t>(0, geometryPassCommandListCount, grainSize),
                      [&](const tbb::blocked_range<size_t>& r) {
        for (size_t i = r.begin(); i != r.end(); ++i)
            mGeometryCommandListRecorders[i]->RecordAndPushCommandLists(frameCBuffer);
    }
    );

    return commandListCount;
}

bool
GeometryPass::IsDataValid() const noexcept
{
    for (std::uint32_t i = 0U; i < BUFFERS_COUNT; ++i) {
        if (mGeometryBuffers[i] == nullptr) {
            return false;
        }
    }

    for (std::uint32_t i = 0U; i < BUFFERS_COUNT; ++i) {
        if (mGeometryBufferRenderTargetViews[i].ptr == 0UL) {
            return false;
        }
    }

    return mGeometryCommandListRecorders.empty() == false;
}

std::uint32_t
GeometryPass::RecordAndPushPrePassCommandLists() noexcept
{
    BRE_ASSERT(IsDataValid());

    ID3D12GraphicsCommandList& commandList = mPrePassCommandListPerFrame.ResetCommandListWithNextCommandAllocator(nullptr);

    D3D12_RESOURCE_BARRIER barriers[BUFFERS_COUNT];
    std::uint32_t barrierCount = 0UL;
    for (std::uint32_t i = 0U; i < BUFFERS_COUNT; ++i) {         if (ResourceStateManager::GetResourceState(*mGeometryBuffers[i]) != D3D12_RESOURCE_STATE_RENDER_TARGET) {             barriers[barrierCount] = ResourceStateManager::ChangeResourceStateAndGetBarrier(*mGeometryBuffers[i],                                                                                             D3D12_RESOURCE_STATE_RENDER_TARGET);             ++barrierCount;         }     }     if (barrierCount > 0UL) {
        commandList.ResourceBarrier(barrierCount, barriers);
    }

    commandList.RSSetViewports(1U, &ApplicationSettings::sScreenViewport);
    commandList.RSSetScissorRects(1U, &ApplicationSettings::sScissorRect);

    float zero[4U] = { 0.0f, 0.0f, 0.0f, 0.0f };
    commandList.ClearRenderTargetView(mGeometryBufferRenderTargetViews[NORMAL_ROUGHNESS], Colors::Black, 0U, nullptr);
    commandList.ClearRenderTargetView(mGeometryBufferRenderTargetViews[BASECOLOR_METALNESS], zero, 0U, nullptr);

    BRE_CHECK_HR(commandList.Close());
    CommandListExecutor::Get().PushCommandList(commandList);

    return 1U;
}

void
GeometryPass::InitShaderResourceViews() noexcept
{
    D3D12_SHADER_RESOURCE_VIEW_DESC srvDescriptors[BUFFERS_COUNT]{};

    for (std::uint32_t i = 0U; i < BUFFERS_COUNT; ++i) {         srvDescriptors[i].Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;         srvDescriptors[i].ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;         srvDescriptors[i].Texture2D.MostDetailedMip = 0;         srvDescriptors[i].Texture2D.ResourceMinLODClamp = 0.0f;         srvDescriptors[i].Format = mGeometryBuffers[i]->GetDesc().Format;
        srvDescriptors[i].Texture2D.MipLevels = mGeometryBuffers[i]->GetDesc().MipLevels;
    }

    const D3D12_GPU_DESCRIPTOR_HANDLE shaderResourceViewBegin =
        CbvSrvUavDescriptorManager::CreateShaderResourceViews(mGeometryBuffers,
                                                              srvDescriptors,
                                                              BUFFERS_COUNT);

    // After creating all the contiguous descriptors, we need to initialize each
    // shader resource view member variables
    const std::size_t descriptorHandleIncrementSize =
        DirectXManager::GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
    for (std::uint32_t i = 0U; i < BUFFERS_COUNT; ++i) {
        mGeometryBufferShaderResourceViews[i].ptr = shaderResourceViewBegin.ptr + i * descriptorHandleIncrementSize;
    }

}
}

As we can see, this pass executes all the command list recorders in parallel using Intel TBB. This is one of the strong points in BRE (NVIDIA recommendation), that you can parallelize command list recording and execution (because command lists are pushed to CommandListExecutor which runs in its own thread). Also, as we already mentioned, NVIDIA recommends minimizing draw calls and PSO changes.

Future Work

  • Currently, we have a single command list recorder per technique, and all the geometry that uses that technique is in the same command list recorder. We should benchmark different scenarios where the number of geometry and techniques is giant.
Advertisements

3 thoughts on “BRE Architecture Series Part 7 – Geometry Pass

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s