diff --git a/src/AcDream.App/Rendering/Wb/ObjectMeshManager.cs b/src/AcDream.App/Rendering/Wb/ObjectMeshManager.cs
index c3a0e74a..f0fd378e 100644
--- a/src/AcDream.App/Rendering/Wb/ObjectMeshManager.cs
+++ b/src/AcDream.App/Rendering/Wb/ObjectMeshManager.cs
@@ -2052,9 +2052,9 @@ namespace AcDream.App.Rendering.Wb
/// DrawMesh @0x0059D4A0 draws them in that order). Runs once per
/// mesh upload; the allocation is not per-frame.
///
- 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 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 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;