BRE Architecture Series Part 2 – Managers

In this opportunity, we are going to see the “managers” of BRE. The managers are static classes (i.e. all its methods and member variables are static). My intention was to have global access and do not need to create instances to use them. There is always a battle between using classes with global access vs restricting its scope the more possible and pass instead instances as function parameters. While there are certain cases that I consider this is correct, I prefer to avoid growing the number of parameters in functions. For example, if I have a function that loads scenes, for example, LoadScene() and that uses the Manager1, Manager2, and Manager3, in my opinion, it is better to have the following implementation

void LoadScene()
{
    Manager1::doSomething();
	Manager2::doSomething();
	Manager3::doSomething();
}

instead of passing the managers as function parameters.

void LoadScene(Manager1& manager1,
               Manager2& manager2,
			   Manager3& manager3)
{
    manager1.doSomething();
	manager2.doSomething();
	manager3.doSomething();
}

Even if LoadScene() is called from other methods that do not use these managers, these functions will need to have extra parameters too. Anyway, these are trade-offs, and I choose to have global data.

What are the BRE managers?

At the time of writing this article, we have the following managers

  • ModelManager
  • FenceManager
  • CommandAllocatorManager
  • CommandListManager
  • CommandQueueManager
  • CbvSrvUavDescriptorManager
  • DepthStencilDescriptorManager
  • RenderTargetDescriptorManager
  • PSOManager
  • ResourceManager
  • UploadBufferManager
  • ResourceStateManager
  • RootSignatureManager
  • ShaderManager

Many of them could have been combined in a single manager but I preferred to separate responsibilities to make them easier to understand, and so that the data of each manager could be used in all its methods, and not only in few of them.

All of them have in common that they are thread safe. I use std::mutex to avoid race conditions when calling DirectX12 API methods, and also I use concurrent containers of Intel TBB.

Model Manager

BRE has a class called GeometryGenerator which purpose is to generate simple geometry like spheres, cylinders, grids, cubes, etc. Also, it supports model loading through Assimp library. ModelManager provides methods to load models from disk using Assimp or to generate geometry from scratch using GeometryGenerator. Its implementation is the following

ModelManager.h

#pragma once

#include <d3d12.h>
#include <mutex>
#include <tbb\concurrent_unordered_set.h>

#include <ModelManager/Model.h>

namespace BRE {
///
/// @brief Responsible to create models or built-in geometry (box, sphere, etc)
///
class ModelManager {
public:
    ModelManager() = delete;
    ~ModelManager() = delete;
    ModelManager(const ModelManager&) = delete;
    const ModelManager& operator=(const ModelManager&) = delete;
    ModelManager(ModelManager&&) = delete;
    ModelManager& operator=(ModelManager&&) = delete;

    ///
    /// @brief Releases all models
    ///
    static void Clear() noexcept;

    ///
    /// @brief Load model
    /// @param modelFilename Model filename. Must be not 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.
    /// @return Model
    ///
    static Model& LoadModel(const char* modelFilename,
                            ID3D12GraphicsCommandList& commandList,
                            Microsoft::WRL::ComPtr<ID3D12Resource>& uploadVertexBuffer,
                            Microsoft::WRL::ComPtr<ID3D12Resource>& uploadIndexBuffer) noexcept;

    ///
    /// @brief Create a box centered at the origin
    /// @param width Width
    /// @param height Height
    /// @param depth Depth
    /// @param numSubdivisions Number of subdivisions. This controls tessellation.
    /// @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.
    /// @return Model
    ///
    static Model& CreateBox(const float width,
                            const float height,
                            const float depth,
                            const std::uint32_t numSubdivisions,
                            ID3D12GraphicsCommandList& commandList,
                            Microsoft::WRL::ComPtr<ID3D12Resource>& uploadVertexBuffer,
                            Microsoft::WRL::ComPtr<ID3D12Resource>& uploadIndexBuffer) noexcept;

    ///
    /// @brief Create a sphere centered at the origin
    /// @param radius Radius
    /// @param sliceCount Slice count. This controls tessellation.
    /// @param stackCount Stack count. This controls tessellation.
    /// @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.
    /// @return Model
    ///
    static Model& CreateSphere(const float radius,
                               const std::uint32_t sliceCount,
                               const std::uint32_t stackCount,
                               ID3D12GraphicsCommandList& commandList,
                               Microsoft::WRL::ComPtr<ID3D12Resource>& uploadVertexBuffer,
                               Microsoft::WRL::ComPtr<ID3D12Resource>& uploadIndexBuffer) noexcept;

    ///
    /// @brief Create a geosphere centered at the origin
    /// @param radius Radius
    /// @param numSubdivisions Number of subdivisions. This controls tessellation.
    /// @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.
    /// @return Model
    ///
    static Model& CreateGeosphere(const float radius,
                                  const std::uint32_t numSubdivisions,
                                  ID3D12GraphicsCommandList& commandList,
                                  Microsoft::WRL::ComPtr<ID3D12Resource>& uploadVertexBuffer,
                                  Microsoft::WRL::ComPtr<ID3D12Resource>& uploadIndexBuffer) noexcept;

    ///
    /// @brief Create a cylinder centered at the origin
    /// @param bottomRadius Bottom radius
    /// @param topRadius Top radius
    /// @param height Height
    /// @param sliceCount Slice count. This controls tessellation.
    /// @param stackCount Stack count. This controls tessellation.
    /// @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.
    /// @return Model
    /// 
    static Model& CreateCylinder(const float bottomRadius,
                                 const float topRadius,
                                 const float height,
                                 const std::uint32_t sliceCount,
                                 const std::uint32_t stackCount,
                                 ID3D12GraphicsCommandList& commandList,
                                 Microsoft::WRL::ComPtr<ID3D12Resource>& uploadVertexBuffer,
                                 Microsoft::WRL::ComPtr<ID3D12Resource>& uploadIndexBuffer) noexcept;

    ///
    /// @brief Create a rows X columns grid in the xz-plane centered at the origin
    /// @param width Width
    /// @param depth Depth
    /// @param rows Grid rows
    /// @param columns Grid columns
    /// @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.
    /// @return Model
    ///
    static Model& CreateGrid(const float width,
                             const float depth,
                             const std::uint32_t rows,
                             const std::uint32_t columns,
                             ID3D12GraphicsCommandList& commandList,
                             Microsoft::WRL::ComPtr<ID3D12Resource>& uploadVertexBuffer,
                             Microsoft::WRL::ComPtr<ID3D12Resource>& uploadIndexBuffer) noexcept;

private:
    static tbb::concurrent_unordered_set<Model*> mModels;

    static std::mutex mMutex;
};
}

ModelManager.cpp

#include "ModelManager.h"

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

namespace BRE {
tbb::concurrent_unordered_set<Model*> ModelManager::mModels;
std::mutex ModelManager::mMutex;

void
ModelManager::Clear() noexcept
{
    for (Model* model : mModels) {
        BRE_ASSERT(model != nullptr);
        delete model;
    }

    mModels.clear();
}

Model&
ModelManager::LoadModel(const char* modelFilename,
                        ID3D12GraphicsCommandList& commandList,
                        Microsoft::WRL::ComPtr<ID3D12Resource>& uploadVertexBuffer,
                        Microsoft::WRL::ComPtr<ID3D12Resource>& uploadIndexBuffer) noexcept
{
    BRE_ASSERT(modelFilename != nullptr);

    Model* model{ nullptr };

    mMutex.lock();
    model = new Model(modelFilename,
                      commandList,
                      uploadVertexBuffer,
                      uploadIndexBuffer);
    mMutex.unlock();

    BRE_ASSERT(model != nullptr);
    mModels.insert(model);

    return *model;
}

Model&
ModelManager::CreateBox(const float width,
                        const float height,
                        const float depth,
                        const std::uint32_t numSubdivisions,
                        ID3D12GraphicsCommandList& commandList,
                        Microsoft::WRL::ComPtr<ID3D12Resource>& uploadVertexBuffer,
                        Microsoft::WRL::ComPtr<ID3D12Resource>& uploadIndexBuffer) noexcept
{
    Model* model{ nullptr };

    GeometryGenerator::MeshData meshData;
    GeometryGenerator::CreateBox(width,
                                 height,
                                 depth,
                                 numSubdivisions, meshData);

    mMutex.lock();
    model = new Model(meshData,
                      commandList,
                      uploadVertexBuffer,
                      uploadIndexBuffer);
    mMutex.unlock();

    BRE_ASSERT(model != nullptr);
    mModels.insert(model);

    return *model;
}

Model&
ModelManager::CreateSphere(const float radius,
                           const std::uint32_t sliceCount,
                           const std::uint32_t stackCount,
                           ID3D12GraphicsCommandList& commandList,
                           Microsoft::WRL::ComPtr<ID3D12Resource>& uploadVertexBuffer,
                           Microsoft::WRL::ComPtr<ID3D12Resource>& uploadIndexBuffer) noexcept
{
    Model* model{ nullptr };

    GeometryGenerator::MeshData meshData;
    GeometryGenerator::CreateSphere(radius,
                                    sliceCount,
                                    stackCount,
                                    meshData);

    mMutex.lock();
    model = new Model(meshData,
                      commandList,
                      uploadVertexBuffer,
                      uploadIndexBuffer);
    mMutex.unlock();

    BRE_ASSERT(model != nullptr);
    mModels.insert(model);

    return *model;
}

Model&
ModelManager::CreateGeosphere(const float radius,
                              const std::uint32_t numSubdivisions,
                              ID3D12GraphicsCommandList& commandList,
                              Microsoft::WRL::ComPtr<ID3D12Resource>& uploadVertexBuffer,
                              Microsoft::WRL::ComPtr<ID3D12Resource>& uploadIndexBuffer) noexcept
{
    Model* model{ nullptr };

    GeometryGenerator::MeshData meshData;
    GeometryGenerator::CreateGeosphere(radius,
                                       numSubdivisions,
                                       meshData);

    mMutex.lock();
    model = new Model(meshData,
                      commandList,
                      uploadVertexBuffer,
                      uploadIndexBuffer);
    mMutex.unlock();

    BRE_ASSERT(model != nullptr);
    mModels.insert(model);

    return *model;
}

Model&
ModelManager::CreateCylinder(const float bottomRadius,
                             const float topRadius,
                             const float height,
                             const std::uint32_t sliceCount,
                             const std::uint32_t stackCount,
                             ID3D12GraphicsCommandList& commandList,
                             Microsoft::WRL::ComPtr<ID3D12Resource>& uploadVertexBuffer,
                             Microsoft::WRL::ComPtr<ID3D12Resource>& uploadIndexBuffer) noexcept
{
    Model* model{ nullptr };

    GeometryGenerator::MeshData meshData;
    GeometryGenerator::CreateCylinder(bottomRadius,
                                      topRadius,
                                      height,
                                      sliceCount,
                                      stackCount,
                                      meshData);

    mMutex.lock();
    model = new Model(meshData,
                      commandList,
                      uploadVertexBuffer,
                      uploadIndexBuffer);
    mMutex.unlock();

    BRE_ASSERT(model != nullptr);
    mModels.insert(model);

    return *model;
}

Model&
ModelManager::CreateGrid(const float width,
                         const float depth,
                         const std::uint32_t rows,
                         const std::uint32_t columns,
                         ID3D12GraphicsCommandList& commandList,
                         Microsoft::WRL::ComPtr<ID3D12Resource>& uploadVertexBuffer,
                         Microsoft::WRL::ComPtr<ID3D12Resource>& uploadIndexBuffer) noexcept
{
    Model* model{ nullptr };

    GeometryGenerator::MeshData meshData;
    GeometryGenerator::CreateGrid(width,
                                  depth,
                                  rows,
                                  columns,
                                  meshData);

    mMutex.lock();
    model = new Model(meshData,
                      commandList,
                      uploadVertexBuffer,
                      uploadIndexBuffer);
    mMutex.unlock();

    BRE_ASSERT(model != nullptr);
    mModels.insert(model);

    return *model;
}
}

Command Allocator Manager

CommandAllocatorManager is responsible for the creation of command allocators (ID3D12CommandAllocator). Its implementation is the following

CommandAllocatorManager.h

#pragma once

#include <d3d12.h>
#include <mutex>
#include <tbb/concurrent_unordered_set.h>

namespace BRE {
///
/// Class to create command allocators
///
class CommandAllocatorManager {
public:
    CommandAllocatorManager() = delete;
    ~CommandAllocatorManager() = delete;
    CommandAllocatorManager(const CommandAllocatorManager&) = delete;
    const CommandAllocatorManager& operator=(const CommandAllocatorManager&) = delete;
    CommandAllocatorManager(CommandAllocatorManager&&) = delete;
    CommandAllocatorManager& operator=(CommandAllocatorManager&&) = delete;

    ///
    /// @brief Releases all ID3D12CommandAllocator's
    ///
    static void Clear() noexcept;

    ///
    /// @brief Create a ID3D12CommandAllocator
    /// @param commandListType The type of the command list for this ID3D12CommandAllocator
    ///
    static ID3D12CommandAllocator& CreateCommandAllocator(const D3D12_COMMAND_LIST_TYPE& commandListType) noexcept;

private:
    static tbb::concurrent_unordered_set<ID3D12CommandAllocator*> mCommandAllocators;

    static std::mutex mMutex;
};
}

CommandAllocatorManager.cpp

#include "CommandAllocatorManager.h"

#include <DirectXManager/DirectXManager.h>
#include <Utils/DebugUtils.h>

namespace BRE {
tbb::concurrent_unordered_set<ID3D12CommandAllocator*> CommandAllocatorManager::mCommandAllocators;
std::mutex CommandAllocatorManager::mMutex;

void
CommandAllocatorManager::Clear() noexcept
{
    for (ID3D12CommandAllocator* commandAllocator : mCommandAllocators) {
        BRE_ASSERT(commandAllocator != nullptr);
        commandAllocator->Release();
    }

    mCommandAllocators.clear();
}

ID3D12CommandAllocator&
CommandAllocatorManager::CreateCommandAllocator(const D3D12_COMMAND_LIST_TYPE& commandListType) noexcept
{
    ID3D12CommandAllocator* commandAllocator{ nullptr };

    mMutex.lock();
    BRE_CHECK_HR(DirectXManager::GetDevice().CreateCommandAllocator(commandListType, IID_PPV_ARGS(&commandAllocator)));
    mMutex.unlock();

    BRE_ASSERT(commandAllocator != nullptr);
    mCommandAllocators.insert(commandAllocator);

    return *commandAllocator;
}
}

Command List Manager

CommandListManager is responsible for the creation of command lists (ID3D12GraphicsCommandList). For now, it only supports the creation of graphics command lists, because in my case I do not need others, although this is easy to add/support. Its implementation is the following

CommandListManager.h

#pragma once

#include <d3d12.h>
#include <mutex>
#include <tbb/concurrent_unordered_set.h>

namespace BRE {
///
/// @brief Class responsible to create command lists.
///
class CommandListManager {
public:
    CommandListManager() = delete;
    ~CommandListManager() = delete;
    CommandListManager(const CommandListManager&) = delete;
    const CommandListManager& operator=(const CommandListManager&) = delete;
    CommandListManager(CommandListManager&&) = delete;
    CommandListManager& operator=(CommandListManager&&) = delete;

    ///
    /// @brief Releases all command lists
    ///
    static void Clear() noexcept;

    ///
    /// @brief Create command list
    /// @param commandListType Type of the command list to create
    /// @param commandAllocator Command allocator to use to create the command list
    /// @return The created command list
    ///
    static ID3D12GraphicsCommandList& CreateCommandList(const D3D12_COMMAND_LIST_TYPE& commandListType,
                                                        ID3D12CommandAllocator& commandAllocator) noexcept;

private:
    static tbb::concurrent_unordered_set<ID3D12GraphicsCommandList*> mCommandLists;

    static std::mutex mMutex;
};
}

CommandListManager.cpp

#include "CommandListManager.h"

#include <DirectXManager/DirectXManager.h>
#include <Utils/DebugUtils.h>

namespace BRE {
tbb::concurrent_unordered_set<ID3D12GraphicsCommandList*> CommandListManager::mCommandLists;
std::mutex CommandListManager::mMutex;

void
CommandListManager::Clear() noexcept
{
    for (ID3D12GraphicsCommandList* commandList : mCommandLists) {
        BRE_ASSERT(commandList != nullptr);
        commandList->Release();
    }

    mCommandLists.clear();
}

ID3D12GraphicsCommandList&
CommandListManager::CreateCommandList(const D3D12_COMMAND_LIST_TYPE& commandListType,
                                      ID3D12CommandAllocator& commandAllocator) noexcept
{
    ID3D12GraphicsCommandList* commandList{ nullptr };

    mMutex.lock();
    BRE_CHECK_HR(DirectXManager::GetDevice().CreateCommandList(0U,
                                                               commandListType,
                                                               &commandAllocator,
                                                               nullptr,
                                                               IID_PPV_ARGS(&commandList)));
    mMutex.unlock();

    BRE_ASSERT(commandList != nullptr);
    mCommandLists.insert(commandList);

    return *commandList;
}
}

Command Queue Manager

CommandQueueManager is responsible for the creation of command queues (ID3D12CommandQueue). Its implementation is the following

CommandQueueManager.h

#pragma once

#include <d3d12.h>
#include <mutex>
#include <tbb/concurrent_unordered_set.h>

namespace BRE {
///
/// @brief Responsible for command queue creation
///
class CommandQueueManager {
public:
    CommandQueueManager() = delete;
    ~CommandQueueManager() = delete;
    CommandQueueManager(const CommandQueueManager&) = delete;
    const CommandQueueManager& operator=(const CommandQueueManager&) = delete;
    CommandQueueManager(CommandQueueManager&&) = delete;
    CommandQueueManager& operator=(CommandQueueManager&&) = delete;

    ///
    /// @brief Release all command queues
    ///
    static void Clear() noexcept;

    ///
    /// @brief Create a command queue
    /// @param descriptor Descriptor of the command queue
    /// @return The created command queue
    ///
    static ID3D12CommandQueue& CreateCommandQueue(const D3D12_COMMAND_QUEUE_DESC& descriptor) noexcept;

private:
    static tbb::concurrent_unordered_set<ID3D12CommandQueue*> mCommandQueues;

    static std::mutex mMutex;
};
}

CommandQueueManager.cpp

#include "CommandQueueManager.h"

#include <DirectXManager/DirectXManager.h>
#include <Utils/DebugUtils.h>

namespace BRE {
tbb::concurrent_unordered_set<ID3D12CommandQueue*> CommandQueueManager::mCommandQueues;
std::mutex CommandQueueManager::mMutex;

void
CommandQueueManager::Clear() noexcept
{
    for (ID3D12CommandQueue* commandQueue : mCommandQueues) {
        BRE_ASSERT(commandQueue != nullptr);
        commandQueue->Release();
    }

    mCommandQueues.clear();
}

ID3D12CommandQueue&
CommandQueueManager::CreateCommandQueue(const D3D12_COMMAND_QUEUE_DESC& descriptor) noexcept
{
    ID3D12CommandQueue* commandQueue{ nullptr };

    mMutex.lock();
    BRE_CHECK_HR(DirectXManager::GetDevice().CreateCommandQueue(&descriptor,
                                                                IID_PPV_ARGS(&commandQueue)));
    mMutex.unlock();

    BRE_ASSERT(commandQueue != nullptr);
    mCommandQueues.insert(commandQueue);

    return *commandQueue;
}
}

Cbv Srv Uav Descriptor Manager

CBV, SRV, and UAV are the abbreviations of Constant Buffer View, Shader Resource View, and Unordered Access View, respectively. Although I do not like to abbreviate, in this case, it is necessary to avoid a very long name, and also, CBV, SRV, and UAV are very well known abbreviations. CbvSrvUavDescriptorManager is responsible for the creation of the descriptor heap for this kind of views, and also, for the creation of these types of views or descriptors. Its implementation is the following

CbvSrvUavDescriptorManager.h

#pragma once

#include <d3d12.h>
#include <mutex>
#include <wrl.h>

#include <Utils/DebugUtils.h>

namespace BRE {
///
/// @brief Responsible to create constant buffers, shader resource views,
/// and unordered access views.
///
class CbvSrvUavDescriptorManager {
public:
    CbvSrvUavDescriptorManager() = delete;
    ~CbvSrvUavDescriptorManager() = delete;
    CbvSrvUavDescriptorManager(const CbvSrvUavDescriptorManager&) = delete;
    const CbvSrvUavDescriptorManager& operator=(const CbvSrvUavDescriptorManager&) = delete;
    CbvSrvUavDescriptorManager(CbvSrvUavDescriptorManager&&) = delete;
    CbvSrvUavDescriptorManager& operator=(CbvSrvUavDescriptorManager&&) = delete;

    ///
    /// @brief Initializes manager, for example, descriptor heap.
    /// @param numDescriptorsInCbvSrvUavDescriptorHeap Number of descriptors in
    /// descriptor heap of Constant Buffer Views, Shader Resource Views, and Unordered Access Views.
    ///
    static void Init(const std::uint32_t numDescriptorsInCbvSrvUavDescriptorHeap) noexcept;

    ///
    /// @brief Create a constant buffer view
    /// @param cBufferViewDescriptor Constant buffer view descriptor
    /// @return Gpu descriptor handle of the view
    ///
    static D3D12_GPU_DESCRIPTOR_HANDLE
        CreateConstantBufferView(const D3D12_CONSTANT_BUFFER_VIEW_DESC& cBufferViewDescriptor) noexcept;

    ///
    /// @brief Create constant buffer views
    /// @param descriptors Constant buffer views descriptors. It must not be nullptr.
    /// @param descriptorCount Number of constant buffer views descriptors. It must be greater than zero.
    /// @return The GPU descriptor handle to the first element. All the others views are contiguous,
    /// then you can easily build GPU descriptor handle for other view.
    ///
    static D3D12_GPU_DESCRIPTOR_HANDLE CreateConstantBufferViews(const D3D12_CONSTANT_BUFFER_VIEW_DESC* descriptors,
                                                                 const std::uint32_t descriptorCount) noexcept;

    ///
    /// @brief Create a shader resource view
    /// @param resource Resource to create the with to
    /// @param shaderResourceViewDescriptor The shader resource view descriptor
    /// @return The gpu descriptor handle for the view
    /// 
    static D3D12_GPU_DESCRIPTOR_HANDLE CreateShaderResourceView(ID3D12Resource& resource,
                                                                const D3D12_SHADER_RESOURCE_VIEW_DESC& shaderResourceViewDescriptor) noexcept;

    ///
    /// @brief Create shader resource views
    /// @param resources The list of resources to create views. It must not be nullptr.
    /// @param descriptors Shader resource views descriptors. It must not be nullptr.
    /// @param descriptorCount Number of shader resource views descriptors. It must be greater than zero.
    /// @return The GPU descriptor handle to the first element. All the others views are contiguous,
    /// then you can easily build GPU descriptor handle for other view.
    ///
    static D3D12_GPU_DESCRIPTOR_HANDLE CreateShaderResourceViews(ID3D12Resource* *resources,
                                                                 const D3D12_SHADER_RESOURCE_VIEW_DESC* descriptors,
                                                                 const std::uint32_t descriptorCount) noexcept;

    ///
    /// @brief Create unordered access view
    /// @param resource The resource to create the view.
    /// @param descriptor Unordered access view descriptor
    /// @return The GPU descriptor handle for the view
    ///
    static D3D12_GPU_DESCRIPTOR_HANDLE CreateUnorderedAccessView(ID3D12Resource& resource,
                                                                 const D3D12_UNORDERED_ACCESS_VIEW_DESC& descriptor) noexcept;

    ///
    /// @brief Create unordered access resource views
    /// @param resources The list of resources to create views. It must not be nullptr.
    /// @param descriptors Unordered access resource views descriptors. It must not be nullptr.
    /// @param descriptorCount Number of unordered access resource views descriptors. It must be greater than zero.
    /// @return The GPU descriptor handle to the first element. All the others views are contiguous,
    /// then you can easily build GPU descriptor handle for other view.
    ///
    static D3D12_GPU_DESCRIPTOR_HANDLE CreateUnorderedAccessViews(ID3D12Resource* *resources,
                                                                  const D3D12_UNORDERED_ACCESS_VIEW_DESC* descriptors,
                                                                  const std::uint32_t descriptorCount) noexcept;

    ///
    /// @brief Get descriptor heap
    /// @return The descriptor heap
    ///
    static ID3D12DescriptorHeap& GetDescriptorHeap() noexcept
    {
        BRE_ASSERT(mCbvSrvUavDescriptorHeap.Get() != nullptr);
        return *mCbvSrvUavDescriptorHeap.Get();
    }

private:
    static Microsoft::WRL::ComPtr<ID3D12DescriptorHeap> mCbvSrvUavDescriptorHeap;

    static D3D12_GPU_DESCRIPTOR_HANDLE mCurrentCbvSrvUavGpuDescriptorHandle;
    static D3D12_CPU_DESCRIPTOR_HANDLE mCurrentCbvSrvUavCpuDescriptorHandle;

    static std::mutex mMutex;
};
}

CbvSrvUavDescriptorManager.cpp

#include "CbvSrvUavDescriptorManager.h"

#include <memory>

#include <DirectXManager\DirectXManager.h>
#include <DXUtils/d3dx12.h>

namespace BRE {
Microsoft::WRL::ComPtr<ID3D12DescriptorHeap> CbvSrvUavDescriptorManager::mCbvSrvUavDescriptorHeap;
D3D12_GPU_DESCRIPTOR_HANDLE CbvSrvUavDescriptorManager::mCurrentCbvSrvUavGpuDescriptorHandle{ 0UL };
D3D12_CPU_DESCRIPTOR_HANDLE CbvSrvUavDescriptorManager::mCurrentCbvSrvUavCpuDescriptorHandle{ 0UL };
std::mutex CbvSrvUavDescriptorManager::mMutex;

void
CbvSrvUavDescriptorManager::Init(const std::uint32_t numDescriptorsInCbvSrvUavDescriptorHeap) noexcept
{
    D3D12_DESCRIPTOR_HEAP_DESC cbvSrvUavDescriptorHeapDescriptor{};
    cbvSrvUavDescriptorHeapDescriptor.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
    cbvSrvUavDescriptorHeapDescriptor.NodeMask = 0U;
    cbvSrvUavDescriptorHeapDescriptor.NumDescriptors = numDescriptorsInCbvSrvUavDescriptorHeap;
    cbvSrvUavDescriptorHeapDescriptor.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;

    mMutex.lock();
    BRE_CHECK_HR(DirectXManager::GetDevice().CreateDescriptorHeap(&cbvSrvUavDescriptorHeapDescriptor,
                                                                  IID_PPV_ARGS(mCbvSrvUavDescriptorHeap.GetAddressOf())));
    mMutex.unlock();

    mCurrentCbvSrvUavGpuDescriptorHandle = mCbvSrvUavDescriptorHeap->GetGPUDescriptorHandleForHeapStart();
    mCurrentCbvSrvUavCpuDescriptorHandle = mCbvSrvUavDescriptorHeap->GetCPUDescriptorHandleForHeapStart();
}

D3D12_GPU_DESCRIPTOR_HANDLE
CbvSrvUavDescriptorManager::CreateConstantBufferView(const D3D12_CONSTANT_BUFFER_VIEW_DESC& descriptor) noexcept
{
    D3D12_GPU_DESCRIPTOR_HANDLE gpuDescriptorHandle{};

    mMutex.lock();
    gpuDescriptorHandle = mCurrentCbvSrvUavGpuDescriptorHandle;

    DirectXManager::GetDevice().CreateConstantBufferView(&descriptor, mCurrentCbvSrvUavCpuDescriptorHandle);

    mCurrentCbvSrvUavGpuDescriptorHandle.ptr +=
        DirectXManager::GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);

    mCurrentCbvSrvUavCpuDescriptorHandle.ptr +=
        DirectXManager::GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);

    mMutex.unlock();

    return gpuDescriptorHandle;
}

D3D12_GPU_DESCRIPTOR_HANDLE
CbvSrvUavDescriptorManager::CreateConstantBufferViews(const D3D12_CONSTANT_BUFFER_VIEW_DESC* descriptors,
                                                      const std::uint32_t descriptorCount) noexcept
{
    BRE_ASSERT(descriptors != nullptr);
    BRE_ASSERT(descriptorCount > 0U);

    D3D12_GPU_DESCRIPTOR_HANDLE gpuDescriptorHandle{};

    mMutex.lock();
    gpuDescriptorHandle = mCurrentCbvSrvUavGpuDescriptorHandle;

    for (std::uint32_t i = 0U; i < descriptorCount; ++i) {
        DirectXManager::GetDevice().CreateConstantBufferView(&descriptors[i], mCurrentCbvSrvUavCpuDescriptorHandle);
        mCurrentCbvSrvUavCpuDescriptorHandle.ptr +=
            DirectXManager::GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
    }

    mCurrentCbvSrvUavGpuDescriptorHandle.ptr +=
        descriptorCount * DirectXManager::GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);

    mMutex.unlock();

    return gpuDescriptorHandle;
}

D3D12_GPU_DESCRIPTOR_HANDLE
CbvSrvUavDescriptorManager::CreateShaderResourceView(ID3D12Resource& resource,
                                                     const D3D12_SHADER_RESOURCE_VIEW_DESC& descriptor) noexcept
{
    D3D12_GPU_DESCRIPTOR_HANDLE gpuDescriptorHandle{};

    mMutex.lock();
    gpuDescriptorHandle = mCurrentCbvSrvUavGpuDescriptorHandle;

    DirectXManager::GetDevice().CreateShaderResourceView(&resource,
                                                         &descriptor,
                                                         mCurrentCbvSrvUavCpuDescriptorHandle);

    mCurrentCbvSrvUavGpuDescriptorHandle.ptr +=
        DirectXManager::GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);

    mCurrentCbvSrvUavCpuDescriptorHandle.ptr +=
        DirectXManager::GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);

    mMutex.unlock();

    return gpuDescriptorHandle;
}

D3D12_GPU_DESCRIPTOR_HANDLE
CbvSrvUavDescriptorManager::CreateShaderResourceViews(ID3D12Resource* *resources,
                                                      const D3D12_SHADER_RESOURCE_VIEW_DESC* descriptors,
                                                      const std::uint32_t descriptorCount) noexcept
{
    BRE_ASSERT(resources != nullptr);
    BRE_ASSERT(descriptors != nullptr);
    BRE_ASSERT(descriptorCount > 0U);

    D3D12_GPU_DESCRIPTOR_HANDLE gpuDescriptorHandle{};

    mMutex.lock();
    gpuDescriptorHandle = mCurrentCbvSrvUavGpuDescriptorHandle;

    for (std::uint32_t i = 0U; i < descriptorCount; ++i) {
        BRE_ASSERT(resources[i] != nullptr);
        DirectXManager::GetDevice().CreateShaderResourceView(resources[i],
                                                             &descriptors[i],
                                                             mCurrentCbvSrvUavCpuDescriptorHandle);
        mCurrentCbvSrvUavCpuDescriptorHandle.ptr +=
            DirectXManager::GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
    }

    mCurrentCbvSrvUavGpuDescriptorHandle.ptr +=
        descriptorCount * DirectXManager::GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);

    mMutex.unlock();

    return gpuDescriptorHandle;
}

D3D12_GPU_DESCRIPTOR_HANDLE
CbvSrvUavDescriptorManager::CreateUnorderedAccessView(ID3D12Resource& resource,
                                                      const D3D12_UNORDERED_ACCESS_VIEW_DESC& descriptor) noexcept
{
    D3D12_GPU_DESCRIPTOR_HANDLE gpuDescriptorHandle{};

    mMutex.lock();
    gpuDescriptorHandle = mCurrentCbvSrvUavGpuDescriptorHandle;

    DirectXManager::GetDevice().CreateUnorderedAccessView(&resource,
                                                          nullptr,
                                                          &descriptor,
                                                          mCurrentCbvSrvUavCpuDescriptorHandle);

    mCurrentCbvSrvUavGpuDescriptorHandle.ptr +=
        DirectXManager::GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);

    mCurrentCbvSrvUavCpuDescriptorHandle.ptr +=
        DirectXManager::GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);

    mMutex.unlock();

    return gpuDescriptorHandle;
}

D3D12_GPU_DESCRIPTOR_HANDLE
CbvSrvUavDescriptorManager::CreateUnorderedAccessViews(ID3D12Resource* *resources,
                                                       const D3D12_UNORDERED_ACCESS_VIEW_DESC* descriptors,
                                                       const std::uint32_t descriptorCount) noexcept
{
    BRE_ASSERT(resources != nullptr);
    BRE_ASSERT(descriptors != nullptr);
    BRE_ASSERT(descriptorCount > 0U);

    D3D12_GPU_DESCRIPTOR_HANDLE gpuDescriptorHandle{};

    mMutex.lock();
    gpuDescriptorHandle = mCurrentCbvSrvUavGpuDescriptorHandle;

    for (std::uint32_t i = 0U; i < descriptorCount; ++i) {
        BRE_ASSERT(resources[i] != nullptr);
        DirectXManager::GetDevice().CreateUnorderedAccessView(resources[i],
                                                              nullptr,
                                                              &descriptors[i],
                                                              mCurrentCbvSrvUavCpuDescriptorHandle);
        mCurrentCbvSrvUavCpuDescriptorHandle.ptr +=
            DirectXManager::GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
    }

    mCurrentCbvSrvUavGpuDescriptorHandle.ptr +=
        descriptorCount * DirectXManager::GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);

    mMutex.unlock();

    return gpuDescriptorHandle;
}
}

Depth Stencil Descriptor Manager

DepthStencilDescriptorManager is responsible for the creation of the descriptor heap for depth stencil views, and also for the creation of depth stencil views. Its implementation is the following

DepthStencilDescriptorManager.h

#pragma once

#include <d3d12.h>
#include <mutex>
#include <wrl.h>

namespace BRE {
///
/// @brief Responsible to create depth stencil descriptors and heaps
///
class DepthStencilDescriptorManager {
public:
    DepthStencilDescriptorManager() = delete;
    ~DepthStencilDescriptorManager() = delete;
    DepthStencilDescriptorManager(const DepthStencilDescriptorManager&) = delete;
    const DepthStencilDescriptorManager& operator=(const DepthStencilDescriptorManager&) = delete;
    DepthStencilDescriptorManager(DepthStencilDescriptorManager&&) = delete;
    DepthStencilDescriptorManager& operator=(DepthStencilDescriptorManager&&) = delete;

    ///
    /// @brief Initializes the manager
    ///
    static void Init() noexcept;

    ///
    /// @brief Create a depth stencil view
    /// @param resource Resource to create the view to
    /// @param descriptor Depth stencil view descriptor
    /// @param cpuDescriptorHandle Optional parameter. It will store
    /// the CPU descriptor handle.
    /// @return The GPU descriptor handle to the view
    ///
    static D3D12_GPU_DESCRIPTOR_HANDLE CreateDepthStencilView(ID3D12Resource& resource,
                                                              const D3D12_DEPTH_STENCIL_VIEW_DESC& descriptor,
                                                              D3D12_CPU_DESCRIPTOR_HANDLE* cpuDescriptorHandle = nullptr) noexcept;

    ///
    /// @brief Create depth stencil views
    /// @param resources The list of resources to create depth stencil views to them. It must not be nullptr.
    /// @param descriptors The list of depth stencil view descriptors. It must not be nullptr.
    /// @param descriptorCount The number of depth stencil view descriptors. It must be greater than zero.
    /// @param firstViewCpuDescriptorHandle Optional parameter. It will store the CPU descriptor handle to the first element.
    /// As we guarantee all the other views are contiguous, then
    /// you can easily build GPU descriptor handle for other views.
    /// @return It returns the GPU descriptor handle to the first element. 
    /// As we guarantee all the other views are contiguous, then you can easily build GPU descriptor handle for other view.
    ///
    static D3D12_GPU_DESCRIPTOR_HANDLE CreateDepthStencilViews(ID3D12Resource* *resources,
                                                               const D3D12_DEPTH_STENCIL_VIEW_DESC* descriptors,
                                                               const std::uint32_t descriptorCount,
                                                               D3D12_CPU_DESCRIPTOR_HANDLE* firstViewCpuDescriptorHandle = nullptr) noexcept;

private:
    static Microsoft::WRL::ComPtr<ID3D12DescriptorHeap> mDepthStencilViewDescriptorHeap;

    static D3D12_GPU_DESCRIPTOR_HANDLE mCurrentDepthStencilViewGpuDescriptorHandle;
    static D3D12_CPU_DESCRIPTOR_HANDLE mCurrentDepthStencilCpuDescriptorHandle;

    static std::mutex mMutex;
};
}

DepthStencilDescriptorManager.cpp

#include "DepthStencilDescriptorManager.h"

#include <memory>

#include <DirectXManager\DirectXManager.h>
#include <DXUtils/d3dx12.h>
#include <Utils/DebugUtils.h>

namespace BRE {
Microsoft::WRL::ComPtr<ID3D12DescriptorHeap> DepthStencilDescriptorManager::mDepthStencilViewDescriptorHeap;
D3D12_GPU_DESCRIPTOR_HANDLE DepthStencilDescriptorManager::mCurrentDepthStencilViewGpuDescriptorHandle{ 0UL };
D3D12_CPU_DESCRIPTOR_HANDLE DepthStencilDescriptorManager::mCurrentDepthStencilCpuDescriptorHandle{ 0UL };
std::mutex DepthStencilDescriptorManager::mMutex;

void
DepthStencilDescriptorManager::Init() noexcept
{
    D3D12_DESCRIPTOR_HEAP_DESC depthStencilViewDescriptorHeapDescriptor{};
    depthStencilViewDescriptorHeapDescriptor.NumDescriptors = 1U;
    depthStencilViewDescriptorHeapDescriptor.Type = D3D12_DESCRIPTOR_HEAP_TYPE_DSV;
    depthStencilViewDescriptorHeapDescriptor.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
    depthStencilViewDescriptorHeapDescriptor.NodeMask = 0U;

    mMutex.lock();
    BRE_CHECK_HR(DirectXManager::GetDevice().CreateDescriptorHeap(&depthStencilViewDescriptorHeapDescriptor,
                                                                  IID_PPV_ARGS(mDepthStencilViewDescriptorHeap.GetAddressOf())));
    mMutex.unlock();

    mCurrentDepthStencilViewGpuDescriptorHandle = mDepthStencilViewDescriptorHeap->GetGPUDescriptorHandleForHeapStart();
    mCurrentDepthStencilCpuDescriptorHandle = mDepthStencilViewDescriptorHeap->GetCPUDescriptorHandleForHeapStart();
}

D3D12_GPU_DESCRIPTOR_HANDLE
DepthStencilDescriptorManager::CreateDepthStencilView(ID3D12Resource& resource,
                                                      const D3D12_DEPTH_STENCIL_VIEW_DESC& descriptor,
                                                      D3D12_CPU_DESCRIPTOR_HANDLE* cpuDescriptorHandle) noexcept
{
    D3D12_GPU_DESCRIPTOR_HANDLE gpuDescriptorHandle{};

    mMutex.lock();
    gpuDescriptorHandle = mCurrentDepthStencilViewGpuDescriptorHandle;

    if (cpuDescriptorHandle != nullptr) {
        *cpuDescriptorHandle = mCurrentDepthStencilCpuDescriptorHandle;
    }

    DirectXManager::GetDevice().CreateDepthStencilView(&resource,
                                                       &descriptor,
                                                       mCurrentDepthStencilCpuDescriptorHandle);

    mCurrentDepthStencilViewGpuDescriptorHandle.ptr
        += DirectXManager::GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_DSV);

    mCurrentDepthStencilCpuDescriptorHandle.ptr +=
        DirectXManager::GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_DSV);

    mMutex.unlock();

    return gpuDescriptorHandle;
}

D3D12_GPU_DESCRIPTOR_HANDLE
DepthStencilDescriptorManager::CreateDepthStencilViews(ID3D12Resource* *resources,
                                                       const D3D12_DEPTH_STENCIL_VIEW_DESC* descriptors,
                                                       const std::uint32_t descriptorCount,
                                                       D3D12_CPU_DESCRIPTOR_HANDLE* firstViewCpuDescriptorHandle) noexcept
{
    BRE_ASSERT(resources != nullptr);
    BRE_ASSERT(descriptors != nullptr);
    BRE_ASSERT(descriptorCount > 0U);

    D3D12_GPU_DESCRIPTOR_HANDLE gpuDescriptorHandle{};

    mMutex.lock();
    gpuDescriptorHandle = mCurrentDepthStencilViewGpuDescriptorHandle;

    if (firstViewCpuDescriptorHandle != nullptr) {
        *firstViewCpuDescriptorHandle = mCurrentDepthStencilCpuDescriptorHandle;
    }

    for (std::uint32_t i = 0U; i < descriptorCount; ++i) {
        BRE_ASSERT(resources[i] != nullptr);
        DirectXManager::GetDevice().CreateDepthStencilView(resources[i],
                                                           &descriptors[i],
                                                           mCurrentDepthStencilCpuDescriptorHandle);
        mCurrentDepthStencilCpuDescriptorHandle.ptr +=
            DirectXManager::GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_DSV);
    }

    mCurrentDepthStencilViewGpuDescriptorHandle.ptr +=
        descriptorCount * DirectXManager::GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_DSV);

    mMutex.unlock();

    return gpuDescriptorHandle;
}
}

Render Target Descriptor Manager

RenderTargetDescriptorManager is responsible for the creation of the descriptor heap for the render target views, and also, for the creation of the render target views. Its implementation is the following

RenderTargetDescriptorManager.h

#pragma once

#include <d3d12.h>
#include <mutex>
#include <wrl.h>

namespace BRE {
///
/// @brief Responsible to create render target descriptors and heaps.
///
class RenderTargetDescriptorManager {
public:
    RenderTargetDescriptorManager() = delete;
    ~RenderTargetDescriptorManager() = delete;
    RenderTargetDescriptorManager(const RenderTargetDescriptorManager&) = delete;
    const RenderTargetDescriptorManager& operator=(const RenderTargetDescriptorManager&) = delete;
    RenderTargetDescriptorManager(RenderTargetDescriptorManager&&) = delete;
    RenderTargetDescriptorManager& operator=(RenderTargetDescriptorManager&&) = delete;

    ///
    /// @brief Initializes the manager
    /// @param numDescriptorsInRenderTargetDescriptorHeap Number of descriptors in render target
    /// descriptor heap.
    ///
    static void Init(const std::uint32_t numDescriptorsInRenderTargetDescriptorHeap) noexcept;

    ///
    /// @brief Create a render target view
    /// @param resource Resource to create the view to
    /// @param descriptor Render target view descriptor
    /// @param cpuDescriptorHandle Optional parameter. It will store
    /// the CPU descriptor handle.
    /// @return The GPU descriptor handle to the view
    ///
    static D3D12_GPU_DESCRIPTOR_HANDLE CreateRenderTargetView(ID3D12Resource& resource,
                                                              const D3D12_RENDER_TARGET_VIEW_DESC& descriptor,
                                                              D3D12_CPU_DESCRIPTOR_HANDLE* firstViewCpuDescriptorHandle = nullptr) noexcept;

    ///
    /// @brief Create render target views
    /// @param resources The list of resources to create render target views to them. It must not be nullptr.
    /// @param descriptors The list of render target view descriptors. It must not be nullptr.
    /// @param descriptorCount The number of render target view descriptors. It must be greater than zero.
    /// @param firstViewCpuDescriptorHandle Optional parameter. It will store the CPU descriptor handle to the first element.
    /// As we guarantee all the other views are contiguous, then
    /// you can easily build GPU descriptor handle for other views.
    /// @return It returns the GPU descriptor handle to the first element. 
    /// As we guarantee all the other views are contiguous, then you can easily build GPU descriptor handle for other view.
    ///
    static D3D12_GPU_DESCRIPTOR_HANDLE CreateRenderTargetViews(ID3D12Resource* *resources,
                                                               const D3D12_RENDER_TARGET_VIEW_DESC* descriptors,
                                                               const std::uint32_t descriptorCount,
                                                               D3D12_CPU_DESCRIPTOR_HANDLE* firstViewCpuDescriptorHandle = nullptr) noexcept;

private:
    static Microsoft::WRL::ComPtr<ID3D12DescriptorHeap> mRenderTargetViewDescriptorHeap;

    static D3D12_GPU_DESCRIPTOR_HANDLE mCurrentRenderTargetViewDescriptorHandle;
    static D3D12_CPU_DESCRIPTOR_HANDLE mCurrentRenderTargetViewCpuDescriptorHandle;

    static std::mutex mMutex;
};
}

RenderTargetDescriptorManager.cpp

#include "RenderTargetDescriptorManager.h"

#include <memory>

#include <DirectXManager\DirectXManager.h>
#include <DXUtils/d3dx12.h>
#include <Utils/DebugUtils.h>

namespace BRE {
Microsoft::WRL::ComPtr<ID3D12DescriptorHeap> RenderTargetDescriptorManager::mRenderTargetViewDescriptorHeap;
D3D12_GPU_DESCRIPTOR_HANDLE RenderTargetDescriptorManager::mCurrentRenderTargetViewDescriptorHandle{ 0UL };
D3D12_CPU_DESCRIPTOR_HANDLE RenderTargetDescriptorManager::mCurrentRenderTargetViewCpuDescriptorHandle{ 0UL };
std::mutex RenderTargetDescriptorManager::mMutex;

void
RenderTargetDescriptorManager::Init(const std::uint32_t numDescriptorsInRenderTargetDescriptorHeap) noexcept
{
    D3D12_DESCRIPTOR_HEAP_DESC renderTargetViewDescriptorHeapDescriptor{};
    renderTargetViewDescriptorHeapDescriptor.NumDescriptors = numDescriptorsInRenderTargetDescriptorHeap;
    renderTargetViewDescriptorHeapDescriptor.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
    renderTargetViewDescriptorHeapDescriptor.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
    renderTargetViewDescriptorHeapDescriptor.NodeMask = 0;

    mMutex.lock();
    BRE_CHECK_HR(DirectXManager::GetDevice().CreateDescriptorHeap(&renderTargetViewDescriptorHeapDescriptor,
                                                                  IID_PPV_ARGS(mRenderTargetViewDescriptorHeap.GetAddressOf())));
    mMutex.unlock();

    mCurrentRenderTargetViewDescriptorHandle = mRenderTargetViewDescriptorHeap->GetGPUDescriptorHandleForHeapStart();
    mCurrentRenderTargetViewCpuDescriptorHandle = mRenderTargetViewDescriptorHeap->GetCPUDescriptorHandleForHeapStart();
}

D3D12_GPU_DESCRIPTOR_HANDLE
RenderTargetDescriptorManager::CreateRenderTargetView(ID3D12Resource& resource,
                                                      const D3D12_RENDER_TARGET_VIEW_DESC& descriptor,
                                                      D3D12_CPU_DESCRIPTOR_HANDLE* firstViewCpuDescriptorHandle) noexcept
{
    D3D12_GPU_DESCRIPTOR_HANDLE gpuDescriptorHandle{};

    mMutex.lock();
    gpuDescriptorHandle = mCurrentRenderTargetViewDescriptorHandle;

    if (firstViewCpuDescriptorHandle != nullptr) {
        *firstViewCpuDescriptorHandle = mCurrentRenderTargetViewCpuDescriptorHandle;
    }

    DirectXManager::GetDevice().CreateRenderTargetView(&resource,
                                                       &descriptor,
                                                       mCurrentRenderTargetViewCpuDescriptorHandle);

    mCurrentRenderTargetViewDescriptorHandle.ptr +=
        DirectXManager::GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
        
    mCurrentRenderTargetViewCpuDescriptorHandle.ptr +=
        DirectXManager::GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);

    mMutex.unlock();

    return gpuDescriptorHandle;
}

D3D12_GPU_DESCRIPTOR_HANDLE
RenderTargetDescriptorManager::CreateRenderTargetViews(ID3D12Resource* *resources,
                                                       const D3D12_RENDER_TARGET_VIEW_DESC* descriptors,
                                                       const std::uint32_t descriptorCount,
                                                       D3D12_CPU_DESCRIPTOR_HANDLE* firstViewCpuDescriptorHandle) noexcept
{
    BRE_ASSERT(resources != nullptr);
    BRE_ASSERT(descriptors != nullptr);
    BRE_ASSERT(descriptorCount > 0U);

    D3D12_GPU_DESCRIPTOR_HANDLE gpuDescriptorHandle{};

    mMutex.lock();
    gpuDescriptorHandle = mCurrentRenderTargetViewDescriptorHandle;

    if (firstViewCpuDescriptorHandle != nullptr) {
        *firstViewCpuDescriptorHandle = mCurrentRenderTargetViewCpuDescriptorHandle;
    }

    for (std::uint32_t i = 0U; i < descriptorCount; ++i) {
        BRE_ASSERT(resources[i] != nullptr);
        DirectXManager::GetDevice().CreateRenderTargetView(resources[i],
                                                           &descriptors[i],
                                                           mCurrentRenderTargetViewCpuDescriptorHandle);
        mCurrentRenderTargetViewCpuDescriptorHandle.ptr +=
            DirectXManager::GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
    }

    mCurrentRenderTargetViewDescriptorHandle.ptr +=
        descriptorCount * DirectXManager::GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);

    mMutex.unlock();

    return gpuDescriptorHandle;
}
}

Pso Manager

PSO is the abbreviation of Pipeline State Object. Although I do not like to abbreviate, in this case, I did it because PSO is a very well known terminology. PsoManager provides methods to create pipeline state objects, although for now it only creates graphics PSOs because they are the types of PSOs that I need. Other types can be added easily. In my case, I always prefer to have the smallest possible interface. Its implementation is the following

PsoManager.h

#pragma once

#include <d3d12.h>
#include <mutex>
#include <tbb/concurrent_unordered_set.h>

#include <DXUtils/D3DFactory.h>

namespace BRE {
///
/// @brief Responsible to create pipeline state objects
///
class PSOManager {
public:
    PSOManager() = delete;
    ~PSOManager() = delete;
    PSOManager(const PSOManager&) = delete;
    const PSOManager& operator=(const PSOManager&) = delete;
    PSOManager(PSOManager&&) = delete;
    PSOManager& operator=(PSOManager&&) = delete;

    ///
    /// @brief Releases all pipeline state objects
    ///
    static void Clear() noexcept;

    struct PSOCreationData {
        PSOCreationData() = default;
        ~PSOCreationData() = default;
        PSOCreationData(const PSOCreationData&) = delete;
        const PSOCreationData& operator=(const PSOCreationData&) = delete;
        PSOCreationData(PSOCreationData&&) = delete;
        PSOCreationData& operator=(PSOCreationData&&) = delete;

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

        std::vector<D3D12_INPUT_ELEMENT_DESC> mInputLayoutDescriptors{};

        ID3D12RootSignature* mRootSignature{ nullptr };

        // If a shader bytecode is not valid, then we do not load it.
        D3D12_SHADER_BYTECODE mVertexShaderBytecode{ 0UL };
        D3D12_SHADER_BYTECODE mGeometryShaderBytecode{ 0UL };
        D3D12_SHADER_BYTECODE mDomainShaderBytecode{ 0UL };
        D3D12_SHADER_BYTECODE mHullShaderBytecode{ 0UL };
        D3D12_SHADER_BYTECODE mPixelShaderBytecode{ 0UL };

        D3D12_BLEND_DESC mBlendDescriptor = D3DFactory::GetDisabledBlendDesc();
        D3D12_RASTERIZER_DESC mRasterizerDescriptor = D3DFactory::GetDefaultRasterizerDesc();
        D3D12_DEPTH_STENCIL_DESC mDepthStencilDescriptor = D3DFactory::GetDefaultDepthStencilDesc();
        std::uint32_t mNumRenderTargets{ 0U };
        DXGI_FORMAT mRenderTargetFormats[D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT]{ DXGI_FORMAT_UNKNOWN };
        DXGI_SAMPLE_DESC mSampleDescriptor{ 1U, 0U };
        std::uint32_t mSampleMask{ UINT_MAX };
        D3D12_PRIMITIVE_TOPOLOGY_TYPE mPrimitiveTopologyType{ D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE };
    };

