perf(mesh): skip immutable index re-upload on RT refit UPDATE (#68) #103
2 changed files with 15 additions and 8 deletions
perf(mesh): skip immutable index re-upload on RT refit UPDATE (#68)
A hardware acceleration-structure UPDATE cannot change topology, so the index buffer is immutable on the Refit UPDATE path. Re-uploading it was a wasted memcpy + FlushDevice + barrier of unchanged data every refit — genuinely per-frame for deforming meshes. Skip the index memcpy and its FlushDevice on the UPDATE branch; the RecordBLASBuild call still reads the stable, unchanged indexBuffer.address. The `indicies` span is now read only for its count. Document the API nuance: a bitwise-different but topologically-equivalent index array is ignored on refit, consistent with "a refit may only move vertex positions." The full-rebuild fallback path still re-uploads both buffers. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
commit
a55f15d332
|
|
@ -357,12 +357,16 @@ void Mesh::Refit(std::span<Vector<float, 3, 3>> verticies, std::span<std::uint32
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Same-sized buffers: overwrite in place so the device addresses (and
|
// Same-sized buffers: overwrite the vertex positions in place so the
|
||||||
// thus the geometry the UPDATE reads) stay stable.
|
// device address (and thus the geometry the UPDATE reads) stays stable.
|
||||||
|
// The index buffer is deliberately left untouched: a hardware AS UPDATE
|
||||||
|
// cannot change topology, so the indices are immutable here — re-uploading
|
||||||
|
// them would be a wasted memcpy + flush + barrier of unchanged data every
|
||||||
|
// refit. This means a bitwise-different-but-topologically-equivalent index
|
||||||
|
// array passed on refit is ignored, consistent with the documented
|
||||||
|
// contract that a refit may only move vertex positions.
|
||||||
std::memcpy(vertexBuffer.value, verticies.data(), verticies.size() * sizeof(Vector<float, 3, 3>));
|
std::memcpy(vertexBuffer.value, verticies.data(), verticies.size() * sizeof(Vector<float, 3, 3>));
|
||||||
std::memcpy(indexBuffer.value, indicies.data(), indicies.size() * sizeof(std::uint32_t));
|
|
||||||
vertexBuffer.FlushDevice(cmd, VK_ACCESS_MEMORY_READ_BIT, VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR);
|
vertexBuffer.FlushDevice(cmd, VK_ACCESS_MEMORY_READ_BIT, VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR);
|
||||||
indexBuffer.FlushDevice(cmd, VK_ACCESS_MEMORY_READ_BIT, VK_PIPELINE_STAGE_ACCELERATION_STRUCTURE_BUILD_BIT_KHR);
|
|
||||||
|
|
||||||
RecordBLASBuild(*this, static_cast<std::uint32_t>(verticies.size()), static_cast<std::uint32_t>(indicies.size()), buildFlags, /*update*/ true, cmd);
|
RecordBLASBuild(*this, static_cast<std::uint32_t>(verticies.size()), static_cast<std::uint32_t>(indicies.size()), buildFlags, /*update*/ true, cmd);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -148,10 +148,13 @@ export namespace Crafter {
|
||||||
// a hardware UPDATE-mode build — much cheaper than a rebuild, and the
|
// a hardware UPDATE-mode build — much cheaper than a rebuild, and the
|
||||||
// BLAS handle / blasAddr are preserved, so TLAS instances referencing
|
// BLAS handle / blasAddr are preserved, so TLAS instances referencing
|
||||||
// it stay valid. A hardware update may only move vertex positions,
|
// it stay valid. A hardware update may only move vertex positions,
|
||||||
// not change connectivity; the indices are re-uploaded for symmetry
|
// not change connectivity, so on the UPDATE path only the vertex
|
||||||
// with the rebuild path but must describe the same topology. Without
|
// buffer is re-uploaded; the index buffer is left untouched (its
|
||||||
// allowUpdate (or if the counts changed) it falls back to a full
|
// contents are immutable for an UPDATE). The `indicies` span is read
|
||||||
// rebuild. Lifetime contract matches Build: the spans need only
|
// only for its count — a bitwise-different but topologically-equivalent
|
||||||
|
// index array is ignored on refit. Without allowUpdate (or if the
|
||||||
|
// counts changed) it falls back to a full rebuild, which does re-upload
|
||||||
|
// both buffers. Lifetime contract matches Build: the spans need only
|
||||||
// outlive this call. Call this per frame to track a deforming mesh.
|
// outlive this call. Call this per frame to track a deforming mesh.
|
||||||
void Refit(std::span<Vector<float, 3, 3>> verticies, std::span<std::uint32_t> indicies, VkCommandBuffer cmd);
|
void Refit(std::span<Vector<float, 3, 3>> verticies, std::span<std::uint32_t> indicies, VkCommandBuffer cmd);
|
||||||
// Procedural analog of Refit: new object-space boxes, same count.
|
// Procedural analog of Refit: new object-space boxes, same count.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue