feat(webgpu): HDR post-process primitives — float textures, storage-texture bindings, RESOLVE→HDR target (#27)

Adds the minimal WebGPU-backend surface an HDR bloom/post chain needs:

1. wgpuCreateStorageImage2D + StorageImage2D<>: runtime rgba16float (or
   other format) textures with STORAGE_BINDING|TEXTURE_BINDING usage, no
   CompressedTextureAsset upload — GPU-produced HDR targets.
2. UICustomBindingKind::StorageTexture (kind 5): write-only storage-texture
   binding carrying a texel format (the freed UICustomBinding::format byte),
   wired through all three binding paths (UI custom / RT / plain compute).
   Float textures already sample via SampledTexture (sampleType float).
3. PipelineRTWebGPU::Init(..., hdrOutputFormat): RESOLVE writes the linear
   accumulator into a user rgba16float texture (RTPass::outTexHandle)
   instead of the rgba8unorm canvas, leaving the composite→swapchain pass to
   the app. Default RGBA8Unorm keeps the canvas path byte-identical.

Existing RT examples updated for the _pad→format field rename; the default
paths are unchanged (verified RTStress builds + renders).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
catbot 2026-06-09 12:55:14 +00:00
commit 097cc37347
12 changed files with 198 additions and 22 deletions

View file

@ -109,6 +109,17 @@ namespace Crafter::WebGPU {
const void* srcPtr, std::int32_t byteSize,
std::int32_t w, std::int32_t h);
// Runtime storage texture — STORAGE_BINDING | TEXTURE_BINDING usage,
// caller-chosen format (`format` is the WebGPUTexelFormat enum, e.g.
// 1 = rgba16float). Unlike wgpuCreateImage2D this needs no
// CompressedTextureAsset upload: the content is produced on the GPU
// (e.g. an HDR post-process target a compute pass writes via a
// StorageTexture binding and later samples via SampledTexture). Backs
// StorageImage2D<T> in :Image2D.
__attribute__((import_module("env"), import_name("wgpuCreateStorageImage2D")))
extern "C" std::uint32_t wgpuCreateStorageImage2D(std::int32_t w, std::int32_t h,
std::int32_t format);
__attribute__((import_module("env"), import_name("wgpuCreateLinearClampSampler")))
extern "C" std::uint32_t wgpuCreateLinearClampSampler();
@ -190,10 +201,16 @@ namespace Crafter::WebGPU {
// UICustomBinding-shaped (8 bytes each) declaring extra @group(2)+
// resources the user's closest-hit / miss / raygen WGSL references.
// Pass (nullptr, 0) for a pipeline with no user-declared bindings.
// `hdrOutputFormat` (0 = none) makes the RESOLVE stage write the linear
// accumulator into a user-supplied storage texture of that
// WebGPUTexelFormat (e.g. 1 = rgba16float) instead of the rgba8unorm
// canvas ping-pong — the entry point for an HDR post-process chain. The
// matching output texture handle is passed to wgpuDispatchRT.
// Returns an opaque pipeline handle.
__attribute__((import_module("env"), import_name("wgpuLoadRTPipeline")))
extern "C" std::uint32_t wgpuLoadRTPipeline(const void* wgslPtr, std::int32_t wgslLen,
const void* bindingsPtr, std::int32_t bindingsCount);
const void* bindingsPtr, std::int32_t bindingsCount,
std::int32_t hdrOutputFormat);
// Dispatch a TraceRays-equivalent pass: the RT pipeline is dispatched
// over a (gx, gy) tile grid; the library writes the push data (camera,
@ -202,6 +219,9 @@ namespace Crafter::WebGPU {
// `handles[]` carries resolved WebGPU resource handles for every user
// binding declared at pipeline-load time, in the same order. Pass
// (nullptr, 0) for a pipeline with no user bindings.
// `outTexHandle` is the destination texture for an HDR-output pipeline
// (one loaded with hdrOutputFormat != 0); ignored (pass 0) for the
// default canvas path.
__attribute__((import_module("env"), import_name("wgpuDispatchRT")))
extern "C" void wgpuDispatchRT(std::uint32_t pipelineHandle,
const void* pushPtr, std::int32_t pushBytes,
@ -209,7 +229,8 @@ namespace Crafter::WebGPU {
std::int32_t instanceCount,
std::int32_t gx, std::int32_t gy,
const void* handlesPtr, std::int32_t handlesCount,
std::int32_t maxDepth);
std::int32_t maxDepth,
std::uint32_t outTexHandle);
// GPU TLAS-build dispatch. Two sequential compute passes:
// 1. tlasBuildMain — per-instance world AABB + identity permutation