    ///
    /// @brief Create graphics pipeline state object
    /// @param psoCreationData Pipeline state object creation data. It must be valid
    /// @return Pipeline state object
    ///
    static ID3D12PipelineState& CreateGraphicsPSO(const PSOManager::PSOCreationData& psoCreationData) noexcept;

private:
    ///
    /// @brief Create graphics pipeline state object by descriptor
    /// @param psoDescriptor Graphics pipeline state object descriptor
    /// @return Pipeline state object
    ///
    static ID3D12PipelineState& CreateGraphicsPSOByDescriptor(const D3D12_GRAPHICS_PIPELINE_STATE_DESC& psoDescriptor) noexcept;

    static tbb::concurrent_unordered_set<ID3D12PipelineState*> mPSOs;

    static std::mutex mMutex;
};
}

PsoManager.cpp

#include "PSOManager.h"

#include <DirectXManager/DirectXManager.h>
#include <ApplicationSettings\ApplicationSettings.h>
#include <Utils/DebugUtils.h>

namespace BRE {
tbb::concurrent_unordered_set<ID3D12PipelineState*> PSOManager::mPSOs;
std::mutex PSOManager::mMutex;

void
PSOManager::Clear() noexcept
{
    for (ID3D12PipelineState* pso : mPSOs) {
        BRE_ASSERT(pso != nullptr);
        pso->Release();
    }

    mPSOs.clear();
}

bool
PSOManager::PSOCreationData::IsDataValid() const noexcept
{
    if (mNumRenderTargets == 0 ||
        mNumRenderTargets > D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT ||
        mRootSignature == nullptr) {
        return false;
    }

    for (std::uint32_t i = mNumRenderTargets; i < D3D12_SIMULTANEOUS_RENDER_TARGET_COUNT; ++i) {
        if (mRenderTargetFormats[i] != DXGI_FORMAT_UNKNOWN) {
            return false;
        }
    }

    return true;
}

ID3D12PipelineState&
PSOManager::CreateGraphicsPSO(const PSOManager::PSOCreationData& psoData) noexcept
{
    BRE_ASSERT(psoData.IsDataValid());

    D3D12_GRAPHICS_PIPELINE_STATE_DESC psoDescriptor = {};
    psoDescriptor.BlendState = psoData.mBlendDescriptor;
    psoDescriptor.DepthStencilState = psoData.mDepthStencilDescriptor;
    psoDescriptor.DS = psoData.mDomainShaderBytecode;
    psoDescriptor.DSVFormat = ApplicationSettings::sDepthStencilViewFormat;
    psoDescriptor.GS = psoData.mGeometryShaderBytecode;
    psoDescriptor.HS = psoData.mHullShaderBytecode;
    psoDescriptor.InputLayout =
    {
        psoData.mInputLayoutDescriptors.empty()
        ? nullptr
        : psoData.mInputLayoutDescriptors.data(), static_cast<std::uint32_t>(psoData.mInputLayoutDescriptors.size())
    };
    psoDescriptor.NumRenderTargets = psoData.mNumRenderTargets;
    psoDescriptor.PrimitiveTopologyType = psoData.mPrimitiveTopologyType;
    psoDescriptor.pRootSignature = psoData.mRootSignature;
    psoDescriptor.PS = psoData.mPixelShaderBytecode;
    psoDescriptor.RasterizerState = psoData.mRasterizerDescriptor;
    memcpy(psoDescriptor.RTVFormats, psoData.mRenderTargetFormats, sizeof(psoData.mRenderTargetFormats));
    psoDescriptor.SampleDesc = psoData.mSampleDescriptor;
    psoDescriptor.SampleMask = psoData.mSampleMask;
    psoDescriptor.VS = psoData.mVertexShaderBytecode;

    return CreateGraphicsPSOByDescriptor(psoDescriptor);
}

ID3D12PipelineState&
PSOManager::CreateGraphicsPSOByDescriptor(const D3D12_GRAPHICS_PIPELINE_STATE_DESC& psoDescriptor) noexcept
{
    ID3D12PipelineState* pso{ nullptr };

    mMutex.lock();
    BRE_CHECK_HR(DirectXManager::GetDevice().CreateGraphicsPipelineState(&psoDescriptor, IID_PPV_ARGS(&pso)));
    mMutex.unlock();

    BRE_ASSERT(pso != nullptr);
    mPSOs.insert(pso);

    return *pso;
}
}

Resource Manager

ResourceManager is responsible for the creation of resources (ID3D12Resource) like, for example, a texture file loaded from disk, a committed resource, or a default buffer. Its implementation is the following

ResourceManager.h

#pragma once

#include <d3d12.h>
#include <mutex>
#include <tbb/concurrent_unordered_set.h>

#include <ResourceManager/UploadBuffer.h>

namespace BRE {
///
/// @brief Responsible to create textures, buffers and resources.
///
class ResourceManager {
public:
    ResourceManager() = delete;
    ~ResourceManager() = delete;
    ResourceManager(const ResourceManager&) = delete;
    const ResourceManager& operator=(const ResourceManager&) = delete;
    ResourceManager(ResourceManager&&) = delete;
    ResourceManager& operator=(ResourceManager&&) = delete;

    ///
    /// @brief Releases all resources
    ///
    static void Clear() noexcept;

    ///
    /// @brief Loads texture from file
    /// @param textureFilename Texture filename. Must be not nullptr
    /// @param commandList Command list used to upload texture content to GPU.
    /// It must be executed after this function call to upload texture content to GPU.
    /// It must be in recording state before calling this method.
    /// @param uploadBuffer Upload buffer to upload the texture 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 uploadBuffer after it knows the copy has been executed.
    /// @param resourceName Resource name. If it is nullptr, then it will have the default name.
    ///
    static ID3D12Resource& LoadTextureFromFile(const char* textureFilename,
                                               ID3D12GraphicsCommandList& commandList,
                                               Microsoft::WRL::ComPtr<ID3D12Resource>& uploadBuffer,
                                               const wchar_t* resourceName) noexcept;

    ///
    /// @brief Creates default buffer
    /// @param sourceData Source data for the buffer
    /// @param sourceDataSize Source data size for the buffer
    /// @param commandList Command list used to upload buffer content to GPU.
    /// It must be executed after this function call to upload buffer content to GPU.
    /// It must be in recording state before calling this method.
    /// @param uploadBuffer Upload buffer to upload the 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 uploadBuffer after it knows the copy has been executed.
    /// @param resourceName Resource name. If it is nullptr, then it will have the default name.
    ///
    static ID3D12Resource& CreateDefaultBuffer(const void* sourceData,
                                               const std::size_t sourceDataSize,
                                               ID3D12GraphicsCommandList& commandList,
                                               Microsoft::WRL::ComPtr<ID3D12Resource>& uploadBuffer,
                                               const wchar_t* resourceName) noexcept;

    ///
    /// @brief Creates committed resource
    /// @param heapProperties Heap properties
    /// @param heapFlags Heap flags
    /// @param resourceDescriptor Resource descriptor
    /// @param resourceStates Resource states
    /// @param clearValue Clear value
    /// @param resourceName Resource name. If it is nullptr, then it will have the default name.
    ///
    static ID3D12Resource& CreateCommittedResource(const D3D12_HEAP_PROPERTIES& heapProperties,
                                                   const D3D12_HEAP_FLAGS& heapFlags,
                                                   const D3D12_RESOURCE_DESC& resourceDescriptor,
                                                   const D3D12_RESOURCE_STATES& resourceStates,
                                                   const D3D12_CLEAR_VALUE* clearValue,
                                                   const wchar_t* resourceName) noexcept;

private:
    static tbb::concurrent_unordered_set<ID3D12Resource*> mResources;

    static std::mutex mMutex;
};
}

ResourceManager.cpp

#include "ResourceManager.h"

#include <DirectXManager/DirectXManager.h>
#include <DXUtils/d3dx12.h>
#include <ResourceManager\DDSTextureLoader.h>
#include <ResourceStateManager\ResourceStateManager.h>
#include <ApplicationSettings\ApplicationSettings.h>
#include <Utils/DebugUtils.h>
#include <Utils\StringUtils.h>

