summaryrefslogtreecommitdiff
path: root/doc/EFFECT_WORKFLOW.md
diff options
context:
space:
mode:
Diffstat (limited to 'doc/EFFECT_WORKFLOW.md')
-rw-r--r--doc/EFFECT_WORKFLOW.md277
1 files changed, 172 insertions, 105 deletions
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