Rework shader bindings (WIP)
This commit is contained in:
@@ -152,6 +152,16 @@ namespace Nz
|
||||
std::move(defaultValues)
|
||||
});
|
||||
|
||||
// Common data
|
||||
settings.textures.push_back({
|
||||
3,
|
||||
"TextureOverlay",
|
||||
ImageType::E2D
|
||||
});
|
||||
|
||||
settings.sharedUniformBlocks.push_back(PredefinedInstanceData::GetUniformBlock(4, ShaderStageType::Vertex));
|
||||
settings.sharedUniformBlocks.push_back(PredefinedViewerData::GetUniformBlock(5, ShaderStageType_All));
|
||||
|
||||
settings.shaders = std::move(uberShaders);
|
||||
|
||||
for (std::shared_ptr<UberShader> uberShader : settings.shaders)
|
||||
|
||||
@@ -67,13 +67,6 @@ namespace Nz
|
||||
|
||||
MaterialPipeline::Initialize();
|
||||
|
||||
RenderPipelineLayoutInfo referenceLayoutInfo;
|
||||
FillDrawDataPipelineLayout(referenceLayoutInfo);
|
||||
FillViewerPipelineLayout(referenceLayoutInfo);
|
||||
FillWorldPipelineLayout(referenceLayoutInfo);
|
||||
|
||||
m_referencePipelineLayout = m_renderDevice->InstantiateRenderPipelineLayout(std::move(referenceLayoutInfo));
|
||||
|
||||
BuildDefaultTextures();
|
||||
BuildFullscreenVertexBuffer();
|
||||
BuildBlitPipeline();
|
||||
|
||||
@@ -29,8 +29,7 @@ namespace Nz
|
||||
MaterialPass::MaterialPass(std::shared_ptr<const MaterialSettings> settings) :
|
||||
m_settings(std::move(settings)),
|
||||
m_forceCommandBufferRegeneration(false),
|
||||
m_pipelineUpdated(false),
|
||||
m_shaderBindingUpdated(false)
|
||||
m_pipelineUpdated(false)
|
||||
{
|
||||
m_pipelineInfo.settings = m_settings;
|
||||
|
||||
@@ -44,14 +43,14 @@ namespace Nz
|
||||
const auto& textureSettings = m_settings->GetTextures();
|
||||
const auto& uboSettings = m_settings->GetUniformBlocks();
|
||||
|
||||
m_textures.resize(m_settings->GetTextures().size());
|
||||
m_textures.resize(textureSettings.size());
|
||||
|
||||
m_uniformBuffers.reserve(m_settings->GetUniformBlocks().size());
|
||||
for (const auto& uniformBufferInfo : m_settings->GetUniformBlocks())
|
||||
m_uniformBuffers.reserve(uboSettings.size());
|
||||
for (const auto& uniformBufferInfo : uboSettings)
|
||||
{
|
||||
auto& uniformBuffer = m_uniformBuffers.emplace_back();
|
||||
|
||||
uniformBuffer.buffer = Graphics::Instance()->GetRenderDevice()->InstantiateBuffer(Nz::BufferType::Uniform);
|
||||
uniformBuffer.buffer = Graphics::Instance()->GetRenderDevice()->InstantiateBuffer(BufferType::Uniform);
|
||||
if (!uniformBuffer.buffer->Initialize(uniformBufferInfo.blockSize, BufferUsage::Dynamic))
|
||||
throw std::runtime_error("failed to initialize UBO memory");
|
||||
|
||||
@@ -60,20 +59,10 @@ namespace Nz
|
||||
uniformBuffer.data.resize(uniformBufferInfo.blockSize);
|
||||
std::memcpy(uniformBuffer.data.data(), uniformBufferInfo.defaultValues.data(), uniformBufferInfo.defaultValues.size());
|
||||
}
|
||||
|
||||
UpdateShaderBinding();
|
||||
}
|
||||
|
||||
bool MaterialPass::Update(RenderFrame& renderFrame, CommandBufferBuilder& builder)
|
||||
{
|
||||
if (!m_shaderBindingUpdated)
|
||||
{
|
||||
renderFrame.PushForRelease(std::move(m_shaderBinding));
|
||||
m_shaderBinding.reset();
|
||||
|
||||
UpdateShaderBinding();
|
||||
}
|
||||
|
||||
UploadPool& uploadPool = renderFrame.GetUploadPool();
|
||||
|
||||
for (auto& ubo : m_uniformBuffers)
|
||||
@@ -118,60 +107,4 @@ namespace Nz
|
||||
m_pipeline = MaterialPipeline::Get(m_pipelineInfo);
|
||||
m_pipelineUpdated = true;
|
||||
}
|
||||
|
||||
void MaterialPass::UpdateShaderBinding()
|
||||
{
|
||||
assert(!m_shaderBinding);
|
||||
|
||||
const auto& textureSettings = m_settings->GetTextures();
|
||||
const auto& uboSettings = m_settings->GetUniformBlocks();
|
||||
|
||||
// TODO: Use StackVector
|
||||
std::vector<ShaderBinding::Binding> bindings;
|
||||
|
||||
// Textures
|
||||
for (std::size_t i = 0; i < m_textures.size(); ++i)
|
||||
{
|
||||
const auto& textureSetting = textureSettings[i];
|
||||
const auto& textureSlot = m_textures[i];
|
||||
|
||||
if (!textureSlot.sampler)
|
||||
{
|
||||
TextureSamplerCache& samplerCache = Graphics::Instance()->GetSamplerCache();
|
||||
textureSlot.sampler = samplerCache.Get(textureSlot.samplerInfo);
|
||||
}
|
||||
|
||||
//TODO: Use "missing" texture
|
||||
if (textureSlot.texture)
|
||||
{
|
||||
bindings.push_back({
|
||||
textureSetting.bindingIndex,
|
||||
ShaderBinding::TextureBinding {
|
||||
textureSlot.texture.get(), textureSlot.sampler.get()
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Shared UBO (TODO)
|
||||
|
||||
// Owned UBO
|
||||
for (std::size_t i = 0; i < m_uniformBuffers.size(); ++i)
|
||||
{
|
||||
const auto& uboSetting = uboSettings[i];
|
||||
const auto& uboSlot = m_uniformBuffers[i];
|
||||
|
||||
bindings.push_back({
|
||||
uboSetting.bindingIndex,
|
||||
ShaderBinding::UniformBufferBinding {
|
||||
uboSlot.buffer.get(), 0, uboSlot.buffer->GetSize()
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
m_shaderBinding = m_settings->GetRenderPipelineLayout()->AllocateShaderBinding(Graphics::MaterialBindingSet);
|
||||
m_shaderBinding->Update(bindings.data(), bindings.size());
|
||||
|
||||
m_shaderBindingUpdated = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
namespace Nz
|
||||
{
|
||||
PredefinedLightData PredefinedLightData::GetOffset()
|
||||
PredefinedLightData PredefinedLightData::GetOffsets()
|
||||
{
|
||||
PredefinedLightData lightData;
|
||||
|
||||
@@ -34,7 +34,7 @@ namespace Nz
|
||||
|
||||
MaterialSettings::SharedUniformBlock PredefinedLightData::GetUniformBlock()
|
||||
{
|
||||
PredefinedLightData lightData = GetOffset();
|
||||
PredefinedLightData lightData = GetOffsets();
|
||||
|
||||
std::vector<MaterialSettings::UniformVariable> lightDataVariables;
|
||||
for (std::size_t i = 0; i < lightData.lightArray.size(); ++i)
|
||||
@@ -67,6 +67,27 @@ namespace Nz
|
||||
return instanceData;
|
||||
}
|
||||
|
||||
MaterialSettings::SharedUniformBlock PredefinedInstanceData::GetUniformBlock(UInt32 bindingIndex, ShaderStageTypeFlags shaderStages)
|
||||
{
|
||||
PredefinedInstanceData instanceData = GetOffsets();
|
||||
|
||||
std::vector<MaterialSettings::UniformVariable> variables = {
|
||||
{
|
||||
{ "WorldMatrix", instanceData.worldMatrixOffset },
|
||||
{ "InvWorldMatrix", instanceData.invWorldMatrixOffset }
|
||||
}
|
||||
};
|
||||
|
||||
MaterialSettings::SharedUniformBlock uniformBlock = {
|
||||
bindingIndex,
|
||||
"InstanceData",
|
||||
std::move(variables),
|
||||
shaderStages
|
||||
};
|
||||
|
||||
return uniformBlock;
|
||||
}
|
||||
|
||||
PredefinedViewerData PredefinedViewerData::GetOffsets()
|
||||
{
|
||||
FieldOffsets viewerStruct(StructLayout::Std140);
|
||||
@@ -86,4 +107,33 @@ namespace Nz
|
||||
|
||||
return viewerData;
|
||||
}
|
||||
|
||||
MaterialSettings::SharedUniformBlock PredefinedViewerData::GetUniformBlock(UInt32 bindingIndex, ShaderStageTypeFlags shaderStages)
|
||||
{
|
||||
PredefinedViewerData viewerData = GetOffsets();
|
||||
|
||||
std::vector<MaterialSettings::UniformVariable> variables = {
|
||||
{
|
||||
{ "EyePosition", viewerData.eyePositionOffset },
|
||||
{ "InvProjMatrix", viewerData.invProjMatrixOffset },
|
||||
{ "InvTargetSize", viewerData.invTargetSizeOffset },
|
||||
{ "InvViewMatrix", viewerData.invViewMatrixOffset },
|
||||
{ "InvViewProjMatrix", viewerData.invViewProjMatrixOffset },
|
||||
{ "ProjMatrix", viewerData.projMatrixOffset },
|
||||
{ "TargetSize", viewerData.targetSizeOffset },
|
||||
{ "ViewMatrix", viewerData.viewMatrixOffset },
|
||||
{ "ViewProjMatrix", viewerData.viewProjMatrixOffset }
|
||||
}
|
||||
};
|
||||
|
||||
MaterialSettings::SharedUniformBlock uniformBlock = {
|
||||
bindingIndex,
|
||||
"ViewerData",
|
||||
std::move(variables),
|
||||
shaderStages
|
||||
};
|
||||
|
||||
return uniformBlock;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -65,10 +65,10 @@ namespace Nz
|
||||
const VertexDeclaration* currentVertexDeclaration = nullptr;
|
||||
AbstractBuffer* currentVertexBuffer = nullptr;
|
||||
const RenderPipeline* currentPipeline = nullptr;
|
||||
const ShaderBinding* currentDrawDataBinding = nullptr;
|
||||
const ShaderBinding* currentInstanceBinding = nullptr;
|
||||
const ShaderBinding* currentMaterialBinding = nullptr;
|
||||
const ShaderBinding* currentShaderBinding = nullptr;
|
||||
const Texture* currentTextureOverlay = nullptr;
|
||||
const ViewerInstance* currentViewerInstance = nullptr;
|
||||
const WorldInstance* currentWorldInstance = nullptr;
|
||||
|
||||
auto FlushDrawCall = [&]()
|
||||
{
|
||||
@@ -79,7 +79,7 @@ namespace Nz
|
||||
{
|
||||
FlushDrawCall();
|
||||
|
||||
currentDrawDataBinding = nullptr;
|
||||
currentShaderBinding = nullptr;
|
||||
};
|
||||
|
||||
auto Flush = [&]()
|
||||
@@ -122,24 +122,24 @@ namespace Nz
|
||||
currentVertexDeclaration = vertexDeclaration;
|
||||
}
|
||||
|
||||
if (currentPipeline != spriteChain.GetRenderPipeline())
|
||||
if (currentPipeline != &spriteChain.GetRenderPipeline())
|
||||
{
|
||||
FlushDrawCall();
|
||||
currentPipeline = spriteChain.GetRenderPipeline();
|
||||
currentPipeline = &spriteChain.GetRenderPipeline();
|
||||
}
|
||||
|
||||
if (currentMaterialBinding != &spriteChain.GetMaterialBinding())
|
||||
if (currentViewerInstance != &spriteChain.GetViewerInstance())
|
||||
{
|
||||
FlushDrawCall();
|
||||
currentMaterialBinding = &spriteChain.GetMaterialBinding();
|
||||
currentViewerInstance = &spriteChain.GetViewerInstance();
|
||||
}
|
||||
|
||||
if (currentInstanceBinding != &spriteChain.GetInstanceBinding())
|
||||
if (currentWorldInstance != &spriteChain.GetWorldInstance())
|
||||
{
|
||||
// TODO: Flushing draw calls on instance binding means we can have e.g. 1000 sprites rendered using a draw call for each one
|
||||
// which is far from being efficient, using some bindless could help (or at least instancing?)
|
||||
FlushDrawCall();
|
||||
currentInstanceBinding = &spriteChain.GetInstanceBinding();
|
||||
currentWorldInstance = &spriteChain.GetWorldInstance();
|
||||
}
|
||||
|
||||
if (currentTextureOverlay != spriteChain.GetTextureOverlay())
|
||||
@@ -177,9 +177,9 @@ namespace Nz
|
||||
data.vertexBuffers.emplace_back(std::move(vertexBuffer));
|
||||
}
|
||||
|
||||
if (!currentDrawDataBinding)
|
||||
if (!currentShaderBinding)
|
||||
{
|
||||
ShaderBindingPtr drawDataBinding = Graphics::Instance()->GetReferencePipelineLayout()->AllocateShaderBinding(Graphics::DrawDataBindingSet);
|
||||
ShaderBindingPtr drawDataBinding = currentPipeline->GetPipelineInfo().pipelineLayout->AllocateShaderBinding(0);
|
||||
drawDataBinding->Update({
|
||||
{
|
||||
0,
|
||||
@@ -189,7 +189,7 @@ namespace Nz
|
||||
}
|
||||
});
|
||||
|
||||
currentDrawDataBinding = drawDataBinding.get();
|
||||
currentShaderBinding = drawDataBinding.get();
|
||||
|
||||
data.shaderBindings.emplace_back(std::move(drawDataBinding));
|
||||
}
|
||||
@@ -199,9 +199,7 @@ namespace Nz
|
||||
data.drawCalls.push_back(SpriteChainRendererData::DrawCall{
|
||||
currentVertexBuffer,
|
||||
currentPipeline,
|
||||
currentDrawDataBinding,
|
||||
currentInstanceBinding,
|
||||
currentMaterialBinding,
|
||||
currentShaderBinding,
|
||||
6 * firstQuadIndex,
|
||||
0,
|
||||
});
|
||||
@@ -263,9 +261,9 @@ namespace Nz
|
||||
|
||||
const AbstractBuffer* currentVertexBuffer = nullptr;
|
||||
const RenderPipeline* currentPipeline = nullptr;
|
||||
const ShaderBinding* currentDrawDataBinding = nullptr;
|
||||
const ShaderBinding* currentInstanceBinding = nullptr;
|
||||
const ShaderBinding* currentMaterialBinding = nullptr;
|
||||
const ShaderBinding* currentShaderBinding = nullptr;
|
||||
const ViewerInstance* currentViewerInstance = nullptr;
|
||||
const WorldInstance* currentWorldInstance = nullptr;
|
||||
|
||||
const RenderSpriteChain* firstSpriteChain = static_cast<const RenderSpriteChain*>(elements[0]);
|
||||
auto it = data.drawCallPerElement.find(firstSpriteChain);
|
||||
@@ -289,22 +287,10 @@ namespace Nz
|
||||
currentPipeline = drawCall.renderPipeline;
|
||||
}
|
||||
|
||||
if (currentDrawDataBinding != drawCall.drawDataBinding)
|
||||
if (currentShaderBinding != drawCall.shaderBinding)
|
||||
{
|
||||
commandBuffer.BindShaderBinding(Graphics::DrawDataBindingSet, *drawCall.drawDataBinding);
|
||||
currentDrawDataBinding = drawCall.drawDataBinding;
|
||||
}
|
||||
|
||||
if (currentMaterialBinding != drawCall.materialBinding)
|
||||
{
|
||||
commandBuffer.BindShaderBinding(Graphics::MaterialBindingSet, *drawCall.materialBinding);
|
||||
currentMaterialBinding = drawCall.materialBinding;
|
||||
}
|
||||
|
||||
if (currentInstanceBinding != drawCall.instanceBinding)
|
||||
{
|
||||
commandBuffer.BindShaderBinding(Graphics::WorldBindingSet, *drawCall.instanceBinding);
|
||||
currentInstanceBinding = drawCall.instanceBinding;
|
||||
commandBuffer.BindShaderBinding(0, *drawCall.shaderBinding);
|
||||
currentShaderBinding = drawCall.shaderBinding;
|
||||
}
|
||||
|
||||
commandBuffer.DrawIndexed(drawCall.quadCount * 6, 1U, drawCall.firstIndex);
|
||||
|
||||
@@ -14,49 +14,39 @@
|
||||
namespace Nz
|
||||
{
|
||||
ViewerInstance::ViewerInstance() :
|
||||
m_invProjectionMatrix(Nz::Matrix4f::Identity()),
|
||||
m_invViewProjMatrix(Nz::Matrix4f::Identity()),
|
||||
m_invViewMatrix(Nz::Matrix4f::Identity()),
|
||||
m_projectionMatrix(Nz::Matrix4f::Identity()),
|
||||
m_viewProjMatrix(Nz::Matrix4f::Identity()),
|
||||
m_viewMatrix(Nz::Matrix4f::Identity()),
|
||||
m_targetSize(Nz::Vector2f(0.f, 0.f)),
|
||||
m_invProjectionMatrix(Matrix4f::Identity()),
|
||||
m_invViewProjMatrix(Matrix4f::Identity()),
|
||||
m_invViewMatrix(Matrix4f::Identity()),
|
||||
m_projectionMatrix(Matrix4f::Identity()),
|
||||
m_viewProjMatrix(Matrix4f::Identity()),
|
||||
m_viewMatrix(Matrix4f::Identity()),
|
||||
m_targetSize(Vector2f(0.f, 0.f)),
|
||||
m_dataInvalided(true)
|
||||
{
|
||||
Nz::PredefinedViewerData viewerUboOffsets = Nz::PredefinedViewerData::GetOffsets();
|
||||
PredefinedViewerData viewerUboOffsets = PredefinedViewerData::GetOffsets();
|
||||
|
||||
m_viewerDataBuffer = Graphics::Instance()->GetRenderDevice()->InstantiateBuffer(BufferType::Uniform);
|
||||
if (!m_viewerDataBuffer->Initialize(viewerUboOffsets.totalSize, Nz::BufferUsage::DeviceLocal | Nz::BufferUsage::Dynamic))
|
||||
if (!m_viewerDataBuffer->Initialize(viewerUboOffsets.totalSize, BufferUsage::DeviceLocal | BufferUsage::Dynamic))
|
||||
throw std::runtime_error("failed to initialize viewer data UBO");
|
||||
|
||||
m_shaderBinding = Graphics::Instance()->GetReferencePipelineLayout()->AllocateShaderBinding(Graphics::ViewerBindingSet);
|
||||
m_shaderBinding->Update({
|
||||
{
|
||||
0,
|
||||
ShaderBinding::UniformBufferBinding {
|
||||
m_viewerDataBuffer.get(), 0, m_viewerDataBuffer->GetSize()
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void ViewerInstance::UpdateBuffers(UploadPool& uploadPool, CommandBufferBuilder& builder)
|
||||
{
|
||||
if (m_dataInvalided)
|
||||
{
|
||||
Nz::PredefinedViewerData viewerDataOffsets = Nz::PredefinedViewerData::GetOffsets();
|
||||
PredefinedViewerData viewerDataOffsets = PredefinedViewerData::GetOffsets();
|
||||
|
||||
auto& allocation = uploadPool.Allocate(viewerDataOffsets.totalSize);
|
||||
Nz::AccessByOffset<Nz::Vector3f&>(allocation.mappedPtr, viewerDataOffsets.eyePositionOffset) = m_viewMatrix.GetTranslation();
|
||||
Nz::AccessByOffset<Nz::Vector2f&>(allocation.mappedPtr, viewerDataOffsets.invTargetSizeOffset) = 1.f / m_targetSize;
|
||||
Nz::AccessByOffset<Nz::Vector2f&>(allocation.mappedPtr, viewerDataOffsets.targetSizeOffset) = m_targetSize;
|
||||
AccessByOffset<Vector3f&>(allocation.mappedPtr, viewerDataOffsets.eyePositionOffset) = m_viewMatrix.GetTranslation();
|
||||
AccessByOffset<Vector2f&>(allocation.mappedPtr, viewerDataOffsets.invTargetSizeOffset) = 1.f / m_targetSize;
|
||||
AccessByOffset<Vector2f&>(allocation.mappedPtr, viewerDataOffsets.targetSizeOffset) = m_targetSize;
|
||||
|
||||
Nz::AccessByOffset<Nz::Matrix4f&>(allocation.mappedPtr, viewerDataOffsets.invProjMatrixOffset) = m_invProjectionMatrix;
|
||||
Nz::AccessByOffset<Nz::Matrix4f&>(allocation.mappedPtr, viewerDataOffsets.invViewMatrixOffset) = m_invViewMatrix;
|
||||
Nz::AccessByOffset<Nz::Matrix4f&>(allocation.mappedPtr, viewerDataOffsets.invViewProjMatrixOffset) = m_invViewProjMatrix;
|
||||
Nz::AccessByOffset<Nz::Matrix4f&>(allocation.mappedPtr, viewerDataOffsets.projMatrixOffset) = m_projectionMatrix;
|
||||
Nz::AccessByOffset<Nz::Matrix4f&>(allocation.mappedPtr, viewerDataOffsets.viewProjMatrixOffset) = m_viewProjMatrix;
|
||||
Nz::AccessByOffset<Nz::Matrix4f&>(allocation.mappedPtr, viewerDataOffsets.viewMatrixOffset) = m_viewMatrix;
|
||||
AccessByOffset<Matrix4f&>(allocation.mappedPtr, viewerDataOffsets.invProjMatrixOffset) = m_invProjectionMatrix;
|
||||
AccessByOffset<Matrix4f&>(allocation.mappedPtr, viewerDataOffsets.invViewMatrixOffset) = m_invViewMatrix;
|
||||
AccessByOffset<Matrix4f&>(allocation.mappedPtr, viewerDataOffsets.invViewProjMatrixOffset) = m_invViewProjMatrix;
|
||||
AccessByOffset<Matrix4f&>(allocation.mappedPtr, viewerDataOffsets.projMatrixOffset) = m_projectionMatrix;
|
||||
AccessByOffset<Matrix4f&>(allocation.mappedPtr, viewerDataOffsets.viewProjMatrixOffset) = m_viewProjMatrix;
|
||||
AccessByOffset<Matrix4f&>(allocation.mappedPtr, viewerDataOffsets.viewMatrixOffset) = m_viewMatrix;
|
||||
|
||||
builder.CopyBuffer(allocation, m_viewerDataBuffer.get());
|
||||
|
||||
|
||||
@@ -14,25 +14,15 @@
|
||||
namespace Nz
|
||||
{
|
||||
WorldInstance::WorldInstance() :
|
||||
m_invWorldMatrix(Nz::Matrix4f::Identity()),
|
||||
m_worldMatrix(Nz::Matrix4f::Identity()),
|
||||
m_invWorldMatrix(Matrix4f::Identity()),
|
||||
m_worldMatrix(Matrix4f::Identity()),
|
||||
m_dataInvalided(true)
|
||||
{
|
||||
PredefinedInstanceData instanceUboOffsets = PredefinedInstanceData::GetOffsets();
|
||||
|
||||
m_instanceDataBuffer = Graphics::Instance()->GetRenderDevice()->InstantiateBuffer(BufferType::Uniform);
|
||||
if (!m_instanceDataBuffer->Initialize(instanceUboOffsets.totalSize, Nz::BufferUsage::DeviceLocal | Nz::BufferUsage::Dynamic))
|
||||
if (!m_instanceDataBuffer->Initialize(instanceUboOffsets.totalSize, BufferUsage::DeviceLocal | BufferUsage::Dynamic))
|
||||
throw std::runtime_error("failed to initialize viewer data UBO");
|
||||
|
||||
m_shaderBinding = Graphics::Instance()->GetReferencePipelineLayout()->AllocateShaderBinding(Graphics::WorldBindingSet);
|
||||
m_shaderBinding->Update({
|
||||
{
|
||||
0,
|
||||
ShaderBinding::UniformBufferBinding {
|
||||
m_instanceDataBuffer.get(), 0, m_instanceDataBuffer->GetSize()
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void WorldInstance::UpdateBuffers(UploadPool& uploadPool, CommandBufferBuilder& builder)
|
||||
|
||||
Reference in New Issue
Block a user