namespace BRE {
tbb::concurrent_unordered_set<ID3D12Resource*> ResourceManager::mResources;
std::mutex ResourceManager::mMutex;

void
ResourceManager::Clear() noexcept
{
    for (ID3D12Resource* resource : mResources) {
        BRE_ASSERT(resource != nullptr);
        resource->Release();
    }

    mResources.clear();
}

ID3D12Resource&
ResourceManager::LoadTextureFromFile(const char* textureFilename,
                                     ID3D12GraphicsCommandList& commandList,
                                     Microsoft::WRL::ComPtr<ID3D12Resource>& uploadBuffer,
                                     const wchar_t* resourceName) noexcept
{
    ID3D12Resource* resource{ nullptr };

    BRE_ASSERT(textureFilename != nullptr);
    const std::string filePath(textureFilename);
    const std::wstring filePathW(StringUtils::AnsiToWideString(filePath));

    Microsoft::WRL::ComPtr<ID3D12Resource> resourcePtr;
    mMutex.lock();
    BRE_CHECK_HR(DirectX::CreateDDSTextureFromFile12(&DirectXManager::GetDevice(),
                                                     &commandList,
                                                     filePathW.c_str(),
                                                     resourcePtr,
                                                     uploadBuffer));
    mMutex.unlock();

    resource = resourcePtr.Detach();

    BRE_ASSERT(resource != nullptr);
    mResources.insert(resource);

    if (resourceName != nullptr) {
        resource->SetName(resourceName);
    }

    return *resource;
}

ID3D12Resource&
ResourceManager::CreateDefaultBuffer(const void* sourceData,
                                     const std::size_t sourceDataSize,
                                     ID3D12GraphicsCommandList& commandList,
                                     Microsoft::WRL::ComPtr<ID3D12Resource>& uploadBuffer,
                                     const wchar_t* resourceName) noexcept
{
    BRE_ASSERT(sourceData != nullptr);
    BRE_ASSERT(sourceDataSize > 0);

    ID3D12Resource* resource{ nullptr };

    // Create the actual default buffer resource.
    D3D12_HEAP_PROPERTIES heapProps{};
    heapProps.Type = D3D12_HEAP_TYPE_DEFAULT;
    heapProps.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;
    heapProps.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN;
    heapProps.CreationNodeMask = 1U;
    heapProps.VisibleNodeMask = 1U;

    CD3DX12_RESOURCE_DESC resDesc{ CD3DX12_RESOURCE_DESC::Buffer(sourceDataSize) };
    mMutex.lock();
    BRE_CHECK_HR(DirectXManager::GetDevice().CreateCommittedResource(&heapProps,
                                                                     D3D12_HEAP_FLAG_NONE,
                                                                     &resDesc,
                                                                     D3D12_RESOURCE_STATE_COMMON,
                                                                     nullptr,
                                                                     IID_PPV_ARGS(&resource)));

    // In order to copy CPU memory data into our default buffer, we need to create
    // an intermediate upload heap. 
    heapProps = D3D12_HEAP_PROPERTIES{};
    heapProps.Type = D3D12_HEAP_TYPE_UPLOAD;
    heapProps.CPUPageProperty = D3D12_CPU_PAGE_PROPERTY_UNKNOWN;
    heapProps.MemoryPoolPreference = D3D12_MEMORY_POOL_UNKNOWN;
    heapProps.CreationNodeMask = 1U;
    heapProps.VisibleNodeMask = 1U;
    resDesc = CD3DX12_RESOURCE_DESC::Buffer(sourceDataSize);

    BRE_CHECK_HR(DirectXManager::GetDevice().CreateCommittedResource(&heapProps,
                                                                     D3D12_HEAP_FLAG_NONE,
                                                                     &resDesc,
                                                                     D3D12_RESOURCE_STATE_GENERIC_READ,
                                                                     nullptr,
                                                                     IID_PPV_ARGS(uploadBuffer.GetAddressOf())));
    mMutex.unlock();

    // Describe the data we want to copy into the default buffer.
    D3D12_SUBRESOURCE_DATA subResourceData = {};
    subResourceData.pData = sourceData;
    subResourceData.RowPitch = sourceDataSize;
    subResourceData.SlicePitch = subResourceData.RowPitch;

    // Schedule to copy the data to the default buffer resource. At a high level, the helper function UpdateSubresources
    // will copy the CPU memory into the intermediate upload heap.  Then, using ID3D12CommandList::CopySubresourceRegion,
    // the intermediate upload heap data will be copied to mBuffer.
    CD3DX12_RESOURCE_BARRIER resBarrier{ CD3DX12_RESOURCE_BARRIER::Transition(resource,
                                                                              D3D12_RESOURCE_STATE_COMMON, D3D12_RESOURCE_STATE_COPY_DEST) };
    commandList.ResourceBarrier(1, &resBarrier);
    UpdateSubresources<1>(&commandList, resource, uploadBuffer.Get(), 0, 0, 1, &subResourceData);
    resBarrier = CD3DX12_RESOURCE_BARRIER::Transition(resource,
                                                      D3D12_RESOURCE_STATE_COPY_DEST,
                                                      D3D12_RESOURCE_STATE_GENERIC_READ);
    commandList.ResourceBarrier(1, &resBarrier);

    BRE_ASSERT(resource != nullptr);
    mResources.insert(resource);

    if (resourceName != nullptr) {
        resource->SetName(resourceName);
    }

    return *resource;
}

ID3D12Resource&
ResourceManager::CreateCommittedResource(const D3D12_HEAP_PROPERTIES& heapProperties,
                                         const D3D12_HEAP_FLAGS& heapFlags,
                                         const D3D12_RESOURCE_DESC& resourceDescriptor,
                                         const D3D12_RESOURCE_STATES& resourceStates,
                                         const D3D12_CLEAR_VALUE* clearValue,
                                         const wchar_t* resourceName) noexcept
{
    ID3D12Resource* resource{ nullptr };

    mMutex.lock();
    BRE_CHECK_HR(DirectXManager::GetDevice().CreateCommittedResource(&heapProperties,
                                                                     heapFlags,
                                                                     &resourceDescriptor,
                                                                     resourceStates,
                                                                     clearValue,
                                                                     IID_PPV_ARGS(&resource)));
    mMutex.unlock();

    ResourceStateManager::AddResource(*resource, resourceStates);

    BRE_ASSERT(resource != nullptr);
    mResources.insert(resource);

    if (resourceName != nullptr) {
        resource->SetName(resourceName);
    }

    return *resource;
}
}

Upload Buffer Manager

An UploadBuffer is a committed resource that is used to upload data from CPU to GPU. The responsibility of the UploadBufferManager is to create this kind of resources. The implementation of the UploadBuffer is the following

UploadBuffer.h

#pragma once

#include <cstddef>
#include <cstdint>
#include <d3d12.h>
#include <wrl.h>

namespace BRE {
///
/// @brief Represents a buffer to upload data to GPU
///
class UploadBuffer {
public:
    ///
    /// @brief Upload buffer constructor
    /// @param device Device needed to create the buffer
    /// @param elementSize Size in bytes of the type of element in the buffer
    /// @param elementCount Number of elements in the buffer
    ///
    explicit UploadBuffer(ID3D12Device& device,
                          const std::size_t elementSize,
                          const std::uint32_t elementCount);

    ~UploadBuffer();
    UploadBuffer(const UploadBuffer&) = delete;
    const UploadBuffer& operator=(const UploadBuffer&) = delete;
    UploadBuffer(UploadBuffer&&) = delete;
    UploadBuffer& operator=(UploadBuffer&&) = delete;

    ///
    /// @brief Get resource
    /// @return Resource
    ///
    __forceinline ID3D12Resource* GetResource() const noexcept
    {
        return mBuffer.Get();
    }

    ///
    /// @brief Copy data
    /// @param elementIndex Element index to copy
    /// @param sourceData Source data to copy to the element in @p elementIndex. Must not be nullptr
    /// @param sourceDataSize Size of the source data. Must be greater than zero.
    ///
    void CopyData(const std::uint32_t elementIndex,
                  const void* sourceData,
                  const std::size_t sourceDataSize) const noexcept;

    ///
    /// @brief Get rounded constant buffer size in bytes
    ///
    /// Constant buffers must be a multiple of the minimum hardware
    /// allocation size (usually 256 bytes). So round up to nearest
    /// multiple of 256.
    ///
    /// @param sizeInBytes Size in bytes of the data. Must be greater than zero
    /// @return Rounded size
    ///
    static std::size_t GetRoundedConstantBufferSizeInBytes(const std::size_t sizeInBytes);

private:
    Microsoft::WRL::ComPtr<ID3D12Resource> mBuffer;
    std::uint8_t* mMappedData{ nullptr };
    std::size_t mElementSize{ 0U };
};
}

UploadBuffer.cpp

#include "UploadBuffer.h"

#include <DxUtils/d3dx12.h>
#include <Utils/DebugUtils.h>

namespace BRE {
UploadBuffer::UploadBuffer(ID3D12Device& device,
                           const std::size_t elementSize,
                           const std::uint32_t elementCount)
    : mElementSize(elementSize)
{
    BRE_ASSERT(elementSize > 0);
    BRE_ASSERT(elementCount > 0);

    CD3DX12_HEAP_PROPERTIES heapProperties{ D3D12_HEAP_TYPE_UPLOAD };
    CD3DX12_RESOURCE_DESC resourceDescriptor{ CD3DX12_RESOURCE_DESC::Buffer(mElementSize * elementCount) };
    BRE_CHECK_HR(device.CreateCommittedResource(&heapProperties,
                                                D3D12_HEAP_FLAG_NONE,
                                                &resourceDescriptor,
                                                D3D12_RESOURCE_STATE_GENERIC_READ,
                                                nullptr,
                                                IID_PPV_ARGS(&mBuffer)));
    BRE_CHECK_HR(mBuffer->Map(0, nullptr, reinterpret_cast<void**>(&mMappedData)));
}

UploadBuffer::~UploadBuffer()
{
    BRE_ASSERT(mBuffer);
    BRE_ASSERT(mElementSize > 0);
    mBuffer->Unmap(0, nullptr);
    mMappedData = nullptr;
}

void
UploadBuffer::CopyData(const std::uint32_t elementIndex,
                       const void* sourceData,
                       const std::size_t sourceDataSize) const noexcept
{
    BRE_ASSERT(sourceData);
    memcpy(mMappedData + elementIndex * mElementSize, sourceData, sourceDataSize);
}

std::size_t
UploadBuffer::GetRoundedConstantBufferSizeInBytes(const std::size_t sizeInBytes)
{
    // Constant buffers must be a multiple of the minimum hardware
    // allocation size (usually 256 bytes).  So round up to nearest
    // multiple of 256.  We do this by adding 255 and then masking off
    // the lower 2 bytes which store all bits < 256.
    // Example: Suppose sizeInBytes = 300.
    // (300 + 255) & ~255
    // 555 & ~255
    // 0x022B & ~0x00ff
    // 0x022B & 0xff00
    // 0x0200
    // 512
    return (sizeInBytes + 255U) & ~255U;
}
}

and the implementation of the UploadBufferManager is the following

UploadBufferManager.h

#pragma once

#include <d3d12.h>
#include <mutex>
#include <tbb/concurrent_unordered_set.h>

#include <ResourceManager/UploadBuffer.h>

namespace BRE {
///
/// @brief Responsible to create upload buffers
///
class UploadBufferManager {
public:
    UploadBufferManager() = delete;
    ~UploadBufferManager() = delete;
    UploadBufferManager(const UploadBufferManager&) = delete;
    const UploadBufferManager& operator=(const UploadBufferManager&) = delete;
    UploadBufferManager(UploadBufferManager&&) = delete;
    UploadBufferManager& operator=(UploadBufferManager&&) = delete;

    ///
    /// @brief Releases all upload buffers
    ///
    static void Clear() noexcept;

    ///
    /// @brief Creates upload buffer
    /// @param elementSize Size of the element in the upload buffer. Must be greater than zero
    /// @param elementCount Number of elements in the upload buffer. Must be greater than zero.
    /// @return Upload buffer
    ///
    static UploadBuffer& CreateUploadBuffer(const std::size_t elementSize,
                                            const std::uint32_t elementCount) noexcept;

private:
    static tbb::concurrent_unordered_set<UploadBuffer*> mUploadBuffers;

    static std::mutex mMutex;
};
}

UploadBufferManager.cpp

#include "UploadBufferManager.h"

#include <DirectXManager/DirectXManager.h>
#include <Utils/DebugUtils.h>

namespace BRE {
tbb::concurrent_unordered_set<UploadBuffer*> UploadBufferManager::mUploadBuffers;
std::mutex UploadBufferManager::mMutex;

void
UploadBufferManager::Clear() noexcept
{
    for (UploadBuffer* uploadBuffer : mUploadBuffers) {
        BRE_ASSERT(uploadBuffer != nullptr);
        delete uploadBuffer;
    }

    mUploadBuffers.clear();
}

UploadBuffer&
UploadBufferManager::CreateUploadBuffer(const std::size_t elementSize,
                                        const std::uint32_t elementCount) noexcept
{
    BRE_ASSERT(elementSize > 0UL);
    BRE_ASSERT(elementCount > 0U);

    UploadBuffer* uploadBuffer = new UploadBuffer(DirectXManager::GetDevice(),
                                                  elementSize,
                                                  elementCount);
    mUploadBuffers.insert(uploadBuffer);

    return *uploadBuffer;
}
}

that, as you can see, uses the ResourceManager to create the committed resource.

