perf(mesh): dirty-range vertex upload for deforming-mesh Refit (#119)
Mesh::Refit re-uploaded the entire vertex array every frame on the in-place UPDATE path (full host write + flush + barrier on the direct path; full re-stage + copy on the staged path), even when only a handful of vertices moved. Add VulkanBuffer::UploadDeviceLocalRange — a dirty-range counterpart to UploadDeviceLocal that writes/flushes/stages/copies and barriers only the half-open element range [offset, offset+count) of an already-allocated device-local buffer. It picks direct-map vs staged-copy from the memory type the buffer was actually allocated with (not the sub-range size, which PreferDirectDeviceWrite would mis-route), and the direct path's ranged flush is rounded to nonCoherentAtomSize and clamped to the allocation size (mappedSize is now recorded for every buffer, not just mapped ones). Add a dirty-range Refit overload taking the full vertex/index arrays plus a (dirtyVertexOffset, dirtyVertexCount) window. The full-span Refit now delegates to it with the whole array as the window. On the in-place UPDATE path only the declared window is uploaded — the rest of the device buffer retains last refit's positions; when an UPDATE isn't possible it falls back to the full-span rebuild, which is why the full arrays are still passed. The WebGPU/DOM backend keeps API symmetry: it has no hardware AS, so it ignores the window and rebuilds the host BVH from the full geometry. BLASBuildOptions exercises the dirty-range refit on the direct path, the staged path, and the count-change rebuild fallback, asserting AS-handle / blasAddr stability and zero Vulkan validation errors. Resolves #119 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
5948bb91c1
commit
1f4c77000a
5 changed files with 199 additions and 8 deletions
|
|
@ -164,6 +164,22 @@ export namespace Crafter {
|
|||
// both buffers. Lifetime contract matches Build: the spans need only
|
||||
// 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);
|
||||
// Dirty-range refit: same as Refit above, but only the contiguous block
|
||||
// of vertices in [dirtyVertexOffset, dirtyVertexOffset + dirtyVertexCount)
|
||||
// actually moved, so on the in-place UPDATE path only that sub-range is
|
||||
// re-uploaded — host-write + flush + barrier (direct) or re-stage + copy
|
||||
// (staged) scale with the moved vertices, not the whole array (#119). Use
|
||||
// this for a deforming mesh that nudges a small, known window each frame.
|
||||
// `verticies` / `indicies` are still the *full* arrays (same contract and
|
||||
// topology rule as the full-span Refit — the index buffer is read only
|
||||
// for its count on the UPDATE path); the dirty window simply tells the
|
||||
// upload which slice changed, and is clamped to the array bounds. When an
|
||||
// in-place UPDATE is not possible (allowUpdate was not set, or the counts
|
||||
// changed) this falls back to the full-span Refit, which re-uploads
|
||||
// everything from `verticies` / `indicies` — so passing the full arrays
|
||||
// keeps that fallback correct. The AS handle / blasAddr are preserved on
|
||||
// the UPDATE path exactly as in the full-span Refit.
|
||||
void Refit(std::span<Vector<float, 3, 3>> verticies, std::span<std::uint32_t> indicies, std::uint32_t dirtyVertexOffset, std::uint32_t dirtyVertexCount, VkCommandBuffer cmd);
|
||||
// Procedural analog of Refit: new object-space boxes, same count.
|
||||
void RefitProcedural(std::span<const RTAabb> aabbs, VkCommandBuffer cmd);
|
||||
// Zero-copy procedural refit: the device-buffer counterpart of
|
||||
|
|
@ -307,6 +323,17 @@ export namespace Crafter {
|
|||
void Refit(std::span<Crafter::Vector<float, 3, 3>> vertices,
|
||||
std::span<std::uint32_t> indices,
|
||||
WebGPUCommandEncoderRef cmd = 0);
|
||||
// Dirty-range refit (#119). The software path has no hardware AS to
|
||||
// update a sub-range of — it rebuilds the host BVH over the full
|
||||
// geometry regardless — so the dirty window is ignored here and this
|
||||
// behaves exactly like the full-span Refit above (a fresh build that
|
||||
// re-publishes blasAddr). The overload exists so portable deforming-mesh
|
||||
// code that passes a dirty window compiles and stays correct on WebGPU.
|
||||
void Refit(std::span<Crafter::Vector<float, 3, 3>> vertices,
|
||||
std::span<std::uint32_t> indices,
|
||||
std::uint32_t dirtyVertexOffset,
|
||||
std::uint32_t dirtyVertexCount,
|
||||
WebGPUCommandEncoderRef cmd = 0);
|
||||
void RefitProcedural(std::span<const RTAabb> aabbs,
|
||||
WebGPUCommandEncoderRef cmd = 0);
|
||||
// Zero-copy procedural refit: re-copy the boxes from the device buffer
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue