/* 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; #ifndef CRAFTER_GRAPHICS_WINDOW_DOM #include "vulkan/vulkan.h" #endif // !CRAFTER_GRAPHICS_WINDOW_DOM export module Crafter.Graphics:RenderingElement3D; import :RT; #ifndef CRAFTER_GRAPHICS_WINDOW_DOM import std; import :Mesh; import :VulkanBuffer; import Crafter.Math; import :Window; export namespace Crafter { struct TlasWithBuffer { VkDeviceAddress address = 0; VulkanBuffer buffer; VkAccelerationStructureKHR accelerationStructure = VK_NULL_HANDLE; VulkanBuffer instanceBuffer; VulkanBuffer scratchBuffer; // Parallel to instanceBuffer, indexed by TLAS instance ID. Filled // from each element's userMetadata during BuildTLAS. Consumers // (e.g. ray-query collision) bind this in the descriptor heap and // look up via rayQueryGetIntersectionInstanceIdEXT to recover // application-side per-instance data without touching the // Vulkan-mandated instanceCustomIndex (which renderers may already // use for their own encoding). VulkanBuffer metadataBuffer; // Last instance count this TLAS was built (not refit) for. When // elements.size() matches this, BuildTLAS does an in-place refit // (UPDATE mode) which is dramatically cheaper than a full rebuild // — refit walks the existing BVH and updates AABBs, while rebuild // reconstructs the topology from scratch. A change in count forces // a fresh rebuild because the AS is sized for that primitive count. std::uint32_t builtInstanceCount = 0; }; class RenderingElement3D { public: RTInstance instance; // Position in `elements`, maintained by Add/Remove for O(1) swap-and-pop. // Sentinel value = not currently registered. std::uint32_t indexInElements = std::numeric_limits::max(); // Application-defined per-instance tag, copied verbatim into // tlases[*].metadataBuffer at this element's TLAS instance ID // every BuildTLAS. Crafter doesn't interpret it. std::uint32_t userMetadata = 0; // When true, BuildTLAS skips copying instance.transform into the // TLAS instance buffer — the application's compute shader writes // the transform field directly into instanceBuffer at this // element's TLAS instance ID. Other instance fields (mask, // customIndex, SBT offset, BLAS reference) are still copied from // the CPU instance struct. // // Used to take per-frame transform updates off the CPU for bodies // whose transforms derive from GPU-side state (physics nodes that // already live on the GPU). bool transformOwnedByGpu = false; static std::vector elements; inline static TlasWithBuffer tlases[Window::numFrames]; static void BuildTLAS(VkCommandBuffer cmd, std::uint32_t index); // Register / unregister with `elements`. Use these instead of touching // the vector directly: linear find+erase is O(n) and pathological at // the body counts physics targets (millions of braces). static void Add(RenderingElement3D* e); static void Remove(RenderingElement3D* e); }; } #endif // !CRAFTER_GRAPHICS_WINDOW_DOM #ifdef CRAFTER_GRAPHICS_WINDOW_DOM import std; import :Mesh; import :WebGPU; import :WebGPUBuffer; import :Window; export namespace Crafter { // Per-frame TLAS storage. WebGPU has no real swapchain frame count // (Window::numFrames = 1 on DOM), so this is effectively a singleton — // the array form is kept for API symmetry with the Vulkan side so user // code that indexes `tlases[frameIdx]` ports unchanged. struct TlasWithBuffer { // Host-visible instance buffer holding RTInstance entries — same // layout as Vulkan's VkAccelerationStructureInstanceKHR, so user // code touching .instance.mask / .flags / .transform.matrix is // identical across backends. Also bound as a storage SSBO so // application compute shaders (e.g. physics-tlas-transform.comp.wgsl) // can write the .transform field directly when // RenderingElement3D::transformOwnedByGpu is set. WebGPUBuffer instanceBuffer; // Per-instance application metadata; parallel to instanceBuffer, // identical semantics to the Vulkan-side counterpart. WebGPUBuffer metadataBuffer; // GPU-built TLAS data: one TLASEntry per instance, written each // BuildTLAS by a compute pass on the JS bridge. Read by traceRay / // rayQuery as `@group(1) @binding(0) tlas: array`. // TLASEntry layout: 96 bytes — aabbMin (12) + maskHGoffset (4) + // aabbMax (12) + blasHandle (4) + invTransform 3x4 mat (48) + // customIndex (4) + _pad (12). Defined in the WGSL traversal // library; never directly read by C++. WebGPUBuffer buffer; std::uint32_t builtInstanceCount = 0; }; class RenderingElement3D { public: RTInstance instance{}; std::uint32_t indexInElements = std::numeric_limits::max(); std::uint32_t userMetadata = 0; // Application compute shader writes the transform field of this // element's instanceBuffer slot directly — BuildTLAS preserves it. bool transformOwnedByGpu = false; static std::vector elements; inline static TlasWithBuffer tlases[Window::numFrames]; // Repopulate the TLAS for frame `index`. WebGPU path always does // a fresh build (no refit) — the GPU build pass is cheap at the // ~10–100 instance counts the design targets; LBVH-for-TLAS is a // future optimization for larger scenes. static void BuildTLAS(WebGPUCommandEncoderRef cmd, std::uint32_t index); static void Add(RenderingElement3D* e); static void Remove(RenderingElement3D* e); }; } #endif // CRAFTER_GRAPHICS_WINDOW_DOM