perf(render) #429: allocation-exact streamed-mesh completion
UploadGfxObjMeshData built every completed mesh's index data three-plus times over in LINQ transients (per-batch Indices.ToArray copies plus an unsized SelectMany growth) on the render thread, up to the per-frame upload budget. The conversion now fills one exact-size retained CPUIndices array (the same one the B.4b pick path keeps) and hands the shared arena (offset, count) segments of it; CPUPositions fills by a direct pre-sized loop; the Sum/Any/FirstOrDefault transients are gone. GlobalMeshBuffer.UploadMesh takes the segment form — the staged bytes per batch are unchanged. Gate: a warmed completion must allocate near its retained-copy size (MeshPipelineDeviceSeamTests). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
5b9d0260bb
commit
0330fcd0d1
3 changed files with 183 additions and 34 deletions
|
|
@ -286,9 +286,15 @@ public sealed class GlobalMeshBuffer : IDisposable
|
|||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// #429: index batches arrive as (offset, count) segments of one shared
|
||||
/// index array — the caller's retained CPU pick copy — instead of one
|
||||
/// managed array per batch. The bytes staged per batch are unchanged.
|
||||
/// </summary>
|
||||
internal GlobalMeshAllocation UploadMesh(
|
||||
VertexPositionNormalTexture[] vertices,
|
||||
IReadOnlyList<ushort[]> indexBatches)
|
||||
ushort[] indices,
|
||||
ReadOnlySpan<(int Offset, int Count)> indexBatches)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
_retirementLedger.RetryPendingPublications();
|
||||
|
|
@ -296,16 +302,21 @@ public sealed class GlobalMeshBuffer : IDisposable
|
|||
if (_migration is not null)
|
||||
throw new InvalidOperationException("A mesh upload cannot mutate the arena while a backing-buffer migration is in progress.");
|
||||
ArgumentNullException.ThrowIfNull(vertices);
|
||||
ArgumentNullException.ThrowIfNull(indexBatches);
|
||||
ArgumentNullException.ThrowIfNull(indices);
|
||||
if (vertices.Length == 0)
|
||||
throw new ArgumentException("A global mesh allocation requires vertices.", nameof(vertices));
|
||||
|
||||
int totalIndices = 0;
|
||||
for (int i = 0; i < indexBatches.Count; i++)
|
||||
for (int i = 0; i < indexBatches.Length; i++)
|
||||
{
|
||||
ushort[] batch = indexBatches[i]
|
||||
?? throw new ArgumentException("Index batches cannot contain null.", nameof(indexBatches));
|
||||
totalIndices = checked(totalIndices + batch.Length);
|
||||
(int offset, int count) = indexBatches[i];
|
||||
if (offset < 0 || count <= 0 || (long)offset + count > indices.Length)
|
||||
{
|
||||
throw new ArgumentException(
|
||||
$"Index batch {i} ({offset}, {count}) is outside the shared index array ({indices.Length}).",
|
||||
nameof(indexBatches));
|
||||
}
|
||||
totalIndices = checked(totalIndices + count);
|
||||
}
|
||||
if (totalIndices == 0)
|
||||
throw new ArgumentException("A global mesh allocation requires indices.", nameof(indexBatches));
|
||||
|
|
@ -322,7 +333,7 @@ public sealed class GlobalMeshBuffer : IDisposable
|
|||
throw;
|
||||
}
|
||||
|
||||
var firstIndices = new int[indexBatches.Count];
|
||||
var firstIndices = new int[indexBatches.Length];
|
||||
try
|
||||
{
|
||||
// IGpuBuffer.Upload stages through a neutral binding point of the
|
||||
|
|
@ -337,18 +348,15 @@ public sealed class GlobalMeshBuffer : IDisposable
|
|||
|
||||
IGpuBuffer indexStore = RequireStore(_indexBuffer);
|
||||
int indexOffset = indexRange.Offset;
|
||||
for (int i = 0; i < indexBatches.Count; i++)
|
||||
for (int i = 0; i < indexBatches.Length; i++)
|
||||
{
|
||||
ushort[] batch = indexBatches[i];
|
||||
(int offset, int count) = indexBatches[i];
|
||||
firstIndices[i] = indexOffset;
|
||||
if (batch.Length > 0)
|
||||
{
|
||||
long indexOffsetBytes = checked((long)indexOffset * sizeof(ushort));
|
||||
indexStore.Upload(
|
||||
indexOffsetBytes,
|
||||
MemoryMarshal.AsBytes(new ReadOnlySpan<ushort>(batch)));
|
||||
indexOffset = checked(indexOffset + batch.Length);
|
||||
}
|
||||
long indexOffsetBytes = checked((long)indexOffset * sizeof(ushort));
|
||||
indexStore.Upload(
|
||||
indexOffsetBytes,
|
||||
MemoryMarshal.AsBytes(new ReadOnlySpan<ushort>(indices, offset, count)));
|
||||
indexOffset = checked(indexOffset + count);
|
||||
}
|
||||
}
|
||||
catch
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue