Crafter.Graphics/interfaces/Crafter.Graphics-Mesh.cppm

351 lines
20 KiB
Text
Raw Normal View History

2026-01-28 01:07:41 +01:00
/*
Crafter®.Graphics
Copyright (C) 2026 Catcrafts®
catcrafts.net
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License version 3.0 as published by the Free Software Foundation;
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
module;
2026-05-18 02:07:48 +02:00
#ifndef CRAFTER_GRAPHICS_WINDOW_DOM
2026-03-02 23:53:13 +01:00
#include "vulkan/vulkan.h"
2026-01-28 01:07:41 +01:00
2026-05-18 02:07:48 +02:00
#endif // !CRAFTER_GRAPHICS_WINDOW_DOM
2026-01-28 01:07:41 +01:00
export module Crafter.Graphics:Mesh;
2026-05-18 02:07:48 +02:00
#ifndef CRAFTER_GRAPHICS_WINDOW_DOM
2026-01-28 01:07:41 +01:00
import std;
2026-02-22 00:46:38 +01:00
import Crafter.Math;
2026-05-12 00:24:48 +02:00
import Crafter.Asset;
2026-01-28 01:07:41 +01:00
import :VulkanBuffer;
export namespace Crafter {
// One procedural primitive's axis-aligned bounding box, in object
// space. Layout-compatible with VkAabbPositionsKHR (24-byte stride),
// so a span of these uploads directly as a
// VK_GEOMETRY_TYPE_AABBS_KHR build input. Mirrors the DOM-side
// definition below so portable user code compiles unchanged.
struct RTAabb {
float min[3];
float max[3];
};
static_assert(sizeof(RTAabb) == sizeof(VkAabbPositionsKHR));
// Build-time tuning for a BLAS, mapped onto the preference bits of
// VkBuildAccelerationStructureFlagBitsKHR.
enum class RTBuildPreference {
// VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_TRACE_BIT_KHR — spend
// more time building for faster traversal. The right default for
// static geometry that is traced many times.
FastTrace,
// VK_BUILD_ACCELERATION_STRUCTURE_PREFER_FAST_BUILD_BIT_KHR — build
// quickly at the cost of traversal speed. For geometry rebuilt
// (or first-frame streamed) often enough that build time dominates.
FastBuild,
};
// Per-build options for Mesh::Build / BuildProcedural.
struct RTBuildOptions {
RTBuildPreference preference = RTBuildPreference::FastTrace;
// Set VK_BUILD_ACCELERATION_STRUCTURE_ALLOW_UPDATE_BIT_KHR so the
// BLAS can later be refit in place via Refit()/RefitProcedural().
// Required for any subsequent refit; refitting a BLAS built without
// it falls back to a full rebuild. PREFER_FAST_TRACE and
// PREFER_FAST_BUILD are mutually exclusive, so `preference` selects
// exactly one — allowUpdate is layered on top of whichever is set.
bool allowUpdate = false;
};
2026-01-28 01:07:41 +01:00
class Mesh {
public:
2026-04-10 22:26:15 +02:00
VulkanBuffer<char, false> scratchBuffer;
VulkanBuffer<char, false> blasBuffer;
perf(mesh): place RT geometry in device-local memory via #89 upload strategy (#73) vertexBuffer/indexBuffer/aabbBuffer were HOST_VISIBLE (system RAM), so the BLAS build, every refit, and any hit-shader geometry fetch read them over PCIe. The usage flags (SHADER_DEVICE_ADDRESS, plus STORAGE on the index buffer) exist precisely to expose geometry for hit-shader fetch, the dominant RT shading pattern. Add VulkanBuffer::UploadDeviceLocal, which places a CPU-written/GPU-read buffer in device-local memory and picks the upload mechanism at runtime via the #89 strategy (Device::PreferDirectDeviceWrite): - ReBAR/UMA: allocate HOST_VISIBLE|DEVICE_LOCAL (DEVICE_LOCAL preferred), map transiently, memcpy, flush-if-non-coherent. No staging buffer. - no/small BAR: allocate pure DEVICE_LOCAL + TRANSFER_DST, fill a transient HOST_VISIBLE staging buffer, vkCmdCopyBuffer, then DeferredClear the staging buffer onto the fence-keyed deletion queue (#101/#102) so it outlives the copy submit. The geometry buffers become non-mapped so the destination is free to be device-local-only. Same-size re-uploads reuse the allocation, so the device address stays stable across an in-place AS UPDATE refit. The compressed Build path now allocates vertex/index as pure DEVICE_LOCAL — the GPU decompressor fills them directly, no host write. Tests: BLASBuildOptions asserts device-local placement on the triangle, procedural, and (budget-forced) staged paths, and exercises the staged copy + deferred-deletion + in-place refit under GPU-assisted validation with zero validation errors. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 17:49:25 +00:00
// Non-mapped (device-local) RT geometry: VulkanBuffer::UploadDeviceLocal
// places these in device memory and picks direct-map vs staged-copy per
// the #89 upload strategy, so they live in VRAM for hit-shader fetch and
// BLAS-build/refit reads instead of being read from system RAM over PCIe
// every trace (#73). The compressed Build path allocates them pure
// DEVICE_LOCAL and lets the GPU decompressor fill them directly.
VulkanBuffer<Vector<float, 3, 3>, false> vertexBuffer;
VulkanBuffer<std::uint32_t, false> indexBuffer;
// AABB build input for the procedural path (BuildProcedural).
// Lifetime contract matches vertexBuffer/indexBuffer: must stay
// alive until the build submitted on `cmd` completes.
perf(mesh): place RT geometry in device-local memory via #89 upload strategy (#73) vertexBuffer/indexBuffer/aabbBuffer were HOST_VISIBLE (system RAM), so the BLAS build, every refit, and any hit-shader geometry fetch read them over PCIe. The usage flags (SHADER_DEVICE_ADDRESS, plus STORAGE on the index buffer) exist precisely to expose geometry for hit-shader fetch, the dominant RT shading pattern. Add VulkanBuffer::UploadDeviceLocal, which places a CPU-written/GPU-read buffer in device-local memory and picks the upload mechanism at runtime via the #89 strategy (Device::PreferDirectDeviceWrite): - ReBAR/UMA: allocate HOST_VISIBLE|DEVICE_LOCAL (DEVICE_LOCAL preferred), map transiently, memcpy, flush-if-non-coherent. No staging buffer. - no/small BAR: allocate pure DEVICE_LOCAL + TRANSFER_DST, fill a transient HOST_VISIBLE staging buffer, vkCmdCopyBuffer, then DeferredClear the staging buffer onto the fence-keyed deletion queue (#101/#102) so it outlives the copy submit. The geometry buffers become non-mapped so the destination is free to be device-local-only. Same-size re-uploads reuse the allocation, so the device address stays stable across an in-place AS UPDATE refit. The compressed Build path now allocates vertex/index as pure DEVICE_LOCAL — the GPU decompressor fills them directly, no host write. Tests: BLASBuildOptions asserts device-local placement on the triangle, procedural, and (budget-forced) staged paths, and exercises the staged copy + deferred-deletion + in-place refit under GPU-assisted validation with zero validation errors. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-17 17:49:25 +00:00
VulkanBuffer<RTAabb, false> aabbBuffer;
// Transient host-visible staging for the compressed Build path. Kept as
// a member only so the recorded vkCmdDecompressMemoryEXT has a stable
// address to reference; the compressed Build releases it via
// DeferredClear() right after recording the decompress, so it is freed
// by the fence-keyed deletion queue (#101/#102) once that submit's frame
// has cleared — no longer pinned for the mesh's life. Between Builds the
// handle is null; the next Build's Resize re-creates it.
2026-05-12 00:24:48 +02:00
VulkanBuffer<std::byte, true> compressedStaging;
2026-01-28 01:07:41 +01:00
VkAccelerationStructureGeometryTrianglesDataKHR blasData;
VkAccelerationStructureGeometryKHR blas;
VkAccelerationStructureKHR accelerationStructure = VK_NULL_HANDLE;
2026-02-22 00:46:38 +01:00
VkDeviceAddress blasAddr;
2026-01-28 01:07:41 +01:00
bool opaque;
// ─── Build-option / refit state ──────────────────────────────────
// Flags the BLAS was last (re)built with — carried so Refit can
// re-issue an identical-flags UPDATE build (the FAST_TRACE/FAST_BUILD
// preference plus, when opted in, ALLOW_UPDATE).
VkBuildAccelerationStructureFlagsKHR buildFlags = 0;
// Primitive count of the current build (triangles = indexCount/3,
// procedural = aabb count). An in-place UPDATE must preserve it.
std::uint32_t builtPrimitiveCount = 0;
// Element count the vertex / aabb input buffer was sized for. Refit
// re-uploads in place only when the new data matches this; a change
// forces a full rebuild (topology changed).
std::uint32_t builtInputCount = 0;
// Whether ALLOW_UPDATE was set on the last build, i.e. whether an
// in-place refit is possible at all.
bool allowUpdate = false;
void Build(std::span<Vector<float, 3, 3>> verticies, std::span<std::uint32_t> indicies, VkCommandBuffer cmd, RTBuildOptions options = {});
2026-05-12 00:24:48 +02:00
// GPU path: decompresses vertex (region 0) and index (region 1) streams
// from asset.blob into vertexBuffer / indexBuffer using
// VK_EXT_memory_decompression. Falls back to CPU decode + the
// uncompressed Build if Device::memoryDecompressionSupported is false.
// Region 2 (data) is not consumed here — the caller decompresses it
// into their own buffer if needed (or uses Compression::DecompressCPU).
void Build(const ::Crafter::CompressedMeshAsset& asset, VkCommandBuffer cmd, RTBuildOptions options = {});
// Build an AABB (procedural) BLAS from a list of object-space
// boxes (VK_GEOMETRY_TYPE_AABBS_KHR). The hit group bound to
// instances of this mesh must be a
// VK_RAY_TRACING_SHADER_GROUP_TYPE_PROCEDURAL_HIT_GROUP_KHR group
// carrying an intersection shader; that shader is invoked for
// each box the ray enters and reports the surface hit via
// reportIntersectionEXT. `opaque` maps to
// VK_GEOMETRY_OPAQUE_BIT_KHR: pass false (the WebGPU-path
// default) to let any-hit shaders run. Same scratch/lifetime
// handling as the triangle Build; instances reference blasAddr
// exactly like a triangle BLAS.
void BuildProcedural(std::span<const RTAabb> aabbs, bool opaque, VkCommandBuffer cmd, RTBuildOptions options = {});
// Zero-copy procedural build: take the AABB build input straight from
// an existing device buffer (by device address + primitive count)
// rather than memcpy-ing a host span through aabbBuffer. The intended
// producer is a GPU compute pass that writes the boxes itself — e.g.
// a GPU-resident particle system whose per-frame AABBs never touch the
// CPU. The buffer must carry
// VK_BUFFER_USAGE_ACCELERATION_STRUCTURE_BUILD_INPUT_READ_ONLY_BIT_KHR
// and VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT, its boxes laid out as
// VkAabbPositionsKHR (`stride`, default sizeof(RTAabb) == 24), and the
// caller must pipeline-barrier the producing writes (e.g. a compute
// dispatch) before the build on `cmd` reads them. The buffer's
// lifetime is the caller's responsibility — it is never copied, so it
// must outlive the build submitted on `cmd`. Everything else (opaque
// bit, hit-group contract, instance wiring) matches the host-span
// BuildProcedural above.
void BuildProcedural(VkDeviceAddress aabbAddress, std::uint32_t count, bool opaque, VkCommandBuffer cmd, RTBuildOptions options = {}, std::uint32_t stride = sizeof(RTAabb));
// Refit the triangle BLAS against new geometry of the *same
// topology* (same vertex count and index count as the original
// Build). When that Build set RTBuildOptions::allowUpdate this issues
// a hardware UPDATE-mode build — much cheaper than a rebuild, and the
// BLAS handle / blasAddr are preserved, so TLAS instances referencing
// it stay valid. A hardware update may only move vertex positions,
// not change connectivity, so on the UPDATE path only the vertex
// buffer is re-uploaded; the index buffer is left untouched (its
// contents are immutable for an UPDATE). The `indicies` span is read
// 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.
void Refit(std::span<Vector<float, 3, 3>> verticies, std::span<std::uint32_t> indicies, VkCommandBuffer cmd);
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>
2026-06-17 19:50:48 +00:00
// 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
// RefitProcedural above and the input-source pairing for the device
// BuildProcedural. The GPU producer rewrote the boxes in `aabbAddress`
// in place; this re-records the build straight from that address —
// an in-place hardware UPDATE when the original build set allowUpdate
// and `count` is unchanged (handle / blasAddr preserved), otherwise a
// full rebuild. Same buffer-usage / barrier / lifetime contract as the
// device BuildProcedural. Call per frame to track GPU-written boxes
// with zero host involvement.
void RefitProcedural(VkDeviceAddress aabbAddress, std::uint32_t count, VkCommandBuffer cmd, std::uint32_t stride = sizeof(RTAabb));
2026-01-28 01:07:41 +01:00
};
2026-05-18 02:07:48 +02:00
}
#endif // !CRAFTER_GRAPHICS_WINDOW_DOM
2026-05-18 18:43:30 +02:00
#ifdef CRAFTER_GRAPHICS_WINDOW_DOM
import std;
import Crafter.Math;
2026-05-19 00:27:09 +02:00
import Crafter.Asset;
2026-05-18 18:43:30 +02:00
import :WebGPU;
export namespace Crafter {
// Software-RT BLAS node, packed to 32 bytes. Matches the WGSL
// `BVHNode` struct in the RT WGSL prelude (additional/dom-webgpu.js,
// rtWgslPrelude) byte-for-byte.
//
// primCount == 0 → inner node, children at indices
// firstChildOrPrim and firstChildOrPrim+1.
// primCount > 0 → leaf, `primCount` primitives starting at
// primIndex `firstChildOrPrim` in the
// global primRemap heap.
//
// SAH-built BVH2; constructed CPU-side at Build() time, never refit.
struct BVHNode {
float aabbMin[3];
std::uint32_t firstChildOrPrim;
float aabbMax[3];
std::uint32_t primCount;
};
static_assert(sizeof(BVHNode) == 32);
// One procedural primitive's axis-aligned bounding box, in object
// space. The analog of VkAabbPositionsKHR — the BLAS stores these
// instead of triangles and an intersection shader (registered in the
// hit group as a ProceduralHitGroup) reports the actual surface hit
// for each AABB the ray enters.
struct RTAabb {
float min[3];
float max[3];
};
static_assert(sizeof(RTAabb) == 24);
// Mirror of the native build-option types so portable code compiles
// unchanged. The software-RT path has no hardware acceleration
// structure, so the FastTrace/FastBuild preference has no effect (the
// SAH BVH2 is always built the same way) and a "refit" simply rebuilds
// the BVH on the host. The types exist purely for API symmetry.
enum class RTBuildPreference {
FastTrace,
FastBuild,
};
struct RTBuildOptions {
RTBuildPreference preference = RTBuildPreference::FastTrace;
bool allowUpdate = false;
};
2026-05-18 18:43:30 +02:00
class Mesh {
public:
// BLAS "handle": opaque identity that goes into
// RTInstance::accelerationStructureReference. Set by Build() to a
// stable u32 (widened to u64 for Vulkan-struct layout parity), used
// by the WebGPU TLAS-build compute shader to look up the BLAS root
// AABB and per-mesh heap offsets. Handle 0 is the unassigned
// sentinel; never returned by Build().
std::uint64_t blasAddr = 0;
std::uint32_t triangleCount = 0;
2026-05-24 13:32:08 +02:00
std::uint32_t vertexCount = 0;
2026-05-18 18:43:30 +02:00
bool opaque = true;
// Build BLAS from raw triangle data. Runs the CPU SAH BVH2 builder
// and forwards vertex/index/BVH/remap arrays to the JS-side mesh
// heap (additional/dom-webgpu.js), which queue.writeBuffers them
// into the global heaps and records the per-mesh offsets keyed by
// the returned handle. The `cmd` parameter is unused on WebGPU —
// kept for API symmetry with the Vulkan signature.
void Build(std::span<Crafter::Vector<float, 3, 3>> vertices,
std::span<std::uint32_t> indices,
WebGPUCommandEncoderRef cmd = 0,
RTBuildOptions options = {});
2026-05-19 00:27:09 +02:00
// CPU-decompress the .cmesh blob (no VK_EXT_memory_decompression
// equivalent in WebGPU) and forward to the positions+indices path,
// plus push the optional `data` region into the per-vertex attribs
// heap so closest-hit shaders can sample UVs / normals / tangents.
// The data layout is example-defined — the heap is exposed in WGSL
// as `vertexAttribs : array<u32>` with a per-mesh u32-word offset.
void Build(const ::Crafter::CompressedMeshAsset& asset,
WebGPUCommandEncoderRef cmd = 0,
RTBuildOptions options = {});
// Build an AABB (procedural) BLAS from a list of object-space boxes
// — the WebGPU analog of a VK_GEOMETRY_TYPE_AABBS_KHR geometry. The
// hit group bound to instances of this mesh must be a
// ProceduralHitGroup carrying an intersection shader; that shader is
// invoked for each box the ray enters and reports the surface hit.
// `opaque` is the geometry's opaque bit: pass false to let any-hit
// shaders run (the default for procedural geometry, which is usually
// transparent / volumetric). The `cmd` parameter is unused on
// WebGPU — kept for API symmetry with the triangle path.
void BuildProcedural(std::span<const RTAabb> aabbs,
bool opaque = false,
WebGPUCommandEncoderRef cmd = 0,
RTBuildOptions options = {});
// Zero-copy procedural build: the boxes already live in an existing
// device buffer (`aabbBuffer`, a GPUBuffer a compute pass wrote), fed
// straight into the BLAS with no host copy — the WebGPU analog of the
// device-address Vulkan BuildProcedural. The software path has no
// hardware AS and builds its BVH on the host, but here there is no
// host copy to build over, so the device boxes are copied GPU→GPU into
// the mesh heap and wrapped in a single root leaf bounded by
// `worldBounds` (object-space min/max enclosing all `count` boxes —
// the producer supplies it since the CPU never sees the boxes). The
// traversal then linearly intersects every box. `opaque` is the
// geometry opaque bit (false lets any-hit run). `options` has no effect
// (no hardware AS to tune), kept for API symmetry.
void BuildProcedural(WebGPUBufferRef aabbBuffer,
std::uint32_t count,
RTAabb worldBounds,
bool opaque = false,
WebGPUCommandEncoderRef cmd = 0,
RTBuildOptions options = {});
// Refit analogs of the native API. With no hardware AS to update,
// these simply re-run the host BVH build over the new data, so they
// accept the full geometry (not just moved positions). Provided so
// portable code that calls Refit/RefitProcedural compiles and
// behaves correctly on the WebGPU backend.
void Refit(std::span<Crafter::Vector<float, 3, 3>> vertices,
std::span<std::uint32_t> indices,
WebGPUCommandEncoderRef cmd = 0);
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>
2026-06-17 19:50:48 +00:00
// 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
// into this mesh's heap region (count unchanged) and update the root
// leaf bounds, keeping blasAddr stable — unlike the host-span
// RefitProcedural, which rebuilds and re-publishes a NEW handle. The
// device counterpart of the device-address Vulkan RefitProcedural;
// call per frame to track GPU-written boxes with no host copy.
void RefitProcedural(WebGPUBufferRef aabbBuffer,
std::uint32_t count,
RTAabb worldBounds,
WebGPUCommandEncoderRef cmd = 0);
2026-05-18 18:43:30 +02:00
};
}
#endif // CRAFTER_GRAPHICS_WINDOW_DOM