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;