fix(render): pair cell-shell index segments with their reordered batches
The S1 review round ordered cell-shell batches by source surface index in ObjectMeshManager.UploadGfxObjMeshData but left the index segments handed to the arena in TextureBatches dictionary order. FirstIndex is positional over the segments, so every cell shell's batch pointed at another batch's index range: magenta placeholder walls, stretched textures, and missing faces in every dungeon and house (G1 FAIL 2026-09-02). One upload order is now computed once and used by the count, fill, and batch loops alike. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
e2543d0ef0
commit
8c6563cada
1 changed files with 26 additions and 23 deletions
|
|
@ -2052,9 +2052,9 @@ namespace AcDream.App.Rendering.Wb
|
|||
/// <c>DrawMesh</c> @0x0059D4A0 draws them in that order). Runs once per
|
||||
/// mesh upload; the allocation is not per-frame.
|
||||
/// </summary>
|
||||
private static IEnumerable<((int Width, int Height, TextureFormat Format) Format, TextureBatchData Batch)> OrderedUploadBatches(ObjectMeshData meshData)
|
||||
internal static List<((int Width, int Height, TextureFormat Format) Format, TextureBatchData Batch)> OrderedUploadBatches(ObjectMeshData meshData)
|
||||
{
|
||||
var pairs = new List<((int Width, int Height, TextureFormat Format), TextureBatchData)>();
|
||||
var pairs = new List<((int Width, int Height, TextureFormat Format) Format, TextureBatchData Batch)>();
|
||||
bool cellShell = false;
|
||||
foreach (var (format, batches) in meshData.TextureBatches)
|
||||
{
|
||||
|
|
@ -2065,7 +2065,7 @@ namespace AcDream.App.Rendering.Wb
|
|||
}
|
||||
}
|
||||
if (cellShell)
|
||||
return pairs.OrderBy(p => p.Item2.SourceSurfaceIndex); // stable: OrderBy preserves storage order on ties
|
||||
return pairs.OrderBy(p => p.Batch.SourceSurfaceIndex).ToList(); // stable: OrderBy preserves storage order on ties
|
||||
return pairs;
|
||||
}
|
||||
|
||||
|
|
@ -2081,16 +2081,22 @@ namespace AcDream.App.Rendering.Wb
|
|||
// every batch twice (a per-batch ToArray plus an unsized
|
||||
// SelectMany growth) — megabytes of transient garbage per mesh on
|
||||
// the render thread.
|
||||
// ONE upload order for this mesh. The index segments handed to the
|
||||
// arena, the per-batch FirstIndex, and the renderBatches list are
|
||||
// all positional over this same sequence, so it is computed once
|
||||
// and used by every loop below. (The S1 review round ordered only
|
||||
// the batch loop and desynchronized every cell shell's geometry
|
||||
// from its texture; G1 failed on it. Never split these again.)
|
||||
List<((int Width, int Height, TextureFormat Format) Format, TextureBatchData Batch)> uploadOrder =
|
||||
OrderedUploadBatches(meshData);
|
||||
|
||||
int totalIndexCount = 0;
|
||||
int nonEmptyBatchCount = 0;
|
||||
foreach (List<TextureBatchData> formatBatches in meshData.TextureBatches.Values)
|
||||
foreach (var (_, formatBatch) in uploadOrder)
|
||||
{
|
||||
foreach (TextureBatchData formatBatch in formatBatches)
|
||||
{
|
||||
if (formatBatch.Indices.Count == 0) continue;
|
||||
totalIndexCount = checked(totalIndexCount + formatBatch.Indices.Count);
|
||||
nonEmptyBatchCount++;
|
||||
}
|
||||
if (formatBatch.Indices.Count == 0) continue;
|
||||
totalIndexCount = checked(totalIndexCount + formatBatch.Indices.Count);
|
||||
nonEmptyBatchCount++;
|
||||
}
|
||||
|
||||
var cpuIndices = new ushort[totalIndexCount];
|
||||
|
|
@ -2098,17 +2104,14 @@ namespace AcDream.App.Rendering.Wb
|
|||
{
|
||||
int fillOffset = 0;
|
||||
int segmentIndex = 0;
|
||||
foreach (List<TextureBatchData> formatBatches in meshData.TextureBatches.Values)
|
||||
foreach (var (_, formatBatch) in uploadOrder)
|
||||
{
|
||||
foreach (TextureBatchData formatBatch in formatBatches)
|
||||
{
|
||||
int count = formatBatch.Indices.Count;
|
||||
if (count == 0) continue;
|
||||
CollectionsMarshal.AsSpan(formatBatch.Indices)
|
||||
.CopyTo(cpuIndices.AsSpan(fillOffset, count));
|
||||
indexSegments[segmentIndex++] = (fillOffset, count);
|
||||
fillOffset = checked(fillOffset + count);
|
||||
}
|
||||
int count = formatBatch.Indices.Count;
|
||||
if (count == 0) continue;
|
||||
CollectionsMarshal.AsSpan(formatBatch.Indices)
|
||||
.CopyTo(cpuIndices.AsSpan(fillOffset, count));
|
||||
indexSegments[segmentIndex++] = (fillOffset, count);
|
||||
fillOffset = checked(fillOffset + count);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2147,9 +2150,9 @@ namespace AcDream.App.Rendering.Wb
|
|||
// (ConstructMesh @0x0059DFA0 attribute-range scan, DrawMesh
|
||||
// @0x0059D4A0 ascending subset loop). So cell-shell batches
|
||||
// are uploaded in that order; ordinary GfxObj meshes keep the
|
||||
// storage order. This runs once per mesh at upload time, not
|
||||
// per frame.
|
||||
foreach (var (format, batch) in OrderedUploadBatches(meshData))
|
||||
// storage order. `uploadOrder` is the SAME sequence the index
|
||||
// segments above were filled from; the two must never diverge.
|
||||
foreach (var (format, batch) in uploadOrder)
|
||||
{
|
||||
{
|
||||
if (batch.Indices.Count == 0) continue;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue