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

@ -62,7 +62,7 @@ int main() {
// One user binding: the camera storage buffer at @group(3).
std::array<UICustomBinding, 1> bindings {{
{ .group = 3, .binding = 0, .kind = UICustomBindingKind::Buffer, ._pad = 0, .pushOffset = 0 },
{ .group = 3, .binding = 0, .kind = UICustomBindingKind::Buffer, .pushOffset = 0 },
}};
PipelineRTWebGPU pipeline;

View file

@ -74,7 +74,7 @@ int main() {
} }};
std::array<UICustomBinding, 1> bindings {{
{ .group = 3, .binding = 0, .kind = UICustomBindingKind::Buffer, ._pad = 0, .pushOffset = 0 },
{ .group = 3, .binding = 0, .kind = UICustomBindingKind::Buffer, .pushOffset = 0 },
}};
PipelineRTWebGPU pipeline;

View file

@ -87,7 +87,7 @@ int main() {
std::array<RTShaderGroup, 1> hitGroups {{ { .type = RTShaderGroupType::TrianglesHitGroup, .closestHitShader = 2 } }};
std::array<UICustomBinding, 1> bindings {{
{ .group = 3, .binding = 0, .kind = UICustomBindingKind::Buffer, ._pad = 0, .pushOffset = 0 },
{ .group = 3, .binding = 0, .kind = UICustomBindingKind::Buffer, .pushOffset = 0 },
}};
PipelineRTWebGPU pipeline;
@ -172,7 +172,7 @@ int main() {
// ── rayQuery pick shader + output buffer. ──────────────────────────
static PlainComputeShader pickShader;
std::array<UICustomBinding, 1> pickBindings {{
{ .group = 2, .binding = 0, .kind = UICustomBindingKind::BufferReadWrite, ._pad = 0, .pushOffset = 0 },
{ .group = 2, .binding = 0, .kind = UICustomBindingKind::BufferReadWrite, .pushOffset = 0 },
}};
pickShader.Load(fs::path("rayquery_pick.wgsl"),
static_cast<std::uint32_t>(sizeof(PickPush)),

View file

@ -278,9 +278,9 @@ int main() {
// binding 1 — sampler (linear clamp)
// binding 2 — Camera storage buffer (host-driven, updated per frame)
std::array<UICustomBinding, 3> bindings {{
{ .group = 3, .binding = 0, .kind = UICustomBindingKind::SampledTextureArray, ._pad = 0, .pushOffset = 0 },
{ .group = 3, .binding = 1, .kind = UICustomBindingKind::Sampler, ._pad = 0, .pushOffset = 0 },
{ .group = 3, .binding = 2, .kind = UICustomBindingKind::Buffer, ._pad = 0, .pushOffset = 0 },
{ .group = 3, .binding = 0, .kind = UICustomBindingKind::SampledTextureArray, .pushOffset = 0 },
{ .group = 3, .binding = 1, .kind = UICustomBindingKind::Sampler, .pushOffset = 0 },
{ .group = 3, .binding = 2, .kind = UICustomBindingKind::Buffer, .pushOffset = 0 },
}};
PipelineRTWebGPU pipeline;