summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--TODO.md2
-rw-r--r--cmake/DemoSourceLists.cmake6
-rw-r--r--cmake/Validation.cmake4
-rw-r--r--doc/AI_RULES.md18
-rw-r--r--doc/ARCHITECTURE.md2
-rw-r--r--doc/AUXILIARY_TEXTURE_INIT.md2
-rw-r--r--doc/CMAKE_MODULES.md2
-rw-r--r--doc/CONTRIBUTING.md36
-rw-r--r--doc/EFFECT_WORKFLOW.md277
-rw-r--r--doc/RECIPE.md2
-rw-r--r--doc/UNIFORM_BUFFER_GUIDELINES.md12
-rw-r--r--src/effects/flash_effect.cc47
-rw-r--r--src/effects/flash_effect.h22
-rw-r--r--src/effects/gaussian_blur.wgsl17
-rw-r--r--src/effects/gaussian_blur_effect.cc71
-rw-r--r--src/effects/gaussian_blur_effect.h32
-rw-r--r--src/effects/heptagon_effect.cc49
-rw-r--r--src/effects/heptagon_effect.h20
-rw-r--r--src/effects/passthrough_effect.cc60
-rw-r--r--src/effects/passthrough_effect.h20
-rw-r--r--src/effects/scratch_effect.h19
-rw-r--r--src/gpu/demo_effects.h1
-rw-r--r--src/gpu/wgsl_effect.cc (renamed from src/effects/scratch_effect.cc)34
-rw-r--r--src/gpu/wgsl_effect.h38
24 files changed, 321 insertions, 472 deletions
diff --git a/TODO.md b/TODO.md
index 25069d6..c89c9a6 100644
--- a/TODO.md
+++ b/TODO.md
@@ -23,7 +23,7 @@ Reduce weights from f16 (~3.2 KB) to i8 (~1.6 KB).
## Priority 3: Test Infrastructure Maintenance [ONGOING]
-**Status:** 34/34 tests passing
+**Status:** 35/35 tests passing
**Outstanding TODOs:**
diff --git a/cmake/DemoSourceLists.cmake b/cmake/DemoSourceLists.cmake
index 1aaef59..3917fcd 100644
--- a/cmake/DemoSourceLists.cmake
+++ b/cmake/DemoSourceLists.cmake
@@ -32,18 +32,14 @@ set(UTIL_SOURCES src/util/asset_manager.cc src/util/file_watcher.cc)
set(COMMON_GPU_EFFECTS
src/gpu/sequence.cc
src/gpu/effect.cc
- src/effects/passthrough_effect.cc
+ src/gpu/wgsl_effect.cc
src/effects/placeholder_effect.cc
- src/effects/gaussian_blur_effect.cc
- src/effects/heptagon_effect.cc
src/effects/particles_effect.cc
src/effects/rotating_cube_effect.cc
src/effects/hybrid3_d_effect.cc
- src/effects/flash_effect.cc
src/effects/peak_meter_effect.cc
src/effects/scene1_effect.cc
src/effects/scene2_effect.cc
- src/effects/scratch_effect.cc
# TODO: Port CNN effects to v2 (complex v1 dependencies)
# cnn_v1/src/cnn_v1_effect.cc
# cnn_v2/src/cnn_v2_effect.cc
diff --git a/cmake/Validation.cmake b/cmake/Validation.cmake
index b4a3784..beee832 100644
--- a/cmake/Validation.cmake
+++ b/cmake/Validation.cmake
@@ -14,9 +14,7 @@ file(GLOB WGSL_FILES RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE
set(VALIDATION_CPP_FILES
${CMAKE_CURRENT_SOURCE_DIR}/src/gpu/post_process_helper.h
${CMAKE_CURRENT_SOURCE_DIR}/src/gpu/demo_effects.h
- ${CMAKE_CURRENT_SOURCE_DIR}/src/effects/heptagon_effect.cc
- ${CMAKE_CURRENT_SOURCE_DIR}/src/effects/gaussian_blur_effect.cc
- ${CMAKE_CURRENT_SOURCE_DIR}/src/effects/passthrough_effect.cc
+ ${CMAKE_CURRENT_SOURCE_DIR}/src/gpu/wgsl_effect.h
${CMAKE_CURRENT_SOURCE_DIR}/src/effects/placeholder_effect.cc
${CMAKE_CURRENT_SOURCE_DIR}/src/effects/particles_effect.cc
${CMAKE_CURRENT_SOURCE_DIR}/src/effects/rotating_cube_effect.cc
diff --git a/doc/AI_RULES.md b/doc/AI_RULES.md
index 1a4ee78..5500a9a 100644
--- a/doc/AI_RULES.md
+++ b/doc/AI_RULES.md
@@ -10,17 +10,27 @@
**IMPORTANT:** When adding new visual effects, follow the complete workflow in `doc/EFFECT_WORKFLOW.md`.
-**Required steps (must complete ALL):**
+**Simple post-process (WgslEffect path — no .cc, no CMake):**
+1. Create `src/effects/<name>.wgsl`
+2. Add shader to `workspaces/main/assets.txt`
+3. Create `src/effects/<name>_effect.h` as thin wrapper over `WgslEffect`
+4. Add extern to `shaders.h` / `shaders.cc`
+5. Include header in `src/gpu/demo_effects.h`
+6. Add to test list in `src/tests/gpu/test_demo_effects.cc`
+7. Add to timeline with `EFFECT +` (priority modifier REQUIRED)
+8. Build and verify: `cmake --build build -j4 && cd build && ./test_demo_effects`
+
+**Complex effects (custom uniforms / 3D / compute — full class):**
1. Create effect files (.h, .cc, .wgsl)
2. Add shader to `workspaces/main/assets.txt`
-3. Add `.cc` to CMakeLists.txt GPU_SOURCES (BOTH sections: headless and normal)
+3. Add `.cc` to `cmake/DemoSourceLists.cmake` COMMON_GPU_EFFECTS
4. Include header in `src/gpu/demo_effects.h`
-5. Add to timeline with `EFFECT +` (priority modifier is REQUIRED)
+5. Add to timeline with `EFFECT +` (priority modifier REQUIRED)
6. Add to test list in `src/tests/gpu/test_demo_effects.cc`
7. Build and verify: `cmake --build build -j4 && cd build && ./test_demo_effects`
**Common mistakes to avoid:**
- Missing priority modifier in timeline (`EFFECT` must be `EFFECT +`, `EFFECT =`, or `EFFECT -`)
-- Adding `.cc` to only one CMakeLists.txt section (need BOTH headless and normal)
+- Adding `.cc` to CMake for WgslEffect thin wrappers (not needed)
- Wrong asset ID (check assets.txt entry name → `ASSET_SHADER_<NAME>`)
- Forgetting to add to test file
diff --git a/doc/ARCHITECTURE.md b/doc/ARCHITECTURE.md
index 582903b..47f6927 100644
--- a/doc/ARCHITECTURE.md
+++ b/doc/ARCHITECTURE.md
@@ -40,7 +40,7 @@ Detailed system architecture for the 64k demo project.
**Effect**: Abstract base for visual elements. Supports `compute` and `render` phases.
-**PostProcessEffect**: Subclass for full-screen post-processing effects.
+**WgslEffect**: Generic subclass for single-pass post-processing effects driven by a WGSL shader string. Use thin wrapper headers (no `.cc`, no CMake entry) for simple effects.
**SDFEffect**: Subclass for SDF raymarching effects with camera management (see SDF Camera System below).
diff --git a/doc/AUXILIARY_TEXTURE_INIT.md b/doc/AUXILIARY_TEXTURE_INIT.md
index 036cbf7..23d111e 100644
--- a/doc/AUXILIARY_TEXTURE_INIT.md
+++ b/doc/AUXILIARY_TEXTURE_INIT.md
@@ -132,7 +132,7 @@ void MyEffect::init(MainSequence* demo) {
## Testing
-All 36 tests pass. Verified:
+All 35 tests pass. Verified:
- Auxiliary textures at correct resolution
- Renderer3D effects don't crash on resize-before-init
- No hardcoded resolution artifacts
diff --git a/doc/CMAKE_MODULES.md b/doc/CMAKE_MODULES.md
index 9f71d91..7cd3e26 100644
--- a/doc/CMAKE_MODULES.md
+++ b/doc/CMAKE_MODULES.md
@@ -17,7 +17,7 @@ The build system is split into 10 specialized modules under `cmake/`:
- **DemoTools.cmake** - Build tools (asset_packer, seq_compiler, tracker_compiler)
- **DemoCodegen.cmake** - Code generation (assets, timeline, music)
- **DemoExecutables.cmake** - Main binaries (demo64k, test_demo)
-- **DemoTests.cmake** - Test infrastructure (36 tests)
+- **DemoTests.cmake** - Test infrastructure (35 tests)
- **Validation.cmake** - Uniform buffer validation
---
diff --git a/doc/CONTRIBUTING.md b/doc/CONTRIBUTING.md
index 7fbfd64..c077342 100644
--- a/doc/CONTRIBUTING.md
+++ b/doc/CONTRIBUTING.md
@@ -66,22 +66,30 @@ See `doc/CODING_STYLE.md` for detailed examples.
### Adding Visual Effect
-**For SDF/raymarching effects:** Use `SDFEffect` base class (see `doc/SDF_EFFECT_GUIDE.md`).
+**Full workflow:** See `doc/EFFECT_WORKFLOW.md`.
-**For standard effects:**
-1. Create effect class files (each effect should have its own `.h` and `.cc` file, e.g., `src/effects/my_effect.h` and `src/effects/my_effect.cc`). Use `tools/shadertoy/convert_shadertoy.py` or templates.
-2. Add shader to `workspaces/main/assets.txt`
-3. Add effect `.cc` file to `CMakeLists.txt` GPU_SOURCES (both sections)
-4. Include header in `src/gpu/demo_effects.h` (which now serves as a central include for all individual effect headers)
-5. Add to workspace `timeline.seq` (e.g., `workspaces/main/timeline.seq`)
-6. **Update `src/tests/gpu/test_demo_effects.cc`**:
- - Add to `post_process_effects` list (lines 80-93) or `scene_effects` list (lines 125-137)
- - Example: `{"MyEffect", std::make_shared<MyEffect>(fixture.ctx())},`
-7. Verify:
+**Simple post-process (shader only) — WgslEffect path:**
+1. Write `src/effects/<name>.wgsl`
+2. Add to `workspaces/main/assets.txt`
+3. Create `src/effects/<name>_effect.h` as a thin wrapper (no `.cc`, no CMake entry):
+ ```cpp
+ struct MyEffect : public WgslEffect {
+ MyEffect(const GpuContext& ctx, const std::vector<std::string>& inputs,
+ const std::vector<std::string>& outputs, float s, float e)
+ : WgslEffect(ctx, inputs, outputs, s, e, my_shader_wgsl) {}
+ };
+ ```
+4. Add `extern const char* my_shader_wgsl;` to `shaders.h`, definition to `shaders.cc`
+5. Include in `src/gpu/demo_effects.h`, add test entry in `test_demo_effects.cc`
+6. Add to `workspaces/main/timeline.seq`
+
+**Complex effects (custom uniforms / 3D / compute):** follow all 7 steps in `doc/EFFECT_WORKFLOW.md`.
+
+**For SDF/raymarching effects:** See `doc/SDF_EFFECT_GUIDE.md`.
+
+Verify:
```bash
-cmake -S . -B build -DDEMO_BUILD_TESTS=ON
-cmake --build build -j4 --target test_demo_effects
-cd build && ./test_demo_effects
+cmake --build build -j4 && cd build && ./test_demo_effects
```
### Audio Initialization
diff --git a/doc/EFFECT_WORKFLOW.md b/doc/EFFECT_WORKFLOW.md
index b71e76d..5d6f017 100644
--- a/doc/EFFECT_WORKFLOW.md
+++ b/doc/EFFECT_WORKFLOW.md
@@ -8,30 +8,139 @@ Checklist for adding visual effects.
## Quick Reference
-**ShaderToy:** `tools/shadertoy/convert_shadertoy.py` then follow steps below
-**SDF/Raymarching:** See `doc/SDF_EFFECT_GUIDE.md`
-**Custom effects:** Follow all steps 1-6
+| Effect type | Path |
+|---|---|
+| Simple post-process (shader only) | **WgslEffect** — 3 files, see below |
+| Custom uniforms / multi-pass / compute / 3D | **Full Effect class** — 6 steps |
+| SDF / Raymarching | See `doc/SDF_EFFECT_GUIDE.md` |
+| ShaderToy port | `tools/shadertoy/convert_shadertoy.py` then Full Effect |
---
-## Workflow
+## Path A: WgslEffect (simple post-process)
+
+Use when the effect needs only a WGSL shader and standard per-frame uniforms
+(`time`, `beat_phase`, `audio_intensity`, `resolution`). No custom C++ logic.
+
+**3 files to touch:**
+
+### 1. Write the shader
+
+`src/effects/<name>.wgsl`
+
+Standard bindings (all provided automatically):
+```wgsl
+#include "sequence_uniforms" // or "common_uniforms"
+#include "render/fullscreen_vs"
+
+@group(0) @binding(0) var smplr: sampler;
+@group(0) @binding(1) var txt: texture_2d<f32>;
+@group(0) @binding(2) var<uniform> uniforms: UniformsSequenceParams;
+
+// Optional: generic params from WgslEffectParams (see below)
+// struct WgslEffectParams { p: vec4f, c: vec4f }
+// @group(0) @binding(3) var<uniform> effect_params: WgslEffectParams;
+
+@fragment fn fs_main(@builtin(position) pos: vec4f) -> @location(0) vec4f {
+ let uv = pos.xy / uniforms.resolution;
+ // ... your effect
+ return textureSample(txt, smplr, uv);
+}
+```
+
+### 2. Register shader as asset
+
+`workspaces/main/assets.txt`:
+```
+SHADER_<UPPER_NAME>, NONE, ../../src/effects/<name>.wgsl, "Description"
+```
+
+### 3. Add to timeline
+
+`workspaces/main/timeline.seq`:
+```
+EFFECT + WgslEffect source -> sink 0.0 4.0
+```
+
+Wait — the seq_compiler instantiates effects by class name. For a named
+effect (so it appears in test_demo_effects and can be referenced by name),
+create a **thin wrapper header** instead:
+
+`src/effects/<name>_effect.h`:
+```cpp
+#pragma once
+#include "effects/shaders.h"
+#include "gpu/wgsl_effect.h"
+
+struct MyEffect : public WgslEffect {
+ MyEffect(const GpuContext& ctx, const std::vector<std::string>& inputs,
+ const std::vector<std::string>& outputs, float start_time,
+ float end_time)
+ : WgslEffect(ctx, inputs, outputs, start_time, end_time,
+ my_shader_wgsl) {}
+};
+```
+
+Then add `extern const char* my_shader_wgsl;` to `src/effects/shaders.h`
+and `const char* my_shader_wgsl = SafeGetAsset(AssetId::ASSET_SHADER_<UPPER_NAME>);`
+to `src/effects/shaders.cc`.
+
+Finally include the header in `src/gpu/demo_effects.h` and add a test entry
+in `src/tests/gpu/test_demo_effects.cc`. **No `.cc` file and no CMake entry needed.**
+
+### WgslEffectParams (optional static or dynamic params)
+
+`WgslEffect` has a public `effect_params` member (`WgslEffectParams`) uploaded
+to binding 3 each frame. Use it for static configuration or per-frame modulation.
+
+```cpp
+struct WgslEffectParams {
+ float p[4]; // vec4: generic floats (strength, scale, etc.)
+ float c[4]; // vec4: color or secondary params
+};
+```
+
+**Static** (set at construction via thin wrapper):
+```cpp
+: WgslEffect(ctx, inputs, outputs, start, end, my_shader_wgsl,
+ WGPULoadOp_Clear,
+ WgslEffectParams{{8.0f, 0.5f, 1.0f, 0.0f}, {}})
+```
+
+**Dynamic** (per-frame, from generated Sequence subclass or external code):
+```cpp
+WgslEffect* fx = ...; // store typed ptr alongside shared_ptr<Effect>
+// each frame before render:
+fx->effect_params.p[0] = 8.0f + seq_params.audio_intensity * 4.0f;
+```
+
+Prefer computing animated values in WGSL via `uniforms.time/beat_phase/audio_intensity`
+when possible — no CPU→GPU upload needed.
+
+**loadOp** (default `WGPULoadOp_Clear`): pass `WGPULoadOp_Load` to overlay
+on the existing buffer (e.g. HUD overlays like PeakMeter).
+
+---
+
+## Path B: Full Effect Class (complex effects)
+
+Use when the effect needs: custom uniforms (binding 3 typed struct), multi-pass,
+compute shaders, 3D depth buffers, or non-trivial C++ render logic.
### 1. Create Effect Files
-**Files**:
- Header: `src/effects/<name>_effect.h`
- Implementation: `src/effects/<name>_effect.cc`
- Shader: `src/effects/<name>.wgsl`
-**Class name**: `<Name>Effect` (e.g., `TunnelEffect`)
-
-**Base class**: `Effect` (all effects)
+**Base class**: `Effect`
**Constructor**:
```cpp
MyEffect(const GpuContext& ctx,
- const std::vector<std::string>& inputs,
- const std::vector<std::string>& outputs);
+ const std::vector<std::string>& inputs,
+ const std::vector<std::string>& outputs,
+ float start_time, float end_time);
```
**Required methods**:
@@ -40,64 +149,52 @@ void render(WGPUCommandEncoder encoder,
const UniformsSequenceParams& params,
NodeRegistry& nodes) override;
-// Optional: for effects needing temp nodes (depth buffers, intermediate textures)
+// Optional: for effects needing temp nodes (depth buffers, etc.)
void declare_nodes(NodeRegistry& registry) override;
```
**Uniforms** (auto-updated by base class):
```cpp
-// Available in render() via params:
params.time; // Physical seconds
params.beat_time; // Musical beats
params.beat_phase; // Fractional beat 0.0-1.0
params.audio_intensity; // Audio peak
params.resolution; // vec2(width, height)
params.aspect_ratio; // width/height
-
-// uniforms_buffer_ automatically initialized and updated
-// No need to call init_uniforms_buffer() or uniforms_buffer_.update()
```
### 2. Add Shader to Assets
-**File**: `workspaces/main/assets.txt`
-
+`workspaces/main/assets.txt`:
```
SHADER_<UPPER_NAME>, NONE, ../../src/effects/<name>.wgsl, "Description"
```
-Asset ID: `AssetId::ASSET_SHADER_<UPPER_NAME>`
-
### 3. Add to DemoSourceLists.cmake
-**File**: `cmake/DemoSourceLists.cmake`
-
-Add `src/effects/<name>_effect.cc` to the `COMMON_GPU_EFFECTS` list.
+`cmake/DemoSourceLists.cmake` — add to `COMMON_GPU_EFFECTS`:
+```cmake
+src/effects/<name>_effect.cc
+```
### 4. Include in demo_effects.h
-**File**: `src/gpu/demo_effects.h`
-
+`src/gpu/demo_effects.h`:
```cpp
#include "effects/<name>_effect.h"
```
### 5. Add to test_demo_effects.cc
-**File**: `src/tests/gpu/test_demo_effects.cc`
-
-Add entry to the effects vector:
+`src/tests/gpu/test_demo_effects.cc`:
```cpp
{"MyEffect", std::make_shared<MyEffect>(
fixture.ctx(), inputs, outputs, 0.0f, 1000.0f)},
```
-Run with: `./build/test_demo_effects`
-
### 6. Add to Timeline
-**File**: `workspaces/main/timeline.seq`
-
+`workspaces/main/timeline.seq`:
```
SEQUENCE <start> <priority> "name"
EFFECT + MyEffect source -> sink 0.0 4.0
@@ -105,98 +202,78 @@ SEQUENCE <start> <priority> "name"
**Priority modifiers** (REQUIRED): `+` (increment), `=` (same), `-` (decrement)
-### 7. Regenerate and Build
+### 7. Build and Verify
```bash
-# Regenerate timeline.cc
-python3 tools/seq_compiler.py workspaces/main/timeline.seq \
- --output src/generated/timeline.cc
-
-# Build
cmake --build build -j4
-
-# Test
-./build/demo64k
+cd build && ./test_demo_effects
+./demo64k
```
---
## Templates
-### Standard Post-Process Effect
+### WgslEffect thin wrapper
```cpp
-// my_effect.h
+// src/effects/my_effect.h
#pragma once
-#include "gpu/effect.h"
-#include "gpu/uniform_helper.h"
-
-class MyEffect : public Effect {
- public:
- MyEffect(const GpuContext& ctx,
- const std::vector<std::string>& inputs,
- const std::vector<std::string>& outputs);
- ~MyEffect() override;
+#include "effects/shaders.h"
+#include "gpu/wgsl_effect.h"
- void render(WGPUCommandEncoder encoder,
- const UniformsSequenceParams& params,
- NodeRegistry& nodes) override;
-
- private:
- WGPURenderPipeline pipeline_;
- WGPUBindGroup bind_group_;
- UniformBuffer<UniformsSequenceParams> uniforms_buffer_;
+struct MyEffect : public WgslEffect {
+ MyEffect(const GpuContext& ctx, const std::vector<std::string>& inputs,
+ const std::vector<std::string>& outputs, float start_time,
+ float end_time)
+ : WgslEffect(ctx, inputs, outputs, start_time, end_time,
+ my_shader_wgsl) {}
};
```
+No `.cc` file. No CMake entry.
+
+### Full post-process effect
+
```cpp
// my_effect.cc
#include "effects/my_effect.h"
#include "gpu/post_process_helper.h"
-#include "gpu/shaders.h"
+#include "effects/shaders.h"
MyEffect::MyEffect(const GpuContext& ctx,
- const std::vector<std::string>& inputs,
- const std::vector<std::string>& outputs,
- float start_time, float end_time)
+ const std::vector<std::string>& inputs,
+ const std::vector<std::string>& outputs,
+ float start_time, float end_time)
: Effect(ctx, inputs, outputs, start_time, end_time) {
HEADLESS_RETURN_IF_NULL(ctx_.device);
-
- // uniforms_buffer_ already initialized by base class
+ create_linear_sampler();
pipeline_.set(create_post_process_pipeline(ctx_.device,
- WGPUTextureFormat_RGBA8Unorm,
- my_shader_wgsl));
+ WGPUTextureFormat_RGBA8Unorm,
+ my_shader_wgsl));
}
void MyEffect::render(WGPUCommandEncoder encoder,
- const UniformsSequenceParams& params,
- NodeRegistry& nodes) {
+ const UniformsSequenceParams& params,
+ NodeRegistry& nodes) {
WGPUTextureView input_view = nodes.get_view(input_nodes_[0]);
WGPUTextureView output_view = nodes.get_view(output_nodes_[0]);
- // uniforms_buffer_ already updated by base class dispatch_render()
pp_update_bind_group(ctx_.device, pipeline_.get(), bind_group_.get_address(),
input_view, uniforms_buffer_.get(), {nullptr, 0});
- WGPURenderPassColorAttachment color_attachment = {
- .view = output_view,
-#if !defined(DEMO_CROSS_COMPILE_WIN32)
- .depthSlice = WGPU_DEPTH_SLICE_UNDEFINED,
-#endif
- .loadOp = WGPULoadOp_Clear,
- .storeOp = WGPUStoreOp_Store,
- .clearValue = {0.0, 0.0, 0.0, 1.0}
- };
+ WGPURenderPassColorAttachment color_attachment = {};
+ gpu_init_color_attachment(color_attachment, output_view);
- WGPURenderPassDescriptor pass_desc = {
- .colorAttachmentCount = 1,
- .colorAttachments = &color_attachment
- };
+ WGPURenderPassDescriptor pass_desc = {};
+ pass_desc.colorAttachmentCount = 1;
+ pass_desc.colorAttachments = &color_attachment;
- WGPURenderPassEncoder pass = wgpuCommandEncoderBeginRenderPass(encoder, &pass_desc);
- wgpuRenderPassEncoderSetPipeline(pass, pipeline_);
- wgpuRenderPassEncoderSetBindGroup(pass, 0, bind_group_, 0, nullptr);
- wgpuRenderPassEncoderDraw(pass, 3, 1, 0, 0); // Fullscreen triangle
+ WGPURenderPassEncoder pass =
+ wgpuCommandEncoderBeginRenderPass(encoder, &pass_desc);
+ wgpuRenderPassEncoderSetPipeline(pass, pipeline_.get());
+ wgpuRenderPassEncoderSetBindGroup(pass, 0, bind_group_.get(), 0, nullptr);
+ wgpuRenderPassEncoderDraw(pass, 3, 1, 0, 0);
wgpuRenderPassEncoderEnd(pass);
wgpuRenderPassEncoderRelease(pass);
}
@@ -212,7 +289,6 @@ class My3DEffect : public Effect {
: Effect(ctx, inputs, outputs, start_time, end_time),
depth_node_(outputs[0] + "_depth") {
HEADLESS_RETURN_IF_NULL(ctx_.device);
- // Custom uniforms if needed (don't use base uniforms_buffer_)
}
void declare_nodes(NodeRegistry& registry) override {
@@ -224,19 +300,6 @@ class My3DEffect : public Effect {
NodeRegistry& nodes) override {
WGPUTextureView color_view = nodes.get_view(output_nodes_[0]);
WGPUTextureView depth_view = nodes.get_view(depth_node_);
-
- WGPURenderPassDepthStencilAttachment depth_attachment = {
- .view = depth_view,
- .depthLoadOp = WGPULoadOp_Clear,
- .depthStoreOp = WGPUStoreOp_Discard,
- .depthClearValue = 1.0f
- };
-
- WGPURenderPassDescriptor pass_desc = {
- .colorAttachmentCount = 1,
- .colorAttachments = &color_attachment,
- .depthStencilAttachment = &depth_attachment
- };
// ... render 3D scene
}
};
@@ -251,7 +314,8 @@ class My3DEffect : public Effect {
- Asset ID is `ASSET_` + uppercase entry name
**Build Error: "undefined symbol"**
-- Effect not in `cmake/DemoSourceLists.cmake` COMMON_GPU_EFFECTS
+- Full effect `.cc` not in `cmake/DemoSourceLists.cmake` COMMON_GPU_EFFECTS
+- WgslEffect thin wrappers do NOT need a CMake entry
**Runtime Error: "Node not found"**
- Forgot `declare_nodes()` for temp nodes
@@ -265,6 +329,9 @@ class My3DEffect : public Effect {
## See Also
-- `doc/SEQUENCE.md` - Timeline syntax and architecture
-- `tools/seq_compiler.py` - Compiler implementation
-- `src/effects/*.{h,cc}` - Example effects
+- `src/gpu/wgsl_effect.h` — WgslEffect and WgslEffectParams definitions
+- `src/effects/scratch_effect.h` — minimal WgslEffect thin wrapper example
+- `src/effects/gaussian_blur_effect.h` — WgslEffect with static params example
+- `doc/SEQUENCE.md` — Timeline syntax and architecture
+- `doc/SDF_EFFECT_GUIDE.md` — SDF/raymarching effects
+- `tools/seq_compiler.py` — Compiler implementation
diff --git a/doc/RECIPE.md b/doc/RECIPE.md
index f17afde..93c59d3 100644
--- a/doc/RECIPE.md
+++ b/doc/RECIPE.md
@@ -161,7 +161,7 @@ EFFECT + MyEffect 0.0 10.0 strength=0.5 speed=3.0
EFFECT = MyEffect 10.0 20.0 strength=2.0 # speed keeps previous value
```
-**Example:** `src/effects/flash_effect.cc`, `src/effects/chroma_aberration_effect.cc`
+**Example:** `src/effects/chroma_aberration_effect.cc`
## Uniform Buffer Alignment
diff --git a/doc/UNIFORM_BUFFER_GUIDELINES.md b/doc/UNIFORM_BUFFER_GUIDELINES.md
index c6cf9c8..45c7df3 100644
--- a/doc/UNIFORM_BUFFER_GUIDELINES.md
+++ b/doc/UNIFORM_BUFFER_GUIDELINES.md
@@ -97,15 +97,15 @@ static_assert(sizeof(CommonPostProcessUniforms) == 32,
"CommonPostProcessUniforms must be 32 bytes for WGSL alignment");
```
-**Example (C++ GaussianBlurParams):**
+**Example (C++ WgslEffectParams — generic params for WgslEffect thin wrappers):**
```cpp
-struct GaussianBlurParams {
- float strength = 2.0f;
- float _pad = 0.0f;
+struct WgslEffectParams {
+ float p[4]; // vec4: generic float params (strength, scale, etc.)
+ float c[4]; // vec4: color or secondary params
};
-static_assert(sizeof(GaussianBlurParams) == 8,
- "GaussianBlurParams must be 8 bytes for WGSL alignment");
+static_assert(sizeof(WgslEffectParams) == 32,
+ "WgslEffectParams must be 32 bytes");
```
**Example (C++ CameraParams):**
diff --git a/src/effects/flash_effect.cc b/src/effects/flash_effect.cc
deleted file mode 100644
index 7064e9c..0000000
--- a/src/effects/flash_effect.cc
+++ /dev/null
@@ -1,47 +0,0 @@
-// Flash effect for visual sync testing
-// Pulses white based on beat timing
-
-#include "effects/flash_effect.h"
-#include "effects/shaders.h"
-#include "gpu/post_process_helper.h"
-#include "util/fatal_error.h"
-
-Flash::Flash(const GpuContext& ctx, const std::vector<std::string>& inputs,
- const std::vector<std::string>& outputs, float start_time,
- float end_time)
- : Effect(ctx, inputs, outputs, start_time, end_time) {
- HEADLESS_RETURN_IF_NULL(ctx_.device);
-
- create_nearest_sampler();
- create_dummy_scene_texture();
-
- pipeline_.set(create_post_process_pipeline(
- ctx_.device, WGPUTextureFormat_RGBA8Unorm, flash_shader_wgsl));
-}
-
-void Flash::render(WGPUCommandEncoder encoder,
- const UniformsSequenceParams& params, NodeRegistry& nodes) {
- // Get output view (scene effects typically write to output, ignore input)
- WGPUTextureView output_view = nodes.get_view(output_nodes_[0]);
-
- // Update bind group (use dummy texture for scene effect)
- pp_update_bind_group(ctx_.device, pipeline_.get(), bind_group_.get_address(),
- dummy_texture_view_.get(), uniforms_buffer_.get(),
- {nullptr, 0});
-
- // Render pass
- WGPURenderPassColorAttachment color_attachment = {};
- gpu_init_color_attachment(color_attachment, output_view);
-
- WGPURenderPassDescriptor pass_desc = {};
- pass_desc.colorAttachmentCount = 1;
- pass_desc.colorAttachments = &color_attachment;
-
- WGPURenderPassEncoder pass =
- wgpuCommandEncoderBeginRenderPass(encoder, &pass_desc);
- wgpuRenderPassEncoderSetPipeline(pass, pipeline_.get());
- wgpuRenderPassEncoderSetBindGroup(pass, 0, bind_group_.get(), 0, nullptr);
- wgpuRenderPassEncoderDraw(pass, 3, 1, 0, 0);
- wgpuRenderPassEncoderEnd(pass);
- wgpuRenderPassEncoderRelease(pass);
-}
diff --git a/src/effects/flash_effect.h b/src/effects/flash_effect.h
index 052957d..1cfe1ee 100644
--- a/src/effects/flash_effect.h
+++ b/src/effects/flash_effect.h
@@ -1,21 +1,13 @@
-// Flash effect for visual sync testing
-// Pulses white based on beat timing
+// Flash effect - beat-synchronized white flash
#pragma once
-#include "gpu/effect.h"
-#include "gpu/uniform_helper.h"
-#include "gpu/wgpu_resource.h"
+#include "effects/shaders.h"
+#include "gpu/wgsl_effect.h"
-class Flash : public Effect {
- public:
+struct Flash : public WgslEffect {
Flash(const GpuContext& ctx, const std::vector<std::string>& inputs,
const std::vector<std::string>& outputs, float start_time,
- float end_time);
-
- void render(WGPUCommandEncoder encoder, const UniformsSequenceParams& params,
- NodeRegistry& nodes) override;
-
- private:
- RenderPipeline pipeline_;
- BindGroup bind_group_;
+ float end_time)
+ : WgslEffect(ctx, inputs, outputs, start_time, end_time,
+ flash_shader_wgsl) {}
};
diff --git a/src/effects/gaussian_blur.wgsl b/src/effects/gaussian_blur.wgsl
index 68e0720..de0f2cf 100644
--- a/src/effects/gaussian_blur.wgsl
+++ b/src/effects/gaussian_blur.wgsl
@@ -6,21 +6,16 @@
#include "common_uniforms"
#include "render/fullscreen_vs"
-struct GaussianBlurParams {
- strength: f32,
- strength_audio: f32,
- stretch: f32, // dir_y / dir_x
- _pad: f32,
-};
+// effect_params.p: x=strength, y=strength_audio, z=stretch
+struct WgslEffectParams { p: vec4f, c: vec4f }
@group(0) @binding(2) var<uniform> uniforms: CommonUniforms;
-@group(0) @binding(3) var<uniform> params: GaussianBlurParams;
+@group(0) @binding(3) var<uniform> effect_params: WgslEffectParams;
@fragment fn fs_main(@builtin(position) p: vec4f) -> @location(0) vec4f {
- // Parameterized strength + dramatic beat pulsation
- let pulse = 1.0 + uniforms.audio_intensity * params.strength_audio; // Pulsate beat
- let size = params.strength * pulse;
- let dir = vec2f(1., params.stretch) * size / uniforms.resolution.x;
+ let pulse = 1.0 + uniforms.audio_intensity * effect_params.p.y;
+ let size = effect_params.p.x * pulse;
+ let dir = vec2f(1., effect_params.p.z) * size / uniforms.resolution.x;
let uv = p.xy / uniforms.resolution;
var res = vec4f(0.0);
diff --git a/src/effects/gaussian_blur_effect.cc b/src/effects/gaussian_blur_effect.cc
deleted file mode 100644
index 15d4d0a..0000000
--- a/src/effects/gaussian_blur_effect.cc
+++ /dev/null
@@ -1,71 +0,0 @@
-// Gaussian blur effect implementation
-
-#include "effects/gaussian_blur_effect.h"
-#include "effects/shaders.h"
-#include "gpu/post_process_helper.h"
-#include "util/fatal_error.h"
-
-GaussianBlur::GaussianBlur(const GpuContext& ctx,
- const std::vector<std::string>& inputs,
- const std::vector<std::string>& outputs,
- float start_time, float end_time)
- : Effect(ctx, inputs, outputs, start_time, end_time), pipeline_(nullptr),
- bind_group_(nullptr) {
- HEADLESS_RETURN_IF_NULL(ctx_.device);
-
- create_linear_sampler();
- params_buffer_.init(ctx_.device);
-
- pipeline_ = create_post_process_pipeline(
- ctx_.device, WGPUTextureFormat_RGBA8Unorm, gaussian_blur_shader_wgsl);
-}
-
-void GaussianBlur::render(WGPUCommandEncoder encoder,
- const UniformsSequenceParams& params,
- NodeRegistry& nodes) {
- // Get input/output views
- WGPUTextureView input_view = nodes.get_view(input_nodes_[0]);
- WGPUTextureView output_view = nodes.get_view(output_nodes_[0]);
-
- // Update effect-specific params
- params_buffer_.update(ctx_.queue, blur_params_);
-
- // Update bind group
- WGPUBindGroupEntry entries[4] = {};
- entries[0].binding = PP_BINDING_SAMPLER;
- entries[0].sampler = sampler_.get();
- entries[1].binding = PP_BINDING_TEXTURE;
- entries[1].textureView = input_view;
- entries[2].binding = PP_BINDING_UNIFORMS;
- entries[2].buffer = uniforms_buffer_.get().buffer;
- entries[2].size = sizeof(UniformsSequenceParams);
- entries[3].binding = PP_BINDING_EFFECT_PARAMS;
- entries[3].buffer = params_buffer_.get().buffer;
- entries[3].size = sizeof(GaussianBlurParams);
-
- WGPUBindGroupDescriptor bg_desc = {};
- bg_desc.layout = wgpuRenderPipelineGetBindGroupLayout(pipeline_, 0);
- bg_desc.entryCount = 4;
- bg_desc.entries = entries;
-
- if (bind_group_) {
- wgpuBindGroupRelease(bind_group_);
- }
- bind_group_ = wgpuDeviceCreateBindGroup(ctx_.device, &bg_desc);
-
- // Render pass
- WGPURenderPassColorAttachment color_attachment = {};
- gpu_init_color_attachment(color_attachment, output_view);
-
- WGPURenderPassDescriptor pass_desc = {};
- pass_desc.colorAttachmentCount = 1;
- pass_desc.colorAttachments = &color_attachment;
-
- WGPURenderPassEncoder pass =
- wgpuCommandEncoderBeginRenderPass(encoder, &pass_desc);
- wgpuRenderPassEncoderSetPipeline(pass, pipeline_);
- wgpuRenderPassEncoderSetBindGroup(pass, 0, bind_group_, 0, nullptr);
- wgpuRenderPassEncoderDraw(pass, 3, 1, 0, 0);
- wgpuRenderPassEncoderEnd(pass);
- wgpuRenderPassEncoderRelease(pass);
-}
diff --git a/src/effects/gaussian_blur_effect.h b/src/effects/gaussian_blur_effect.h
index f4b8fcf..0c528c5 100644
--- a/src/effects/gaussian_blur_effect.h
+++ b/src/effects/gaussian_blur_effect.h
@@ -1,31 +1,15 @@
// Gaussian blur effect v2 - single-pass blur
+// effect_params.p: x=strength(8.0), y=strength_audio(0.5), z=stretch(1.0)
#pragma once
+#include "effects/shaders.h"
+#include "gpu/wgsl_effect.h"
-#include "gpu/effect.h"
-#include "gpu/uniform_helper.h"
-
-struct GaussianBlurParams {
- float strength = 8.0f;
- float strength_audio = 0.5f;
- float stretch = 1.0f;
- float _pad = 0.0f;
-};
-static_assert(sizeof(GaussianBlurParams) == 16,
- "GaussianBlurParams must be 16 bytes");
-
-class GaussianBlur : public Effect {
- public:
+struct GaussianBlur : public WgslEffect {
GaussianBlur(const GpuContext& ctx, const std::vector<std::string>& inputs,
const std::vector<std::string>& outputs, float start_time,
- float end_time);
-
- void render(WGPUCommandEncoder encoder, const UniformsSequenceParams& params,
- NodeRegistry& nodes) override;
-
- private:
- WGPURenderPipeline pipeline_;
- WGPUBindGroup bind_group_;
- GaussianBlurParams blur_params_;
- UniformBuffer<GaussianBlurParams> params_buffer_;
+ float end_time)
+ : WgslEffect(ctx, inputs, outputs, start_time, end_time,
+ gaussian_blur_shader_wgsl, WGPULoadOp_Clear,
+ WgslEffectParams{{8.0f, 0.5f, 1.0f, 0.0f}, {}}) {}
};
diff --git a/src/effects/heptagon_effect.cc b/src/effects/heptagon_effect.cc
deleted file mode 100644
index ac688df..0000000
--- a/src/effects/heptagon_effect.cc
+++ /dev/null
@@ -1,49 +0,0 @@
-// Heptagon effect implementation
-
-#include "effects/heptagon_effect.h"
-#include "effects/shaders.h"
-#include "gpu/gpu.h"
-#include "gpu/post_process_helper.h"
-#include "util/fatal_error.h"
-
-Heptagon::Heptagon(const GpuContext& ctx,
- const std::vector<std::string>& inputs,
- const std::vector<std::string>& outputs, float start_time,
- float end_time)
- : Effect(ctx, inputs, outputs, start_time, end_time) {
- HEADLESS_RETURN_IF_NULL(ctx_.device);
-
- create_nearest_sampler();
- create_dummy_scene_texture();
-
- pipeline_.set(create_post_process_pipeline(
- ctx_.device, WGPUTextureFormat_RGBA8Unorm, heptagon_shader_wgsl));
-}
-
-void Heptagon::render(WGPUCommandEncoder encoder,
- const UniformsSequenceParams& params,
- NodeRegistry& nodes) {
- // Get output view (scene effects typically write to output, ignore input)
- WGPUTextureView output_view = nodes.get_view(output_nodes_[0]);
-
- // Create bind group (use dummy texture for scene effect)
- pp_update_bind_group(ctx_.device, pipeline_.get(), bind_group_.get_address(),
- dummy_texture_view_.get(), uniforms_buffer_.get(),
- {nullptr, 0});
-
- // Render pass
- WGPURenderPassColorAttachment color_attachment = {};
- gpu_init_color_attachment(color_attachment, output_view);
-
- WGPURenderPassDescriptor pass_desc = {};
- pass_desc.colorAttachmentCount = 1;
- pass_desc.colorAttachments = &color_attachment;
-
- WGPURenderPassEncoder pass =
- wgpuCommandEncoderBeginRenderPass(encoder, &pass_desc);
- wgpuRenderPassEncoderSetPipeline(pass, pipeline_.get());
- wgpuRenderPassEncoderSetBindGroup(pass, 0, bind_group_.get(), 0, nullptr);
- wgpuRenderPassEncoderDraw(pass, 3, 1, 0, 0); // Fullscreen triangle
- wgpuRenderPassEncoderEnd(pass);
- wgpuRenderPassEncoderRelease(pass);
-}
diff --git a/src/effects/heptagon_effect.h b/src/effects/heptagon_effect.h
index ab7db8d..3762202 100644
--- a/src/effects/heptagon_effect.h
+++ b/src/effects/heptagon_effect.h
@@ -1,21 +1,13 @@
// Heptagon effect v2 - scene rendering effect
#pragma once
+#include "effects/shaders.h"
+#include "gpu/wgsl_effect.h"
-#include "gpu/effect.h"
-#include "gpu/uniform_helper.h"
-#include "gpu/wgpu_resource.h"
-
-class Heptagon : public Effect {
- public:
+struct Heptagon : public WgslEffect {
Heptagon(const GpuContext& ctx, const std::vector<std::string>& inputs,
const std::vector<std::string>& outputs, float start_time,
- float end_time);
-
- void render(WGPUCommandEncoder encoder, const UniformsSequenceParams& params,
- NodeRegistry& nodes) override;
-
- private:
- RenderPipeline pipeline_;
- BindGroup bind_group_;
+ float end_time)
+ : WgslEffect(ctx, inputs, outputs, start_time, end_time,
+ heptagon_shader_wgsl) {}
};
diff --git a/src/effects/passthrough_effect.cc b/src/effects/passthrough_effect.cc
deleted file mode 100644
index 217b5d2..0000000
--- a/src/effects/passthrough_effect.cc
+++ /dev/null
@@ -1,60 +0,0 @@
-// Passthrough effect implementation
-
-#include "effects/passthrough_effect.h"
-#include "effects/shaders.h"
-#include "gpu/post_process_helper.h"
-#include "util/fatal_error.h"
-
-Passthrough::Passthrough(const GpuContext& ctx,
- const std::vector<std::string>& inputs,
- const std::vector<std::string>& outputs,
- float start_time, float end_time)
- : Effect(ctx, inputs, outputs, start_time, end_time) {
- HEADLESS_RETURN_IF_NULL(ctx_.device);
-
- create_linear_sampler();
-
- pipeline_.set(create_post_process_pipeline_simple(
- ctx_.device, WGPUTextureFormat_RGBA8Unorm, passthrough_shader_wgsl));
-}
-
-void Passthrough::render(WGPUCommandEncoder encoder,
- const UniformsSequenceParams& params,
- NodeRegistry& nodes) {
- // Get input/output views
- WGPUTextureView input_view = nodes.get_view(input_nodes_[0]);
- WGPUTextureView output_view = nodes.get_view(output_nodes_[0]);
-
- // Manually create bind group with only 3 entries (no effect params needed)
- WGPUBindGroupEntry entries[3] = {};
- entries[0].binding = PP_BINDING_SAMPLER;
- entries[0].sampler = sampler_.get();
- entries[1].binding = PP_BINDING_TEXTURE;
- entries[1].textureView = input_view;
- entries[2].binding = PP_BINDING_UNIFORMS;
- entries[2].buffer = uniforms_buffer_.get().buffer;
- entries[2].size = sizeof(UniformsSequenceParams);
-
- WGPUBindGroupDescriptor bg_desc = {};
- bg_desc.layout = wgpuRenderPipelineGetBindGroupLayout(pipeline_.get(), 0);
- bg_desc.entryCount = 3;
- bg_desc.entries = entries;
-
- bind_group_.replace(wgpuDeviceCreateBindGroup(ctx_.device, &bg_desc));
-
- // Render pass
- WGPURenderPassColorAttachment color_attachment = {};
- gpu_init_color_attachment(color_attachment, output_view);
-
- WGPURenderPassDescriptor pass_desc = {};
- pass_desc.colorAttachmentCount = 1;
- pass_desc.colorAttachments = &color_attachment;
-
- WGPURenderPassEncoder pass =
- wgpuCommandEncoderBeginRenderPass(encoder, &pass_desc);
- wgpuRenderPassEncoderSetPipeline(pass, pipeline_.get());
- wgpuRenderPassEncoderSetBindGroup(pass, 0, bind_group_.get(), 0, nullptr);
- wgpuRenderPassEncoderDraw(pass, 3, 1, 0, 0); // Fullscreen triangle
- wgpuRenderPassEncoderEnd(pass);
- wgpuRenderPassEncoderRelease(pass);
-}
diff --git a/src/effects/passthrough_effect.h b/src/effects/passthrough_effect.h
index 1c60e02..285663f 100644
--- a/src/effects/passthrough_effect.h
+++ b/src/effects/passthrough_effect.h
@@ -1,21 +1,13 @@
// Passthrough effect v2 - simple copy input to output
#pragma once
+#include "effects/shaders.h"
+#include "gpu/wgsl_effect.h"
-#include "gpu/effect.h"
-#include "gpu/uniform_helper.h"
-#include "gpu/wgpu_resource.h"
-
-class Passthrough : public Effect {
- public:
+struct Passthrough : public WgslEffect {
Passthrough(const GpuContext& ctx, const std::vector<std::string>& inputs,
const std::vector<std::string>& outputs, float start_time,
- float end_time);
-
- void render(WGPUCommandEncoder encoder, const UniformsSequenceParams& params,
- NodeRegistry& nodes) override;
-
- private:
- RenderPipeline pipeline_;
- BindGroup bind_group_;
+ float end_time)
+ : WgslEffect(ctx, inputs, outputs, start_time, end_time,
+ passthrough_shader_wgsl) {}
};
diff --git a/src/effects/scratch_effect.h b/src/effects/scratch_effect.h
index fb36a96..6c0e0cb 100644
--- a/src/effects/scratch_effect.h
+++ b/src/effects/scratch_effect.h
@@ -1,20 +1,13 @@
// Scratch effect - film scratch overlay post-process
-// Reads input buffer, additively blends scratch_lines() on top.
#pragma once
-#include "gpu/effect.h"
-#include "gpu/wgpu_resource.h"
+#include "effects/shaders.h"
+#include "gpu/wgsl_effect.h"
-class Scratch : public Effect {
- public:
+struct Scratch : public WgslEffect {
Scratch(const GpuContext& ctx, const std::vector<std::string>& inputs,
const std::vector<std::string>& outputs, float start_time,
- float end_time);
-
- void render(WGPUCommandEncoder encoder, const UniformsSequenceParams& params,
- NodeRegistry& nodes) override;
-
- private:
- RenderPipeline pipeline_;
- BindGroup bind_group_;
+ float end_time)
+ : WgslEffect(ctx, inputs, outputs, start_time, end_time,
+ scratch_shader_wgsl) {}
};
diff --git a/src/gpu/demo_effects.h b/src/gpu/demo_effects.h
index ecc37df..a2500e7 100644
--- a/src/gpu/demo_effects.h
+++ b/src/gpu/demo_effects.h
@@ -15,6 +15,7 @@
#include "gpu/sequence.h"
#include "gpu/texture_manager.h"
#include "gpu/uniform_helper.h"
+#include "gpu/wgsl_effect.h"
// Individual Effect Headers
#include "effects/flash_effect.h"
diff --git a/src/effects/scratch_effect.cc b/src/gpu/wgsl_effect.cc
index f49e601..0ce4730 100644
--- a/src/effects/scratch_effect.cc
+++ b/src/gpu/wgsl_effect.cc
@@ -1,34 +1,44 @@
-// Scratch effect - film scratch overlay post-process
+// WgslEffect: generic shader-only post-process effect.
-#include "effects/scratch_effect.h"
-#include "effects/shaders.h"
+#include "gpu/wgsl_effect.h"
+#include "gpu/gpu.h"
#include "gpu/post_process_helper.h"
#include "util/fatal_error.h"
-Scratch::Scratch(const GpuContext& ctx, const std::vector<std::string>& inputs,
- const std::vector<std::string>& outputs, float start_time,
- float end_time)
- : Effect(ctx, inputs, outputs, start_time, end_time) {
+WgslEffect::WgslEffect(const GpuContext& ctx,
+ const std::vector<std::string>& inputs,
+ const std::vector<std::string>& outputs,
+ float start_time, float end_time,
+ const char* shader_code, WGPULoadOp load_op,
+ WgslEffectParams initial_params)
+ : Effect(ctx, inputs, outputs, start_time, end_time),
+ effect_params(initial_params),
+ load_op_(load_op) {
HEADLESS_RETURN_IF_NULL(ctx_.device);
create_linear_sampler();
+ params_buffer_.init(ctx_.device);
pipeline_.set(create_post_process_pipeline(ctx_.device,
WGPUTextureFormat_RGBA8Unorm,
- scratch_shader_wgsl));
+ shader_code));
}
-void Scratch::render(WGPUCommandEncoder encoder,
- const UniformsSequenceParams& params,
- NodeRegistry& nodes) {
+void WgslEffect::render(WGPUCommandEncoder encoder,
+ const UniformsSequenceParams& params,
+ NodeRegistry& nodes) {
WGPUTextureView input_view = nodes.get_view(input_nodes_[0]);
WGPUTextureView output_view = nodes.get_view(output_nodes_[0]);
+ params_buffer_.update(ctx_.queue, effect_params);
+
pp_update_bind_group(ctx_.device, pipeline_.get(), bind_group_.get_address(),
- input_view, uniforms_buffer_.get(), {nullptr, 0});
+ input_view, uniforms_buffer_.get(),
+ params_buffer_.get());
WGPURenderPassColorAttachment color_attachment = {};
gpu_init_color_attachment(color_attachment, output_view);
+ color_attachment.loadOp = load_op_;
WGPURenderPassDescriptor pass_desc = {};
pass_desc.colorAttachmentCount = 1;
diff --git a/src/gpu/wgsl_effect.h b/src/gpu/wgsl_effect.h
new file mode 100644
index 0000000..062f885
--- /dev/null
+++ b/src/gpu/wgsl_effect.h
@@ -0,0 +1,38 @@
+// WgslEffect: generic shader-only post-process effect.
+// Replaces boilerplate .h/.cc pairs for simple single-pass effects.
+
+#pragma once
+#include "gpu/effect.h"
+#include "gpu/uniform_helper.h"
+#include "gpu/wgpu_resource.h"
+
+// Generic per-effect uniform params (binding 3, 32 bytes).
+// Matches WgslEffectParams in WGSL: struct { p: vec4f, c: vec4f }
+// effect_params.p — 4 generic floats (strength, scale, etc.)
+// effect_params.c — color or secondary vec4
+struct WgslEffectParams {
+ float p[4]; // vec4: generic float params
+ float c[4]; // vec4: color / secondary params
+};
+static_assert(sizeof(WgslEffectParams) == 32, "WgslEffectParams must be 32 bytes");
+
+class WgslEffect : public Effect {
+ public:
+ // Mutate per-frame for dynamic parameter modulation.
+ WgslEffectParams effect_params;
+
+ WgslEffect(const GpuContext& ctx, const std::vector<std::string>& inputs,
+ const std::vector<std::string>& outputs, float start_time,
+ float end_time, const char* shader_code,
+ WGPULoadOp load_op = WGPULoadOp_Clear,
+ WgslEffectParams initial_params = {});
+
+ void render(WGPUCommandEncoder encoder, const UniformsSequenceParams& params,
+ NodeRegistry& nodes) override;
+
+ private:
+ RenderPipeline pipeline_;
+ BindGroup bind_group_;
+ UniformBuffer<WgslEffectParams> params_buffer_;
+ WGPULoadOp load_op_;
+};