From 254ce569fa8fc56706e77c643cfa1f779b1a0166 Mon Sep 17 00:00:00 2001 From: Wojtek Figat Date: Wed, 7 Jul 2021 23:52:41 +0200 Subject: [PATCH] Optimize Foliage rendering with manual instanced draw calls batching --- Source/Engine/Foliage/Foliage.cpp | 187 ++++++++++++++++++++------ Source/Engine/Foliage/Foliage.h | 26 +++- Source/Engine/Renderer/RenderList.cpp | 120 +++++++++++++++-- Source/Engine/Renderer/RenderList.h | 30 +++-- 4 files changed, 294 insertions(+), 69 deletions(-) diff --git a/Source/Engine/Foliage/Foliage.cpp b/Source/Engine/Foliage/Foliage.cpp index f47f873af..4408b201b 100644 --- a/Source/Engine/Foliage/Foliage.cpp +++ b/Source/Engine/Foliage/Foliage.cpp @@ -81,48 +81,40 @@ void Foliage::AddToCluster(ChunkedArrayLODs[lod].Meshes; + for (int32 meshIndex = 0; meshIndex < meshes.Count(); meshIndex++) { - const auto& entry = type.Entries[mesh.GetMaterialSlotIndex()]; - if (!entry.Visible || !mesh.IsInitialized()) - return; - const MaterialSlot& slot = model->MaterialSlots[mesh.GetMaterialSlotIndex()]; - const auto shadowsMode = static_cast(entry.ShadowsMode & slot.ShadowsMode); - const auto drawModes = static_cast(type._drawModes & renderContext.View.GetShadowsDrawPassMask(shadowsMode)); + auto& drawCall = drawCallsLists[lod][meshIndex]; + if (!drawCall.DrawCall.Material) + continue; - // Select material - MaterialBase* material; - if (entry.Material && entry.Material->IsLoaded()) - material = entry.Material; - else if (slot.Material && slot.Material->IsLoaded()) - material = slot.Material; - else - material = GPUDevice::Instance->GetDefaultMaterial(); - if (!material || !material->IsSurface() || drawModes == DrawPass::None) - return; + DrawKey key; + key.Mat = drawCall.DrawCall.Material; + key.Geo = &meshes[meshIndex]; + key.Lightmap = instance.Lightmap.TextureIndex; + auto* e = result.TryGet(key); + if (!e) + { + e = &result[key]; + e->DrawCall.Material = key.Mat; + e->DrawCall.Surface.Lightmap = _staticFlags & StaticFlags::Lightmap ? _scene->LightmapsData.GetReadyLightmap(key.Lightmap) : nullptr; + } - // Submit draw call - DrawCall drawCall; - mesh.GetDrawCallGeometry(drawCall); - drawCall.InstanceCount = 1; - drawCall.Material = material; - drawCall.World = instance.World; - drawCall.ObjectPosition = drawCall.World.GetTranslation(); - drawCall.Surface.GeometrySize = mesh.GetBox().GetSize(); - drawCall.Surface.PrevWorld = instance.World; - drawCall.Surface.Lightmap = _staticFlags & StaticFlags::Lightmap ? _scene->LightmapsData.GetReadyLightmap(instance.Lightmap.TextureIndex) : nullptr; - drawCall.Surface.LightmapUVsArea = instance.Lightmap.UVsArea; - drawCall.Surface.Skinning = nullptr; - drawCall.Surface.LODDitherFactor = lodDitherFactor; - drawCall.WorldDeterminantSign = 1; - drawCall.PerInstanceRandom = instance.Random; - renderContext.List->AddDrawCall(drawModes, _staticFlags, drawCall, entry.ReceiveDecals); + // Add instance to the draw batch + auto& instanceData = e->Instances.AddOne(); + instanceData.InstanceOrigin = Vector3(instance.World.M41, instance.World.M42, instance.World.M43); + instanceData.PerInstanceRandom = instance.Random; + instanceData.InstanceTransform1 = Vector3(instance.World.M11, instance.World.M12, instance.World.M13); + instanceData.LODDitherFactor = lodDitherFactor; + instanceData.InstanceTransform2 = Vector3(instance.World.M21, instance.World.M22, instance.World.M23); + instanceData.InstanceTransform3 = Vector3(instance.World.M31, instance.World.M32, instance.World.M33); + instanceData.InstanceLightmapArea = Half4(instance.Lightmap.UVsArea); } } -void Foliage::DrawCluster(RenderContext& renderContext, FoliageCluster* cluster, FoliageType& type) +void Foliage::DrawCluster(RenderContext& renderContext, FoliageCluster* cluster, FoliageType& type, DrawCallsList* drawCallsLists, BatchedDrawCalls& result) const { // Skip clusters that around too far from view if (Vector3::Distance(renderContext.View.Position, cluster->TotalBoundsSphere.Center) - cluster->TotalBoundsSphere.Radius > cluster->MaxCullDistance) @@ -138,7 +130,7 @@ void Foliage::DrawCluster(RenderContext& renderContext, FoliageCluster* cluster, #define DRAW_CLUSTER(idx) \ if (renderContext.View.CullingFrustum.Intersects(cluster->Children[idx]->TotalBounds)) \ - DrawCluster(renderContext, cluster->Children[idx], type) + DrawCluster(renderContext, cluster->Children[idx], type, drawCallsLists, result) DRAW_CLUSTER(0); DRAW_CLUSTER(1); DRAW_CLUSTER(2); @@ -182,7 +174,7 @@ void Foliage::DrawCluster(RenderContext& renderContext, FoliageCluster* cluster, { const auto prevLOD = model->ClampLODIndex(instance.DrawState.PrevLOD); const float normalizedProgress = static_cast(instance.DrawState.LODTransition) * (1.0f / 255.0f); - DrawInstance(renderContext, instance, type, model, model->LODs[prevLOD], normalizedProgress); + DrawInstance(renderContext, instance, type, model, prevLOD, normalizedProgress, drawCallsLists, result); } } instance.DrawState.PrevFrame = frame; @@ -219,19 +211,19 @@ void Foliage::DrawCluster(RenderContext& renderContext, FoliageCluster* cluster, // Draw if (instance.DrawState.PrevLOD == lodIndex) { - DrawInstance(renderContext, instance, type, model, model->LODs[lodIndex], 0.0f); + DrawInstance(renderContext, instance, type, model, lodIndex, 0.0f, drawCallsLists, result); } else if (instance.DrawState.PrevLOD == -1) { const float normalizedProgress = static_cast(instance.DrawState.LODTransition) * (1.0f / 255.0f); - DrawInstance(renderContext, instance, type, model, model->LODs[lodIndex], 1.0f - normalizedProgress); + DrawInstance(renderContext, instance, type, model, lodIndex, 1.0f - normalizedProgress, drawCallsLists, result); } else { const auto prevLOD = model->ClampLODIndex(instance.DrawState.PrevLOD); const float normalizedProgress = static_cast(instance.DrawState.LODTransition) * (1.0f / 255.0f); - DrawInstance(renderContext, instance, type, model, model->LODs[prevLOD], normalizedProgress); - DrawInstance(renderContext, instance, type, model, model->LODs[lodIndex], normalizedProgress - 1.0f); + DrawInstance(renderContext, instance, type, model, prevLOD, normalizedProgress, drawCallsLists, result); + DrawInstance(renderContext, instance, type, model, lodIndex, normalizedProgress - 1.0f, drawCallsLists, result); } //DebugDraw::DrawSphere(instance.Bounds, Color::YellowGreen); @@ -878,13 +870,17 @@ void Foliage::Draw(RenderContext& renderContext) } // Draw visible clusters -#if FOLIAGE_USE_SINGLE_QUAD_TREE +#if FOLIAGE_USE_SINGLE_QUAD_TREE || !FOLIAGE_USE_DRAW_CALLS_BATCHING Mesh::DrawInfo draw; draw.Flags = GetStaticFlags(); draw.DrawModes = (DrawPass)(DrawPass::Default & view.Pass); draw.LODBias = 0; draw.ForcedLOD = -1; draw.VertexColors = nullptr; +#else + DrawCallsList drawCallsLists[MODEL_MAX_LODS]; +#endif +#if FOLIAGE_USE_SINGLE_QUAD_TREE if (Root) DrawCluster(renderContext, Root, draw); #else @@ -892,7 +888,112 @@ void Foliage::Draw(RenderContext& renderContext) { if (type.Root && type._canDraw && type.Model->CanBeRendered()) { - DrawCluster(renderContext, type.Root, type); +#if FOLIAGE_USE_DRAW_CALLS_BATCHING + // Initialize draw calls for foliage type all LODs meshes + for (int32 lod = 0; lod < type.Model->LODs.Count(); lod++) + { + auto& modelLod = type.Model->LODs[lod]; + DrawCallsList& drawCallsList = drawCallsLists[lod]; + const auto& meshes = modelLod.Meshes; + drawCallsList.Resize(meshes.Count()); + for (int32 meshIndex = 0; meshIndex < meshes.Count(); meshIndex++) + { + const auto& mesh = meshes[meshIndex]; + auto& drawCall = drawCallsList[meshIndex]; + drawCall.DrawCall.Material = nullptr; + + // Check entry visibility + const auto& entry = type.Entries[mesh.GetMaterialSlotIndex()]; + if (!entry.Visible || !mesh.IsInitialized()) + continue; + const MaterialSlot& slot = type.Model->MaterialSlots[mesh.GetMaterialSlotIndex()]; + + // Select material + MaterialBase* material; + if (entry.Material && entry.Material->IsLoaded()) + material = entry.Material; + else if (slot.Material && slot.Material->IsLoaded()) + material = slot.Material; + else + material = GPUDevice::Instance->GetDefaultMaterial(); + if (!material || !material->IsSurface()) + continue; + + // Select draw modes + const auto shadowsMode = static_cast(entry.ShadowsMode & slot.ShadowsMode); + const auto drawModes = static_cast(type._drawModes & renderContext.View.GetShadowsDrawPassMask(shadowsMode)) & material->GetDrawModes(); + if (drawModes == 0) + continue; + + drawCall.DrawCall.Material = material; + } + } + + // Draw instances of the foliage type + BatchedDrawCalls result; + DrawCluster(renderContext, type.Root, type, drawCallsLists, result); + + // Submit draw calls with valid instances added + for (auto& e : result) + { + auto& drawCall = e.Value; + if (drawCall.Instances.IsEmpty()) + continue; + const auto& mesh = *e.Key.Geo; + const auto& entry = type.Entries[mesh.GetMaterialSlotIndex()]; + const MaterialSlot& slot = type.Model->MaterialSlots[mesh.GetMaterialSlotIndex()]; + const auto shadowsMode = static_cast(entry.ShadowsMode & slot.ShadowsMode); + const auto drawModes = (DrawPass)(static_cast(type._drawModes & renderContext.View.GetShadowsDrawPassMask(shadowsMode)) & drawCall.DrawCall.Material->GetDrawModes()); + + // Setup draw call + mesh.GetDrawCallGeometry(drawCall.DrawCall); + drawCall.DrawCall.InstanceCount = 1; + auto& firstInstance = drawCall.Instances[0]; + drawCall.DrawCall.ObjectPosition = firstInstance.InstanceOrigin; + drawCall.DrawCall.PerInstanceRandom = firstInstance.PerInstanceRandom; + auto lightmapArea = firstInstance.InstanceLightmapArea.ToVector4(); + drawCall.DrawCall.Surface.LightmapUVsArea = *(Rectangle*)&lightmapArea; + drawCall.DrawCall.Surface.LODDitherFactor = firstInstance.LODDitherFactor; + drawCall.DrawCall.World.SetRow1(Vector4(firstInstance.InstanceTransform1, 0.0f)); + drawCall.DrawCall.World.SetRow2(Vector4(firstInstance.InstanceTransform2, 0.0f)); + drawCall.DrawCall.World.SetRow3(Vector4(firstInstance.InstanceTransform3, 0.0f)); + drawCall.DrawCall.World.SetRow4(Vector4(firstInstance.InstanceOrigin, 1.0f)); + drawCall.DrawCall.Surface.PrevWorld = drawCall.DrawCall.World; + drawCall.DrawCall.Surface.GeometrySize = mesh.GetBox().GetSize(); + drawCall.DrawCall.Surface.Skinning = nullptr; + drawCall.DrawCall.WorldDeterminantSign = 1; + + const int32 batchIndex = renderContext.List->BatchedDrawCalls.Count(); + renderContext.List->BatchedDrawCalls.Add(MoveTemp(drawCall)); + + // Add draw call to proper draw lists + if (drawModes & DrawPass::Depth) + { + renderContext.List->DrawCallsLists[(int32)DrawCallsListType::Depth].PreBatchedDrawCalls.Add(batchIndex); + } + if (drawModes & DrawPass::GBuffer) + { + if (entry.ReceiveDecals) + renderContext.List->DrawCallsLists[(int32)DrawCallsListType::GBuffer].PreBatchedDrawCalls.Add(batchIndex); + else + renderContext.List->DrawCallsLists[(int32)DrawCallsListType::GBufferNoDecals].PreBatchedDrawCalls.Add(batchIndex); + } + if (drawModes & DrawPass::Forward) + { + renderContext.List->DrawCallsLists[(int32)DrawCallsListType::Forward].PreBatchedDrawCalls.Add(batchIndex); + } + if (drawModes & DrawPass::Distortion) + { + renderContext.List->DrawCallsLists[(int32)DrawCallsListType::Distortion].PreBatchedDrawCalls.Add(batchIndex); + } + if (drawModes & DrawPass::MotionVectors && (_staticFlags & StaticFlags::Transform) == 0) + { + renderContext.List->DrawCallsLists[(int32)DrawCallsListType::MotionVectors].PreBatchedDrawCalls.Add(batchIndex); + } + } +#else + DrawCluster(renderContext, type.Root, draw); +#endif } } #endif diff --git a/Source/Engine/Foliage/Foliage.h b/Source/Engine/Foliage/Foliage.h index 505b7d405..08f3c14a1 100644 --- a/Source/Engine/Foliage/Foliage.h +++ b/Source/Engine/Foliage/Foliage.h @@ -153,8 +153,30 @@ private: void AddToCluster(ChunkedArray& clusters, FoliageCluster* cluster, FoliageInstance& instance); #if !FOLIAGE_USE_SINGLE_QUAD_TREE && FOLIAGE_USE_DRAW_CALLS_BATCHING - void DrawInstance(RenderContext& renderContext, FoliageInstance& instance, FoliageType& type, Model* model, const ModelLOD& modelLod, float lodDitherFactor); - void DrawCluster(RenderContext& renderContext, FoliageCluster* cluster, FoliageType& type); + struct DrawKey + { + IMaterial* Mat; + const Mesh* Geo; + int32 Lightmap; + + friend bool operator==(const DrawKey& lhs, const DrawKey& rhs) + { + return lhs.Mat == rhs.Mat && lhs.Geo == rhs.Geo && lhs.Lightmap == rhs.Lightmap; + } + + friend uint32 GetHash(const DrawKey& key) + { + uint32 hash = (uint32)((int64)(key.Mat) >> 3); + hash ^= (uint32)((int64)(key.Geo) >> 3) + 0x9e3779b9 + (hash << 6) + (hash >> 2); + hash ^= (uint32)key.Lightmap; + return hash; + } + }; + + typedef Array> DrawCallsList; + typedef Dictionary BatchedDrawCalls; + void DrawInstance(RenderContext& renderContext, FoliageInstance& instance, FoliageType& type, Model* model, int32 lod, float lodDitherFactor, DrawCallsList* drawCallsLists, BatchedDrawCalls& result) const; + void DrawCluster(RenderContext& renderContext, FoliageCluster* cluster, FoliageType& type, DrawCallsList* drawCallsLists, BatchedDrawCalls& result) const; #else void DrawCluster(RenderContext& renderContext, FoliageCluster* cluster, Mesh::DrawInfo& draw); #endif diff --git a/Source/Engine/Renderer/RenderList.cpp b/Source/Engine/Renderer/RenderList.cpp index f387f5085..fb6c64c70 100644 --- a/Source/Engine/Renderer/RenderList.cpp +++ b/Source/Engine/Renderer/RenderList.cpp @@ -316,6 +316,19 @@ bool RenderList::HasAnyPostAA(RenderContext& renderContext) const return false; } +void DrawCallsList::Clear() +{ + Indices.Clear(); + PreBatchedDrawCalls.Clear(); + Batches.Clear(); + CanUseInstancing = true; +} + +bool DrawCallsList::IsEmpty() const +{ + return Indices.Count() + PreBatchedDrawCalls.Count() == 0; +} + RenderList::RenderList(const SpawnParams& params) : PersistentScriptingObject(params) , DirectionalLights(4) @@ -341,6 +354,7 @@ void RenderList::Init(RenderContext& renderContext) void RenderList::Clear() { DrawCalls.Clear(); + BatchedDrawCalls.Clear(); for (auto& list : DrawCallsLists) list.Clear(); PointLights.Clear(); @@ -502,13 +516,9 @@ bool CanUseInstancing(DrawPass pass) void RenderList::ExecuteDrawCalls(const RenderContext& renderContext, DrawCallsList& list) { - // Skip if no rendering to perform - if (list.Batches.IsEmpty()) + if (list.IsEmpty()) return; - PROFILE_GPU_CPU("Drawing"); - - const int32 batchesSize = list.Batches.Count(); const auto context = GPUDevice::Instance->GetMainContext(); bool useInstancing = list.CanUseInstancing && CanUseInstancing(renderContext.View.Pass) && GPUDevice::Instance->Limits.HasInstancing; @@ -520,13 +530,17 @@ void RenderList::ExecuteDrawCalls(const RenderContext& renderContext, DrawCallsL { // Prepare buffer memory int32 batchesCount = 0; - for (int32 i = 0; i < batchesSize; i++) + for (int32 i = 0; i < list.Batches.Count(); i++) { auto& batch = list.Batches[i]; if (batch.BatchSize > 1) - { batchesCount += batch.BatchSize; - } + } + for (int32 i = 0; i < list.PreBatchedDrawCalls.Count(); i++) + { + auto& batch = BatchedDrawCalls[list.PreBatchedDrawCalls[i]]; + if (batch.Instances.Count() > 1) + batchesCount += batch.Instances.Count(); } if (batchesCount == 0) { @@ -539,7 +553,7 @@ void RenderList::ExecuteDrawCalls(const RenderContext& renderContext, DrawCallsL auto instanceData = (InstanceData*)_instanceBuffer.Data.Get(); // Write to instance buffer - for (int32 i = 0; i < batchesSize; i++) + for (int32 i = 0; i < list.Batches.Count(); i++) { auto& batch = list.Batches[i]; if (batch.BatchSize > 1) @@ -554,6 +568,15 @@ void RenderList::ExecuteDrawCalls(const RenderContext& renderContext, DrawCallsL } } } + for (int32 i = 0; i < list.PreBatchedDrawCalls.Count(); i++) + { + auto& batch = BatchedDrawCalls[list.PreBatchedDrawCalls[i]]; + if (batch.Instances.Count() > 1) + { + Platform::MemoryCopy(instanceData, batch.Instances.Get(), batch.Instances.Count() * sizeof(InstanceData)); + instanceData += batch.Instances.Count(); + } + } // Upload data _instanceBuffer.Flush(context); @@ -568,7 +591,7 @@ DRAW: int32 instanceBufferOffset = 0; GPUBuffer* vb[4]; uint32 vbOffsets[4]; - for (int32 i = 0; i < batchesSize; i++) + for (int32 i = 0; i < list.Batches.Count(); i++) { auto& batch = list.Batches[i]; auto& drawCall = DrawCalls[list.Indices[batch.StartIndex]]; @@ -615,16 +638,64 @@ DRAW: vbCount++; context->BindVB(ToSpan(vb, vbCount), vbOffsets); context->DrawIndexedInstanced(drawCall.Draw.IndicesCount, batch.InstanceCount, instanceBufferOffset, 0, drawCall.Draw.StartIndex); - instanceBufferOffset += batch.BatchSize; } } } + for (int32 i = 0; i < list.PreBatchedDrawCalls.Count(); i++) + { + auto& batch = BatchedDrawCalls[list.PreBatchedDrawCalls[i]]; + auto& drawCall = batch.DrawCall; + + int32 vbCount = 0; + while (drawCall.Geometry.VertexBuffers[vbCount] && vbCount < ARRAY_COUNT(drawCall.Geometry.VertexBuffers)) + { + vb[vbCount] = drawCall.Geometry.VertexBuffers[vbCount]; + vbOffsets[vbCount] = drawCall.Geometry.VertexBuffersOffsets[vbCount]; + vbCount++; + } + for (int32 j = vbCount; j < ARRAY_COUNT(drawCall.Geometry.VertexBuffers); j++) + { + vb[vbCount] = nullptr; + vbOffsets[vbCount] = 0; + } + + bindParams.FirstDrawCall = &drawCall; + bindParams.DrawCallsCount = batch.Instances.Count(); + drawCall.Material->Bind(bindParams); + + context->BindIB(drawCall.Geometry.IndexBuffer); + + if (drawCall.InstanceCount == 0) + { + ASSERT_LOW_LAYER(batch.Instances.Count() == 1); + context->BindVB(ToSpan(vb, vbCount), vbOffsets); + context->DrawIndexedInstancedIndirect(drawCall.Draw.IndirectArgsBuffer, drawCall.Draw.IndirectArgsOffset); + } + else + { + if (batch.Instances.Count() == 1) + { + context->BindVB(ToSpan(vb, vbCount), vbOffsets); + context->DrawIndexedInstanced(drawCall.Draw.IndicesCount, batch.Instances.Count(), 0, 0, drawCall.Draw.StartIndex); + } + else + { + vbCount = 3; + vb[vbCount] = _instanceBuffer.GetBuffer(); + vbOffsets[vbCount] = 0; + vbCount++; + context->BindVB(ToSpan(vb, vbCount), vbOffsets); + context->DrawIndexedInstanced(drawCall.Draw.IndicesCount, batch.Instances.Count(), instanceBufferOffset, 0, drawCall.Draw.StartIndex); + instanceBufferOffset += batch.Instances.Count(); + } + } + } } else { bindParams.DrawCallsCount = 1; - for (int32 i = 0; i < batchesSize; i++) + for (int32 i = 0; i < list.Batches.Count(); i++) { auto& batch = list.Batches[i]; @@ -647,6 +718,31 @@ DRAW: } } } + for (int32 i = 0; i < list.PreBatchedDrawCalls.Count(); i++) + { + auto& batch = BatchedDrawCalls[list.PreBatchedDrawCalls[i]]; + auto drawCall = batch.DrawCall; + bindParams.FirstDrawCall = &drawCall; + + for (int32 j = 0; j < batch.Instances.Count(); j++) + { + auto& instance = batch.Instances[j]; + drawCall.ObjectPosition = instance.InstanceOrigin; + drawCall.PerInstanceRandom = instance.PerInstanceRandom; + auto lightmapArea = instance.InstanceLightmapArea.ToVector4(); + drawCall.Surface.LightmapUVsArea = *(Rectangle*)&lightmapArea; + drawCall.Surface.LODDitherFactor = instance.LODDitherFactor; + drawCall.World.SetRow1(Vector4(instance.InstanceTransform1, 0.0f)); + drawCall.World.SetRow2(Vector4(instance.InstanceTransform2, 0.0f)); + drawCall.World.SetRow3(Vector4(instance.InstanceTransform3, 0.0f)); + drawCall.World.SetRow4(Vector4(instance.InstanceOrigin, 1.0f)); + drawCall.Material->Bind(bindParams); + + context->BindIB(drawCall.Geometry.IndexBuffer); + context->BindVB(ToSpan(drawCall.Geometry.VertexBuffers, 3), drawCall.Geometry.VertexBuffersOffsets); + context->DrawIndexedInstanced(drawCall.Draw.IndicesCount, drawCall.InstanceCount, 0, 0, drawCall.Draw.StartIndex); + } + } } } diff --git a/Source/Engine/Renderer/RenderList.h b/Source/Engine/Renderer/RenderList.h index bf0f25d0e..efc0c55b7 100644 --- a/Source/Engine/Renderer/RenderList.h +++ b/Source/Engine/Renderer/RenderList.h @@ -203,6 +203,12 @@ struct DrawBatch } }; +struct BatchedDrawCall +{ + DrawCall DrawCall; + Array Instances; +}; + /// /// Represents a list of draw calls. /// @@ -213,6 +219,11 @@ struct DrawCallsList /// Array Indices; + /// + /// The list of external draw calls indices to render. + /// + Array PreBatchedDrawCalls; + /// /// The draw calls batches (for instancing). /// @@ -223,17 +234,8 @@ struct DrawCallsList /// bool CanUseInstancing; - void Clear() - { - Indices.Clear(); - Batches.Clear(); - CanUseInstancing = true; - } - - bool IsEmpty() const - { - return Indices.IsEmpty(); - } + void Clear(); + bool IsEmpty() const; }; /// @@ -242,7 +244,6 @@ struct DrawCallsList API_CLASS(Sealed) class FLAXENGINE_API RenderList : public PersistentScriptingObject { DECLARE_SCRIPTING_TYPE(RenderList); -public: /// /// Allocates the new renderer list object or reuses already allocated one. @@ -268,6 +269,11 @@ public: /// Array DrawCalls; + /// + /// Draw calls list with pre-batched instances (for all draw passes). + /// + Array BatchedDrawCalls; + /// /// The draw calls lists. Each for the separate draw pass. ///