Resource State Manager

ResourceStateManager is responsible to track resources state. You can register the state of a resource, change it, and unregister the resource. This is useful when you want to set a resource barrier and you do not know the previous resource state. Its implementation is the following

ResourceStateManager.h

#pragma once

#include <d3d12.h>
#include <tbb/concurrent_hash_map.h>

#include <DXUtils\d3dx12.h>

namespace BRE {
///
/// @brief Responsible to track resource states.
///
/// Its functionality includes:
/// - Resource state registration
/// - Resource state change
/// - Resource unregistration
///
class ResourceStateManager {
public:
    ResourceStateManager() = delete;
    ~ResourceStateManager() = delete;
    ResourceStateManager(const ResourceStateManager&) = delete;
    const ResourceStateManager& operator=(const ResourceStateManager&) = delete;
    ResourceStateManager(ResourceStateManager&&) = delete;
    ResourceStateManager& operator=(ResourceStateManager&&) = delete;

    ///
    /// @brief Add resource
    ///
    /// Resource must not have been registered
    ///
    /// @param resource Resource to add
    /// @param initialState Initial state of the resource
    ///
    static void AddResource(ID3D12Resource& resource,
                            const D3D12_RESOURCE_STATES initialState) noexcept;

    ///
    /// @brief Change resource state and get barrier
    ///
    /// Resource must have been registered. New state must be different than current state.
    ///
    /// @param resource Resource to change state
    /// @param newState New resource state. It must be different than current state.
    ///
    static CD3DX12_RESOURCE_BARRIER ChangeResourceStateAndGetBarrier(ID3D12Resource& resource,
                                                                     const D3D12_RESOURCE_STATES newState) noexcept;

    ///
    /// @brief Get resource state
    /// @param resource Resource to get state. It must have been registered.
    /// @return Resource state
    ///
    static D3D12_RESOURCE_STATES GetResourceState(ID3D12Resource& resource) noexcept;

private:
    static tbb::concurrent_hash_map<ID3D12Resource*, D3D12_RESOURCE_STATES> mStateByResource;
};
}

ResourceStateManager.cpp

#include "ResourceStateManager.h"

#include <memory>

#include <Utils\DebugUtils.h>

namespace BRE {
tbb::concurrent_hash_map<ID3D12Resource*, D3D12_RESOURCE_STATES> ResourceStateManager::mStateByResource;

void
ResourceStateManager::AddResource(ID3D12Resource& resource,
                                  const D3D12_RESOURCE_STATES initialState) noexcept
{
    tbb::concurrent_hash_map<ID3D12Resource*, D3D12_RESOURCE_STATES>::accessor accessor;
#ifdef _DEBUG
    mStateByResource.find(accessor, &resource);
    BRE_ASSERT(accessor.empty());
#endif
    mStateByResource.insert(accessor, &resource);
    accessor->second = initialState;
    accessor.release();
}

CD3DX12_RESOURCE_BARRIER
ResourceStateManager::ChangeResourceStateAndGetBarrier(ID3D12Resource& resource,
                                                       const D3D12_RESOURCE_STATES newState) noexcept
{
    tbb::concurrent_hash_map<ID3D12Resource*, D3D12_RESOURCE_STATES>::accessor accessor;
    mStateByResource.find(accessor, &resource);
    BRE_ASSERT(accessor.empty() == false);

    const D3D12_RESOURCE_STATES oldState = accessor->second;
    BRE_ASSERT(oldState != newState);
    accessor->second = newState;
    accessor.release();

    return CD3DX12_RESOURCE_BARRIER::Transition(&resource, oldState, newState);
}

D3D12_RESOURCE_STATES
ResourceStateManager::GetResourceState(ID3D12Resource& resource) noexcept
{
    tbb::concurrent_hash_map<ID3D12Resource*, D3D12_RESOURCE_STATES>::accessor accessor;
    mStateByResource.find(accessor, &resource);
    BRE_ASSERT(accessor.empty() == false);
    return accessor->second;
}
}

Root Signature Manager

RootSignatureManager is responsible for the creation of root signatures (ID3D12RootSignature) and its implementation is the following

RootSignatureManager.h

#pragma once

#include <d3d12.h>
#include <mutex>
#include <tbb\concurrent_unordered_set.h>

namespace BRE {
///
/// @brief Responsible to create root signatures
///
class RootSignatureManager {
public:
    RootSignatureManager() = delete;
    ~RootSignatureManager() = delete;
    RootSignatureManager(const RootSignatureManager&) = delete;
    const RootSignatureManager& operator=(const RootSignatureManager&) = delete;
    RootSignatureManager(RootSignatureManager&&) = delete;
    RootSignatureManager& operator=(RootSignatureManager&&) = delete;

    ///
    /// @brief Releases all root signatures
    ///
    static void Clear() noexcept;

    ///
    /// @brief Create root signature from blob
    /// @param blob Blob
    /// @return Root signature
    ///
    static ID3D12RootSignature& CreateRootSignatureFromBlob(ID3DBlob& blob) noexcept;

private:
    static tbb::concurrent_unordered_set<ID3D12RootSignature*> mRootSignatures;

    static std::mutex mMutex;
};
}

RootSignatureManager.cpp

#include "RootSignatureManager.h"

#include <D3Dcompiler.h>

#include <DirectXManager/DirectXManager.h>
#include <Utils/DebugUtils.h>

namespace BRE {
tbb::concurrent_unordered_set<ID3D12RootSignature*> RootSignatureManager::mRootSignatures;
std::mutex RootSignatureManager::mMutex;

void
RootSignatureManager::Clear() noexcept
{
    for (ID3D12RootSignature* rootSignature : mRootSignatures) {
        BRE_ASSERT(rootSignature != nullptr);
        rootSignature->Release();
    }
}

ID3D12RootSignature&
RootSignatureManager::CreateRootSignatureFromBlob(ID3DBlob& blob) noexcept
{
    ID3D12RootSignature* rootSignature{ nullptr };

    mMutex.lock();
    DirectXManager::GetDevice().CreateRootSignature(0U,
                                                    blob.GetBufferPointer(),
                                                    blob.GetBufferSize(),
                                                    IID_PPV_ARGS(&rootSignature));
    mMutex.unlock();

    BRE_ASSERT(rootSignature != nullptr);
    mRootSignatures.insert(rootSignature);

    return *rootSignature;
}
}

Shader Manager

ShaderManager is responsible to load shader files (.cso) from disk and its implementation is the following

ShaderManager.h

#pragma once

#include <d3d12.h>
#include <D3Dcommon.h>
#include <mutex>
#include <tbb\concurrent_unordered_set.h>

namespace BRE {
///
/// @brief Responsible to load and handle shaders
///
class ShaderManager {
public:
    ShaderManager() = delete;
    ~ShaderManager() = delete;
    ShaderManager(const ShaderManager&) = delete;
    const ShaderManager& operator=(const ShaderManager&) = delete;
    ShaderManager(ShaderManager&&) = delete;
    ShaderManager& operator=(ShaderManager&&) = delete;

    ///
    /// @brief Releases all shaders
    ///
    static void Clear() noexcept;

    ///
    /// @brief Load shader file and get blob
    /// @param filename Filename. Must not be nullptr
    /// @return Loaded blob
    ///
    static ID3DBlob& LoadShaderFileAndGetBlob(const char* filename) noexcept;

    ///
    /// @brief Load shader file and get byte code
    /// @param filename Filename. Must not be nullptr
    /// @return Loaded byte code
    ///
    static D3D12_SHADER_BYTECODE LoadShaderFileAndGetBytecode(const char* filename) noexcept;

private:
    static tbb::concurrent_unordered_set<ID3DBlob*> mShaderBlobs;

    static std::mutex mMutex;
};
}

ShaderManager.cpp

#include "ShaderManager.h"

#include <cstdint>
#include <D3Dcompiler.h>
#include <fstream>

#include <Utils/DebugUtils.h>

namespace BRE {
namespace {
///
/// @brief Load a blob
/// @param filename Filename. Must not be nullptr
/// @return Loaded blob
///
ID3DBlob*
LoadBlob(const std::string& filename) noexcept
{
    std::ifstream fileStream{ filename, std::ios::binary };
    BRE_ASSERT(fileStream);

    fileStream.seekg(0, std::ios_base::end);
    std::ifstream::pos_type size{ static_cast<std::int32_t>(fileStream.tellg()) };
    fileStream.seekg(0, std::ios_base::beg);

    ID3DBlob* blob;
    BRE_CHECK_HR(D3DCreateBlob(size, &blob));

    fileStream.read(reinterpret_cast<char*>(blob->GetBufferPointer()), size);
    fileStream.close();

    return blob;
}
}

tbb::concurrent_unordered_set<ID3DBlob*> ShaderManager::mShaderBlobs;
std::mutex ShaderManager::mMutex;

void
ShaderManager::Clear() noexcept
{
    for (ID3DBlob* blob : mShaderBlobs) {
        BRE_ASSERT(blob != nullptr);
        blob->Release();
    }

    mShaderBlobs.clear();
}

ID3DBlob&
ShaderManager::LoadShaderFileAndGetBlob(const char* filename) noexcept
{
    BRE_ASSERT(filename != nullptr);

    ID3DBlob* blob{ nullptr };

    mMutex.lock();
    blob = LoadBlob(filename);
    mMutex.unlock();

    BRE_ASSERT(blob != nullptr);
    mShaderBlobs.insert(blob);

    return *blob;
}

D3D12_SHADER_BYTECODE
ShaderManager::LoadShaderFileAndGetBytecode(const char* filename) noexcept
{
    BRE_ASSERT(filename != nullptr);

    ID3DBlob* blob{ nullptr };

    mMutex.lock();
    blob = LoadBlob(filename);
    mMutex.unlock();

    BRE_ASSERT(blob != nullptr);
    mShaderBlobs.insert(blob);

    D3D12_SHADER_BYTECODE shaderByteCode
    {
        reinterpret_cast<uint8_t*>(blob->GetBufferPointer()),
        blob->GetBufferSize()
    };

    return shaderByteCode;
}
}

Final Notes

As you can see, all the managers have methods to create its respective data and to clear the manager itself (i.e. to erase all). They do not have methods to erase individual items because in BRE we do not need to add/remove elements dynamically from the scene. Everything that is created lasts until the end of the application. I understand and I agree that in real applications like editors, video games, etc, this is not enough, but this is the model I adopted for BRE. My intention from the beginning was to write scenes manually, scenes that are static and are not modified in runtime, to implement different rendering techniques, and to see the result of these techniques in an interactive application. It is not my intention to solve problems for all the possible scenarios.

Advertisements

One thought on “BRE Architecture Series Part 2 – Managers

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