Description
When looking at the Flyweight pattern from the Game Programming Patterns book, it talked about reusing an instance of an object to reduce data stored in the game. When researching this I came upon this subject. This seemed a nice way to reduce data used.
The entire project can be found on Github.
Learning
This process started with the idea to reduce & reuse as much data as possible. When researching this I came upon the term GPU Instancing. This basically means that the GPU keep reusing 1 instance of a mesh to draw all the instances needed in the scene. You can mark some data that you pass as ‘Per Instance Data’ which means that the GPU will only load this data once but the GPU will draw it multiple times.
Code snippets
HRESULT Mesh::CreateInstanceBuffer(ID3D11Device* pDevice)
{
// ********************************
// Create instance buffer: Data
// ********************************
Scene* pScene{ Locator::GetSceneManagerService()->GetActiveGameScene() };
std::vector<GameObject*> pGameObjects{ pScene->GetObjects() };
for (GameObject* pGameObject : pGameObjects)
{
ModelComponent* pModelComponent{ pGameObject->GetComponent<ModelComponent>() };
if (!pModelComponent)
continue;
Mesh* pMesh{ pModelComponent->GetMesh() };
if (pMesh != this)
continue;
TransformComponent* pTransformComponent{ pGameObject->GetComponent<TransformComponent>() };
m_InstancePositionData.push_back(pTransformComponent->GetWorldPosition());
}
// If there is only 1 instance of that mesh than clear out the data vector so it won't render as an instance
if (m_InstancePositionData.size() <= 1)
{
m_InstancePositionData.clear();
return S_OK;
}
// ********************************
// Create instance buffer: DirectX
// ********************************
D3D11_BUFFER_DESC instBuffDesc;
ZeroMemory(&instBuffDesc, sizeof(instBuffDesc));
instBuffDesc.Usage = D3D11_USAGE_DEFAULT;
instBuffDesc.ByteWidth = sizeof(DirectX::XMFLOAT3) * m_AmountInstances;
instBuffDesc.BindFlags = D3D11_BIND_VERTEX_BUFFER;
instBuffDesc.CPUAccessFlags = 0;
instBuffDesc.MiscFlags = 0;
D3D11_SUBRESOURCE_DATA instData;
ZeroMemory(&instData, sizeof(instData));
instData.pSysMem = m_InstancePositionData.data();
/* CreateBuffer - Parameters */
const D3D11_BUFFER_DESC* pInstanceBufferDesc{ &instBuffDesc };
const D3D11_SUBRESOURCE_DATA* pInitialData_InstanceBuffer{ &instData };
ID3D11Buffer** ppInstanceBuffer{ &m_pInstanceBuffer };
// Explanation for all parameters in link below
// Reference: https://docs.microsoft.com/en-us/windows/win32/api/d3d11/nf-d3d11-id3d11device-createbuffer
return pDevice->CreateBuffer(pInstanceBufferDesc, pInitialData_InstanceBuffer, ppInstanceBuffer);
}
HRESULT Mesh::CreateConstantBuffer(ID3D11Device* pDevice)
{
// ********************************
// Create instance buffer: DirectX
// ********************************
D3D11_BUFFER_DESC constantBuffDesc;
ZeroMemory(&constantBuffDesc, sizeof(constantBuffDesc));
constantBuffDesc.Usage = D3D11_USAGE_DEFAULT;
constantBuffDesc.ByteWidth = sizeof(DirectX::XMFLOAT4);
constantBuffDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
constantBuffDesc.CPUAccessFlags = 0;
constantBuffDesc.MiscFlags = 0;
/* CreateBuffer - Parameters */
const D3D11_BUFFER_DESC* pCBufferDesc{ &constantBuffDesc };
const D3D11_SUBRESOURCE_DATA* pInitialData_CBuffer{ nullptr };
ID3D11Buffer** ppCBuffer{ &m_pConstantBuffer };
// Explanation for all parameters in link below
// Reference: https://docs.microsoft.com/en-us/windows/win32/api/d3d11/nf-d3d11-id3d11device-createbuffer
return pDevice->CreateBuffer(pCBufferDesc, pInitialData_CBuffer, ppCBuffer);
}
void Mesh::UpdateBuffer()
{
ID3D11DeviceContext* pDeviceContext{ Locator::GetGraphicsService()->GetDeviceContext() };
// ********************************
// Update sub resource
// ********************************
/* CreateBuffer - Parameters */
ID3D11Resource* pDstResource{ m_pConstantBuffer };
UINT DstSubresource{ 0 };
const D3D11_BOX* pDstBox{ NULL };
const void* pSrcData{ &m_InstancePositionData };
UINT SrcRowPitch{ 0 };
UINT SrcDepthPitch{ 0 };
// Explanation for all parameters in link below
// Reference: https://docs.microsoft.com/en-us/windows/win32/api/d3d11/nf-d3d11-id3d11devicecontext-updatesubresource
pDeviceContext->UpdateSubresource(pDstResource, DstSubresource, pDstBox, pSrcData, SrcRowPitch, SrcDepthPitch);
}
Mesh* ResourceManager::Load3DMesh(const std::string& name, const std::string& path)
{
int meshID{};
// Check if the Mesh exists already and return the id if it does
if (MeshAlreadyParsed(name, meshID))
{
Mesh* pMesh{ m_p3DMeshes[meshID]->pMesh };
// Check if the instance counter is on 0 if so set to 1 to start
if (pMesh->GetAmountInstances() <= 0)
pMesh->IncrementInstanceCount();
pMesh->IncrementInstanceCount();
return pMesh;
}
// If it does not exist
Mesh* pMesh{ new Mesh(path) };
pMesh->Initialize();
// Store the mesh
MeshData* pMeshData{ new MeshData() };
pMeshData->ID = GetFreeMeshID();
pMeshData->name = name;
pMeshData->pMesh = pMesh;
m_p3DMeshes.push_back(pMeshData);
// Return the pointer to the mesh
return pMesh;
}
void RenderComponent::Render(ID3D11DeviceContext* pDeviceContext, Material* pEffect, Mesh* p3DMesh)
{
if (p3DMesh->GetAmountInstances() > 0)
RenderInstanced(pDeviceContext, pEffect, p3DMesh);
else
RenderNormal(pDeviceContext, pEffect, p3DMesh);
}
void RenderComponent::RenderNormal(ID3D11DeviceContext* pDeviceContext, Material* pEffect, Mesh* p3DMesh)
{
ID3DX11EffectTechnique* pTechnique{ pEffect->GetTechnique() };
D3DX11_TECHNIQUE_DESC techDesc{};
pTechnique->GetDesc(&techDesc);
for (UINT p{}; p < techDesc.Passes; ++p)
{
// COMMENT HERE
pTechnique->GetPassByIndex(p)->Apply(0, pDeviceContext);
// ****************************************
// Set up the DrawIndexed function
// ****************************************
/* DrawIndexed - Parameters */
UINT IndexCount{ p3DMesh->GetAmountIndices() };
UINT StartIndexLocation{ 0 };
INT BaseVertexLocation{ 0 };
// Explanation for all parameters in link below
// Reference: https://docs.microsoft.com/en-us/windows/win32/api/d3d11/nf-d3d11-id3d11devicecontext-drawindexed
pDeviceContext->DrawIndexed(IndexCount, StartIndexLocation, BaseVertexLocation);
}
}
void RenderComponent::RenderInstanced(ID3D11DeviceContext* pDeviceContext, Material* pEffect, Mesh* p3DMesh)
{
if (p3DMesh->GetIsRendered())
return;
ID3DX11EffectTechnique* pTechnique{ pEffect->GetTechnique() };
D3DX11_TECHNIQUE_DESC techDesc{};
pTechnique->GetDesc(&techDesc);
for (UINT p{}; p < techDesc.Passes; ++p)
{
// COMMENT HERE
pTechnique->GetPassByIndex(p)->Apply(0, pDeviceContext);
// ****************************************
// Set up the DrawIndexedInstanced function
// ****************************************
/* DrawIndexedInstanced - Parameters */
UINT IndexCountPerInstance{ p3DMesh->GetAmountIndices() };
UINT InstanceCount{ p3DMesh->GetAmountInstances() };
UINT StartIndexLocation{ 0 };
INT BaseVertexLocation{ 0 };
UINT StartInstanceLocation{ 0 };
// Explanation for all parameters in link below
// Reference: https://docs.microsoft.com/en-us/windows/win32/api/d3d11/nf-d3d11-id3d11devicecontext-drawindexedinstanced
pDeviceContext->DrawIndexedInstanced(IndexCountPerInstance, InstanceCount, StartIndexLocation, BaseVertexLocation, StartInstanceLocation);
}
p3DMesh->SetIsRendered(true);
}