Fix draw calls batching to ignore distance in opaque passes for better draw calls instancing

#2271
This commit is contained in:
Wojtek Figat
2025-08-13 21:30:18 +02:00
parent 278dead0bd
commit cdb09847ec
3 changed files with 66 additions and 38 deletions

View File

@@ -514,45 +514,61 @@ void RenderList::Clear()
Memory.Free();
}
struct PackedSortKey
// Sorting order: By Sort Order -> By Material -> By Geometry -> By Distance
PACK_STRUCT(struct PackedSortKey
{
union
{
uint64 Data;
uint32 DistanceKey;
uint8 DrawKey;
uint16 BatchKey;
uint8 SortKey;
});
PACK_BEGIN()
// Sorting order: By Sort Order -> By Material -> By Geometry -> By Distance
PACK_STRUCT(struct PackedSortKeyDistance
{
uint8 DrawKey;
uint16 BatchKey;
uint32 DistanceKey;
uint8 SortKey;
});
struct
{
// Sorting order: By Sort Order -> By Distance -> By Material -> By Geometry
uint8 DrawKey;
uint16 BatchKey;
uint32 DistanceKey;
uint8 SortKey;
} PACK_END();
};
};
static_assert(sizeof(PackedSortKey) == sizeof(uint64), "Invalid sort key size");
static_assert(sizeof(PackedSortKeyDistance) == sizeof(uint64), "Invalid sort key size");
FORCE_INLINE void CalculateSortKey(const RenderContext& renderContext, DrawCall& drawCall, int8 sortOrder)
FORCE_INLINE void CalculateSortKey(const RenderContext& renderContext, DrawCall& drawCall, DrawPass drawModes, int8 sortOrder)
{
const Float3 planeNormal = renderContext.View.Direction;
const float planePoint = -Float3::Dot(planeNormal, renderContext.View.Position);
const float distance = Float3::Dot(planeNormal, drawCall.ObjectPosition) - planePoint;
PackedSortKey key;
key.DistanceKey = RenderTools::ComputeDistanceSortKey(distance);
uint32 distanceKey = RenderTools::ComputeDistanceSortKey(distance);
uint32 batchKey = GetHash(drawCall.Material);
IMaterial::InstancingHandler handler;
if (drawCall.Material->CanUseInstancing(handler))
handler.GetHash(drawCall, batchKey);
key.BatchKey = (uint16)batchKey;
uint32 drawKey = (uint32)(471 * drawCall.WorldDeterminantSign);
drawKey = (drawKey * 397) ^ GetHash(drawCall.Geometry.VertexBuffers[0]);
drawKey = (drawKey * 397) ^ GetHash(drawCall.Geometry.VertexBuffers[1]);
drawKey = (drawKey * 397) ^ GetHash(drawCall.Geometry.VertexBuffers[2]);
drawKey = (drawKey * 397) ^ GetHash(drawCall.Geometry.IndexBuffer);
key.DrawKey = (uint8)drawKey;
key.SortKey = (uint8)(sortOrder - MIN_int8);
drawCall.SortKey = key.Data;
if ((drawModes & DrawPass::Forward) != DrawPass::None)
{
// Distance takes precedence over batching efficiency
PackedSortKeyDistance key;
key.BatchKey = (uint16)batchKey;
key.DistanceKey = distanceKey;
key.DrawKey = (uint8)drawKey;
key.SortKey = (uint8)(sortOrder - MIN_int8);
drawCall.SortKey = *(uint64*)&key;
}
else
{
PackedSortKey key;
key.BatchKey = (uint16)batchKey;
key.DistanceKey = distanceKey;
key.DrawKey = (uint8)drawKey;
key.SortKey = (uint8)(sortOrder - MIN_int8);
drawCall.SortKey = *(uint64*)&key;
}
}
void RenderList::AddDrawCall(const RenderContext& renderContext, DrawPass drawModes, StaticFlags staticFlags, DrawCall& drawCall, bool receivesDecals, int8 sortOrder)
@@ -564,7 +580,7 @@ void RenderList::AddDrawCall(const RenderContext& renderContext, DrawPass drawMo
#endif
// Append draw call data
CalculateSortKey(renderContext, drawCall, sortOrder);
CalculateSortKey(renderContext, drawCall, drawModes, sortOrder);
const int32 index = DrawCalls.Add(drawCall);
// Add draw call to proper draw lists
@@ -603,7 +619,7 @@ void RenderList::AddDrawCall(const RenderContextBatch& renderContextBatch, DrawP
const RenderContext& mainRenderContext = renderContextBatch.Contexts.Get()[0];
// Append draw call data
CalculateSortKey(mainRenderContext, drawCall, sortOrder);
CalculateSortKey(mainRenderContext, drawCall, drawModes, sortOrder);
const int32 index = DrawCalls.Add(drawCall);
// Add draw call to proper draw lists
@@ -678,7 +694,7 @@ void RenderList::BuildObjectsBuffer()
ZoneValue(ObjectBuffer.Data.Count() / 1024); // Objects Buffer size in kB
}
void RenderList::SortDrawCalls(const RenderContext& renderContext, bool reverseDistance, DrawCallsList& list, const RenderListBuffer<DrawCall>& drawCalls, DrawPass pass, bool stable)
void RenderList::SortDrawCalls(const RenderContext& renderContext, bool reverseDistance, DrawCallsList& list, const RenderListBuffer<DrawCall>& drawCalls, DrawCallsListType listType, DrawPass pass)
{
PROFILE_CPU();
PROFILE_MEM(GraphicsCommands);
@@ -698,14 +714,27 @@ void RenderList::SortDrawCalls(const RenderContext& renderContext, bool reverseD
// Setup sort keys
if (reverseDistance)
{
for (int32 i = 0; i < listSize; i++)
if (listType == DrawCallsListType::Forward) // Transparency uses distance over batching for correct draw order
{
const DrawCall& drawCall = drawCallsData[listData[i]];
PackedSortKey key;
key.Data = drawCall.SortKey;
key.DistanceKey ^= MAX_uint32; // Reverse depth
key.SortKey ^= MAX_uint8; // Reverse sort order
sortedKeys[i] = key.Data;
for (int32 i = 0; i < listSize; i++)
{
const DrawCall& drawCall = drawCallsData[listData[i]];
PackedSortKeyDistance key = *(PackedSortKeyDistance*)&drawCall.SortKey;
key.DistanceKey ^= MAX_uint32; // Reverse depth
key.SortKey ^= MAX_uint8; // Reverse sort order
sortedKeys[i] = *(uint64*)&key;
}
}
else
{
for (int32 i = 0; i < listSize; i++)
{
const DrawCall& drawCall = drawCallsData[listData[i]];
PackedSortKey key = *(PackedSortKey*)&drawCall.SortKey;
key.DistanceKey ^= MAX_uint32; // Reverse depth
key.SortKey ^= MAX_uint8; // Reverse sort order
sortedKeys[i] = *(uint64*)&key;
}
}
}
else
@@ -762,7 +791,7 @@ void RenderList::SortDrawCalls(const RenderContext& renderContext, bool reverseD
}
// When using depth buffer draw calls are already almost ideally sorted by Radix Sort but transparency needs more stability to prevent flickering
if (stable)
if (listType == DrawCallsListType::Forward)
{
// Sort draw calls batches by depth
Array<DrawBatch, RendererAllocation> sortingBatches;

View File

@@ -569,8 +569,7 @@ public:
/// <param name="pass">The draw pass (optional).</param>
API_FUNCTION() FORCE_INLINE void SortDrawCalls(API_PARAM(Ref) const RenderContext& renderContext, bool reverseDistance, DrawCallsListType listType, DrawPass pass = DrawPass::All)
{
const bool stable = listType == DrawCallsListType::Forward;
SortDrawCalls(renderContext, reverseDistance, DrawCallsLists[(int32)listType], DrawCalls, pass, stable);
SortDrawCalls(renderContext, reverseDistance, DrawCallsLists[(int32)listType], DrawCalls, listType);
}
/// <summary>
@@ -580,9 +579,9 @@ public:
/// <param name="reverseDistance">If set to <c>true</c> reverse draw call distance to the view. Results in back to front sorting.</param>
/// <param name="list">The collected draw calls indices list.</param>
/// <param name="drawCalls">The collected draw calls list.</param>
/// <param name="listType">The hint about draw calls list type (optional).</param>
/// <param name="pass">The draw pass (optional).</param>
/// <param name="stable">If set to <c>true</c> draw batches will be additionally sorted to prevent any flickering, otherwise Depth Buffer will smooth out any non-stability in sorting.</param>
void SortDrawCalls(const RenderContext& renderContext, bool reverseDistance, DrawCallsList& list, const RenderListBuffer<DrawCall>& drawCalls, DrawPass pass = DrawPass::All, bool stable = false);
void SortDrawCalls(const RenderContext& renderContext, bool reverseDistance, DrawCallsList& list, const RenderListBuffer<DrawCall>& drawCalls, DrawCallsListType listType = DrawCallsListType::GBuffer, DrawPass pass = DrawPass::All);
/// <summary>
/// Executes the collected draw calls.

View File

@@ -507,7 +507,7 @@ void RenderInner(SceneRenderTask* task, RenderContext& renderContext, RenderCont
// Shadow context sorting
auto& shadowContext = RenderContextBatch.Contexts[index - ARRAY_COUNT(MainContextSorting)];
shadowContext.List->SortDrawCalls(shadowContext, false, DrawCallsListType::Depth, DrawPass::Depth);
shadowContext.List->SortDrawCalls(shadowContext, false, shadowContext.List->ShadowDepthDrawCallsList, renderContext.List->DrawCalls, DrawPass::Depth);
shadowContext.List->SortDrawCalls(shadowContext, false, shadowContext.List->ShadowDepthDrawCallsList, renderContext.List->DrawCalls, DrawCallsListType::Depth, DrawPass::Depth);
}
}
} processor = { renderContextBatch };