# Effect Creation Workflow (v2) **Target Audience:** AI coding agents and developers Checklist for adding visual effects using Sequence v2 system. --- ## Quick Reference **ShaderToy:** `tools/shadertoy/convert_shadertoy.py` then follow steps below **SDF/Raymarching:** See `doc/SDF_EFFECT_GUIDE.md` **Custom v2 effects:** Follow all steps 1-6 --- ## Workflow ### 1. Create Effect Files **Files** (v2 naming): - Header: `src/effects/_effect_v2.h` - Implementation: `src/effects/_effect_v2.cc` - Shader: `workspaces/main/shaders/_v2.wgsl` **Class name**: `EffectV2` (e.g., `TunnelEffectV2`) **Base class**: `EffectV2` (all effects) **Constructor**: ```cpp MyEffectV2(const GpuContext& ctx, const std::vector& inputs, const std::vector& outputs); ``` **Required methods**: ```cpp void render(WGPUCommandEncoder encoder, const UniformsSequenceParams& params, NodeRegistry& nodes) override; // Optional: for effects needing temp nodes (depth buffers, intermediate textures) void declare_nodes(NodeRegistry& registry) override; ``` **Uniforms**: ```cpp 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 ``` ### 2. Add Shader to Assets **File**: `workspaces/main/assets.txt` ``` SHADER_, NONE, shaders/_v2.wgsl, "Description" ``` Asset ID: `AssetId::ASSET_SHADER_` ### 3. Add to CMakeLists.txt **File**: `CMakeLists.txt` Add `src/effects/_effect_v2.cc` to **BOTH** GPU_SOURCES sections: - Headless mode (around line 141-167) - Normal mode (around line 171-197) ### 4. Include in demo_effects.h **File**: `src/gpu/demo_effects.h` ```cpp #include "effects/_effect_v2.h" ``` ### 5. Add to Timeline **File**: `workspaces/main/timeline_v2.seq` ``` SEQUENCE "name" EFFECT + MyEffectV2 source -> sink 0.0 4.0 ``` **Priority modifiers** (REQUIRED): `+` (increment), `=` (same), `-` (decrement) ### 6. Regenerate and Build ```bash # Regenerate timeline.cc python3 tools/seq_compiler_v2.py workspaces/main/timeline_v2.seq \ --output src/generated/timeline.cc # Build cmake --build build -j4 # Test ./build/demo64k ``` --- ## Templates ### Standard Post-Process Effect ```cpp // my_effect_v2.h #pragma once #include "gpu/effect_v2.h" #include "gpu/uniform_helper.h" class MyEffectV2 : public EffectV2 { public: MyEffectV2(const GpuContext& ctx, const std::vector& inputs, const std::vector& outputs); ~MyEffectV2() override; void render(WGPUCommandEncoder encoder, const UniformsSequenceParams& params, NodeRegistry& nodes) override; private: WGPURenderPipeline pipeline_; WGPUBindGroup bind_group_; UniformBuffer uniforms_buffer_; }; ``` ```cpp // my_effect_v2.cc #include "effects/my_effect_v2.h" #include "gpu/post_process_helper.h" #include "gpu/shaders.h" MyEffectV2::MyEffectV2(const GpuContext& ctx, const std::vector& inputs, const std::vector& outputs) : EffectV2(ctx, inputs, outputs), pipeline_(nullptr), bind_group_(nullptr) { uniforms_buffer_.init(ctx_.device); pipeline_ = create_post_process_pipeline(ctx_.device, WGPUTextureFormat_RGBA8Unorm, my_shader_v2_wgsl); } MyEffectV2::~MyEffectV2() { if (bind_group_) wgpuBindGroupRelease(bind_group_); if (pipeline_) wgpuRenderPipelineRelease(pipeline_); } void MyEffectV2::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]); uniforms_buffer_.update(ctx_.queue, params); pp_update_bind_group(ctx_.device, pipeline_, &bind_group_, 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} }; WGPURenderPassDescriptor pass_desc = { .colorAttachmentCount = 1, .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 wgpuRenderPassEncoderEnd(pass); wgpuRenderPassEncoderRelease(pass); } ``` ### 3D Effect with Depth ```cpp class My3DEffectV2 : public EffectV2 { std::string depth_node_; My3DEffectV2(const GpuContext& ctx, ...) : EffectV2(ctx, inputs, outputs), depth_node_(outputs[0] + "_depth") {} void declare_nodes(NodeRegistry& registry) override { registry.declare_node(depth_node_, NodeType::DEPTH24, -1, -1); } void render(WGPUCommandEncoder encoder, const UniformsSequenceParams& params, 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 } }; ``` --- ## Common Issues **Build Error: "no member named 'ASSET_..._SHADER'"** - Shader not in `assets.txt` or wrong name - Asset ID is `ASSET_` + uppercase entry name **Build Error: "undefined symbol"** - Effect not in CMakeLists.txt GPU_SOURCES - Must add to BOTH sections (headless + normal) **Runtime Error: "Node not found"** - Forgot `declare_nodes()` for temp nodes - `init_effect_nodes()` not called (check generated timeline.cc) **Runtime Error: "invalid bind group"** - Pipeline format doesn't match framebuffer (use RGBA8Unorm) - Missing texture view or null resource --- ## See Also - `doc/SEQUENCE_v2.md` - Timeline syntax and architecture - `tools/seq_compiler_v2.py` - Compiler implementation - `src/effects/*_v2.{h,cc}` - Example effects