Graphics: Separate pipeline state from Material into a new class, MaterialPipeline

This allows much more efficient batching, along with pipeline reusage and preparation for the Vulkan API


Former-commit-id: 4ed2f66567f7da6b6b6ee073e4d855b9a935000d [formerly b540f468fc700a16d5136d4dbb8632e23882fd3d] [formerly 37fff624ec65cc387130875410b6ea35c1a5bcfb [formerly ab9a88f514f46f6596499e285981fa6da588bb03]]
Former-commit-id: a2e8859196c0f72b7d7ffd8764a887e6c8173743 [formerly c886cdade14769db243ff993a1741f6052a2eb2a]
Former-commit-id: e1d02662fb1ac165c7e888380afee7601350060f
This commit is contained in:
Lynix
2016-08-05 22:09:39 +02:00
parent 8fbe279a50
commit 87b5047b14
31 changed files with 2249 additions and 1422 deletions

View File

@@ -52,6 +52,10 @@ namespace Nz
{
ErrorFlags flags(ErrorFlag_ThrowException, true);
std::array<UInt8, 4> whitePixel = {255, 255, 255, 255};
m_whiteTexture.Create(ImageType_2D, PixelFormatType_RGBA8, 1, 1);
m_whiteTexture.Update(whitePixel.data());
m_vertexBuffer.Create(s_vertexBufferSize, DataStorage_Hardware, BufferUsage_Dynamic);
m_billboardPointBuffer.Reset(&s_billboardVertexDeclaration, &m_vertexBuffer);
@@ -208,6 +212,8 @@ namespace Nz
void DepthRenderTechnique::DrawBasicSprites(const SceneData& sceneData, ForwardRenderQueue::Layer& layer) const
{
NazaraAssert(sceneData.viewer, "Invalid viewer");
const Shader* lastShader = nullptr;
const ShaderUniforms* shaderUniforms = nullptr;
@@ -215,95 +221,102 @@ namespace Nz
Renderer::SetMatrix(MatrixType_World, Matrix4f::Identity());
Renderer::SetVertexBuffer(&m_spriteBuffer);
for (auto& matIt : layer.basicSprites)
for (auto& pipelinePair : layer.basicSprites)
{
const Material* material = matIt.first;
auto& matEntry = matIt.second;
const MaterialPipeline* pipeline = pipelinePair.first;
auto& pipelineEntry = pipelinePair.second;
if (matEntry.enabled)
if (pipelineEntry.enabled)
{
auto& overlayMap = matEntry.overlayMap;
for (auto& overlayIt : overlayMap)
const MaterialPipeline::Instance& pipelineInstance = pipeline->Apply(ShaderFlags_TextureOverlay | ShaderFlags_VertexColor);
const Shader* shader = pipelineInstance.uberInstance->GetShader();
// Uniforms are conserved in our program, there's no point to send them back until they change
if (shader != lastShader)
{
const Texture* overlay = overlayIt.first;
auto& spriteChainVector = overlayIt.second.spriteChains;
// Index of uniforms in the shader
shaderUniforms = GetShaderUniforms(shader);
unsigned int spriteChainCount = spriteChainVector.size();
if (spriteChainCount > 0)
{
// We begin to apply the material (and get the shader activated doing so)
UInt32 flags = 0;
if (overlay)
flags |= ShaderFlags_TextureOverlay;
// Ambiant color of the scene
shader->SendColor(shaderUniforms->sceneAmbient, sceneData.ambientColor);
// Position of the camera
shader->SendVector(shaderUniforms->eyePosition, sceneData.viewer->GetEyePosition());
UInt8 overlayUnit;
const Shader* shader = material->Apply(flags, 0, &overlayUnit);
if (overlay)
{
overlayUnit++;
Renderer::SetTexture(overlayUnit, overlay);
Renderer::SetTextureSampler(overlayUnit, material->GetDiffuseSampler());
}
// Uniforms are conserved in our program, there's no point to send them back until they change
if (shader != lastShader)
{
// Index of uniforms in the shader
shaderUniforms = GetShaderUniforms(shader);
// Overlay
shader->SendInteger(shaderUniforms->textureOverlay, overlayUnit);
// Position of the camera
shader->SendVector(shaderUniforms->eyePosition, Renderer::GetMatrix(MatrixType_ViewProj).GetTranslation());
lastShader = shader;
}
unsigned int spriteChain = 0; // Which chain of sprites are we treating
unsigned int spriteChainOffset = 0; // Where was the last offset where we stopped in the last chain
do
{
// We open the buffer in writing mode
BufferMapper<VertexBuffer> vertexMapper(m_spriteBuffer, BufferAccess_DiscardAndWrite);
VertexStruct_XYZ_Color_UV* vertices = reinterpret_cast<VertexStruct_XYZ_Color_UV*>(vertexMapper.GetPointer());
unsigned int spriteCount = 0;
unsigned int maxSpriteCount = std::min(s_maxQuads, m_spriteBuffer.GetVertexCount()/4);
do
{
ForwardRenderQueue::SpriteChain_XYZ_Color_UV& currentChain = spriteChainVector[spriteChain];
unsigned int count = std::min(maxSpriteCount - spriteCount, currentChain.spriteCount - spriteChainOffset);
std::memcpy(vertices, currentChain.vertices + spriteChainOffset*4, 4*count*sizeof(VertexStruct_XYZ_Color_UV));
vertices += count*4;
spriteCount += count;
spriteChainOffset += count;
// Have we treated the entire chain ?
if (spriteChainOffset == currentChain.spriteCount)
{
spriteChain++;
spriteChainOffset = 0;
}
}
while (spriteCount < maxSpriteCount && spriteChain < spriteChainCount);
vertexMapper.Unmap();
Renderer::DrawIndexedPrimitives(PrimitiveMode_TriangleList, 0, spriteCount*6);
}
while (spriteChain < spriteChainCount);
spriteChainVector.clear();
}
lastShader = shader;
}
// On remet à zéro
matEntry.enabled = false;
for (auto& materialPair : pipelineEntry.materialMap)
{
const Material* material = materialPair.first;
auto& matEntry = materialPair.second;
if (matEntry.enabled)
{
UInt8 overlayUnit;
material->Apply(pipelineInstance, 0, &overlayUnit);
overlayUnit++;
shader->SendInteger(shaderUniforms->textureOverlay, overlayUnit);
Renderer::SetTextureSampler(overlayUnit, material->GetDiffuseSampler());
auto& overlayMap = matEntry.overlayMap;
for (auto& overlayIt : overlayMap)
{
const Texture* overlay = overlayIt.first;
auto& spriteChainVector = overlayIt.second.spriteChains;
unsigned int spriteChainCount = spriteChainVector.size();
if (spriteChainCount > 0)
{
Renderer::SetTexture(overlayUnit, (overlay) ? overlay : &m_whiteTexture);
unsigned int spriteChain = 0; // Which chain of sprites are we treating
unsigned int spriteChainOffset = 0; // Where was the last offset where we stopped in the last chain
do
{
// We open the buffer in writing mode
BufferMapper<VertexBuffer> vertexMapper(m_spriteBuffer, BufferAccess_DiscardAndWrite);
VertexStruct_XYZ_Color_UV* vertices = static_cast<VertexStruct_XYZ_Color_UV*>(vertexMapper.GetPointer());
unsigned int spriteCount = 0;
unsigned int maxSpriteCount = std::min(s_maxQuads, m_spriteBuffer.GetVertexCount() / 4);
do
{
ForwardRenderQueue::SpriteChain_XYZ_Color_UV& currentChain = spriteChainVector[spriteChain];
unsigned int count = std::min(maxSpriteCount - spriteCount, currentChain.spriteCount - spriteChainOffset);
std::memcpy(vertices, currentChain.vertices + spriteChainOffset * 4, 4 * count * sizeof(VertexStruct_XYZ_Color_UV));
vertices += count * 4;
spriteCount += count;
spriteChainOffset += count;
// Have we treated the entire chain ?
if (spriteChainOffset == currentChain.spriteCount)
{
spriteChain++;
spriteChainOffset = 0;
}
} while (spriteCount < maxSpriteCount && spriteChain < spriteChainCount);
vertexMapper.Unmap();
Renderer::DrawIndexedPrimitives(PrimitiveMode_TriangleList, 0, spriteCount * 6);
} while (spriteChain < spriteChainCount);
spriteChainVector.clear();
}
}
// We set it back to zero
matEntry.enabled = false;
}
}
pipelineEntry.enabled = false;
}
}
}
@@ -314,9 +327,11 @@ namespace Nz
* \param sceneData Data of the scene
* \param layer Layer of the rendering
*/
void DepthRenderTechnique::DrawBillboards(const SceneData& sceneData, ForwardRenderQueue::Layer& layer) const
{
NazaraAssert(sceneData.viewer, "Invalid viewer");
const Shader* lastShader = nullptr;
const ShaderUniforms* shaderUniforms = nullptr;
@@ -327,17 +342,16 @@ namespace Nz
Renderer::SetVertexBuffer(&s_quadVertexBuffer);
for (auto& matIt : layer.billboards)
for (auto& pipelinePair : layer.billboards)
{
const Material* material = matIt.first;
auto& entry = matIt.second;
auto& billboardVector = entry.billboards;
const MaterialPipeline* pipeline = pipelinePair.first;
auto& pipelineEntry = pipelinePair.second;
unsigned int billboardCount = billboardVector.size();
if (billboardCount > 0)
if (pipelineEntry.enabled)
{
// We begin to apply the material (and get the shader activated doing so)
const Shader* shader = material->Apply(ShaderFlags_Billboard | ShaderFlags_Instancing | ShaderFlags_VertexColor);
const MaterialPipeline::Instance& pipelineInstance = pipeline->Apply(ShaderFlags_Billboard | ShaderFlags_Instancing | ShaderFlags_VertexColor);
const Shader* shader = pipelineInstance.uberInstance->GetShader();
// Uniforms are conserved in our program, there's no point to send them back until they change
if (shader != lastShader)
@@ -345,27 +359,43 @@ namespace Nz
// Index of uniforms in the shader
shaderUniforms = GetShaderUniforms(shader);
// Ambiant color of the scene
shader->SendColor(shaderUniforms->sceneAmbient, sceneData.ambientColor);
// Position of the camera
shader->SendVector(shaderUniforms->eyePosition, Renderer::GetMatrix(MatrixType_ViewProj).GetTranslation());
shader->SendVector(shaderUniforms->eyePosition, sceneData.viewer->GetEyePosition());
lastShader = shader;
}
const ForwardRenderQueue::BillboardData* data = &billboardVector[0];
unsigned int maxBillboardPerDraw = instanceBuffer->GetVertexCount();
do
for (auto& matIt : pipelinePair.second.materialMap)
{
unsigned int renderedBillboardCount = std::min(billboardCount, maxBillboardPerDraw);
billboardCount -= renderedBillboardCount;
const Material* material = matIt.first;
auto& entry = matIt.second;
auto& billboardVector = entry.billboards;
instanceBuffer->Fill(data, 0, renderedBillboardCount, true);
data += renderedBillboardCount;
unsigned int billboardCount = billboardVector.size();
if (billboardCount > 0)
{
// We begin to apply the material (and get the shader activated doing so)
material->Apply(pipelineInstance);
Renderer::DrawPrimitivesInstanced(renderedBillboardCount, PrimitiveMode_TriangleStrip, 0, 4);
const ForwardRenderQueue::BillboardData* data = &billboardVector[0];
unsigned int maxBillboardPerDraw = instanceBuffer->GetVertexCount();
do
{
unsigned int renderedBillboardCount = std::min(billboardCount, maxBillboardPerDraw);
billboardCount -= renderedBillboardCount;
instanceBuffer->Fill(data, 0, renderedBillboardCount, true);
data += renderedBillboardCount;
Renderer::DrawPrimitivesInstanced(renderedBillboardCount, PrimitiveMode_TriangleStrip, 0, 4);
}
while (billboardCount > 0);
billboardVector.clear();
}
}
while (billboardCount > 0);
billboardVector.clear();
}
}
}
@@ -374,17 +404,16 @@ namespace Nz
Renderer::SetIndexBuffer(&s_quadIndexBuffer);
Renderer::SetVertexBuffer(&m_billboardPointBuffer);
for (auto& matIt : layer.billboards)
for (auto& pipelinePair : layer.billboards)
{
const Material* material = matIt.first;
auto& entry = matIt.second;
auto& billboardVector = entry.billboards;
const MaterialPipeline* pipeline = pipelinePair.first;
auto& pipelineEntry = pipelinePair.second;
unsigned int billboardCount = billboardVector.size();
if (billboardCount > 0)
if (pipelineEntry.enabled)
{
// We begin to apply the material (and get the shader activated doing so)
const Shader* shader = material->Apply(ShaderFlags_Billboard | ShaderFlags_VertexColor);
const MaterialPipeline::Instance& pipelineInstance = pipeline->Apply(ShaderFlags_Billboard | ShaderFlags_VertexColor);
const Shader* shader = pipelineInstance.uberInstance->GetShader();
// Uniforms are conserved in our program, there's no point to send them back until they change
if (shader != lastShader)
@@ -392,63 +421,73 @@ namespace Nz
// Index of uniforms in the shader
shaderUniforms = GetShaderUniforms(shader);
// Ambiant color of the scene
shader->SendColor(shaderUniforms->sceneAmbient, sceneData.ambientColor);
// Position of the camera
shader->SendVector(shaderUniforms->eyePosition, Renderer::GetMatrix(MatrixType_ViewProj).GetTranslation());
shader->SendVector(shaderUniforms->eyePosition, sceneData.viewer->GetEyePosition());
lastShader = shader;
}
const ForwardRenderQueue::BillboardData* data = &billboardVector[0];
unsigned int maxBillboardPerDraw = std::min(s_maxQuads, m_billboardPointBuffer.GetVertexCount()/4);
do
for (auto& matIt : pipelinePair.second.materialMap)
{
unsigned int renderedBillboardCount = std::min(billboardCount, maxBillboardPerDraw);
billboardCount -= renderedBillboardCount;
const Material* material = matIt.first;
auto& entry = matIt.second;
auto& billboardVector = entry.billboards;
BufferMapper<VertexBuffer> vertexMapper(m_billboardPointBuffer, BufferAccess_DiscardAndWrite, 0, renderedBillboardCount*4);
BillboardPoint* vertices = reinterpret_cast<BillboardPoint*>(vertexMapper.GetPointer());
const ForwardRenderQueue::BillboardData* data = &billboardVector[0];
unsigned int maxBillboardPerDraw = std::min(s_maxQuads, m_billboardPointBuffer.GetVertexCount() / 4);
for (unsigned int i = 0; i < renderedBillboardCount; ++i)
unsigned int billboardCount = billboardVector.size();
do
{
const ForwardRenderQueue::BillboardData& billboard = *data++;
unsigned int renderedBillboardCount = std::min(billboardCount, maxBillboardPerDraw);
billboardCount -= renderedBillboardCount;
vertices->color = billboard.color;
vertices->position = billboard.center;
vertices->sinCos = billboard.sinCos;
vertices->size = billboard.size;
vertices->uv.Set(0.f, 1.f);
vertices++;
BufferMapper<VertexBuffer> vertexMapper(m_billboardPointBuffer, BufferAccess_DiscardAndWrite, 0, renderedBillboardCount * 4);
BillboardPoint* vertices = static_cast<BillboardPoint*>(vertexMapper.GetPointer());
vertices->color = billboard.color;
vertices->position = billboard.center;
vertices->sinCos = billboard.sinCos;
vertices->size = billboard.size;
vertices->uv.Set(1.f, 1.f);
vertices++;
for (unsigned int i = 0; i < renderedBillboardCount; ++i)
{
const ForwardRenderQueue::BillboardData& billboard = *data++;
vertices->color = billboard.color;
vertices->position = billboard.center;
vertices->sinCos = billboard.sinCos;
vertices->size = billboard.size;
vertices->uv.Set(0.f, 0.f);
vertices++;
vertices->color = billboard.color;
vertices->position = billboard.center;
vertices->sinCos = billboard.sinCos;
vertices->size = billboard.size;
vertices->uv.Set(0.f, 1.f);
vertices++;
vertices->color = billboard.color;
vertices->position = billboard.center;
vertices->sinCos = billboard.sinCos;
vertices->size = billboard.size;
vertices->uv.Set(1.f, 0.f);
vertices++;
vertices->color = billboard.color;
vertices->position = billboard.center;
vertices->sinCos = billboard.sinCos;
vertices->size = billboard.size;
vertices->uv.Set(1.f, 1.f);
vertices++;
vertices->color = billboard.color;
vertices->position = billboard.center;
vertices->sinCos = billboard.sinCos;
vertices->size = billboard.size;
vertices->uv.Set(0.f, 0.f);
vertices++;
vertices->color = billboard.color;
vertices->position = billboard.center;
vertices->sinCos = billboard.sinCos;
vertices->size = billboard.size;
vertices->uv.Set(1.f, 0.f);
vertices++;
}
vertexMapper.Unmap();
Renderer::DrawIndexedPrimitives(PrimitiveMode_TriangleList, 0, renderedBillboardCount * 6);
}
while (billboardCount > 0);
vertexMapper.Unmap();
Renderer::DrawIndexedPrimitives(PrimitiveMode_TriangleList, 0, renderedBillboardCount*6);
billboardVector.clear();
}
while (billboardCount > 0);
billboardVector.clear();
}
}
}
@@ -460,116 +499,131 @@ namespace Nz
* \param sceneData Data of the scene
* \param layer Layer of the rendering
*/
void DepthRenderTechnique::DrawOpaqueModels(const SceneData& sceneData, ForwardRenderQueue::Layer& layer) const
{
NazaraAssert(sceneData.viewer, "Invalid viewer");
const Shader* lastShader = nullptr;
const ShaderUniforms* shaderUniforms = nullptr;
for (auto& matIt : layer.opaqueModels)
for (auto& pipelinePair : layer.opaqueModels)
{
auto& matEntry = matIt.second;
const MaterialPipeline* pipeline = pipelinePair.first;
auto& pipelineEntry = pipelinePair.second;
if (matEntry.enabled)
if (pipelineEntry.maxInstanceCount > 0)
{
ForwardRenderQueue::MeshInstanceContainer& meshInstances = matEntry.meshMap;
bool instancing = (pipelineEntry.maxInstanceCount > NAZARA_GRAPHICS_INSTANCING_MIN_INSTANCES_COUNT);
const MaterialPipeline::Instance& pipelineInstance = pipeline->Apply((instancing) ? ShaderFlags_Instancing : 0);
if (!meshInstances.empty())
const Shader* shader = pipelineInstance.uberInstance->GetShader();
// Uniforms are conserved in our program, there's no point to send them back until they change
if (shader != lastShader)
{
const Material* material = matIt.first;
// Index of uniforms in the shader
shaderUniforms = GetShaderUniforms(shader);
bool instancing = m_instancingEnabled && matEntry.instancingEnabled;
// Ambiant color of the scene
shader->SendColor(shaderUniforms->sceneAmbient, sceneData.ambientColor);
// Position of the camera
shader->SendVector(shaderUniforms->eyePosition, sceneData.viewer->GetEyePosition());
// We begin to apply the material (and get the shader activated doing so)
UInt8 freeTextureUnit;
const Shader* shader = material->Apply((instancing) ? ShaderFlags_Instancing : 0, 0, &freeTextureUnit);
lastShader = shader;
}
// Uniforms are conserved in our program, there's no point to send them back until they change
if (shader != lastShader)
for (auto& materialPair : pipelineEntry.materialMap)
{
const Material* material = materialPair.first;
auto& matEntry = materialPair.second;
if (matEntry.enabled)
{
// Index of uniforms in the shader
shaderUniforms = GetShaderUniforms(shader);
lastShader = shader;
}
UInt8 freeTextureUnit;
material->Apply(pipelineInstance, 0, &freeTextureUnit);
// Meshes
for (auto& meshIt : meshInstances)
{
const MeshData& meshData = meshIt.first;
auto& meshEntry = meshIt.second;
ForwardRenderQueue::MeshInstanceContainer& meshInstances = matEntry.meshMap;
const Spheref& squaredBoundingSphere = meshEntry.squaredBoundingSphere;
std::vector<Matrix4f>& instances = meshEntry.instances;
if (!instances.empty())
// Meshes
for (auto& meshIt : meshInstances)
{
const IndexBuffer* indexBuffer = meshData.indexBuffer;
const VertexBuffer* vertexBuffer = meshData.vertexBuffer;
const MeshData& meshData = meshIt.first;
auto& meshEntry = meshIt.second;
// Handle draw call before rendering loop
Renderer::DrawCall drawFunc;
Renderer::DrawCallInstanced instancedDrawFunc;
unsigned int indexCount;
const Spheref& squaredBoundingSphere = meshEntry.squaredBoundingSphere;
std::vector<Matrix4f>& instances = meshEntry.instances;
if (indexBuffer)
if (!instances.empty())
{
drawFunc = Renderer::DrawIndexedPrimitives;
instancedDrawFunc = Renderer::DrawIndexedPrimitivesInstanced;
indexCount = indexBuffer->GetIndexCount();
}
else
{
drawFunc = Renderer::DrawPrimitives;
instancedDrawFunc = Renderer::DrawPrimitivesInstanced;
indexCount = vertexBuffer->GetVertexCount();
}
const IndexBuffer* indexBuffer = meshData.indexBuffer;
const VertexBuffer* vertexBuffer = meshData.vertexBuffer;
Renderer::SetIndexBuffer(indexBuffer);
Renderer::SetVertexBuffer(vertexBuffer);
// Handle draw call before rendering loop
Renderer::DrawCall drawFunc;
Renderer::DrawCallInstanced instancedDrawFunc;
unsigned int indexCount;
if (instancing)
{
// We compute the number of instances that we will be able to draw this time (depending on the instancing buffer size)
VertexBuffer* instanceBuffer = Renderer::GetInstanceBuffer();
instanceBuffer->SetVertexDeclaration(VertexDeclaration::Get(VertexLayout_Matrix4));
if (indexBuffer)
{
drawFunc = Renderer::DrawIndexedPrimitives;
instancedDrawFunc = Renderer::DrawIndexedPrimitivesInstanced;
indexCount = indexBuffer->GetIndexCount();
}
else
{
drawFunc = Renderer::DrawPrimitives;
instancedDrawFunc = Renderer::DrawPrimitivesInstanced;
indexCount = vertexBuffer->GetVertexCount();
}
const Matrix4f* instanceMatrices = &instances[0];
unsigned int instanceCount = instances.size();
unsigned int maxInstanceCount = instanceBuffer->GetVertexCount(); // The maximum number of instances in one batch
Renderer::SetIndexBuffer(indexBuffer);
Renderer::SetVertexBuffer(vertexBuffer);
while (instanceCount > 0)
if (instancing)
{
// We compute the number of instances that we will be able to draw this time (depending on the instancing buffer size)
unsigned int renderedInstanceCount = std::min(instanceCount, maxInstanceCount);
instanceCount -= renderedInstanceCount;
VertexBuffer* instanceBuffer = Renderer::GetInstanceBuffer();
instanceBuffer->SetVertexDeclaration(VertexDeclaration::Get(VertexLayout_Matrix4));
// We fill the instancing buffer with our world matrices
instanceBuffer->Fill(instanceMatrices, 0, renderedInstanceCount, true);
instanceMatrices += renderedInstanceCount;
const Matrix4f* instanceMatrices = &instances[0];
unsigned int instanceCount = instances.size();
unsigned int maxInstanceCount = instanceBuffer->GetVertexCount(); // Maximum number of instance in one batch
// And we draw
instancedDrawFunc(renderedInstanceCount, meshData.primitiveMode, 0, indexCount);
while (instanceCount > 0)
{
// We compute the number of instances that we will be able to draw this time (depending on the instancing buffer size)
unsigned int renderedInstanceCount = std::min(instanceCount, maxInstanceCount);
instanceCount -= renderedInstanceCount;
// We fill the instancing buffer with our world matrices
instanceBuffer->Fill(instanceMatrices, 0, renderedInstanceCount, true);
instanceMatrices += renderedInstanceCount;
// And we draw
instancedDrawFunc(renderedInstanceCount, meshData.primitiveMode, 0, indexCount);
}
}
}
else
{
// Without instancing, we must do a draw call for each instance
else
{
// Without instancing, we must do a draw call for each instance
// This may be faster than instancing under a certain number
// Due to the time to modify the instancing buffer
for (const Matrix4f& matrix : instances)
{
Renderer::SetMatrix(MatrixType_World, matrix);
drawFunc(meshData.primitiveMode, 0, indexCount);
for (const Matrix4f& matrix : instances)
{
Renderer::SetMatrix(MatrixType_World, matrix);
drawFunc(meshData.primitiveMode, 0, indexCount);
}
}
instances.clear();
}
instances.clear();
}
matEntry.enabled = false;
}
}
// And we set the data back to zero
matEntry.enabled = false;
matEntry.instancingEnabled = false;
pipelineEntry.maxInstanceCount = 0;
}
}
}