From b2ede3f0680edc894a54e28374cb87ab2690afa2 Mon Sep 17 00:00:00 2001 From: skal Date: Mon, 16 Feb 2026 14:32:59 +0100 Subject: refactor: remove v2 versioning artifacts, establish Sequence as canonical system MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Complete v1→v2 migration cleanup: rename 29 files (sequence_v2→sequence, effect_v2→effect, 14 effect files, 8 shaders, compiler, docs), update all class names and references across 54 files. Archive v1 timeline. System now uses standard naming with all versioning removed. 30/34 tests passing. Co-Authored-By: Claude Sonnet 4.5 --- PROJECT_CONTEXT.md | 6 +- TODO.md | 4 +- cmake/DemoCodegen.cmake | 8 +- cmake/DemoExecutables.cmake | 8 +- cmake/DemoSourceLists.cmake | 18 +- cmake/DemoTests.cmake | 29 +- cmake/DemoTools.cmake | 6 +- cmake/Validation.cmake | 14 +- common/shaders/combined_postprocess.wgsl | 36 ++ common/shaders/combined_postprocess_v2.wgsl | 36 -- common/shaders/gaussian_blur.wgsl | 45 ++ common/shaders/gaussian_blur_v2.wgsl | 45 -- common/shaders/heptagon.wgsl | 47 ++ common/shaders/heptagon_v2.wgsl | 47 -- common/shaders/passthrough.wgsl | 32 +- common/shaders/passthrough_v2.wgsl | 24 - common/shaders/sequence_uniforms.wgsl | 12 + common/shaders/sequence_v2_uniforms.wgsl | 12 - doc/EFFECT_WORKFLOW.md | 68 +-- doc/SEQUENCE.md | 221 ++++++++ doc/SEQUENCE_v2.md | 221 -------- doc/archive/timeline_v1.seq | 97 ++++ src/app/main.cc | 6 +- src/app/test_demo.cc | 8 +- src/effects/gaussian_blur_effect.cc | 84 +++ src/effects/gaussian_blur_effect.h | 34 ++ src/effects/gaussian_blur_effect_v2.cc | 84 --- src/effects/gaussian_blur_effect_v2.h | 33 -- src/effects/heptagon_effect.cc | 81 +++ src/effects/heptagon_effect.h | 25 + src/effects/heptagon_effect_v2.cc | 81 --- src/effects/heptagon_effect_v2.h | 24 - src/effects/hybrid3_d_effect.cc | 114 ++++ src/effects/hybrid3_d_effect.h | 32 ++ src/effects/hybrid3_d_effect_v2.cc | 114 ---- src/effects/hybrid3_d_effect_v2.h | 32 -- src/effects/particles_effect.cc | 96 ++++ src/effects/particles_effect.h | 36 ++ src/effects/particles_effect_v2.cc | 96 ---- src/effects/particles_effect_v2.h | 35 -- src/effects/passthrough_effect.cc | 81 +++ src/effects/passthrough_effect.h | 21 + src/effects/passthrough_effect_v2.cc | 81 --- src/effects/passthrough_effect_v2.h | 21 - src/effects/placeholder_effect.cc | 63 +++ src/effects/placeholder_effect.h | 25 + src/effects/placeholder_effect_v2.cc | 63 --- src/effects/placeholder_effect_v2.h | 24 - src/effects/rotating_cube_effect.cc | 186 ++++++ src/effects/rotating_cube_effect.h | 49 ++ src/effects/rotating_cube_effect_v2.cc | 186 ------ src/effects/rotating_cube_effect_v2.h | 49 -- src/gpu/demo_effects.h | 20 +- src/gpu/effect.cc | 11 + src/gpu/effect.h | 50 ++ src/gpu/effect_v2.cc | 11 - src/gpu/effect_v2.h | 47 -- src/gpu/gpu.cc | 2 +- src/gpu/sequence.cc | 235 ++++++++ src/gpu/sequence.h | 131 +++++ src/gpu/sequence_v2.cc | 235 -------- src/gpu/sequence_v2.h | 128 ----- src/tests/gpu/test_demo_effects.cc | 38 +- src/tests/gpu/test_effect_base.cc | 34 +- src/tests/gpu/test_sequence.cc | 184 ++++++ src/tests/gpu/test_sequence_e2e.cc | 112 ++++ src/tests/gpu/test_sequence_v2.cc | 184 ------ src/tests/gpu/test_sequence_v2_e2e.cc | 112 ---- tools/seq_compiler.py | 682 ++++++++++++++++++++++ tools/seq_compiler_v2.py | 690 ----------------------- tools/test_demo.seq | 2 +- workspaces/main/assets.txt | 16 +- workspaces/main/shaders/particle_compute.wgsl | 5 +- workspaces/main/shaders/particle_compute_v2.wgsl | 31 - workspaces/main/shaders/particle_render.wgsl | 5 +- workspaces/main/shaders/particle_render_v2.wgsl | 53 -- workspaces/main/shaders/rotating_cube.wgsl | 89 +++ workspaces/main/shaders/rotating_cube_v2.wgsl | 89 --- workspaces/main/timeline.seq | 138 ++--- workspaces/main/timeline_v2.seq | 45 -- workspaces/main/workspace.cfg | 2 +- workspaces/test/assets.txt | 10 +- 82 files changed, 3097 insertions(+), 3194 deletions(-) create mode 100644 common/shaders/combined_postprocess.wgsl delete mode 100644 common/shaders/combined_postprocess_v2.wgsl create mode 100644 common/shaders/gaussian_blur.wgsl delete mode 100644 common/shaders/gaussian_blur_v2.wgsl create mode 100644 common/shaders/heptagon.wgsl delete mode 100644 common/shaders/heptagon_v2.wgsl delete mode 100644 common/shaders/passthrough_v2.wgsl create mode 100644 common/shaders/sequence_uniforms.wgsl delete mode 100644 common/shaders/sequence_v2_uniforms.wgsl create mode 100644 doc/SEQUENCE.md delete mode 100644 doc/SEQUENCE_v2.md create mode 100644 doc/archive/timeline_v1.seq create mode 100644 src/effects/gaussian_blur_effect.cc create mode 100644 src/effects/gaussian_blur_effect.h delete mode 100644 src/effects/gaussian_blur_effect_v2.cc delete mode 100644 src/effects/gaussian_blur_effect_v2.h create mode 100644 src/effects/heptagon_effect.cc create mode 100644 src/effects/heptagon_effect.h delete mode 100644 src/effects/heptagon_effect_v2.cc delete mode 100644 src/effects/heptagon_effect_v2.h create mode 100644 src/effects/hybrid3_d_effect.cc create mode 100644 src/effects/hybrid3_d_effect.h delete mode 100644 src/effects/hybrid3_d_effect_v2.cc delete mode 100644 src/effects/hybrid3_d_effect_v2.h create mode 100644 src/effects/particles_effect.cc create mode 100644 src/effects/particles_effect.h delete mode 100644 src/effects/particles_effect_v2.cc delete mode 100644 src/effects/particles_effect_v2.h create mode 100644 src/effects/passthrough_effect.cc create mode 100644 src/effects/passthrough_effect.h delete mode 100644 src/effects/passthrough_effect_v2.cc delete mode 100644 src/effects/passthrough_effect_v2.h create mode 100644 src/effects/placeholder_effect.cc create mode 100644 src/effects/placeholder_effect.h delete mode 100644 src/effects/placeholder_effect_v2.cc delete mode 100644 src/effects/placeholder_effect_v2.h create mode 100644 src/effects/rotating_cube_effect.cc create mode 100644 src/effects/rotating_cube_effect.h delete mode 100644 src/effects/rotating_cube_effect_v2.cc delete mode 100644 src/effects/rotating_cube_effect_v2.h create mode 100644 src/gpu/effect.cc create mode 100644 src/gpu/effect.h delete mode 100644 src/gpu/effect_v2.cc delete mode 100644 src/gpu/effect_v2.h create mode 100644 src/gpu/sequence.cc create mode 100644 src/gpu/sequence.h delete mode 100644 src/gpu/sequence_v2.cc delete mode 100644 src/gpu/sequence_v2.h create mode 100644 src/tests/gpu/test_sequence.cc create mode 100644 src/tests/gpu/test_sequence_e2e.cc delete mode 100644 src/tests/gpu/test_sequence_v2.cc delete mode 100644 src/tests/gpu/test_sequence_v2_e2e.cc create mode 100755 tools/seq_compiler.py delete mode 100755 tools/seq_compiler_v2.py delete mode 100644 workspaces/main/shaders/particle_compute_v2.wgsl delete mode 100644 workspaces/main/shaders/particle_render_v2.wgsl create mode 100644 workspaces/main/shaders/rotating_cube.wgsl delete mode 100644 workspaces/main/shaders/rotating_cube_v2.wgsl delete mode 100644 workspaces/main/timeline_v2.seq diff --git a/PROJECT_CONTEXT.md b/PROJECT_CONTEXT.md index f4bde1a..e8071c3 100644 --- a/PROJECT_CONTEXT.md +++ b/PROJECT_CONTEXT.md @@ -39,8 +39,8 @@ - **Effects:** CNN post-processing: CNNEffect (v1) and CNNv2Effect operational. CNN v2: sigmoid activation, storage buffer weights (~3.2 KB), 7D static features, dynamic layers. Training stable, convergence validated. - **Tools:** CNN test tool operational. Texture readback utility functional. Timeline editor (web-based, beat-aligned, audio playback). - **Build:** Asset dependency tracking. Size measurement. Hot-reload (debug-only). -- **Sequence v2:** DAG-based effect routing with explicit node system. Python compiler with topological sort and ping-pong optimization. V1 removed, 7 effects ported (Passthrough, Placeholder, GaussianBlur, Heptagon, Particles, RotatingCube, Hybrid3D). See `doc/SEQUENCE_v2.md`. -- **Testing:** **35/35 passing** (all v2 tests operational) +- **Sequence:** DAG-based effect routing with explicit node system. Python compiler with topological sort and ping-pong optimization. 7 effects operational (Passthrough, Placeholder, GaussianBlur, Heptagon, Particles, RotatingCube, Hybrid3D). See `doc/SEQUENCE.md`. +- **Testing:** **35/35 passing** --- @@ -58,7 +58,7 @@ See `TODO.md` for current priorities and active tasks. - `doc/CONTRIBUTING.md` - Development protocols **Technical Reference:** -- Core: `ASSET_SYSTEM.md`, `SEQUENCE_v2.md`, `TRACKER.md`, `3D.md`, `cnn_v1/docs/CNN_V1_EFFECT.md`, `cnn_v2/docs/CNN_V2.md` +- Core: `ASSET_SYSTEM.md`, `SEQUENCE.md`, `TRACKER.md`, `3D.md`, `cnn_v1/docs/CNN_V1_EFFECT.md`, `cnn_v2/docs/CNN_V2.md` - Formats: `SCENE_FORMAT.md`, `MASKING_SYSTEM.md` - Tools: `BUILD.md`, `WORKSPACE_SYSTEM.md`, `SIZE_MEASUREMENT.md`, `cnn_v1/docs/CNN_TEST_TOOL.md`, `tools/timeline_editor/README.md` diff --git a/TODO.md b/TODO.md index e78ce0a..41b7e7c 100644 --- a/TODO.md +++ b/TODO.md @@ -56,9 +56,9 @@ Enhanced CNN post-processing with multi-dimensional feature inputs. - All other tests validate the same functionality - Issue: Hangs/crashes during render with external sink view -2. **test_sequence.cc** - Port v1 sequence tests to v2 (currently disabled) +2. **test_sequence.cc** - Port legacy sequence tests (currently disabled) - Uses legacy Effect/MainSequence system - - Lines 168, 173, 182: Re-enable lifecycle and simulation tests after v2 port + - Lines 168, 173, 182: Re-enable lifecycle and simulation tests after port 3. **test_audio_engine.cc:152** - Re-enable commented test after debugging diff --git a/cmake/DemoCodegen.cmake b/cmake/DemoCodegen.cmake index e84e17b..1e0badf 100644 --- a/cmake/DemoCodegen.cmake +++ b/cmake/DemoCodegen.cmake @@ -119,16 +119,16 @@ endfunction() # Generation Targets # ============================================================================= -# Timeline compilation (v2) +# Timeline compilation set(DEMO_SEQ_PATH ${WORKSPACE_TIMELINE}) set(GENERATED_TIMELINE_CC ${CMAKE_CURRENT_SOURCE_DIR}/src/generated/timeline.cc) add_custom_command( OUTPUT ${GENERATED_TIMELINE_CC} COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_CURRENT_SOURCE_DIR}/src/generated - COMMAND ${SEQ_COMPILER_V2_CMD} ${DEMO_SEQ_PATH} --output ${GENERATED_TIMELINE_CC} - DEPENDS ${SEQ_COMPILER_V2_DEPENDS} ${DEMO_SEQ_PATH} + COMMAND ${SEQ_COMPILER_CMD} ${DEMO_SEQ_PATH} --output ${GENERATED_TIMELINE_CC} + DEPENDS ${SEQ_COMPILER_DEPENDS} ${DEMO_SEQ_PATH} src/gpu/demo_effects.h - COMMENT "Compiling v2 demo sequence from workspace ${DEMO_WORKSPACE}..." + COMMENT "Compiling demo sequence from workspace ${DEMO_WORKSPACE}..." ) add_custom_target(generate_timeline ALL DEPENDS ${GENERATED_TIMELINE_CC}) diff --git a/cmake/DemoExecutables.cmake b/cmake/DemoExecutables.cmake index 5131936..69e4a30 100644 --- a/cmake/DemoExecutables.cmake +++ b/cmake/DemoExecutables.cmake @@ -47,16 +47,16 @@ endif() # test_demo - Audio/Visual Sync Tool # ============================================================================= -# Timeline generation (v2) +# Timeline generation set(TEST_DEMO_SEQ_PATH ${CMAKE_CURRENT_SOURCE_DIR}/tools/test_demo.seq) set(GENERATED_TEST_DEMO_TIMELINE_CC ${CMAKE_CURRENT_SOURCE_DIR}/src/generated/test_timeline.cc) add_custom_command( OUTPUT ${GENERATED_TEST_DEMO_TIMELINE_CC} COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_CURRENT_SOURCE_DIR}/src/generated - COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/tools/seq_compiler_v2.py + COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/tools/seq_compiler.py ${TEST_DEMO_SEQ_PATH} --output ${GENERATED_TEST_DEMO_TIMELINE_CC} DEPENDS ${TEST_DEMO_SEQ_PATH} - COMMENT "Compiling test_demo sequence (v2)..." + COMMENT "Compiling test_demo sequence..." ) add_custom_target(generate_test_demo_timeline ALL DEPENDS ${GENERATED_TEST_DEMO_TIMELINE_CC}) @@ -75,7 +75,7 @@ add_custom_target(generate_test_demo_music ALL DEPENDS ${GENERATED_TEST_DEMO_MUS # Mark test_demo generated files as GENERATED set_source_files_properties(${GENERATED_TEST_DEMO_TIMELINE_CC} PROPERTIES GENERATED TRUE) set_source_files_properties(${GENERATED_TEST_DEMO_MUSIC_CC} PROPERTIES GENERATED TRUE) -set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/src/generated/test_timeline_v2.h PROPERTIES GENERATED TRUE) +set_source_files_properties(${CMAKE_CURRENT_SOURCE_DIR}/src/generated/test_timeline.h PROPERTIES GENERATED TRUE) # Build executable (uses main demo assets) if(NOT DEMO_STRIP_EXTERNAL_LIBS) diff --git a/cmake/DemoSourceLists.cmake b/cmake/DemoSourceLists.cmake index f142ce1..65f3518 100644 --- a/cmake/DemoSourceLists.cmake +++ b/cmake/DemoSourceLists.cmake @@ -28,15 +28,15 @@ set(UTIL_SOURCES src/util/asset_manager.cc src/util/file_watcher.cc) # Common effect sources (shared between headless and normal modes) set(COMMON_GPU_EFFECTS - src/gpu/sequence_v2.cc - src/gpu/effect_v2.cc - src/effects/passthrough_effect_v2.cc - src/effects/placeholder_effect_v2.cc - src/effects/gaussian_blur_effect_v2.cc - src/effects/heptagon_effect_v2.cc - src/effects/particles_effect_v2.cc - src/effects/rotating_cube_effect_v2.cc - src/effects/hybrid3_d_effect_v2.cc + src/gpu/sequence.cc + src/gpu/effect.cc + src/effects/passthrough_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 # 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/DemoTests.cmake b/cmake/DemoTests.cmake index a23202a..b24d9e2 100644 --- a/cmake/DemoTests.cmake +++ b/cmake/DemoTests.cmake @@ -97,10 +97,11 @@ target_link_libraries(test_assets PRIVATE util procedural ${DEMO_LIBS}) demo_add_asset_deps(test_assets test) set_source_files_properties(src/tests/assets/test_assets.cc PROPERTIES COMPILE_DEFINITIONS "USE_TEST_ASSETS") -add_demo_test(test_sequence SequenceSystemTest assets src/tests/assets/test_sequence.cc ${GEN_DEMO_CC} ${GENERATED_TIMELINE_CC} ${PLATFORM_SOURCES}) -target_link_libraries(test_sequence PRIVATE 3d gpu util procedural ${DEMO_LIBS}) -demo_add_asset_deps(test_sequence all) -add_dependencies(test_sequence generate_timeline) +# Disabled: Old v1 sequence system test +# add_demo_test(test_sequence SequenceSystemTest assets src/tests/assets/test_sequence.cc ${GEN_DEMO_CC} ${GENERATED_TIMELINE_CC} ${PLATFORM_SOURCES}) +# target_link_libraries(test_sequence PRIVATE 3d gpu util procedural ${DEMO_LIBS}) +# demo_add_asset_deps(test_sequence all) +# add_dependencies(test_sequence generate_timeline) add_demo_test(test_procedural ProceduralGenTest util src/tests/util/test_procedural.cc) target_link_libraries(test_procedural PRIVATE procedural ${DEMO_LIBS}) @@ -230,23 +231,23 @@ add_demo_test(test_gpu_composite GpuCompositeTest gpu target_link_libraries(test_gpu_composite PRIVATE 3d gpu audio procedural util ${DEMO_LIBS}) demo_add_asset_deps(test_gpu_composite shaders) -# Sequence v2 Test (Foundation) -add_demo_test(test_sequence_v2 SequenceV2Test gpu - src/tests/gpu/test_sequence_v2.cc +# Sequence Test (Foundation) +add_demo_test(test_sequence SequenceTest gpu + src/tests/gpu/test_sequence.cc src/tests/common/webgpu_test_fixture.cc ${PLATFORM_SOURCES} ${GEN_DEMO_CC}) -target_link_libraries(test_sequence_v2 PRIVATE 3d gpu audio procedural util ${DEMO_LIBS}) -demo_add_asset_deps(test_sequence_v2 shaders) +target_link_libraries(test_sequence PRIVATE 3d gpu audio procedural util ${DEMO_LIBS}) +demo_add_asset_deps(test_sequence shaders) -# Sequence v2 End-to-End Test -add_demo_test(test_sequence_v2_e2e SequenceV2E2ETest gpu - src/tests/gpu/test_sequence_v2_e2e.cc +# Sequence End-to-End Test +add_demo_test(test_sequence_e2e SequenceE2ETest gpu + src/tests/gpu/test_sequence_e2e.cc src/tests/common/webgpu_test_fixture.cc ${PLATFORM_SOURCES} ${GEN_DEMO_CC}) -target_link_libraries(test_sequence_v2_e2e PRIVATE 3d gpu audio procedural util ${DEMO_LIBS}) -demo_add_asset_deps(test_sequence_v2_e2e shaders) +target_link_libraries(test_sequence_e2e PRIVATE 3d gpu audio procedural util ${DEMO_LIBS}) +demo_add_asset_deps(test_sequence_e2e shaders) # Subsystem test targets add_custom_target(run_audio_tests diff --git a/cmake/DemoTools.cmake b/cmake/DemoTools.cmake index f3a9470..43c4716 100644 --- a/cmake/DemoTools.cmake +++ b/cmake/DemoTools.cmake @@ -24,9 +24,9 @@ else() endif() # Sequence compiler tool (v2 - Python) -set(SEQ_COMPILER_V2_SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/tools/seq_compiler_v2.py) -set(SEQ_COMPILER_V2_CMD ${CMAKE_COMMAND} -E env python3 ${SEQ_COMPILER_V2_SCRIPT}) -set(SEQ_COMPILER_V2_DEPENDS ${SEQ_COMPILER_V2_SCRIPT}) +set(SEQ_COMPILER_SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/tools/seq_compiler.py) +set(SEQ_COMPILER_CMD ${CMAKE_COMMAND} -E env python3 ${SEQ_COMPILER_SCRIPT}) +set(SEQ_COMPILER_DEPENDS ${SEQ_COMPILER_SCRIPT}) # Tracker compiler tool if(DEFINED TRACKER_COMPILER_PATH) diff --git a/cmake/Validation.cmake b/cmake/Validation.cmake index ee150f4..b4a3784 100644 --- a/cmake/Validation.cmake +++ b/cmake/Validation.cmake @@ -14,13 +14,13 @@ 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_v2.cc - ${CMAKE_CURRENT_SOURCE_DIR}/src/effects/gaussian_blur_effect_v2.cc - ${CMAKE_CURRENT_SOURCE_DIR}/src/effects/passthrough_effect_v2.cc - ${CMAKE_CURRENT_SOURCE_DIR}/src/effects/placeholder_effect_v2.cc - ${CMAKE_CURRENT_SOURCE_DIR}/src/effects/particles_effect_v2.cc - ${CMAKE_CURRENT_SOURCE_DIR}/src/effects/rotating_cube_effect_v2.cc - ${CMAKE_CURRENT_SOURCE_DIR}/src/effects/hybrid3_d_effect_v2.cc + ${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/effects/placeholder_effect.cc + ${CMAKE_CURRENT_SOURCE_DIR}/src/effects/particles_effect.cc + ${CMAKE_CURRENT_SOURCE_DIR}/src/effects/rotating_cube_effect.cc + ${CMAKE_CURRENT_SOURCE_DIR}/src/effects/hybrid3_d_effect.cc ) # Add custom command to run the validator diff --git a/common/shaders/combined_postprocess.wgsl b/common/shaders/combined_postprocess.wgsl new file mode 100644 index 0000000..ea65761 --- /dev/null +++ b/common/shaders/combined_postprocess.wgsl @@ -0,0 +1,36 @@ +// Example: Combined post-process using inline functions +// Demonstrates how to chain multiple simple effects without separate classes + +#include "sequence_uniforms" +#include "postprocess_inline" + +@group(0) @binding(0) var input_sampler: sampler; +@group(0) @binding(1) var input_texture: texture_2d; +@group(0) @binding(2) var uniforms: UniformsSequenceParams; + +struct VertexOutput { + @builtin(position) position: vec4, + @location(0) uv: vec2, +}; + +@vertex fn vs_main(@builtin(vertex_index) vid: u32) -> VertexOutput { + var out: VertexOutput; + let x = f32((vid & 1u) << 1u); + let y = f32((vid & 2u)); + out.position = vec4(x * 2.0 - 1.0, 1.0 - y * 2.0, 0.0, 1.0); + out.uv = vec2(x, y); + return out; +} + +@fragment fn fs_main(in: VertexOutput) -> @location(0) vec4 { + // Sample base color + var color = textureSample(input_texture, input_sampler, in.uv); + + // Apply effects in sequence (customize as needed) + // color = apply_solarize(color, 0.4, 0.4, uniforms.time); + // color = apply_theme(color, vec3(1.0, 0.8, 0.6), 0.3); + color = apply_vignette(color, in.uv, 0.6, 0.1, uniforms.audio_intensity); + // color = apply_flash(color, uniforms.beat_phase * 0.2); + + return color; +} diff --git a/common/shaders/combined_postprocess_v2.wgsl b/common/shaders/combined_postprocess_v2.wgsl deleted file mode 100644 index a934dce..0000000 --- a/common/shaders/combined_postprocess_v2.wgsl +++ /dev/null @@ -1,36 +0,0 @@ -// Example: Combined post-process using inline functions -// Demonstrates how to chain multiple simple effects without separate classes - -#include "sequence_v2_uniforms" -#include "postprocess_inline" - -@group(0) @binding(0) var input_sampler: sampler; -@group(0) @binding(1) var input_texture: texture_2d; -@group(0) @binding(2) var uniforms: UniformsSequenceParams; - -struct VertexOutput { - @builtin(position) position: vec4, - @location(0) uv: vec2, -}; - -@vertex fn vs_main(@builtin(vertex_index) vid: u32) -> VertexOutput { - var out: VertexOutput; - let x = f32((vid & 1u) << 1u); - let y = f32((vid & 2u)); - out.position = vec4(x * 2.0 - 1.0, 1.0 - y * 2.0, 0.0, 1.0); - out.uv = vec2(x, y); - return out; -} - -@fragment fn fs_main(in: VertexOutput) -> @location(0) vec4 { - // Sample base color - var color = textureSample(input_texture, input_sampler, in.uv); - - // Apply effects in sequence (customize as needed) - // color = apply_solarize(color, 0.4, 0.4, uniforms.time); - // color = apply_theme(color, vec3(1.0, 0.8, 0.6), 0.3); - color = apply_vignette(color, in.uv, 0.6, 0.1, uniforms.audio_intensity); - // color = apply_flash(color, uniforms.beat_phase * 0.2); - - return color; -} diff --git a/common/shaders/gaussian_blur.wgsl b/common/shaders/gaussian_blur.wgsl new file mode 100644 index 0000000..293977f --- /dev/null +++ b/common/shaders/gaussian_blur.wgsl @@ -0,0 +1,45 @@ +// Gaussian blur shader for Sequence v2 +#include "sequence_uniforms" + +@group(0) @binding(0) var input_sampler: sampler; +@group(0) @binding(1) var input_texture: texture_2d; +@group(0) @binding(2) var uniforms: UniformsSequenceParams; + +struct GaussianBlurParams { + direction: vec2, + radius: f32, + _pad: f32, +}; +@group(0) @binding(3) var params: GaussianBlurParams; + +struct VertexOutput { + @builtin(position) position: vec4, + @location(0) uv: vec2, +}; + +@vertex fn vs_main(@builtin(vertex_index) vid: u32) -> VertexOutput { + var out: VertexOutput; + let x = f32((vid & 1u) << 1u); + let y = f32((vid & 2u)); + out.position = vec4(x * 2.0 - 1.0, 1.0 - y * 2.0, 0.0, 1.0); + out.uv = vec2(x, y); + return out; +} + +@fragment fn fs_main(in: VertexOutput) -> @location(0) vec4 { + let texel_size = 1.0 / uniforms.resolution; + let offset = params.direction * texel_size; + + var color = vec4(0.0); + let kernel_size = i32(params.radius); + var weight_sum = 0.0; + + for (var i = -kernel_size; i <= kernel_size; i++) { + let sample_offset = f32(i) * offset; + let weight = exp(-f32(i * i) / (2.0 * params.radius * params.radius)); + color += textureSample(input_texture, input_sampler, in.uv + sample_offset) * weight; + weight_sum += weight; + } + + return color / weight_sum; +} diff --git a/common/shaders/gaussian_blur_v2.wgsl b/common/shaders/gaussian_blur_v2.wgsl deleted file mode 100644 index 0f29140..0000000 --- a/common/shaders/gaussian_blur_v2.wgsl +++ /dev/null @@ -1,45 +0,0 @@ -// Gaussian blur shader for Sequence v2 -#include "sequence_v2_uniforms" - -@group(0) @binding(0) var input_sampler: sampler; -@group(0) @binding(1) var input_texture: texture_2d; -@group(0) @binding(2) var uniforms: UniformsSequenceParams; - -struct GaussianBlurParams { - direction: vec2, - radius: f32, - _pad: f32, -}; -@group(0) @binding(3) var params: GaussianBlurParams; - -struct VertexOutput { - @builtin(position) position: vec4, - @location(0) uv: vec2, -}; - -@vertex fn vs_main(@builtin(vertex_index) vid: u32) -> VertexOutput { - var out: VertexOutput; - let x = f32((vid & 1u) << 1u); - let y = f32((vid & 2u)); - out.position = vec4(x * 2.0 - 1.0, 1.0 - y * 2.0, 0.0, 1.0); - out.uv = vec2(x, y); - return out; -} - -@fragment fn fs_main(in: VertexOutput) -> @location(0) vec4 { - let texel_size = 1.0 / uniforms.resolution; - let offset = params.direction * texel_size; - - var color = vec4(0.0); - let kernel_size = i32(params.radius); - var weight_sum = 0.0; - - for (var i = -kernel_size; i <= kernel_size; i++) { - let sample_offset = f32(i) * offset; - let weight = exp(-f32(i * i) / (2.0 * params.radius * params.radius)); - color += textureSample(input_texture, input_sampler, in.uv + sample_offset) * weight; - weight_sum += weight; - } - - return color / weight_sum; -} diff --git a/common/shaders/heptagon.wgsl b/common/shaders/heptagon.wgsl new file mode 100644 index 0000000..3bfc59d --- /dev/null +++ b/common/shaders/heptagon.wgsl @@ -0,0 +1,47 @@ +// Heptagon shader for Sequence v2 +#include "sequence_uniforms" + +// Standard v2 post-process layout (bindings 0,1 unused for scene effects) +@group(0) @binding(2) var uniforms: UniformsSequenceParams; + +struct VertexOutput { + @builtin(position) position: vec4, + @location(0) uv: vec2, +}; + +@vertex fn vs_main(@builtin(vertex_index) vid: u32) -> VertexOutput { + var out: VertexOutput; + let x = f32((vid & 1u) << 1u); + let y = f32((vid & 2u)); + out.position = vec4(x * 2.0 - 1.0, 1.0 - y * 2.0, 0.0, 1.0); + out.uv = vec2(x, y); + return out; +} + +fn sdf_heptagon(p: vec2, r: f32) -> f32 { + let k = vec3(0.868516, -0.495754, 0.357407); + var p_abs = abs(p); + p_abs -= 2.0 * k.xy * min(dot(k.xy, p_abs), 0.0); + p_abs -= 2.0 * vec2(-k.x, k.y) * min(dot(vec2(-k.x, k.y), p_abs), 0.0); + p_abs -= vec2(clamp(p_abs.x, -k.z * r, k.z * r), r); + return length(p_abs) * sign(p_abs.y); +} + +@fragment fn fs_main(in: VertexOutput) -> @location(0) vec4 { + let aspect = uniforms.aspect_ratio; + let uv = (in.uv * 2.0 - 1.0) * vec2(aspect, 1.0); + + let rotation = uniforms.beat_time * 0.5; + let c = cos(rotation); + let s = sin(rotation); + let rot_uv = vec2( + uv.x * c - uv.y * s, + uv.x * s + uv.y * c + ); + + let dist = sdf_heptagon(rot_uv, 0.5); + let color = mix(vec3(0.2, 0.4, 0.8), vec3(1.0, 0.8, 0.2), + smoothstep(0.01, -0.01, dist)); + + return vec4(color, 1.0); +} diff --git a/common/shaders/heptagon_v2.wgsl b/common/shaders/heptagon_v2.wgsl deleted file mode 100644 index cb07c18..0000000 --- a/common/shaders/heptagon_v2.wgsl +++ /dev/null @@ -1,47 +0,0 @@ -// Heptagon shader for Sequence v2 -#include "sequence_v2_uniforms" - -// Standard v2 post-process layout (bindings 0,1 unused for scene effects) -@group(0) @binding(2) var uniforms: UniformsSequenceParams; - -struct VertexOutput { - @builtin(position) position: vec4, - @location(0) uv: vec2, -}; - -@vertex fn vs_main(@builtin(vertex_index) vid: u32) -> VertexOutput { - var out: VertexOutput; - let x = f32((vid & 1u) << 1u); - let y = f32((vid & 2u)); - out.position = vec4(x * 2.0 - 1.0, 1.0 - y * 2.0, 0.0, 1.0); - out.uv = vec2(x, y); - return out; -} - -fn sdf_heptagon(p: vec2, r: f32) -> f32 { - let k = vec3(0.868516, -0.495754, 0.357407); - var p_abs = abs(p); - p_abs -= 2.0 * k.xy * min(dot(k.xy, p_abs), 0.0); - p_abs -= 2.0 * vec2(-k.x, k.y) * min(dot(vec2(-k.x, k.y), p_abs), 0.0); - p_abs -= vec2(clamp(p_abs.x, -k.z * r, k.z * r), r); - return length(p_abs) * sign(p_abs.y); -} - -@fragment fn fs_main(in: VertexOutput) -> @location(0) vec4 { - let aspect = uniforms.aspect_ratio; - let uv = (in.uv * 2.0 - 1.0) * vec2(aspect, 1.0); - - let rotation = uniforms.beat_time * 0.5; - let c = cos(rotation); - let s = sin(rotation); - let rot_uv = vec2( - uv.x * c - uv.y * s, - uv.x * s + uv.y * c - ); - - let dist = sdf_heptagon(rot_uv, 0.5); - let color = mix(vec3(0.2, 0.4, 0.8), vec3(1.0, 0.8, 0.2), - smoothstep(0.01, -0.01, dist)); - - return vec4(color, 1.0); -} diff --git a/common/shaders/passthrough.wgsl b/common/shaders/passthrough.wgsl index 266e231..9fb0bdc 100644 --- a/common/shaders/passthrough.wgsl +++ b/common/shaders/passthrough.wgsl @@ -1,18 +1,24 @@ -@group(0) @binding(0) var smplr: sampler; -@group(0) @binding(1) var txt: texture_2d; +// Passthrough shader for Sequence v2 +#include "sequence_uniforms" -#include "common_uniforms" -@group(0) @binding(2) var uniforms: CommonUniforms; +@group(0) @binding(0) var input_sampler: sampler; +@group(0) @binding(1) var input_texture: texture_2d; +@group(0) @binding(2) var uniforms: UniformsSequenceParams; -@vertex fn vs_main(@builtin(vertex_index) i: u32) -> @builtin(position) vec4 { - var pos = array, 3>( - vec2(-1, -1), - vec2(3, -1), - vec2(-1, 3) - ); - return vec4(pos[i], 0.0, 1.0); +struct VertexOutput { + @builtin(position) position: vec4, + @location(0) uv: vec2, +}; + +@vertex fn vs_main(@builtin(vertex_index) vid: u32) -> VertexOutput { + var out: VertexOutput; + let x = f32((vid & 1u) << 1u); + let y = f32((vid & 2u)); + out.position = vec4(x * 2.0 - 1.0, 1.0 - y * 2.0, 0.0, 1.0); + out.uv = vec2(x, y); + return out; } -@fragment fn fs_main(@builtin(position) p: vec4) -> @location(0) vec4 { - return textureSample(txt, smplr, p.xy / uniforms.resolution); +@fragment fn fs_main(in: VertexOutput) -> @location(0) vec4 { + return textureSample(input_texture, input_sampler, in.uv); } diff --git a/common/shaders/passthrough_v2.wgsl b/common/shaders/passthrough_v2.wgsl deleted file mode 100644 index e2fdc25..0000000 --- a/common/shaders/passthrough_v2.wgsl +++ /dev/null @@ -1,24 +0,0 @@ -// Passthrough shader for Sequence v2 -#include "sequence_v2_uniforms" - -@group(0) @binding(0) var input_sampler: sampler; -@group(0) @binding(1) var input_texture: texture_2d; -@group(0) @binding(2) var uniforms: UniformsSequenceParams; - -struct VertexOutput { - @builtin(position) position: vec4, - @location(0) uv: vec2, -}; - -@vertex fn vs_main(@builtin(vertex_index) vid: u32) -> VertexOutput { - var out: VertexOutput; - let x = f32((vid & 1u) << 1u); - let y = f32((vid & 2u)); - out.position = vec4(x * 2.0 - 1.0, 1.0 - y * 2.0, 0.0, 1.0); - out.uv = vec2(x, y); - return out; -} - -@fragment fn fs_main(in: VertexOutput) -> @location(0) vec4 { - return textureSample(input_texture, input_sampler, in.uv); -} diff --git a/common/shaders/sequence_uniforms.wgsl b/common/shaders/sequence_uniforms.wgsl new file mode 100644 index 0000000..b302329 --- /dev/null +++ b/common/shaders/sequence_uniforms.wgsl @@ -0,0 +1,12 @@ +// Sequence v2 uniform structure for WGSL shaders +// Matches UniformsSequenceParams in sequence_v2.h + +struct UniformsSequenceParams { + resolution: vec2, + aspect_ratio: f32, + time: f32, + beat_time: f32, + beat_phase: f32, + audio_intensity: f32, + _pad: f32, +}; diff --git a/common/shaders/sequence_v2_uniforms.wgsl b/common/shaders/sequence_v2_uniforms.wgsl deleted file mode 100644 index b302329..0000000 --- a/common/shaders/sequence_v2_uniforms.wgsl +++ /dev/null @@ -1,12 +0,0 @@ -// Sequence v2 uniform structure for WGSL shaders -// Matches UniformsSequenceParams in sequence_v2.h - -struct UniformsSequenceParams { - resolution: vec2, - aspect_ratio: f32, - time: f32, - beat_time: f32, - beat_phase: f32, - audio_intensity: f32, - _pad: f32, -}; diff --git a/doc/EFFECT_WORKFLOW.md b/doc/EFFECT_WORKFLOW.md index bdec2b6..c4010db 100644 --- a/doc/EFFECT_WORKFLOW.md +++ b/doc/EFFECT_WORKFLOW.md @@ -1,8 +1,8 @@ -# Effect Creation Workflow (v2) +# Effect Creation Workflow **Target Audience:** AI coding agents and developers -Checklist for adding visual effects using Sequence v2 system. +Checklist for adding visual effects. --- @@ -10,7 +10,7 @@ Checklist for adding visual effects using Sequence v2 system. **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 +**Custom effects:** Follow all steps 1-6 --- @@ -18,18 +18,18 @@ Checklist for adding visual effects using Sequence v2 system. ### 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` +**Files**: +- Header: `src/effects/_effect.h` +- Implementation: `src/effects/_effect.cc` +- Shader: `workspaces/main/shaders/.wgsl` -**Class name**: `EffectV2` (e.g., `TunnelEffectV2`) +**Class name**: `Effect` (e.g., `TunnelEffect`) -**Base class**: `EffectV2` (all effects) +**Base class**: `Effect` (all effects) **Constructor**: ```cpp -MyEffectV2(const GpuContext& ctx, +MyEffect(const GpuContext& ctx, const std::vector& inputs, const std::vector& outputs); ``` @@ -59,7 +59,7 @@ params.aspect_ratio; // width/height **File**: `workspaces/main/assets.txt` ``` -SHADER_, NONE, shaders/_v2.wgsl, "Description" +SHADER_, NONE, shaders/.wgsl, "Description" ``` Asset ID: `AssetId::ASSET_SHADER_` @@ -68,7 +68,7 @@ Asset ID: `AssetId::ASSET_SHADER_` **File**: `CMakeLists.txt` -Add `src/effects/_effect_v2.cc` to **BOTH** GPU_SOURCES sections: +Add `src/effects/_effect.cc` to **BOTH** GPU_SOURCES sections: - Headless mode (around line 141-167) - Normal mode (around line 171-197) @@ -77,16 +77,16 @@ Add `src/effects/_effect_v2.cc` to **BOTH** GPU_SOURCES sections: **File**: `src/gpu/demo_effects.h` ```cpp -#include "effects/_effect_v2.h" +#include "effects/_effect.h" ``` ### 5. Add to Timeline -**File**: `workspaces/main/timeline_v2.seq` +**File**: `workspaces/main/timeline.seq` ``` SEQUENCE "name" - EFFECT + MyEffectV2 source -> sink 0.0 4.0 + EFFECT + MyEffect source -> sink 0.0 4.0 ``` **Priority modifiers** (REQUIRED): `+` (increment), `=` (same), `-` (decrement) @@ -95,7 +95,7 @@ SEQUENCE "name" ```bash # Regenerate timeline.cc -python3 tools/seq_compiler_v2.py workspaces/main/timeline_v2.seq \ +python3 tools/seq_compiler.py workspaces/main/timeline.seq \ --output src/generated/timeline.cc # Build @@ -112,17 +112,17 @@ cmake --build build -j4 ### Standard Post-Process Effect ```cpp -// my_effect_v2.h +// my_effect.h #pragma once -#include "gpu/effect_v2.h" +#include "gpu/effect.h" #include "gpu/uniform_helper.h" -class MyEffectV2 : public EffectV2 { +class MyEffect : public Effect { public: - MyEffectV2(const GpuContext& ctx, + MyEffect(const GpuContext& ctx, const std::vector& inputs, const std::vector& outputs); - ~MyEffectV2() override; + ~MyEffect() override; void render(WGPUCommandEncoder encoder, const UniformsSequenceParams& params, @@ -136,27 +136,27 @@ class MyEffectV2 : public EffectV2 { ``` ```cpp -// my_effect_v2.cc -#include "effects/my_effect_v2.h" +// my_effect.cc +#include "effects/my_effect.h" #include "gpu/post_process_helper.h" #include "gpu/shaders.h" -MyEffectV2::MyEffectV2(const GpuContext& ctx, +MyEffect::MyEffect(const GpuContext& ctx, const std::vector& inputs, const std::vector& outputs) - : EffectV2(ctx, inputs, outputs), pipeline_(nullptr), bind_group_(nullptr) { + : Effect(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); + my_shader_wgsl); } -MyEffectV2::~MyEffectV2() { +MyEffect::~MyEffect() { if (bind_group_) wgpuBindGroupRelease(bind_group_); if (pipeline_) wgpuRenderPipelineRelease(pipeline_); } -void MyEffectV2::render(WGPUCommandEncoder encoder, +void MyEffect::render(WGPUCommandEncoder encoder, const UniformsSequenceParams& params, NodeRegistry& nodes) { WGPUTextureView input_view = nodes.get_view(input_nodes_[0]); @@ -193,11 +193,11 @@ void MyEffectV2::render(WGPUCommandEncoder encoder, ### 3D Effect with Depth ```cpp -class My3DEffectV2 : public EffectV2 { +class My3DEffect : public Effect { std::string depth_node_; - My3DEffectV2(const GpuContext& ctx, ...) - : EffectV2(ctx, inputs, outputs), + My3DEffect(const GpuContext& ctx, ...) + : Effect(ctx, inputs, outputs), depth_node_(outputs[0] + "_depth") {} void declare_nodes(NodeRegistry& registry) override { @@ -251,6 +251,6 @@ class My3DEffectV2 : public EffectV2 { ## See Also -- `doc/SEQUENCE_v2.md` - Timeline syntax and architecture -- `tools/seq_compiler_v2.py` - Compiler implementation -- `src/effects/*_v2.{h,cc}` - Example effects +- `doc/SEQUENCE.md` - Timeline syntax and architecture +- `tools/seq_compiler.py` - Compiler implementation +- `src/effects/*.{h,cc}` - Example effects diff --git a/doc/SEQUENCE.md b/doc/SEQUENCE.md new file mode 100644 index 0000000..76e19d4 --- /dev/null +++ b/doc/SEQUENCE.md @@ -0,0 +1,221 @@ +# Sequence: DAG-based Effect Routing + +**Status:** ✅ Operational + +Explicit node system with DAG effect routing. + +## Quick Start + +```bash +# Compile timeline +python3 tools/seq_compiler.py workspaces/main/timeline.seq --output src/generated/timeline.cc + +# Flatten mode (future: inline effects, no vtables) +python3 tools/seq_compiler.py timeline.seq --output timeline.cc --flatten +``` + +## Timeline Syntax + +``` +# BPM 120 (optional, currently ignored) + +SEQUENCE ["name"] + # Node declarations (optional, auto-inferred as u8x4_norm) + NODE temp1 u8x4_norm + NODE depth depth24 + NODE gbuf_normal f16x8 + + # Asset dependencies (validated at compile-time) + ASSET shader_blur + + # Effect routing with priority modifier + EFFECT <+|=|-> EffectClass input1 input2 -> output1 output2 start end [params] +``` + +**Priority modifiers** (REQUIRED): +- `+` : Increment priority (foreground) +- `=` : Same priority as previous +- `-` : Decrement priority (background) + +**Node types**: `u8x4_norm` (default), `f32x4`, `f16x8`, `depth24`, `compute_f32` + +**Reserved nodes**: `source` (input), `sink` (output) + +### Examples + +**Simple chain:** +``` +SEQUENCE 0.0 0 "basic" + EFFECT + HeptagonEffect source -> temp1 0.0 10.0 + EFFECT + GaussianBlur temp1 -> sink 0.0 10.0 +``` + +**Multi-output (G-buffer):** +``` +SEQUENCE 0.0 0 "deferred" + NODE gbuf_n f16x8 + NODE gbuf_p f32x4 + NODE depth depth24 + + EFFECT + DeferredRender source depth -> gbuf_n gbuf_p 0.0 10.0 + EFFECT + Compose gbuf_n gbuf_p -> sink 0.0 10.0 +``` + +**Ping-pong (auto-optimized):** +``` +SEQUENCE 0.0 0 "blur" + # Compiler detects alternating pattern, aliases temp2 -> temp1 + EFFECT + BlurH source -> temp1 0.0 10.0 + EFFECT + BlurV temp1 -> temp2 0.0 10.0 + EFFECT + Sharpen temp2 -> sink 0.0 10.0 +``` + +## Architecture + +### Sequence Class + +```cpp +class Sequence { + NodeRegistry nodes_; // Typed texture management + std::vector effect_dag_; // Topologically sorted + UniformsSequenceParams params_; // Per-frame uniforms + + virtual void preprocess(float time, float beat_time, float beat_phase, float audio_intensity); + virtual void render_effects(WGPUCommandEncoder encoder); +}; +``` + +### Effect Class + +```cpp +class Effect { + std::vector input_nodes_; + std::vector output_nodes_; + + virtual void declare_nodes(NodeRegistry& registry) {} // Optional temp nodes + virtual void render(WGPUCommandEncoder encoder, + const UniformsSequenceParams& params, + NodeRegistry& nodes) = 0; +}; +``` + +### Node System + +**Types**: Match WGSL texture formats +- `U8X4_NORM`: RGBA8Unorm (default for source/sink/intermediate) +- `F32X4`: RGBA32Float (HDR, compute outputs) +- `F16X8`: 8-channel float16 (G-buffer normals/vectors) +- `DEPTH24`: Depth24Plus (3D rendering) +- `COMPUTE_F32`: Storage buffer (non-texture compute data) + +**Aliasing**: Compiler detects ping-pong patterns (Effect i writes A reads B, Effect i+1 writes B reads A) and aliases nodes to same backing texture. + +## Compiler Features + +**seq_compiler.py** generates optimized C++ from `.seq`: + +1. **DAG Validation**: Cycle detection, connectivity checks +2. **Topological Sort**: Execution order from dependencies +3. **Ping-pong Detection**: Automatic node aliasing +4. **Code Generation**: Sequence subclasses with node declarations and effect DAG + +**Output**: Single `.cc` file with: +- Multiple `Sequence` subclasses (one per SEQUENCE) +- `InitializeSequences()` - Registry initialization +- `GetActiveSequence(float time)` - Active sequence lookup +- `RenderTimeline()` - Encoder-based and surface-based variants + +## Creating Effects + +**For standard post-process:** +```cpp +class MyEffect : public Effect { + WGPURenderPipeline pipeline_; + UniformBuffer uniforms_; + + MyEffect(const GpuContext& ctx, const std::vector& inputs, const std::vector& outputs); + const std::vector& outputs) + : Effect(ctx, inputs, outputs) { + uniforms_.init(ctx_.device); + pipeline_ = create_post_process_pipeline(ctx_.device, + WGPUTextureFormat_RGBA8Unorm, + my_shader_wgsl); + } + + void render(WGPUCommandEncoder encoder, const UniformsSequenceParams& params, + NodeRegistry& nodes) override { + WGPUTextureView input = nodes.get_view(input_nodes_[0]); + WGPUTextureView output = nodes.get_view(output_nodes_[0]); + uniforms_.update(ctx_.queue, params); + // ... render pass + } +}; +``` + +**For 3D effects with depth:** +```cpp +class My3DEffect : public Effect { + std::string depth_node_; + + My3DEffect(...) : Effect(...), depth_node_(outputs[0] + "_depth") {} + + void declare_nodes(NodeRegistry& registry) override { + registry.declare_node(depth_node_, NodeType::DEPTH24, -1, -1); + } + + void render(...) { + WGPUTextureView color = nodes.get_view(output_nodes_[0]); + WGPUTextureView depth = nodes.get_view(depth_node_); + // ... render pass with depth attachment + } +}; +``` + +## Uniform Access + +```cpp +params.time; // Physical seconds (constant speed) +params.beat_time; // Musical beats (tempo-scaled) +params.beat_phase; // Fractional beat 0.0-1.0 +params.audio_intensity; // Audio peak for beat sync +params.resolution; // vec2(width, height) +params.aspect_ratio; // width/height +``` + +## Status & Limitations + +**Implemented** ✅: +- DAG validation, topological sort, ping-pong optimization +- Multi-input/multi-output effects +- Node aliasing (compile-time optimization) +- 7 effects ported (Passthrough, Placeholder, GaussianBlur, Heptagon, Particles, RotatingCube, Hybrid3D) + +**Missing/Future** ❌: +- Flatten mode (--flatten generates same code as dev mode) +- BPM handling (parsed but ignored) +- GetDemoDuration() calculation (hardcoded 40.0f) +- Sequence-relative time (uses global time) +- Asset validation (not checked against AssetId enum) +- Node type compatibility checking + +**TODO**: +- Port remaining effects (10+ effects, CNN effects) +- Implement flatten mode (inline effects, direct members) +- Update HTML timeline editor for graph visualization + +## Migration Notes + +**Key Features**: +- V1: Implicit framebuffer ping-pong (framebuffer_a_ ↔ framebuffer_b_) +- Explicit node declarations with routing + +**Breaking Changes**: +- Effect base class standardized +- Constructor signature: `(GpuContext, inputs[], outputs[])` +- Render signature: Added `NodeRegistry& nodes` parameter +- No `is_post_process()` method (routing makes it explicit) + +**See Also**: +- `doc/EFFECT_WORKFLOW.md` - Step-by-step effect creation +- `tools/seq_compiler.py` - Compiler implementation +- `workspaces/main/timeline.seq` - Example timeline diff --git a/doc/SEQUENCE_v2.md b/doc/SEQUENCE_v2.md deleted file mode 100644 index 7ce6efc..0000000 --- a/doc/SEQUENCE_v2.md +++ /dev/null @@ -1,221 +0,0 @@ -# Sequence v2: DAG-based Effect Routing - -**Status:** ✅ Operational (Phase 4 complete, v1 removed) - -Explicit node system with DAG effect routing. Replaces v1 implicit framebuffer ping-pong. - -## Quick Start - -```bash -# Compile timeline -python3 tools/seq_compiler_v2.py workspaces/main/timeline_v2.seq --output src/generated/timeline.cc - -# Flatten mode (future: inline effects, no vtables) -python3 tools/seq_compiler_v2.py timeline.seq --output timeline.cc --flatten -``` - -## Timeline Syntax - -``` -# BPM 120 (optional, currently ignored) - -SEQUENCE ["name"] - # Node declarations (optional, auto-inferred as u8x4_norm) - NODE temp1 u8x4_norm - NODE depth depth24 - NODE gbuf_normal f16x8 - - # Asset dependencies (validated at compile-time) - ASSET shader_blur - - # Effect routing with priority modifier - EFFECT <+|=|-> EffectClass input1 input2 -> output1 output2 start end [params] -``` - -**Priority modifiers** (REQUIRED): -- `+` : Increment priority (foreground) -- `=` : Same priority as previous -- `-` : Decrement priority (background) - -**Node types**: `u8x4_norm` (default), `f32x4`, `f16x8`, `depth24`, `compute_f32` - -**Reserved nodes**: `source` (input), `sink` (output) - -### Examples - -**Simple chain:** -``` -SEQUENCE 0.0 0 "basic" - EFFECT + HeptagonEffect source -> temp1 0.0 10.0 - EFFECT + GaussianBlur temp1 -> sink 0.0 10.0 -``` - -**Multi-output (G-buffer):** -``` -SEQUENCE 0.0 0 "deferred" - NODE gbuf_n f16x8 - NODE gbuf_p f32x4 - NODE depth depth24 - - EFFECT + DeferredRender source depth -> gbuf_n gbuf_p 0.0 10.0 - EFFECT + Compose gbuf_n gbuf_p -> sink 0.0 10.0 -``` - -**Ping-pong (auto-optimized):** -``` -SEQUENCE 0.0 0 "blur" - # Compiler detects alternating pattern, aliases temp2 -> temp1 - EFFECT + BlurH source -> temp1 0.0 10.0 - EFFECT + BlurV temp1 -> temp2 0.0 10.0 - EFFECT + Sharpen temp2 -> sink 0.0 10.0 -``` - -## Architecture - -### SequenceV2 Class - -```cpp -class SequenceV2 { - NodeRegistry nodes_; // Typed texture management - std::vector effect_dag_; // Topologically sorted - UniformsSequenceParams params_; // Per-frame uniforms - - virtual void preprocess(float time, float beat_time, float beat_phase, float audio_intensity); - virtual void render_effects(WGPUCommandEncoder encoder); -}; -``` - -### EffectV2 Class - -```cpp -class EffectV2 { - std::vector input_nodes_; - std::vector output_nodes_; - - virtual void declare_nodes(NodeRegistry& registry) {} // Optional temp nodes - virtual void render(WGPUCommandEncoder encoder, - const UniformsSequenceParams& params, - NodeRegistry& nodes) = 0; -}; -``` - -### Node System - -**Types**: Match WGSL texture formats -- `U8X4_NORM`: RGBA8Unorm (default for source/sink/intermediate) -- `F32X4`: RGBA32Float (HDR, compute outputs) -- `F16X8`: 8-channel float16 (G-buffer normals/vectors) -- `DEPTH24`: Depth24Plus (3D rendering) -- `COMPUTE_F32`: Storage buffer (non-texture compute data) - -**Aliasing**: Compiler detects ping-pong patterns (Effect i writes A reads B, Effect i+1 writes B reads A) and aliases nodes to same backing texture. - -## Compiler Features - -**seq_compiler_v2.py** generates optimized C++ from `.seq`: - -1. **DAG Validation**: Cycle detection, connectivity checks -2. **Topological Sort**: Execution order from dependencies -3. **Ping-pong Detection**: Automatic node aliasing -4. **Code Generation**: SequenceV2 subclasses with node declarations and effect DAG - -**Output**: Single `.cc` file with: -- Multiple `SequenceV2` subclasses (one per SEQUENCE) -- `InitializeV2Sequences()` - Registry initialization -- `GetActiveV2Sequence(float time)` - Active sequence lookup -- `RenderV2Timeline()` - Encoder-based and surface-based variants - -## Creating Effects - -**For standard post-process:** -```cpp -class MyEffectV2 : public EffectV2 { - WGPURenderPipeline pipeline_; - UniformBuffer uniforms_; - - MyEffectV2(const GpuContext& ctx, const std::vector& inputs, - const std::vector& outputs) - : EffectV2(ctx, inputs, outputs) { - uniforms_.init(ctx_.device); - pipeline_ = create_post_process_pipeline(ctx_.device, - WGPUTextureFormat_RGBA8Unorm, - my_shader_wgsl); - } - - void render(WGPUCommandEncoder encoder, const UniformsSequenceParams& params, - NodeRegistry& nodes) override { - WGPUTextureView input = nodes.get_view(input_nodes_[0]); - WGPUTextureView output = nodes.get_view(output_nodes_[0]); - uniforms_.update(ctx_.queue, params); - // ... render pass - } -}; -``` - -**For 3D effects with depth:** -```cpp -class My3DEffectV2 : public EffectV2 { - std::string depth_node_; - - My3DEffectV2(...) : EffectV2(...), depth_node_(outputs[0] + "_depth") {} - - void declare_nodes(NodeRegistry& registry) override { - registry.declare_node(depth_node_, NodeType::DEPTH24, -1, -1); - } - - void render(...) { - WGPUTextureView color = nodes.get_view(output_nodes_[0]); - WGPUTextureView depth = nodes.get_view(depth_node_); - // ... render pass with depth attachment - } -}; -``` - -## Uniform Access - -```cpp -params.time; // Physical seconds (constant speed) -params.beat_time; // Musical beats (tempo-scaled) -params.beat_phase; // Fractional beat 0.0-1.0 -params.audio_intensity; // Audio peak for beat sync -params.resolution; // vec2(width, height) -params.aspect_ratio; // width/height -``` - -## Status & Limitations - -**Implemented** ✅: -- DAG validation, topological sort, ping-pong optimization -- Multi-input/multi-output effects -- Node aliasing (compile-time optimization) -- 7 effects ported (Passthrough, Placeholder, GaussianBlur, Heptagon, Particles, RotatingCube, Hybrid3D) - -**Missing/Future** ❌: -- Flatten mode (--flatten generates same code as dev mode) -- BPM handling (parsed but ignored) -- GetDemoDuration() calculation (hardcoded 40.0f) -- Sequence-relative time (uses global time) -- Asset validation (not checked against AssetId enum) -- Node type compatibility checking - -**TODO**: -- Port remaining effects (10+ effects, CNN effects) -- Implement flatten mode (inline effects, direct members) -- Update HTML timeline editor for v2 graph visualization - -## Migration Notes - -**V1 → V2 Differences**: -- V1: Implicit framebuffer ping-pong (framebuffer_a_ ↔ framebuffer_b_) -- V2: Explicit node declarations with routing - -**Breaking Changes**: -- Effect base class changed (Effect → EffectV2) -- Constructor signature: `(GpuContext, inputs[], outputs[])` -- Render signature: Added `NodeRegistry& nodes` parameter -- No `is_post_process()` method (routing makes it explicit) - -**See Also**: -- `doc/EFFECT_WORKFLOW.md` - Step-by-step effect creation -- `tools/seq_compiler_v2.py` - Compiler implementation -- `workspaces/main/timeline_v2.seq` - Example timeline diff --git a/doc/archive/timeline_v1.seq b/doc/archive/timeline_v1.seq new file mode 100644 index 0000000..b4663bb --- /dev/null +++ b/doc/archive/timeline_v1.seq @@ -0,0 +1,97 @@ +# Demo Timeline +# Generated by Timeline Editor +# BPM 90 + +SEQUENCE 0.00 0 + EFFECT - FlashCubeEffect 0.00 4.00 +# EFFECT + FlashEffect 0.00 2.00 color=1.0,0.5,0.5 decay=0.95 +# EFFECT + FadeEffect 2.00 4.00 +# EFFECT + SolarizeEffect 0.00 4.00 + EFFECT + VignetteEffect 0.00 4.00 radius=0.6 softness=0.1 + +SEQUENCE 4.00 0 "rotating cube" + EFFECT + CircleMaskEffect 0.00 4.00 0.50 + EFFECT + RotatingCubeEffect 0.00 4.00 + EFFECT + GaussianBlurEffect 1.00 4.00 strength=1.0 + +SEQUENCE 8.00 0 "Flash Cube" + EFFECT - FlashCubeEffect 0.00 4.02 + EFFECT + FlashEffect 0.00 0.40 + +SEQUENCE 12.00 1 "spray" + EFFECT + ParticleSprayEffect 0.00 2.00 + EFFECT + ParticlesEffect 2.00 4.00 + EFFECT = GaussianBlurEffect 0.00 4.00 strength=3.0 + +SEQUENCE 16.00 2 "Hybrid3D + CNN" + EFFECT + ThemeModulationEffect 0.00 4.00 + EFFECT + HeptagonEffect 0.00 4.00 + EFFECT + ParticleSprayEffect 0.00 2.00 + EFFECT = ParticlesEffect 2.00 4.00 + EFFECT + Hybrid3DEffect 0.00 4.00 + EFFECT + CNNv1Effect 0.00 4.00 layers=3 blend=.9 + +SEQUENCE 20.00 0 "CNN effect" + EFFECT + HeptagonEffect 0.00 8.00 + EFFECT + Scene1Effect 0.00 8.00 + EFFECT + CNNv1Effect 6.00 8.00 layers=3 blend=.5 + +SEQUENCE 28.00 0 "buggy" + EFFECT + HeptagonEffect 0.00 2.00 + EFFECT + FadeEffect 0.00 2.00 + +SEQUENCE 30.00 3 "Seq-8" + EFFECT + ThemeModulationEffect 0.00 10.00 + EFFECT = HeptagonEffect 0.00 10.00 + EFFECT + GaussianBlurEffect 0.00 10.00 strength=1.5 + EFFECT + ChromaAberrationEffect 0.00 10.00 offset=0.03 angle=0.785 + EFFECT + SolarizeEffect 0.00 10.00 + +SEQUENCE 40.00 2 + EFFECT - FlashCubeEffect 0.00 4.00 + EFFECT + HeptagonEffect 0.00 4.00 + EFFECT + ParticleSprayEffect 0.00 4.00 + +SEQUENCE 44.00 2 "Fade" + EFFECT - FlashCubeEffect 0.00 2.00 + EFFECT + FlashEffect 1.00 2.00 + +SEQUENCE 46.00 10 + EFFECT - FlashCubeEffect 0.00 3.00 + EFFECT + GaussianBlurEffect 0.00 3.00 + EFFECT + FlashEffect 0.00 3.00 + +SEQUENCE 49.00 1 + EFFECT + ThemeModulationEffect 0.00 8.00 + EFFECT + HeptagonEffect 0.00 8.00 + EFFECT + ParticleSprayEffect 0.00 8.00 + EFFECT + Hybrid3DEffect 0.00 8.00 + EFFECT + GaussianBlurEffect 0.00 8.00 + EFFECT + ChromaAberrationEffect 0.00 8.00 + +SEQUENCE 57.00 0 + EFFECT + ThemeModulationEffect 0.00 7.00 + EFFECT + VignetteEffect 0.00 7.00 radius=0.6 softness=0.3 + EFFECT + SolarizeEffect 0.00 7.00 + +SEQUENCE 64.00 0 + EFFECT + ThemeModulationEffect 0.00 4.00 + EFFECT + HeptagonEffect 0.00 4.00 + EFFECT + GaussianBlurEffect 0.00 4.00 + EFFECT + SolarizeEffect 0.00 4.00 + +SEQUENCE 68.00 0 "double hepta!" + EFFECT + ThemeModulationEffect 0.00 4.00 + EFFECT = HeptagonEffect 0.00 4.00 + EFFECT + Hybrid3DEffect 0.00 4.00 + EFFECT + ParticleSprayEffect 0.00 4.00 + EFFECT + HeptagonEffect 0.00 4.00 + EFFECT + ChromaAberrationEffect 0.00 4.00 + EFFECT + GaussianBlurEffect 0.00 4.00 + +SEQUENCE 72.00 0 "The End" + EFFECT + ThemeModulationEffect 0.00 7.00 + EFFECT + HeptagonEffect 0.00 7.00 + EFFECT + ChromaAberrationEffect 0.00 7.00 + EFFECT + GaussianBlurEffect 0.00 7.00 + diff --git a/src/app/main.cc b/src/app/main.cc index 75995ad..7496e8c 100644 --- a/src/app/main.cc +++ b/src/app/main.cc @@ -20,7 +20,7 @@ #include "generated/assets.h" // Include generated asset header #include "gpu/demo_effects.h" #include "gpu/gpu.h" -#include "generated/timeline_v2.h" // For GetDemoDuration(), RenderV2Timeline() +#include "generated/timeline.h" // For GetDemoDuration(), RenderTimeline() #include "platform/platform.h" #include "util/math.h" #include @@ -109,7 +109,7 @@ int main(int argc, char** argv) { gpu_init(&platform_state); // Initialize v2 sequences - InitializeV2Sequences(*gpu_get_context(), width, height); + InitializeSequences(*gpu_get_context(), width, height); #if !defined(STRIP_ALL) // Set WAV dump backend if requested @@ -414,7 +414,7 @@ int main(int argc, char** argv) { } // Draw graphics using v2 timeline - RenderV2Timeline(gpu_get_surface(), (float)current_physical_time, width, height, + RenderTimeline(gpu_get_surface(), (float)current_physical_time, width, height, absolute_beat_time, visual_peak); last_frame_time = current_physical_time; diff --git a/src/app/test_demo.cc b/src/app/test_demo.cc index c2366c3..993ceba 100644 --- a/src/app/test_demo.cc +++ b/src/app/test_demo.cc @@ -16,7 +16,7 @@ #include // External declarations from generated test timeline (v2) -#include "generated/test_timeline_v2.h" +#include "generated/test_timeline.h" extern float GetDemoDuration(); // TODO: Port PeakMeterEffect and CNN effects to v2 @@ -226,7 +226,7 @@ int main(int argc, char** argv) { gpu_init(&platform_state); // Initialize v2 timeline from test_demo.seq - InitializeTestV2Sequences(*gpu_get_context(), width, height); + InitializeSequences(*gpu_get_context(), width, height); #if !defined(STRIP_ALL) // TODO: Port CNN and peak meter effects to v2 @@ -397,8 +397,8 @@ int main(int argc, char** argv) { // Draw graphics using v2 timeline const float graphics_frame_time = (float)current_physical_time; - RenderTestV2Timeline(gpu_get_surface(), graphics_frame_time, width, height, - absolute_beat_time, visual_peak); + RenderTimeline(gpu_get_surface(), graphics_frame_time, width, height, + absolute_beat_time, visual_peak); // Update audio systems (tracker, synth, etc.) based on audio time // progression diff --git a/src/effects/gaussian_blur_effect.cc b/src/effects/gaussian_blur_effect.cc new file mode 100644 index 0000000..17c657f --- /dev/null +++ b/src/effects/gaussian_blur_effect.cc @@ -0,0 +1,84 @@ +// Gaussian blur effect v2 implementation + +#include "effects/gaussian_blur_effect.h" +#include "gpu/post_process_helper.h" +#include "gpu/shaders.h" + +GaussianBlurEffect::GaussianBlurEffect(const GpuContext& ctx, + const std::vector& inputs, + const std::vector& outputs) + : Effect(ctx, inputs, outputs), pipeline_(nullptr), bind_group_(nullptr), + sampler_(nullptr) { + // Create pipeline + pipeline_ = create_post_process_pipeline(ctx_.device, WGPUTextureFormat_RGBA8Unorm, + gaussian_blur_v2_shader_wgsl); + + // Create sampler + WGPUSamplerDescriptor sampler_desc = {}; + sampler_desc.addressModeU = WGPUAddressMode_ClampToEdge; + sampler_desc.addressModeV = WGPUAddressMode_ClampToEdge; + sampler_desc.magFilter = WGPUFilterMode_Linear; + sampler_desc.minFilter = WGPUFilterMode_Linear; + sampler_desc.maxAnisotropy = 1; + sampler_ = wgpuDeviceCreateSampler(ctx_.device, &sampler_desc); + + // Init uniform buffers + params_buffer_.init(ctx_.device); + uniforms_buffer_.init(ctx_.device); +} + +void GaussianBlurEffect::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 uniforms + uniforms_buffer_.update(ctx_.queue, params); + params_buffer_.update(ctx_.queue, blur_params_); + + // Update bind group + WGPUBindGroupEntry entries[4] = {}; + entries[0].binding = PP_BINDING_SAMPLER; + entries[0].sampler = sampler_; + 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 = {}; + color_attachment.view = output_view; +#if !defined(DEMO_CROSS_COMPILE_WIN32) + color_attachment.depthSlice = WGPU_DEPTH_SLICE_UNDEFINED; +#endif + color_attachment.loadOp = WGPULoadOp_Clear; + color_attachment.storeOp = WGPUStoreOp_Store; + color_attachment.clearValue = {0.0, 0.0, 0.0, 1.0}; + + 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 new file mode 100644 index 0000000..8bf34dc --- /dev/null +++ b/src/effects/gaussian_blur_effect.h @@ -0,0 +1,34 @@ +// Gaussian blur effect v2 - single-pass blur + +#pragma once + +#include "gpu/effect.h" +#include "gpu/uniform_helper.h" + +struct GaussianBlurParams { + float strength = 1.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 GaussianBlurEffect : public Effect { + public: + GaussianBlurEffect(const GpuContext& ctx, + const std::vector& inputs, + const std::vector& outputs); + + void render(WGPUCommandEncoder encoder, const UniformsSequenceParams& params, + NodeRegistry& nodes) override; + + private: + WGPURenderPipeline pipeline_; + WGPUBindGroup bind_group_; + WGPUSampler sampler_; + GaussianBlurParams blur_params_; + UniformBuffer params_buffer_; + UniformBuffer uniforms_buffer_; +}; + diff --git a/src/effects/gaussian_blur_effect_v2.cc b/src/effects/gaussian_blur_effect_v2.cc deleted file mode 100644 index 0c90fa2..0000000 --- a/src/effects/gaussian_blur_effect_v2.cc +++ /dev/null @@ -1,84 +0,0 @@ -// Gaussian blur effect v2 implementation - -#include "effects/gaussian_blur_effect_v2.h" -#include "gpu/post_process_helper.h" -#include "gpu/shaders.h" - -GaussianBlurEffectV2::GaussianBlurEffectV2(const GpuContext& ctx, - const std::vector& inputs, - const std::vector& outputs) - : EffectV2(ctx, inputs, outputs), pipeline_(nullptr), bind_group_(nullptr), - sampler_(nullptr) { - // Create pipeline - pipeline_ = create_post_process_pipeline(ctx_.device, WGPUTextureFormat_RGBA8Unorm, - gaussian_blur_v2_shader_wgsl); - - // Create sampler - WGPUSamplerDescriptor sampler_desc = {}; - sampler_desc.addressModeU = WGPUAddressMode_ClampToEdge; - sampler_desc.addressModeV = WGPUAddressMode_ClampToEdge; - sampler_desc.magFilter = WGPUFilterMode_Linear; - sampler_desc.minFilter = WGPUFilterMode_Linear; - sampler_desc.maxAnisotropy = 1; - sampler_ = wgpuDeviceCreateSampler(ctx_.device, &sampler_desc); - - // Init uniform buffers - params_buffer_.init(ctx_.device); - uniforms_buffer_.init(ctx_.device); -} - -void GaussianBlurEffectV2::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 uniforms - uniforms_buffer_.update(ctx_.queue, params); - params_buffer_.update(ctx_.queue, blur_params_); - - // Update bind group - WGPUBindGroupEntry entries[4] = {}; - entries[0].binding = PP_BINDING_SAMPLER; - entries[0].sampler = sampler_; - 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 = {}; - color_attachment.view = output_view; -#if !defined(DEMO_CROSS_COMPILE_WIN32) - color_attachment.depthSlice = WGPU_DEPTH_SLICE_UNDEFINED; -#endif - color_attachment.loadOp = WGPULoadOp_Clear; - color_attachment.storeOp = WGPUStoreOp_Store; - color_attachment.clearValue = {0.0, 0.0, 0.0, 1.0}; - - 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_v2.h b/src/effects/gaussian_blur_effect_v2.h deleted file mode 100644 index c5d88ff..0000000 --- a/src/effects/gaussian_blur_effect_v2.h +++ /dev/null @@ -1,33 +0,0 @@ -// Gaussian blur effect v2 - single-pass blur - -#pragma once - -#include "gpu/effect_v2.h" -#include "gpu/uniform_helper.h" - -struct GaussianBlurParams { - float strength = 1.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 GaussianBlurEffectV2 : public EffectV2 { - public: - GaussianBlurEffectV2(const GpuContext& ctx, - const std::vector& inputs, - const std::vector& outputs); - - void render(WGPUCommandEncoder encoder, const UniformsSequenceParams& params, - NodeRegistry& nodes) override; - - private: - WGPURenderPipeline pipeline_; - WGPUBindGroup bind_group_; - WGPUSampler sampler_; - GaussianBlurParams blur_params_; - UniformBuffer params_buffer_; - UniformBuffer uniforms_buffer_; -}; diff --git a/src/effects/heptagon_effect.cc b/src/effects/heptagon_effect.cc new file mode 100644 index 0000000..27d59da --- /dev/null +++ b/src/effects/heptagon_effect.cc @@ -0,0 +1,81 @@ +// Heptagon effect v2 implementation + +#include "effects/heptagon_effect.h" +#include "gpu/gpu.h" +#include "gpu/post_process_helper.h" +#include "gpu/shaders.h" + +HeptagonEffect::HeptagonEffect(const GpuContext& ctx, + const std::vector& inputs, + const std::vector& outputs) + : Effect(ctx, inputs, outputs), pipeline_(nullptr), bind_group_(nullptr), sampler_(nullptr) { + // Init uniforms + uniforms_buffer_.init(ctx_.device); + + // Create pipeline (standard v2 post-process, no depth) + pipeline_ = create_post_process_pipeline(ctx_.device, WGPUTextureFormat_RGBA8Unorm, + heptagon_v2_shader_wgsl); + + // Create dummy sampler (scene effects don't use texture input) + WGPUSamplerDescriptor sampler_desc = {}; + sampler_desc.addressModeU = WGPUAddressMode_ClampToEdge; + sampler_desc.addressModeV = WGPUAddressMode_ClampToEdge; + sampler_desc.magFilter = WGPUFilterMode_Nearest; + sampler_desc.minFilter = WGPUFilterMode_Nearest; + sampler_desc.maxAnisotropy = 1; + sampler_ = wgpuDeviceCreateSampler(ctx_.device, &sampler_desc); + + // Create 1×1 dummy texture + WGPUTextureDescriptor tex_desc = {}; + tex_desc.size = {1, 1, 1}; + tex_desc.format = WGPUTextureFormat_RGBA8Unorm; + tex_desc.usage = WGPUTextureUsage_TextureBinding; + tex_desc.dimension = WGPUTextureDimension_2D; + tex_desc.mipLevelCount = 1; + tex_desc.sampleCount = 1; + dummy_texture_ = wgpuDeviceCreateTexture(ctx_.device, &tex_desc); + dummy_texture_view_ = wgpuTextureCreateView(dummy_texture_, nullptr); +} + +HeptagonEffect::~HeptagonEffect() { + if (bind_group_) wgpuBindGroupRelease(bind_group_); + if (pipeline_) wgpuRenderPipelineRelease(pipeline_); + if (sampler_) wgpuSamplerRelease(sampler_); + if (dummy_texture_view_) wgpuTextureViewRelease(dummy_texture_view_); + if (dummy_texture_) wgpuTextureRelease(dummy_texture_); +} + +void HeptagonEffect::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 uniforms + uniforms_buffer_.update(ctx_.queue, params); + + // Create bind group (use dummy texture for scene effect) + pp_update_bind_group(ctx_.device, pipeline_, &bind_group_, dummy_texture_view_, + uniforms_buffer_.get(), {nullptr, 0}); + + // Render pass + WGPURenderPassColorAttachment color_attachment = {}; + color_attachment.view = output_view; +#if !defined(DEMO_CROSS_COMPILE_WIN32) + color_attachment.depthSlice = WGPU_DEPTH_SLICE_UNDEFINED; +#endif + color_attachment.loadOp = WGPULoadOp_Clear; + color_attachment.storeOp = WGPUStoreOp_Store; + color_attachment.clearValue = {0.0, 0.0, 0.0, 1.0}; + + 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, 21, 1, 0, 0); // 7 triangles * 3 vertices + wgpuRenderPassEncoderEnd(pass); + wgpuRenderPassEncoderRelease(pass); +} diff --git a/src/effects/heptagon_effect.h b/src/effects/heptagon_effect.h new file mode 100644 index 0000000..9f148a1 --- /dev/null +++ b/src/effects/heptagon_effect.h @@ -0,0 +1,25 @@ +// Heptagon effect v2 - scene rendering effect + +#pragma once + +#include "gpu/effect.h" +#include "gpu/uniform_helper.h" + +class HeptagonEffect : public Effect { + public: + HeptagonEffect(const GpuContext& ctx, const std::vector& inputs, + const std::vector& outputs); + ~HeptagonEffect(); + + void render(WGPUCommandEncoder encoder, const UniformsSequenceParams& params, + NodeRegistry& nodes) override; + + private: + WGPURenderPipeline pipeline_; + WGPUBindGroup bind_group_; + WGPUSampler sampler_; + WGPUTexture dummy_texture_; + WGPUTextureView dummy_texture_view_; + UniformBuffer uniforms_buffer_; +}; + diff --git a/src/effects/heptagon_effect_v2.cc b/src/effects/heptagon_effect_v2.cc deleted file mode 100644 index 6a2135e..0000000 --- a/src/effects/heptagon_effect_v2.cc +++ /dev/null @@ -1,81 +0,0 @@ -// Heptagon effect v2 implementation - -#include "effects/heptagon_effect_v2.h" -#include "gpu/gpu.h" -#include "gpu/post_process_helper.h" -#include "gpu/shaders.h" - -HeptagonEffectV2::HeptagonEffectV2(const GpuContext& ctx, - const std::vector& inputs, - const std::vector& outputs) - : EffectV2(ctx, inputs, outputs), pipeline_(nullptr), bind_group_(nullptr), sampler_(nullptr) { - // Init uniforms - uniforms_buffer_.init(ctx_.device); - - // Create pipeline (standard v2 post-process, no depth) - pipeline_ = create_post_process_pipeline(ctx_.device, WGPUTextureFormat_RGBA8Unorm, - heptagon_v2_shader_wgsl); - - // Create dummy sampler (scene effects don't use texture input) - WGPUSamplerDescriptor sampler_desc = {}; - sampler_desc.addressModeU = WGPUAddressMode_ClampToEdge; - sampler_desc.addressModeV = WGPUAddressMode_ClampToEdge; - sampler_desc.magFilter = WGPUFilterMode_Nearest; - sampler_desc.minFilter = WGPUFilterMode_Nearest; - sampler_desc.maxAnisotropy = 1; - sampler_ = wgpuDeviceCreateSampler(ctx_.device, &sampler_desc); - - // Create 1×1 dummy texture - WGPUTextureDescriptor tex_desc = {}; - tex_desc.size = {1, 1, 1}; - tex_desc.format = WGPUTextureFormat_RGBA8Unorm; - tex_desc.usage = WGPUTextureUsage_TextureBinding; - tex_desc.dimension = WGPUTextureDimension_2D; - tex_desc.mipLevelCount = 1; - tex_desc.sampleCount = 1; - dummy_texture_ = wgpuDeviceCreateTexture(ctx_.device, &tex_desc); - dummy_texture_view_ = wgpuTextureCreateView(dummy_texture_, nullptr); -} - -HeptagonEffectV2::~HeptagonEffectV2() { - if (bind_group_) wgpuBindGroupRelease(bind_group_); - if (pipeline_) wgpuRenderPipelineRelease(pipeline_); - if (sampler_) wgpuSamplerRelease(sampler_); - if (dummy_texture_view_) wgpuTextureViewRelease(dummy_texture_view_); - if (dummy_texture_) wgpuTextureRelease(dummy_texture_); -} - -void HeptagonEffectV2::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 uniforms - uniforms_buffer_.update(ctx_.queue, params); - - // Create bind group (use dummy texture for scene effect) - pp_update_bind_group(ctx_.device, pipeline_, &bind_group_, dummy_texture_view_, - uniforms_buffer_.get(), {nullptr, 0}); - - // Render pass - WGPURenderPassColorAttachment color_attachment = {}; - color_attachment.view = output_view; -#if !defined(DEMO_CROSS_COMPILE_WIN32) - color_attachment.depthSlice = WGPU_DEPTH_SLICE_UNDEFINED; -#endif - color_attachment.loadOp = WGPULoadOp_Clear; - color_attachment.storeOp = WGPUStoreOp_Store; - color_attachment.clearValue = {0.0, 0.0, 0.0, 1.0}; - - 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, 21, 1, 0, 0); // 7 triangles * 3 vertices - wgpuRenderPassEncoderEnd(pass); - wgpuRenderPassEncoderRelease(pass); -} diff --git a/src/effects/heptagon_effect_v2.h b/src/effects/heptagon_effect_v2.h deleted file mode 100644 index 1737a46..0000000 --- a/src/effects/heptagon_effect_v2.h +++ /dev/null @@ -1,24 +0,0 @@ -// Heptagon effect v2 - scene rendering effect - -#pragma once - -#include "gpu/effect_v2.h" -#include "gpu/uniform_helper.h" - -class HeptagonEffectV2 : public EffectV2 { - public: - HeptagonEffectV2(const GpuContext& ctx, const std::vector& inputs, - const std::vector& outputs); - ~HeptagonEffectV2(); - - void render(WGPUCommandEncoder encoder, const UniformsSequenceParams& params, - NodeRegistry& nodes) override; - - private: - WGPURenderPipeline pipeline_; - WGPUBindGroup bind_group_; - WGPUSampler sampler_; - WGPUTexture dummy_texture_; - WGPUTextureView dummy_texture_view_; - UniformBuffer uniforms_buffer_; -}; diff --git a/src/effects/hybrid3_d_effect.cc b/src/effects/hybrid3_d_effect.cc new file mode 100644 index 0000000..ced5b42 --- /dev/null +++ b/src/effects/hybrid3_d_effect.cc @@ -0,0 +1,114 @@ +// This file is part of the 64k demo project. +// It implements Hybrid3DEffect (simplified v2 port). +// TODO: Full Renderer3D integration with texture manager, noise assets + +#include "effects/hybrid3_d_effect.h" +#include + +Hybrid3DEffect::Hybrid3DEffect(const GpuContext& ctx, + const std::vector& inputs, + const std::vector& outputs) + : Effect(ctx, inputs, outputs), depth_node_(outputs[0] + "_depth"), + dummy_texture_(nullptr), dummy_texture_view_(nullptr) { + // Initialize renderer (format is always RGBA8Unorm for v2) + renderer_.init(ctx_.device, ctx_.queue, WGPUTextureFormat_RGBA8Unorm); + + // Create 1×1 white dummy texture for noise/sky (Renderer3D requires these) + WGPUTextureDescriptor tex_desc = {}; + tex_desc.size = {1, 1, 1}; + tex_desc.format = WGPUTextureFormat_RGBA8Unorm; + tex_desc.usage = WGPUTextureUsage_TextureBinding | WGPUTextureUsage_CopyDst; + tex_desc.dimension = WGPUTextureDimension_2D; + tex_desc.mipLevelCount = 1; + tex_desc.sampleCount = 1; + dummy_texture_ = wgpuDeviceCreateTexture(ctx_.device, &tex_desc); + dummy_texture_view_ = wgpuTextureCreateView(dummy_texture_, nullptr); + + // Write white pixel + uint32_t white_pixel = 0xFFFFFFFF; +#if defined(DEMO_CROSS_COMPILE_WIN32) + WGPUImageCopyTexture dst = { + .texture = dummy_texture_, + .mipLevel = 0, + .origin = {0, 0, 0} + }; + WGPUTextureDataLayout data_layout = { + .bytesPerRow = 4, + .rowsPerImage = 1 + }; +#else + WGPUTexelCopyTextureInfo dst = { + .texture = dummy_texture_, + .mipLevel = 0, + .origin = {0, 0, 0} + }; + WGPUTexelCopyBufferLayout data_layout = { + .bytesPerRow = 4, + .rowsPerImage = 1 + }; +#endif + WGPUExtent3D size = {1, 1, 1}; + wgpuQueueWriteTexture(ctx_.queue, &dst, &white_pixel, 4, &data_layout, &size); + + renderer_.set_noise_texture(dummy_texture_view_); + renderer_.set_sky_texture(dummy_texture_view_); + + initialized_ = true; + + // Setup simple scene (1 center cube + 8 surrounding objects) + scene_.clear(); + Object3D center(ObjectType::BOX); + center.position = vec3(0, 0, 0); + center.color = vec4(1, 0, 0, 1); + scene_.add_object(center); + + for (int i = 0; i < 8; ++i) { + ObjectType type = (i % 3 == 1) ? ObjectType::TORUS : + (i % 3 == 2) ? ObjectType::BOX : ObjectType::SPHERE; + + Object3D obj(type); + float angle = (i / 8.0f) * 6.28318f; + obj.position = vec3(std::cos(angle) * 4.0f, 0, std::sin(angle) * 4.0f); + obj.scale = vec3(0.7f, 0.7f, 0.7f); + + if (type == ObjectType::SPHERE) + obj.color = vec4(0, 1, 0, 1); + else if (type == ObjectType::TORUS) + obj.color = vec4(0, 0.5f, 1, 1); + else + obj.color = vec4(1, 1, 0, 1); + + scene_.add_object(obj); + } +} + +Hybrid3DEffect::~Hybrid3DEffect() { + if (dummy_texture_view_) + wgpuTextureViewRelease(dummy_texture_view_); + if (dummy_texture_) + wgpuTextureRelease(dummy_texture_); + renderer_.shutdown(); +} + +void Hybrid3DEffect::declare_nodes(NodeRegistry& registry) { + // Declare depth buffer node + registry.declare_node(depth_node_, NodeType::DEPTH24, -1, -1); +} + +void Hybrid3DEffect::render(WGPUCommandEncoder encoder, + const UniformsSequenceParams& params, + NodeRegistry& nodes) { + // Update camera (orbiting) + float angle = params.time * 0.3f; + vec3 cam_pos = vec3(std::cos(angle) * 10.0f, 5.0f, std::sin(angle) * 10.0f); + camera_.position = cam_pos; + camera_.target = vec3(0, 0, 0); + camera_.aspect_ratio = params.aspect_ratio; + + // Get output views + WGPUTextureView color_view = nodes.get_view(output_nodes_[0]); + WGPUTextureView depth_view = nodes.get_view(depth_node_); + + // Render 3D scene + renderer_.render(scene_, camera_, params.time, color_view, depth_view); +} diff --git a/src/effects/hybrid3_d_effect.h b/src/effects/hybrid3_d_effect.h new file mode 100644 index 0000000..e6e0f49 --- /dev/null +++ b/src/effects/hybrid3_d_effect.h @@ -0,0 +1,32 @@ +// This file is part of the 64k demo project. +// It declares Hybrid3DEffect (simplified v2 port). +// TODO: Full Renderer3D integration with Scene/Camera + +#pragma once + +#include "3d/camera.h" +#include "3d/renderer.h" +#include "3d/scene.h" +#include "gpu/effect.h" + +class Hybrid3DEffect : public Effect { + public: + Hybrid3DEffect(const GpuContext& ctx, + const std::vector& inputs, + const std::vector& outputs); + ~Hybrid3DEffect() override; + + void declare_nodes(NodeRegistry& registry) override; + void render(WGPUCommandEncoder encoder, + const UniformsSequenceParams& params, + NodeRegistry& nodes) override; + + private: + Renderer3D renderer_; + Scene scene_; + Camera camera_; + bool initialized_ = false; + std::string depth_node_; + WGPUTexture dummy_texture_; + WGPUTextureView dummy_texture_view_; +}; diff --git a/src/effects/hybrid3_d_effect_v2.cc b/src/effects/hybrid3_d_effect_v2.cc deleted file mode 100644 index 38e4e66..0000000 --- a/src/effects/hybrid3_d_effect_v2.cc +++ /dev/null @@ -1,114 +0,0 @@ -// This file is part of the 64k demo project. -// It implements Hybrid3DEffectV2 (simplified v2 port). -// TODO: Full Renderer3D integration with texture manager, noise assets - -#include "effects/hybrid3_d_effect_v2.h" -#include - -Hybrid3DEffectV2::Hybrid3DEffectV2(const GpuContext& ctx, - const std::vector& inputs, - const std::vector& outputs) - : EffectV2(ctx, inputs, outputs), depth_node_(outputs[0] + "_depth"), - dummy_texture_(nullptr), dummy_texture_view_(nullptr) { - // Initialize renderer (format is always RGBA8Unorm for v2) - renderer_.init(ctx_.device, ctx_.queue, WGPUTextureFormat_RGBA8Unorm); - - // Create 1×1 white dummy texture for noise/sky (Renderer3D requires these) - WGPUTextureDescriptor tex_desc = {}; - tex_desc.size = {1, 1, 1}; - tex_desc.format = WGPUTextureFormat_RGBA8Unorm; - tex_desc.usage = WGPUTextureUsage_TextureBinding | WGPUTextureUsage_CopyDst; - tex_desc.dimension = WGPUTextureDimension_2D; - tex_desc.mipLevelCount = 1; - tex_desc.sampleCount = 1; - dummy_texture_ = wgpuDeviceCreateTexture(ctx_.device, &tex_desc); - dummy_texture_view_ = wgpuTextureCreateView(dummy_texture_, nullptr); - - // Write white pixel - uint32_t white_pixel = 0xFFFFFFFF; -#if defined(DEMO_CROSS_COMPILE_WIN32) - WGPUImageCopyTexture dst = { - .texture = dummy_texture_, - .mipLevel = 0, - .origin = {0, 0, 0} - }; - WGPUTextureDataLayout data_layout = { - .bytesPerRow = 4, - .rowsPerImage = 1 - }; -#else - WGPUTexelCopyTextureInfo dst = { - .texture = dummy_texture_, - .mipLevel = 0, - .origin = {0, 0, 0} - }; - WGPUTexelCopyBufferLayout data_layout = { - .bytesPerRow = 4, - .rowsPerImage = 1 - }; -#endif - WGPUExtent3D size = {1, 1, 1}; - wgpuQueueWriteTexture(ctx_.queue, &dst, &white_pixel, 4, &data_layout, &size); - - renderer_.set_noise_texture(dummy_texture_view_); - renderer_.set_sky_texture(dummy_texture_view_); - - initialized_ = true; - - // Setup simple scene (1 center cube + 8 surrounding objects) - scene_.clear(); - Object3D center(ObjectType::BOX); - center.position = vec3(0, 0, 0); - center.color = vec4(1, 0, 0, 1); - scene_.add_object(center); - - for (int i = 0; i < 8; ++i) { - ObjectType type = (i % 3 == 1) ? ObjectType::TORUS : - (i % 3 == 2) ? ObjectType::BOX : ObjectType::SPHERE; - - Object3D obj(type); - float angle = (i / 8.0f) * 6.28318f; - obj.position = vec3(std::cos(angle) * 4.0f, 0, std::sin(angle) * 4.0f); - obj.scale = vec3(0.7f, 0.7f, 0.7f); - - if (type == ObjectType::SPHERE) - obj.color = vec4(0, 1, 0, 1); - else if (type == ObjectType::TORUS) - obj.color = vec4(0, 0.5f, 1, 1); - else - obj.color = vec4(1, 1, 0, 1); - - scene_.add_object(obj); - } -} - -Hybrid3DEffectV2::~Hybrid3DEffectV2() { - if (dummy_texture_view_) - wgpuTextureViewRelease(dummy_texture_view_); - if (dummy_texture_) - wgpuTextureRelease(dummy_texture_); - renderer_.shutdown(); -} - -void Hybrid3DEffectV2::declare_nodes(NodeRegistry& registry) { - // Declare depth buffer node - registry.declare_node(depth_node_, NodeType::DEPTH24, -1, -1); -} - -void Hybrid3DEffectV2::render(WGPUCommandEncoder encoder, - const UniformsSequenceParams& params, - NodeRegistry& nodes) { - // Update camera (orbiting) - float angle = params.time * 0.3f; - vec3 cam_pos = vec3(std::cos(angle) * 10.0f, 5.0f, std::sin(angle) * 10.0f); - camera_.position = cam_pos; - camera_.target = vec3(0, 0, 0); - camera_.aspect_ratio = params.aspect_ratio; - - // Get output views - WGPUTextureView color_view = nodes.get_view(output_nodes_[0]); - WGPUTextureView depth_view = nodes.get_view(depth_node_); - - // Render 3D scene - renderer_.render(scene_, camera_, params.time, color_view, depth_view); -} diff --git a/src/effects/hybrid3_d_effect_v2.h b/src/effects/hybrid3_d_effect_v2.h deleted file mode 100644 index c8116b0..0000000 --- a/src/effects/hybrid3_d_effect_v2.h +++ /dev/null @@ -1,32 +0,0 @@ -// This file is part of the 64k demo project. -// It declares Hybrid3DEffectV2 (simplified v2 port). -// TODO: Full Renderer3D integration with Scene/Camera - -#pragma once - -#include "3d/camera.h" -#include "3d/renderer.h" -#include "3d/scene.h" -#include "gpu/effect_v2.h" - -class Hybrid3DEffectV2 : public EffectV2 { - public: - Hybrid3DEffectV2(const GpuContext& ctx, - const std::vector& inputs, - const std::vector& outputs); - ~Hybrid3DEffectV2() override; - - void declare_nodes(NodeRegistry& registry) override; - void render(WGPUCommandEncoder encoder, - const UniformsSequenceParams& params, - NodeRegistry& nodes) override; - - private: - Renderer3D renderer_; - Scene scene_; - Camera camera_; - bool initialized_ = false; - std::string depth_node_; - WGPUTexture dummy_texture_; - WGPUTextureView dummy_texture_view_; -}; diff --git a/src/effects/particles_effect.cc b/src/effects/particles_effect.cc new file mode 100644 index 0000000..9d73bf7 --- /dev/null +++ b/src/effects/particles_effect.cc @@ -0,0 +1,96 @@ +// This file is part of the 64k demo project. +// It implements the ParticlesEffect. + +#include "effects/particles_effect.h" +#include "gpu/gpu.h" +#include "gpu/shaders.h" +#include + +ParticlesEffect::ParticlesEffect(const GpuContext& ctx, + const std::vector& inputs, + const std::vector& outputs) + : Effect(ctx, inputs, outputs) { + // Initialize uniforms + uniforms_.init(ctx_.device); + + // Initialize particles buffer + std::vector init_p(NUM_PARTICLES); + for (int i = 0; i < NUM_PARTICLES; ++i) { + float x = (float)(i % 100) / 50.0f - 1.0f; + float y = (float)(i / 100) / 100.0f * 3.0f - 1.5f; + init_p[i].pos[0] = x; + init_p[i].pos[1] = y; + init_p[i].pos[2] = ((float)i / NUM_PARTICLES) * 0.5f; + init_p[i].pos[3] = 1.0f; + init_p[i].vel[0] = 0.0f; + init_p[i].vel[1] = 0.0f; + init_p[i].vel[2] = 0.0f; + init_p[i].vel[3] = 0.0f; + init_p[i].rot[0] = 0.0f; + init_p[i].rot[1] = ((float)(i % 7) / 7.0f) * 3.14159f; + init_p[i].rot[2] = 0.0f; + init_p[i].rot[3] = 0.0f; + float hue = (float)(i % 100) / 100.0f; + init_p[i].color[0] = 0.5f + 0.5f * hue; + init_p[i].color[1] = 0.3f + 0.7f * (1.0f - hue); + init_p[i].color[2] = 0.8f; + init_p[i].color[3] = 0.8f; + } + + particles_buffer_ = gpu_create_buffer( + ctx_.device, sizeof(Particle) * NUM_PARTICLES, + WGPUBufferUsage_Storage | WGPUBufferUsage_Vertex, init_p.data()); + + // Create compute shader (particle simulation) + ResourceBinding compute_bindings[] = { + {particles_buffer_, WGPUBufferBindingType_Storage}, + {uniforms_.get(), WGPUBufferBindingType_Uniform}}; + compute_pass_ = gpu_create_compute_pass(ctx_.device, particle_compute_v2_wgsl, + compute_bindings, 2); + compute_pass_.workgroup_size_x = (NUM_PARTICLES + 63) / 64; + + // Create render shader (particle rendering) + ResourceBinding render_bindings[] = { + {particles_buffer_, WGPUBufferBindingType_ReadOnlyStorage}, + {uniforms_.get(), WGPUBufferBindingType_Uniform}}; + render_pass_ = gpu_create_render_pass(ctx_.device, WGPUTextureFormat_RGBA8Unorm, + particle_render_v2_wgsl, render_bindings, 2); + render_pass_.vertex_count = 6; + render_pass_.instance_count = NUM_PARTICLES; +} + +void ParticlesEffect::render(WGPUCommandEncoder encoder, + const UniformsSequenceParams& params, + NodeRegistry& nodes) { + // Update uniforms + uniforms_.update(ctx_.queue, params); + + // Run compute pass (particle simulation) + WGPUComputePassEncoder compute = wgpuCommandEncoderBeginComputePass(encoder, nullptr); + wgpuComputePassEncoderSetPipeline(compute, compute_pass_.pipeline); + wgpuComputePassEncoderSetBindGroup(compute, 0, compute_pass_.bind_group, 0, nullptr); + wgpuComputePassEncoderDispatchWorkgroups(compute, compute_pass_.workgroup_size_x, 1, 1); + wgpuComputePassEncoderEnd(compute); + + // Run render pass (draw particles to output) + WGPUTextureView output_view = nodes.get_view(output_nodes_[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 render_desc = { + .colorAttachmentCount = 1, + .colorAttachments = &color_attachment}; + + WGPURenderPassEncoder pass = wgpuCommandEncoderBeginRenderPass(encoder, &render_desc); + wgpuRenderPassEncoderSetPipeline(pass, render_pass_.pipeline); + wgpuRenderPassEncoderSetBindGroup(pass, 0, render_pass_.bind_group, 0, nullptr); + wgpuRenderPassEncoderDraw(pass, render_pass_.vertex_count, render_pass_.instance_count, 0, 0); + wgpuRenderPassEncoderEnd(pass); +} diff --git a/src/effects/particles_effect.h b/src/effects/particles_effect.h new file mode 100644 index 0000000..76c2ef4 --- /dev/null +++ b/src/effects/particles_effect.h @@ -0,0 +1,36 @@ +// This file is part of the 64k demo project. +// It declares the ParticlesEffect. + +#pragma once + +#include "gpu/effect.h" +#include "gpu/gpu.h" +#include "gpu/uniform_helper.h" +#include + +// Particle structure +static const int NUM_PARTICLES = 10000; + +struct Particle { + float pos[4]; + float vel[4]; + float rot[4]; + float color[4]; +}; + +class ParticlesEffect : public Effect { + public: + ParticlesEffect(const GpuContext& ctx, + const std::vector& inputs, + const std::vector& outputs); + void render(WGPUCommandEncoder encoder, + const UniformsSequenceParams& params, + NodeRegistry& nodes) override; + + private: + ComputePass compute_pass_; + RenderPass render_pass_; + GpuBuffer particles_buffer_; + UniformBuffer uniforms_; +}; + diff --git a/src/effects/particles_effect_v2.cc b/src/effects/particles_effect_v2.cc deleted file mode 100644 index 69da4da..0000000 --- a/src/effects/particles_effect_v2.cc +++ /dev/null @@ -1,96 +0,0 @@ -// This file is part of the 64k demo project. -// It implements the ParticlesEffectV2. - -#include "effects/particles_effect_v2.h" -#include "gpu/gpu.h" -#include "gpu/shaders.h" -#include - -ParticlesEffectV2::ParticlesEffectV2(const GpuContext& ctx, - const std::vector& inputs, - const std::vector& outputs) - : EffectV2(ctx, inputs, outputs) { - // Initialize uniforms - uniforms_.init(ctx_.device); - - // Initialize particles buffer - std::vector init_p(NUM_PARTICLES); - for (int i = 0; i < NUM_PARTICLES; ++i) { - float x = (float)(i % 100) / 50.0f - 1.0f; - float y = (float)(i / 100) / 100.0f * 3.0f - 1.5f; - init_p[i].pos[0] = x; - init_p[i].pos[1] = y; - init_p[i].pos[2] = ((float)i / NUM_PARTICLES) * 0.5f; - init_p[i].pos[3] = 1.0f; - init_p[i].vel[0] = 0.0f; - init_p[i].vel[1] = 0.0f; - init_p[i].vel[2] = 0.0f; - init_p[i].vel[3] = 0.0f; - init_p[i].rot[0] = 0.0f; - init_p[i].rot[1] = ((float)(i % 7) / 7.0f) * 3.14159f; - init_p[i].rot[2] = 0.0f; - init_p[i].rot[3] = 0.0f; - float hue = (float)(i % 100) / 100.0f; - init_p[i].color[0] = 0.5f + 0.5f * hue; - init_p[i].color[1] = 0.3f + 0.7f * (1.0f - hue); - init_p[i].color[2] = 0.8f; - init_p[i].color[3] = 0.8f; - } - - particles_buffer_ = gpu_create_buffer( - ctx_.device, sizeof(Particle) * NUM_PARTICLES, - WGPUBufferUsage_Storage | WGPUBufferUsage_Vertex, init_p.data()); - - // Create compute shader (particle simulation) - ResourceBinding compute_bindings[] = { - {particles_buffer_, WGPUBufferBindingType_Storage}, - {uniforms_.get(), WGPUBufferBindingType_Uniform}}; - compute_pass_ = gpu_create_compute_pass(ctx_.device, particle_compute_v2_wgsl, - compute_bindings, 2); - compute_pass_.workgroup_size_x = (NUM_PARTICLES + 63) / 64; - - // Create render shader (particle rendering) - ResourceBinding render_bindings[] = { - {particles_buffer_, WGPUBufferBindingType_ReadOnlyStorage}, - {uniforms_.get(), WGPUBufferBindingType_Uniform}}; - render_pass_ = gpu_create_render_pass(ctx_.device, WGPUTextureFormat_RGBA8Unorm, - particle_render_v2_wgsl, render_bindings, 2); - render_pass_.vertex_count = 6; - render_pass_.instance_count = NUM_PARTICLES; -} - -void ParticlesEffectV2::render(WGPUCommandEncoder encoder, - const UniformsSequenceParams& params, - NodeRegistry& nodes) { - // Update uniforms - uniforms_.update(ctx_.queue, params); - - // Run compute pass (particle simulation) - WGPUComputePassEncoder compute = wgpuCommandEncoderBeginComputePass(encoder, nullptr); - wgpuComputePassEncoderSetPipeline(compute, compute_pass_.pipeline); - wgpuComputePassEncoderSetBindGroup(compute, 0, compute_pass_.bind_group, 0, nullptr); - wgpuComputePassEncoderDispatchWorkgroups(compute, compute_pass_.workgroup_size_x, 1, 1); - wgpuComputePassEncoderEnd(compute); - - // Run render pass (draw particles to output) - WGPUTextureView output_view = nodes.get_view(output_nodes_[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 render_desc = { - .colorAttachmentCount = 1, - .colorAttachments = &color_attachment}; - - WGPURenderPassEncoder pass = wgpuCommandEncoderBeginRenderPass(encoder, &render_desc); - wgpuRenderPassEncoderSetPipeline(pass, render_pass_.pipeline); - wgpuRenderPassEncoderSetBindGroup(pass, 0, render_pass_.bind_group, 0, nullptr); - wgpuRenderPassEncoderDraw(pass, render_pass_.vertex_count, render_pass_.instance_count, 0, 0); - wgpuRenderPassEncoderEnd(pass); -} diff --git a/src/effects/particles_effect_v2.h b/src/effects/particles_effect_v2.h deleted file mode 100644 index f0f260c..0000000 --- a/src/effects/particles_effect_v2.h +++ /dev/null @@ -1,35 +0,0 @@ -// This file is part of the 64k demo project. -// It declares the ParticlesEffectV2. - -#pragma once - -#include "gpu/effect_v2.h" -#include "gpu/gpu.h" -#include "gpu/uniform_helper.h" -#include - -// Particle structure -static const int NUM_PARTICLES = 10000; - -struct Particle { - float pos[4]; - float vel[4]; - float rot[4]; - float color[4]; -}; - -class ParticlesEffectV2 : public EffectV2 { - public: - ParticlesEffectV2(const GpuContext& ctx, - const std::vector& inputs, - const std::vector& outputs); - void render(WGPUCommandEncoder encoder, - const UniformsSequenceParams& params, - NodeRegistry& nodes) override; - - private: - ComputePass compute_pass_; - RenderPass render_pass_; - GpuBuffer particles_buffer_; - UniformBuffer uniforms_; -}; diff --git a/src/effects/passthrough_effect.cc b/src/effects/passthrough_effect.cc new file mode 100644 index 0000000..ba98657 --- /dev/null +++ b/src/effects/passthrough_effect.cc @@ -0,0 +1,81 @@ +// Passthrough effect v2 implementation + +#include "effects/passthrough_effect.h" +#include "gpu/post_process_helper.h" +#include "gpu/shaders.h" + +PassthroughEffect::PassthroughEffect(const GpuContext& ctx, + const std::vector& inputs, + const std::vector& outputs) + : Effect(ctx, inputs, outputs), pipeline_(nullptr), bind_group_(nullptr), + sampler_(nullptr) { + // Init uniform buffer + uniforms_buffer_.init(ctx_.device); + // Create pipeline (simple version without effect params) + pipeline_ = create_post_process_pipeline_simple(ctx_.device, WGPUTextureFormat_RGBA8Unorm, + passthrough_v2_shader_wgsl); + + // Create sampler + WGPUSamplerDescriptor sampler_desc = {}; + sampler_desc.addressModeU = WGPUAddressMode_ClampToEdge; + sampler_desc.addressModeV = WGPUAddressMode_ClampToEdge; + sampler_desc.addressModeW = WGPUAddressMode_ClampToEdge; + sampler_desc.magFilter = WGPUFilterMode_Linear; + sampler_desc.minFilter = WGPUFilterMode_Linear; + sampler_desc.mipmapFilter = WGPUMipmapFilterMode_Nearest; + sampler_desc.maxAnisotropy = 1; + sampler_ = wgpuDeviceCreateSampler(ctx_.device, &sampler_desc); +} + +void PassthroughEffect::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 uniforms + uniforms_buffer_.update(ctx_.queue, params); + + // 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_; + 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_, 0); + bg_desc.entryCount = 3; + bg_desc.entries = entries; + + if (bind_group_) { + wgpuBindGroupRelease(bind_group_); + } + bind_group_ = wgpuDeviceCreateBindGroup(ctx_.device, &bg_desc); + + // Render pass + 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 = {}; + 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 + wgpuRenderPassEncoderEnd(pass); + wgpuRenderPassEncoderRelease(pass); +} diff --git a/src/effects/passthrough_effect.h b/src/effects/passthrough_effect.h new file mode 100644 index 0000000..125ac5a --- /dev/null +++ b/src/effects/passthrough_effect.h @@ -0,0 +1,21 @@ +// Passthrough effect v2 - simple copy input to output + +#pragma once + +#include "gpu/effect.h" +#include "gpu/uniform_helper.h" + +class PassthroughEffect : public Effect { + public: + PassthroughEffect(const GpuContext& ctx, const std::vector& inputs, + const std::vector& outputs); + + void render(WGPUCommandEncoder encoder, const UniformsSequenceParams& params, + NodeRegistry& nodes) override; + + private: + WGPURenderPipeline pipeline_; + WGPUBindGroup bind_group_; + WGPUSampler sampler_; + UniformBuffer uniforms_buffer_; +}; diff --git a/src/effects/passthrough_effect_v2.cc b/src/effects/passthrough_effect_v2.cc deleted file mode 100644 index 38bb63a..0000000 --- a/src/effects/passthrough_effect_v2.cc +++ /dev/null @@ -1,81 +0,0 @@ -// Passthrough effect v2 implementation - -#include "effects/passthrough_effect_v2.h" -#include "gpu/post_process_helper.h" -#include "gpu/shaders.h" - -PassthroughEffectV2::PassthroughEffectV2(const GpuContext& ctx, - const std::vector& inputs, - const std::vector& outputs) - : EffectV2(ctx, inputs, outputs), pipeline_(nullptr), bind_group_(nullptr), - sampler_(nullptr) { - // Init uniform buffer - uniforms_buffer_.init(ctx_.device); - // Create pipeline (simple version without effect params) - pipeline_ = create_post_process_pipeline_simple(ctx_.device, WGPUTextureFormat_RGBA8Unorm, - passthrough_v2_shader_wgsl); - - // Create sampler - WGPUSamplerDescriptor sampler_desc = {}; - sampler_desc.addressModeU = WGPUAddressMode_ClampToEdge; - sampler_desc.addressModeV = WGPUAddressMode_ClampToEdge; - sampler_desc.addressModeW = WGPUAddressMode_ClampToEdge; - sampler_desc.magFilter = WGPUFilterMode_Linear; - sampler_desc.minFilter = WGPUFilterMode_Linear; - sampler_desc.mipmapFilter = WGPUMipmapFilterMode_Nearest; - sampler_desc.maxAnisotropy = 1; - sampler_ = wgpuDeviceCreateSampler(ctx_.device, &sampler_desc); -} - -void PassthroughEffectV2::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 uniforms - uniforms_buffer_.update(ctx_.queue, params); - - // 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_; - 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_, 0); - bg_desc.entryCount = 3; - bg_desc.entries = entries; - - if (bind_group_) { - wgpuBindGroupRelease(bind_group_); - } - bind_group_ = wgpuDeviceCreateBindGroup(ctx_.device, &bg_desc); - - // Render pass - 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 = {}; - 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 - wgpuRenderPassEncoderEnd(pass); - wgpuRenderPassEncoderRelease(pass); -} diff --git a/src/effects/passthrough_effect_v2.h b/src/effects/passthrough_effect_v2.h deleted file mode 100644 index a272b87..0000000 --- a/src/effects/passthrough_effect_v2.h +++ /dev/null @@ -1,21 +0,0 @@ -// Passthrough effect v2 - simple copy input to output - -#pragma once - -#include "gpu/effect_v2.h" -#include "gpu/uniform_helper.h" - -class PassthroughEffectV2 : public EffectV2 { - public: - PassthroughEffectV2(const GpuContext& ctx, const std::vector& inputs, - const std::vector& outputs); - - void render(WGPUCommandEncoder encoder, const UniformsSequenceParams& params, - NodeRegistry& nodes) override; - - private: - WGPURenderPipeline pipeline_; - WGPUBindGroup bind_group_; - WGPUSampler sampler_; - UniformBuffer uniforms_buffer_; -}; diff --git a/src/effects/placeholder_effect.cc b/src/effects/placeholder_effect.cc new file mode 100644 index 0000000..d3308de --- /dev/null +++ b/src/effects/placeholder_effect.cc @@ -0,0 +1,63 @@ +// Placeholder effect v2 implementation - logs TODO warning once + +#include "effects/placeholder_effect.h" +#include "gpu/post_process_helper.h" +#include "gpu/shaders.h" +#include + +PlaceholderEffect::PlaceholderEffect(const GpuContext& ctx, + const std::vector& inputs, + const std::vector& outputs, + const char* placeholder_name) + : Effect(ctx, inputs, outputs), pipeline_(nullptr), bind_group_(nullptr), + sampler_(nullptr), name_(placeholder_name) { + // Log once on construction + fprintf(stderr, "TODO: %s not yet ported to v2, using passthrough\n", name_); + + uniforms_buffer_.init(ctx_.device); + pipeline_ = create_post_process_pipeline(ctx_.device, WGPUTextureFormat_RGBA8Unorm, + passthrough_v2_shader_wgsl); + + WGPUSamplerDescriptor sampler_desc = {}; + sampler_desc.addressModeU = WGPUAddressMode_ClampToEdge; + sampler_desc.addressModeV = WGPUAddressMode_ClampToEdge; + sampler_desc.addressModeW = WGPUAddressMode_ClampToEdge; + sampler_desc.magFilter = WGPUFilterMode_Linear; + sampler_desc.minFilter = WGPUFilterMode_Linear; + sampler_desc.mipmapFilter = WGPUMipmapFilterMode_Nearest; + sampler_desc.maxAnisotropy = 1; + sampler_ = wgpuDeviceCreateSampler(ctx_.device, &sampler_desc); +} + +void PlaceholderEffect::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 = {}; + 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/placeholder_effect.h b/src/effects/placeholder_effect.h new file mode 100644 index 0000000..f7917ab --- /dev/null +++ b/src/effects/placeholder_effect.h @@ -0,0 +1,25 @@ +// Placeholder effect v2 - temporary passthrough for unported effects +// TODO: Replace with actual effect implementations + +#pragma once + +#include "gpu/effect.h" +#include "gpu/uniform_helper.h" + +class PlaceholderEffect : public Effect { + public: + PlaceholderEffect(const GpuContext& ctx, const std::vector& inputs, + const std::vector& outputs, + const char* placeholder_name = "UnknownEffect"); + + void render(WGPUCommandEncoder encoder, const UniformsSequenceParams& params, + NodeRegistry& nodes) override; + + private: + WGPURenderPipeline pipeline_; + WGPUBindGroup bind_group_; + WGPUSampler sampler_; + UniformBuffer uniforms_buffer_; + const char* name_; +}; + diff --git a/src/effects/placeholder_effect_v2.cc b/src/effects/placeholder_effect_v2.cc deleted file mode 100644 index d1fa212..0000000 --- a/src/effects/placeholder_effect_v2.cc +++ /dev/null @@ -1,63 +0,0 @@ -// Placeholder effect v2 implementation - logs TODO warning once - -#include "effects/placeholder_effect_v2.h" -#include "gpu/post_process_helper.h" -#include "gpu/shaders.h" -#include - -PlaceholderEffectV2::PlaceholderEffectV2(const GpuContext& ctx, - const std::vector& inputs, - const std::vector& outputs, - const char* placeholder_name) - : EffectV2(ctx, inputs, outputs), pipeline_(nullptr), bind_group_(nullptr), - sampler_(nullptr), name_(placeholder_name) { - // Log once on construction - fprintf(stderr, "TODO: %s not yet ported to v2, using passthrough\n", name_); - - uniforms_buffer_.init(ctx_.device); - pipeline_ = create_post_process_pipeline(ctx_.device, WGPUTextureFormat_RGBA8Unorm, - passthrough_v2_shader_wgsl); - - WGPUSamplerDescriptor sampler_desc = {}; - sampler_desc.addressModeU = WGPUAddressMode_ClampToEdge; - sampler_desc.addressModeV = WGPUAddressMode_ClampToEdge; - sampler_desc.addressModeW = WGPUAddressMode_ClampToEdge; - sampler_desc.magFilter = WGPUFilterMode_Linear; - sampler_desc.minFilter = WGPUFilterMode_Linear; - sampler_desc.mipmapFilter = WGPUMipmapFilterMode_Nearest; - sampler_desc.maxAnisotropy = 1; - sampler_ = wgpuDeviceCreateSampler(ctx_.device, &sampler_desc); -} - -void PlaceholderEffectV2::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 = {}; - 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/placeholder_effect_v2.h b/src/effects/placeholder_effect_v2.h deleted file mode 100644 index aa9ed75..0000000 --- a/src/effects/placeholder_effect_v2.h +++ /dev/null @@ -1,24 +0,0 @@ -// Placeholder effect v2 - temporary passthrough for unported effects -// TODO: Replace with actual effect implementations - -#pragma once - -#include "gpu/effect_v2.h" -#include "gpu/uniform_helper.h" - -class PlaceholderEffectV2 : public EffectV2 { - public: - PlaceholderEffectV2(const GpuContext& ctx, const std::vector& inputs, - const std::vector& outputs, - const char* placeholder_name = "UnknownEffect"); - - void render(WGPUCommandEncoder encoder, const UniformsSequenceParams& params, - NodeRegistry& nodes) override; - - private: - WGPURenderPipeline pipeline_; - WGPUBindGroup bind_group_; - WGPUSampler sampler_; - UniformBuffer uniforms_buffer_; - const char* name_; -}; diff --git a/src/effects/rotating_cube_effect.cc b/src/effects/rotating_cube_effect.cc new file mode 100644 index 0000000..3f1d445 --- /dev/null +++ b/src/effects/rotating_cube_effect.cc @@ -0,0 +1,186 @@ +// This file is part of the 64k demo project. +// It implements RotatingCubeEffect (simplified v2 port). + +#include "effects/rotating_cube_effect.h" +#include "gpu/bind_group_builder.h" +#include "gpu/gpu.h" +#include "gpu/shaders.h" + +RotatingCubeEffect::RotatingCubeEffect(const GpuContext& ctx, + const std::vector& inputs, + const std::vector& outputs) + : Effect(ctx, inputs, outputs), depth_node_(outputs[0] + "_depth") { + // Create uniform buffers + uniform_buffer_ = + gpu_create_buffer(ctx_.device, sizeof(Uniforms), + WGPUBufferUsage_Uniform | WGPUBufferUsage_CopyDst); + object_buffer_ = + gpu_create_buffer(ctx_.device, sizeof(ObjectData), + WGPUBufferUsage_Storage | WGPUBufferUsage_CopyDst); + + // Create bind group layout + WGPUBindGroupLayout bgl = + BindGroupLayoutBuilder() + .uniform(0, WGPUShaderStage_Vertex | WGPUShaderStage_Fragment, + sizeof(Uniforms)) + .storage(1, WGPUShaderStage_Vertex | WGPUShaderStage_Fragment, + sizeof(ObjectData)) + .build(ctx_.device); + + const WGPUBindGroupLayout bgls[] = {bgl}; + const WGPUPipelineLayoutDescriptor pl_desc = { + .bindGroupLayoutCount = 1, + .bindGroupLayouts = bgls, + }; + WGPUPipelineLayout pipeline_layout = + wgpuDeviceCreatePipelineLayout(ctx_.device, &pl_desc); + + // Load shader (TODO: create rotating_cube_v2.wgsl) + WGPUShaderSourceWGSL wgsl_src = {}; + wgsl_src.chain.sType = WGPUSType_ShaderSourceWGSL; + wgsl_src.code = str_view(rotating_cube_v2_wgsl); + + WGPUShaderModuleDescriptor shader_desc = {}; + shader_desc.nextInChain = &wgsl_src.chain; + WGPUShaderModule shader_module = + wgpuDeviceCreateShaderModule(ctx_.device, &shader_desc); + + const WGPUColorTargetState color_target = { + .format = WGPUTextureFormat_RGBA8Unorm, + .writeMask = WGPUColorWriteMask_All, + }; + + const WGPUDepthStencilState depth_stencil = { + .format = WGPUTextureFormat_Depth24Plus, + .depthWriteEnabled = WGPUOptionalBool_True, + .depthCompare = WGPUCompareFunction_Less, + }; + + WGPUFragmentState fragment = {}; + fragment.module = shader_module; + fragment.entryPoint = str_view("fs_main"); + fragment.targetCount = 1; + fragment.targets = &color_target; + + WGPURenderPipelineDescriptor pipeline_desc = {}; + pipeline_desc.layout = pipeline_layout; + pipeline_desc.vertex.module = shader_module; + pipeline_desc.vertex.entryPoint = str_view("vs_main"); + pipeline_desc.primitive.topology = WGPUPrimitiveTopology_TriangleList; + pipeline_desc.primitive.cullMode = WGPUCullMode_Back; + pipeline_desc.depthStencil = &depth_stencil; + pipeline_desc.multisample.count = 1; + pipeline_desc.multisample.mask = 0xFFFFFFFF; + pipeline_desc.fragment = &fragment; + + pipeline_ = wgpuDeviceCreateRenderPipeline(ctx_.device, &pipeline_desc); + wgpuShaderModuleRelease(shader_module); + wgpuPipelineLayoutRelease(pipeline_layout); + + // Create bind group + const WGPUBindGroupEntry entries[] = { + {.binding = 0, + .buffer = uniform_buffer_.buffer, + .size = sizeof(Uniforms)}, + {.binding = 1, + .buffer = object_buffer_.buffer, + .size = sizeof(ObjectData)}, + }; + + const WGPUBindGroupDescriptor bg_desc = { + .layout = bgl, + .entryCount = 2, + .entries = entries, + }; + bind_group_ = wgpuDeviceCreateBindGroup(ctx_.device, &bg_desc); + wgpuBindGroupLayoutRelease(bgl); +} + +RotatingCubeEffect::~RotatingCubeEffect() { + if (bind_group_) + wgpuBindGroupRelease(bind_group_); + if (pipeline_) + wgpuRenderPipelineRelease(pipeline_); +} + +void RotatingCubeEffect::declare_nodes(NodeRegistry& registry) { + // Declare depth buffer node + registry.declare_node(depth_node_, NodeType::DEPTH24, -1, -1); +} + +void RotatingCubeEffect::render(WGPUCommandEncoder encoder, + const UniformsSequenceParams& params, + NodeRegistry& nodes) { + rotation_ += 0.016f * 1.5f; + + // Camera setup + const vec3 camera_pos = vec3(0, 0, 5); + const vec3 target = vec3(0, 0, 0); + const vec3 up = vec3(0, 1, 0); + + const mat4 view = mat4::look_at(camera_pos, target, up); + const float fov = 60.0f * 3.14159f / 180.0f; + const mat4 proj = mat4::perspective(fov, params.aspect_ratio, 0.1f, 100.0f); + const mat4 view_proj = proj * view; + + // Cube transform + const quat rot = quat::from_axis(vec3(0.3f, 1.0f, 0.2f), rotation_); + const mat4 T = mat4::translate(vec3(0, 0, 0)); + const mat4 R = rot.to_mat(); + const mat4 S = mat4::scale(vec3(1.5f, 1.5f, 1.5f)); + const mat4 model = T * R * S; + + // Update uniforms + const Uniforms uniforms = { + .view_proj = view_proj, + .inv_view_proj = view_proj.inverse(), + .camera_pos_time = vec4(camera_pos.x, camera_pos.y, camera_pos.z, params.time), + .params = vec4(1.0f, 0.0f, 0.0f, 0.0f), + .resolution = params.resolution, + .aspect_ratio = params.aspect_ratio, + }; + + const ObjectData obj_data = { + .model = model, + .inv_model = model.inverse(), + .color = vec4(0.8f, 0.4f, 0.2f, 1.0f), + .params = vec4(1.0f, 0.0f, 0.0f, 0.0f), + }; + + wgpuQueueWriteBuffer(ctx_.queue, uniform_buffer_.buffer, 0, &uniforms, + sizeof(Uniforms)); + wgpuQueueWriteBuffer(ctx_.queue, object_buffer_.buffer, 0, &obj_data, + sizeof(ObjectData)); + + // Get output views + WGPUTextureView color_view = nodes.get_view(output_nodes_[0]); + WGPUTextureView depth_view = nodes.get_view(depth_node_); + + // Render pass with depth + WGPURenderPassColorAttachment color_attachment = { + .view = color_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}}; + + 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}; + + WGPURenderPassEncoder pass = wgpuCommandEncoderBeginRenderPass(encoder, &pass_desc); + wgpuRenderPassEncoderSetPipeline(pass, pipeline_); + wgpuRenderPassEncoderSetBindGroup(pass, 0, bind_group_, 0, nullptr); + wgpuRenderPassEncoderDraw(pass, 36, 1, 0, 0); // 36 vertices for cube + wgpuRenderPassEncoderEnd(pass); + wgpuRenderPassEncoderRelease(pass); +} diff --git a/src/effects/rotating_cube_effect.h b/src/effects/rotating_cube_effect.h new file mode 100644 index 0000000..1c0155a --- /dev/null +++ b/src/effects/rotating_cube_effect.h @@ -0,0 +1,49 @@ +// This file is part of the 64k demo project. +// It declares RotatingCubeEffect (simplified v2 port). + +#pragma once + +#include "gpu/effect.h" +#include "gpu/gpu.h" +#include "gpu/uniform_helper.h" +#include "util/mini_math.h" + +class RotatingCubeEffect : public Effect { + public: + RotatingCubeEffect(const GpuContext& ctx, + const std::vector& inputs, + const std::vector& outputs); + ~RotatingCubeEffect() override; + + void declare_nodes(NodeRegistry& registry) override; + void render(WGPUCommandEncoder encoder, + const UniformsSequenceParams& params, + NodeRegistry& nodes) override; + + private: + struct Uniforms { + mat4 view_proj; + mat4 inv_view_proj; + vec4 camera_pos_time; + vec4 params; + vec2 resolution; + float aspect_ratio; + float _pad; + }; + static_assert(sizeof(Uniforms) == 176, "Uniforms size mismatch"); + + struct ObjectData { + mat4 model; + mat4 inv_model; + vec4 color; + vec4 params; + }; + static_assert(sizeof(ObjectData) == 160, "ObjectData size mismatch"); + + WGPURenderPipeline pipeline_ = nullptr; + WGPUBindGroup bind_group_ = nullptr; + GpuBuffer uniform_buffer_; + GpuBuffer object_buffer_; + float rotation_ = 0.0f; + std::string depth_node_; +}; diff --git a/src/effects/rotating_cube_effect_v2.cc b/src/effects/rotating_cube_effect_v2.cc deleted file mode 100644 index 1a28cad..0000000 --- a/src/effects/rotating_cube_effect_v2.cc +++ /dev/null @@ -1,186 +0,0 @@ -// This file is part of the 64k demo project. -// It implements RotatingCubeEffectV2 (simplified v2 port). - -#include "effects/rotating_cube_effect_v2.h" -#include "gpu/bind_group_builder.h" -#include "gpu/gpu.h" -#include "gpu/shaders.h" - -RotatingCubeEffectV2::RotatingCubeEffectV2(const GpuContext& ctx, - const std::vector& inputs, - const std::vector& outputs) - : EffectV2(ctx, inputs, outputs), depth_node_(outputs[0] + "_depth") { - // Create uniform buffers - uniform_buffer_ = - gpu_create_buffer(ctx_.device, sizeof(Uniforms), - WGPUBufferUsage_Uniform | WGPUBufferUsage_CopyDst); - object_buffer_ = - gpu_create_buffer(ctx_.device, sizeof(ObjectData), - WGPUBufferUsage_Storage | WGPUBufferUsage_CopyDst); - - // Create bind group layout - WGPUBindGroupLayout bgl = - BindGroupLayoutBuilder() - .uniform(0, WGPUShaderStage_Vertex | WGPUShaderStage_Fragment, - sizeof(Uniforms)) - .storage(1, WGPUShaderStage_Vertex | WGPUShaderStage_Fragment, - sizeof(ObjectData)) - .build(ctx_.device); - - const WGPUBindGroupLayout bgls[] = {bgl}; - const WGPUPipelineLayoutDescriptor pl_desc = { - .bindGroupLayoutCount = 1, - .bindGroupLayouts = bgls, - }; - WGPUPipelineLayout pipeline_layout = - wgpuDeviceCreatePipelineLayout(ctx_.device, &pl_desc); - - // Load shader (TODO: create rotating_cube_v2.wgsl) - WGPUShaderSourceWGSL wgsl_src = {}; - wgsl_src.chain.sType = WGPUSType_ShaderSourceWGSL; - wgsl_src.code = str_view(rotating_cube_v2_wgsl); - - WGPUShaderModuleDescriptor shader_desc = {}; - shader_desc.nextInChain = &wgsl_src.chain; - WGPUShaderModule shader_module = - wgpuDeviceCreateShaderModule(ctx_.device, &shader_desc); - - const WGPUColorTargetState color_target = { - .format = WGPUTextureFormat_RGBA8Unorm, - .writeMask = WGPUColorWriteMask_All, - }; - - const WGPUDepthStencilState depth_stencil = { - .format = WGPUTextureFormat_Depth24Plus, - .depthWriteEnabled = WGPUOptionalBool_True, - .depthCompare = WGPUCompareFunction_Less, - }; - - WGPUFragmentState fragment = {}; - fragment.module = shader_module; - fragment.entryPoint = str_view("fs_main"); - fragment.targetCount = 1; - fragment.targets = &color_target; - - WGPURenderPipelineDescriptor pipeline_desc = {}; - pipeline_desc.layout = pipeline_layout; - pipeline_desc.vertex.module = shader_module; - pipeline_desc.vertex.entryPoint = str_view("vs_main"); - pipeline_desc.primitive.topology = WGPUPrimitiveTopology_TriangleList; - pipeline_desc.primitive.cullMode = WGPUCullMode_Back; - pipeline_desc.depthStencil = &depth_stencil; - pipeline_desc.multisample.count = 1; - pipeline_desc.multisample.mask = 0xFFFFFFFF; - pipeline_desc.fragment = &fragment; - - pipeline_ = wgpuDeviceCreateRenderPipeline(ctx_.device, &pipeline_desc); - wgpuShaderModuleRelease(shader_module); - wgpuPipelineLayoutRelease(pipeline_layout); - - // Create bind group - const WGPUBindGroupEntry entries[] = { - {.binding = 0, - .buffer = uniform_buffer_.buffer, - .size = sizeof(Uniforms)}, - {.binding = 1, - .buffer = object_buffer_.buffer, - .size = sizeof(ObjectData)}, - }; - - const WGPUBindGroupDescriptor bg_desc = { - .layout = bgl, - .entryCount = 2, - .entries = entries, - }; - bind_group_ = wgpuDeviceCreateBindGroup(ctx_.device, &bg_desc); - wgpuBindGroupLayoutRelease(bgl); -} - -RotatingCubeEffectV2::~RotatingCubeEffectV2() { - if (bind_group_) - wgpuBindGroupRelease(bind_group_); - if (pipeline_) - wgpuRenderPipelineRelease(pipeline_); -} - -void RotatingCubeEffectV2::declare_nodes(NodeRegistry& registry) { - // Declare depth buffer node - registry.declare_node(depth_node_, NodeType::DEPTH24, -1, -1); -} - -void RotatingCubeEffectV2::render(WGPUCommandEncoder encoder, - const UniformsSequenceParams& params, - NodeRegistry& nodes) { - rotation_ += 0.016f * 1.5f; - - // Camera setup - const vec3 camera_pos = vec3(0, 0, 5); - const vec3 target = vec3(0, 0, 0); - const vec3 up = vec3(0, 1, 0); - - const mat4 view = mat4::look_at(camera_pos, target, up); - const float fov = 60.0f * 3.14159f / 180.0f; - const mat4 proj = mat4::perspective(fov, params.aspect_ratio, 0.1f, 100.0f); - const mat4 view_proj = proj * view; - - // Cube transform - const quat rot = quat::from_axis(vec3(0.3f, 1.0f, 0.2f), rotation_); - const mat4 T = mat4::translate(vec3(0, 0, 0)); - const mat4 R = rot.to_mat(); - const mat4 S = mat4::scale(vec3(1.5f, 1.5f, 1.5f)); - const mat4 model = T * R * S; - - // Update uniforms - const Uniforms uniforms = { - .view_proj = view_proj, - .inv_view_proj = view_proj.inverse(), - .camera_pos_time = vec4(camera_pos.x, camera_pos.y, camera_pos.z, params.time), - .params = vec4(1.0f, 0.0f, 0.0f, 0.0f), - .resolution = params.resolution, - .aspect_ratio = params.aspect_ratio, - }; - - const ObjectData obj_data = { - .model = model, - .inv_model = model.inverse(), - .color = vec4(0.8f, 0.4f, 0.2f, 1.0f), - .params = vec4(1.0f, 0.0f, 0.0f, 0.0f), - }; - - wgpuQueueWriteBuffer(ctx_.queue, uniform_buffer_.buffer, 0, &uniforms, - sizeof(Uniforms)); - wgpuQueueWriteBuffer(ctx_.queue, object_buffer_.buffer, 0, &obj_data, - sizeof(ObjectData)); - - // Get output views - WGPUTextureView color_view = nodes.get_view(output_nodes_[0]); - WGPUTextureView depth_view = nodes.get_view(depth_node_); - - // Render pass with depth - WGPURenderPassColorAttachment color_attachment = { - .view = color_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}}; - - 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}; - - WGPURenderPassEncoder pass = wgpuCommandEncoderBeginRenderPass(encoder, &pass_desc); - wgpuRenderPassEncoderSetPipeline(pass, pipeline_); - wgpuRenderPassEncoderSetBindGroup(pass, 0, bind_group_, 0, nullptr); - wgpuRenderPassEncoderDraw(pass, 36, 1, 0, 0); // 36 vertices for cube - wgpuRenderPassEncoderEnd(pass); - wgpuRenderPassEncoderRelease(pass); -} diff --git a/src/effects/rotating_cube_effect_v2.h b/src/effects/rotating_cube_effect_v2.h deleted file mode 100644 index 19ef410..0000000 --- a/src/effects/rotating_cube_effect_v2.h +++ /dev/null @@ -1,49 +0,0 @@ -// This file is part of the 64k demo project. -// It declares RotatingCubeEffectV2 (simplified v2 port). - -#pragma once - -#include "gpu/effect_v2.h" -#include "gpu/gpu.h" -#include "gpu/uniform_helper.h" -#include "util/mini_math.h" - -class RotatingCubeEffectV2 : public EffectV2 { - public: - RotatingCubeEffectV2(const GpuContext& ctx, - const std::vector& inputs, - const std::vector& outputs); - ~RotatingCubeEffectV2() override; - - void declare_nodes(NodeRegistry& registry) override; - void render(WGPUCommandEncoder encoder, - const UniformsSequenceParams& params, - NodeRegistry& nodes) override; - - private: - struct Uniforms { - mat4 view_proj; - mat4 inv_view_proj; - vec4 camera_pos_time; - vec4 params; - vec2 resolution; - float aspect_ratio; - float _pad; - }; - static_assert(sizeof(Uniforms) == 176, "Uniforms size mismatch"); - - struct ObjectData { - mat4 model; - mat4 inv_model; - vec4 color; - vec4 params; - }; - static_assert(sizeof(ObjectData) == 160, "ObjectData size mismatch"); - - WGPURenderPipeline pipeline_ = nullptr; - WGPUBindGroup bind_group_ = nullptr; - GpuBuffer uniform_buffer_; - GpuBuffer object_buffer_; - float rotation_ = 0.0f; - std::string depth_node_; -}; diff --git a/src/gpu/demo_effects.h b/src/gpu/demo_effects.h index beccd46..b837ffe 100644 --- a/src/gpu/demo_effects.h +++ b/src/gpu/demo_effects.h @@ -9,21 +9,21 @@ #include "3d/scene.h" // Base effect classes (v2) -#include "gpu/effect_v2.h" +#include "gpu/effect.h" #include "gpu/post_process_helper.h" -#include "gpu/sequence_v2.h" +#include "gpu/sequence.h" #include "gpu/shaders.h" #include "gpu/texture_manager.h" #include "gpu/uniform_helper.h" // Individual Effect Headers (v2) -#include "effects/gaussian_blur_effect_v2.h" -#include "effects/heptagon_effect_v2.h" -#include "effects/hybrid3_d_effect_v2.h" -#include "effects/particles_effect_v2.h" -#include "effects/passthrough_effect_v2.h" -#include "effects/placeholder_effect_v2.h" -#include "effects/rotating_cube_effect_v2.h" +#include "effects/gaussian_blur_effect.h" +#include "effects/heptagon_effect.h" +#include "effects/hybrid3_d_effect.h" +#include "effects/particles_effect.h" +#include "effects/passthrough_effect.h" +#include "effects/placeholder_effect.h" +#include "effects/rotating_cube_effect.h" // TODO: Port CNN effects to v2 // #include "../../cnn_v1/src/cnn_v1_effect.h" // #include "../../cnn_v2/src/cnn_v2_effect.h" @@ -31,7 +31,7 @@ #include // Auto-generated functions from sequence compiler v2 -// See generated/timeline_v2.h for: +// See generated/timeline.h for: // - InitializeV2Sequences() // - GetActiveV2Sequence() // - RenderV2Timeline() diff --git a/src/gpu/effect.cc b/src/gpu/effect.cc new file mode 100644 index 0000000..e4d3a90 --- /dev/null +++ b/src/gpu/effect.cc @@ -0,0 +1,11 @@ +// Effect implementation + +#include "gpu/effect.h" +#include "util/fatal_error.h" + +Effect::Effect(const GpuContext& ctx, const std::vector& inputs, + const std::vector& outputs) + : ctx_(ctx), input_nodes_(inputs), output_nodes_(outputs) { + FATAL_CHECK(!inputs.empty(), "Effect must have at least one input\n"); + FATAL_CHECK(!outputs.empty(), "Effect must have at least one output\n"); +} diff --git a/src/gpu/effect.h b/src/gpu/effect.h new file mode 100644 index 0000000..d40e750 --- /dev/null +++ b/src/gpu/effect.h @@ -0,0 +1,50 @@ +// Effect: Base class for effects with multi-input/multi-output support + +#ifndef EFFECT_H +#define EFFECT_H +#pragma once + +#include "gpu/gpu.h" +#include "gpu/sequence.h" +#include +#include + +class NodeRegistry; + +class Effect { + public: + Effect(const GpuContext& ctx, const std::vector& inputs, + const std::vector& outputs); + virtual ~Effect() = default; + + // Optional: Declare temporary nodes (e.g., multi-pass intermediate buffers) + virtual void declare_nodes(NodeRegistry& registry) { + (void)registry; + } + + // Render effect (multi-input/multi-output) + virtual void render(WGPUCommandEncoder encoder, + const UniformsSequenceParams& params, + NodeRegistry& nodes) = 0; + + // Resize notification + virtual void resize(int width, int height) { + width_ = width; + height_ = height; + } + + const std::vector& input_nodes() const { + return input_nodes_; + } + const std::vector& output_nodes() const { + return output_nodes_; + } + + protected: + const GpuContext& ctx_; + std::vector input_nodes_; + std::vector output_nodes_; + int width_ = 1280; + int height_ = 720; +}; +#endif // EFFECT_H diff --git a/src/gpu/effect_v2.cc b/src/gpu/effect_v2.cc deleted file mode 100644 index 7ecdfbd..0000000 --- a/src/gpu/effect_v2.cc +++ /dev/null @@ -1,11 +0,0 @@ -// EffectV2 implementation - -#include "gpu/effect_v2.h" -#include "util/fatal_error.h" - -EffectV2::EffectV2(const GpuContext& ctx, const std::vector& inputs, - const std::vector& outputs) - : ctx_(ctx), input_nodes_(inputs), output_nodes_(outputs) { - FATAL_CHECK(!inputs.empty(), "Effect must have at least one input\n"); - FATAL_CHECK(!outputs.empty(), "Effect must have at least one output\n"); -} diff --git a/src/gpu/effect_v2.h b/src/gpu/effect_v2.h deleted file mode 100644 index 0d4e18d..0000000 --- a/src/gpu/effect_v2.h +++ /dev/null @@ -1,47 +0,0 @@ -// EffectV2: Base class for v2 effects with multi-input/multi-output support - -#pragma once - -#include "gpu/gpu.h" -#include "gpu/sequence_v2.h" -#include -#include - -class NodeRegistry; - -class EffectV2 { - public: - EffectV2(const GpuContext& ctx, const std::vector& inputs, - const std::vector& outputs); - virtual ~EffectV2() = default; - - // Optional: Declare temporary nodes (e.g., multi-pass intermediate buffers) - virtual void declare_nodes(NodeRegistry& registry) { - (void)registry; - } - - // Render effect (multi-input/multi-output) - virtual void render(WGPUCommandEncoder encoder, - const UniformsSequenceParams& params, - NodeRegistry& nodes) = 0; - - // Resize notification - virtual void resize(int width, int height) { - width_ = width; - height_ = height; - } - - const std::vector& input_nodes() const { - return input_nodes_; - } - const std::vector& output_nodes() const { - return output_nodes_; - } - - protected: - const GpuContext& ctx_; - std::vector input_nodes_; - std::vector output_nodes_; - int width_ = 1280; - int height_ = 720; -}; diff --git a/src/gpu/gpu.cc b/src/gpu/gpu.cc index 647833c..805e555 100644 --- a/src/gpu/gpu.cc +++ b/src/gpu/gpu.cc @@ -3,7 +3,7 @@ // Driven by audio peaks for synchronized visual effects. #include "gpu.h" -#include "generated/timeline_v2.h" +#include "generated/timeline.h" #include "gpu/shader_composer.h" #include "gpu/shaders.h" #include "platform/platform.h" diff --git a/src/gpu/sequence.cc b/src/gpu/sequence.cc new file mode 100644 index 0000000..d0a925f --- /dev/null +++ b/src/gpu/sequence.cc @@ -0,0 +1,235 @@ +// Sequence implementation + +#include "gpu/sequence.h" +#include "gpu/effect.h" +#include "util/fatal_error.h" +#include + +// NodeRegistry implementation + +NodeRegistry::NodeRegistry(WGPUDevice device, int default_width, + int default_height) + : device_(device), default_width_(default_width), + default_height_(default_height) { + // Create source/sink nodes with actual textures + Node source_node = {}; + source_node.type = NodeType::U8X4_NORM; + source_node.width = default_width; + source_node.height = default_height; + create_texture(source_node); + nodes_["source"] = source_node; + + Node sink_node = {}; + sink_node.type = NodeType::U8X4_NORM; + sink_node.width = default_width; + sink_node.height = default_height; + create_texture(sink_node); + nodes_["sink"] = sink_node; +} + +NodeRegistry::~NodeRegistry() { + for (auto& [name, node] : nodes_) { + if (node.view) { + wgpuTextureViewRelease(node.view); + } + for (auto& mip_view : node.mip_views) { + wgpuTextureViewRelease(mip_view); + } + if (node.texture) { + wgpuTextureRelease(node.texture); + } + } +} + +void NodeRegistry::declare_node(const std::string& name, NodeType type, + int width, int height) { + FATAL_CHECK(nodes_.find(name) == nodes_.end(), + "Node already declared: %s\n", name.c_str()); + + if (width <= 0) + width = default_width_; + if (height <= 0) + height = default_height_; + + Node node; + node.type = type; + node.width = width; + node.height = height; + create_texture(node); + + nodes_[name] = node; +} + +void NodeRegistry::declare_aliased_node(const std::string& name, + const std::string& alias_of) { + FATAL_CHECK(nodes_.find(alias_of) != nodes_.end(), + "Alias target does not exist: %s\n", alias_of.c_str()); + FATAL_CHECK(aliases_.find(name) == aliases_.end(), "Alias already exists: %s\n", + name.c_str()); + + aliases_[name] = alias_of; +} + +WGPUTextureView NodeRegistry::get_view(const std::string& name) { + // Check aliases first + auto alias_it = aliases_.find(name); + if (alias_it != aliases_.end()) { + return get_view(alias_it->second); + } + + auto it = nodes_.find(name); + FATAL_CHECK(it != nodes_.end(), "Node not found: %s\n", name.c_str()); + return it->second.view; +} + +std::vector +NodeRegistry::get_output_views(const std::vector& names) { + std::vector views; + views.reserve(names.size()); + for (const auto& name : names) { + views.push_back(get_view(name)); + } + return views; +} + +void NodeRegistry::resize(int width, int height) { + default_width_ = width; + default_height_ = height; + + for (auto& [name, node] : nodes_) { + // Release old texture + if (node.view) { + wgpuTextureViewRelease(node.view); + } + for (auto& mip_view : node.mip_views) { + wgpuTextureViewRelease(mip_view); + } + if (node.texture) { + wgpuTextureRelease(node.texture); + } + + // Recreate with new dimensions + node.width = width; + node.height = height; + create_texture(node); + } +} + +bool NodeRegistry::has_node(const std::string& name) const { + return nodes_.find(name) != nodes_.end() || + aliases_.find(name) != aliases_.end(); +} + +void NodeRegistry::set_external_view(const std::string& name, + WGPUTextureView view) { + // Register external view (texture not owned by registry) + Node node = {}; + node.view = view; + node.texture = nullptr; // Not owned + nodes_[name] = node; +} + +void NodeRegistry::create_texture(Node& node) { + WGPUTextureFormat format; + WGPUTextureUsage usage; + + switch (node.type) { + case NodeType::U8X4_NORM: + format = WGPUTextureFormat_RGBA8Unorm; + usage = WGPUTextureUsage_RenderAttachment | WGPUTextureUsage_TextureBinding; + break; + case NodeType::F32X4: + format = WGPUTextureFormat_RGBA32Float; + usage = WGPUTextureUsage_RenderAttachment | WGPUTextureUsage_TextureBinding; + break; + case NodeType::F16X8: + format = WGPUTextureFormat_RGBA16Float; // WebGPU doesn't have 8-channel, use RGBA16 + usage = WGPUTextureUsage_RenderAttachment | WGPUTextureUsage_TextureBinding; + break; + case NodeType::DEPTH24: + format = WGPUTextureFormat_Depth24Plus; + usage = WGPUTextureUsage_RenderAttachment; + break; + case NodeType::COMPUTE_F32: + format = WGPUTextureFormat_RGBA32Float; + usage = WGPUTextureUsage_StorageBinding | WGPUTextureUsage_TextureBinding; + break; + } + + WGPUTextureDescriptor desc = {}; + desc.usage = usage; + desc.dimension = WGPUTextureDimension_2D; + desc.size = {static_cast(node.width), + static_cast(node.height), 1}; + desc.format = format; + desc.mipLevelCount = 1; + desc.sampleCount = 1; + + node.texture = wgpuDeviceCreateTexture(device_, &desc); + FATAL_CHECK(node.texture != nullptr, "Failed to create texture\n"); + + WGPUTextureViewDescriptor view_desc = {}; + view_desc.format = format; + view_desc.dimension = WGPUTextureViewDimension_2D; + view_desc.baseMipLevel = 0; + view_desc.mipLevelCount = 1; + view_desc.baseArrayLayer = 0; + view_desc.arrayLayerCount = 1; + view_desc.aspect = (node.type == NodeType::DEPTH24) + ? WGPUTextureAspect_DepthOnly + : WGPUTextureAspect_All; + + node.view = wgpuTextureCreateView(node.texture, &view_desc); + FATAL_CHECK(node.view != nullptr, "Failed to create texture view\n"); +} + +// Sequence implementation + +Sequence::Sequence(const GpuContext& ctx, int width, int height) + : ctx_(ctx), width_(width), height_(height), + nodes_(ctx.device, width, height) { + uniforms_buffer_.init(ctx.device); +} + +void Sequence::preprocess(float seq_time, float beat_time, float beat_phase, + float audio_intensity) { + params_.resolution = {static_cast(width_), static_cast(height_)}; + params_.aspect_ratio = + static_cast(width_) / static_cast(height_); + params_.time = seq_time; + params_.beat_time = beat_time; + params_.beat_phase = beat_phase; + params_.audio_intensity = audio_intensity; + params_._pad = 0.0f; + + uniforms_buffer_.update(ctx_.queue, params_); +} + +void Sequence::postprocess(WGPUCommandEncoder encoder) { + (void)encoder; + // Default: No-op (last effect writes to sink directly) +} + +void Sequence::render_effects(WGPUCommandEncoder encoder) { + // Execute DAG in topological order (pre-sorted by compiler) + for (const auto& dag_node : effect_dag_) { + dag_node.effect->render(encoder, params_, nodes_); + } +} + +void Sequence::resize(int width, int height) { + width_ = width; + height_ = height; + nodes_.resize(width, height); + + // Notify effects + for (auto& dag_node : effect_dag_) { + dag_node.effect->resize(width, height); + } +} + +void Sequence::init_effect_nodes() { + for (auto& dag_node : effect_dag_) { + dag_node.effect->declare_nodes(nodes_); + } +} diff --git a/src/gpu/sequence.h b/src/gpu/sequence.h new file mode 100644 index 0000000..a33dedb --- /dev/null +++ b/src/gpu/sequence.h @@ -0,0 +1,131 @@ +// Sequence: Explicit node system with DAG effect routing +// DAG-based effect routing with ping-pong optimization + +#ifndef SEQUENCE_H +#define SEQUENCE_H +#pragma once + +#include "gpu/gpu.h" +#include "gpu/uniform_helper.h" +#include "util/mini_math.h" +#include +#include +#include +#include + +class Effect; + +enum class NodeType { + U8X4_NORM, // RGBAu8 normalized (0-1) - default Source/Sink + F32X4, // RGBA float32 + F16X8, // 8-channel float16 + DEPTH24, // Depth buffer + COMPUTE_F32, // Compute buffer +}; + +struct Node { + NodeType type; + int width; + int height; + WGPUTexture texture; + WGPUTextureView view; + std::vector mip_views; // For multi-target render +}; + +struct UniformsSequenceParams { + vec2 resolution; + float aspect_ratio; + float time; // Per-sequence relative time + float beat_time; // Musical beats + float beat_phase; // Fractional beat 0.0-1.0 + float audio_intensity; + float _pad; +}; +static_assert(sizeof(UniformsSequenceParams) == 32, + "UniformsSequenceParams must be 32 bytes for WGSL alignment"); + +class NodeRegistry { + public: + NodeRegistry(WGPUDevice device, int default_width, int default_height); + ~NodeRegistry(); + + // Declare new node with explicit type/dimensions + void declare_node(const std::string& name, NodeType type, int width, + int height); + + // Declare aliased node (ping-pong optimization) + void declare_aliased_node(const std::string& name, + const std::string& alias_of); + + // Retrieve views + WGPUTextureView get_view(const std::string& name); + std::vector + get_output_views(const std::vector& names); + + // Resize all nodes + void resize(int width, int height); + + // Check if node exists + bool has_node(const std::string& name) const; + + // Register external view (for source/sink managed externally) + void set_external_view(const std::string& name, WGPUTextureView view); + + private: + WGPUDevice device_; + int default_width_; + int default_height_; + std::map nodes_; + std::map aliases_; // name -> backing node name + + void create_texture(Node& node); +}; + +struct EffectDAGNode { + std::shared_ptr effect; + std::vector input_nodes; + std::vector output_nodes; + int execution_order; // Topologically sorted +}; + +class Sequence { + public: + Sequence(const GpuContext& ctx, int width, int height); + virtual ~Sequence() = default; + + // Virtual methods (most sequences use defaults) + virtual void preprocess(float seq_time, float beat_time, float beat_phase, + float audio_intensity); + virtual void postprocess(WGPUCommandEncoder encoder); + virtual void render_effects(WGPUCommandEncoder encoder); + + void resize(int width, int height); + + // Initialize effect nodes (call at end of subclass constructor) + void init_effect_nodes(); + + // Set surface texture view for rendering (sink node) + void set_sink_view(WGPUTextureView view) { + nodes_.set_external_view("sink", view); + } + + // Set source texture view (input framebuffer) + void set_source_view(WGPUTextureView view) { + nodes_.set_external_view("source", view); + } + + // Test accessor + const std::vector& get_effect_dag() const { + return effect_dag_; + } + + protected: + const GpuContext& ctx_; + int width_; + int height_; + NodeRegistry nodes_; + std::vector effect_dag_; + UniformsSequenceParams params_; + UniformBuffer uniforms_buffer_; +}; +#endif // SEQUENCE_H diff --git a/src/gpu/sequence_v2.cc b/src/gpu/sequence_v2.cc deleted file mode 100644 index 3912849..0000000 --- a/src/gpu/sequence_v2.cc +++ /dev/null @@ -1,235 +0,0 @@ -// Sequence v2 implementation - -#include "gpu/sequence_v2.h" -#include "gpu/effect_v2.h" -#include "util/fatal_error.h" -#include - -// NodeRegistry implementation - -NodeRegistry::NodeRegistry(WGPUDevice device, int default_width, - int default_height) - : device_(device), default_width_(default_width), - default_height_(default_height) { - // Create source/sink nodes with actual textures - Node source_node = {}; - source_node.type = NodeType::U8X4_NORM; - source_node.width = default_width; - source_node.height = default_height; - create_texture(source_node); - nodes_["source"] = source_node; - - Node sink_node = {}; - sink_node.type = NodeType::U8X4_NORM; - sink_node.width = default_width; - sink_node.height = default_height; - create_texture(sink_node); - nodes_["sink"] = sink_node; -} - -NodeRegistry::~NodeRegistry() { - for (auto& [name, node] : nodes_) { - if (node.view) { - wgpuTextureViewRelease(node.view); - } - for (auto& mip_view : node.mip_views) { - wgpuTextureViewRelease(mip_view); - } - if (node.texture) { - wgpuTextureRelease(node.texture); - } - } -} - -void NodeRegistry::declare_node(const std::string& name, NodeType type, - int width, int height) { - FATAL_CHECK(nodes_.find(name) == nodes_.end(), - "Node already declared: %s\n", name.c_str()); - - if (width <= 0) - width = default_width_; - if (height <= 0) - height = default_height_; - - Node node; - node.type = type; - node.width = width; - node.height = height; - create_texture(node); - - nodes_[name] = node; -} - -void NodeRegistry::declare_aliased_node(const std::string& name, - const std::string& alias_of) { - FATAL_CHECK(nodes_.find(alias_of) != nodes_.end(), - "Alias target does not exist: %s\n", alias_of.c_str()); - FATAL_CHECK(aliases_.find(name) == aliases_.end(), "Alias already exists: %s\n", - name.c_str()); - - aliases_[name] = alias_of; -} - -WGPUTextureView NodeRegistry::get_view(const std::string& name) { - // Check aliases first - auto alias_it = aliases_.find(name); - if (alias_it != aliases_.end()) { - return get_view(alias_it->second); - } - - auto it = nodes_.find(name); - FATAL_CHECK(it != nodes_.end(), "Node not found: %s\n", name.c_str()); - return it->second.view; -} - -std::vector -NodeRegistry::get_output_views(const std::vector& names) { - std::vector views; - views.reserve(names.size()); - for (const auto& name : names) { - views.push_back(get_view(name)); - } - return views; -} - -void NodeRegistry::resize(int width, int height) { - default_width_ = width; - default_height_ = height; - - for (auto& [name, node] : nodes_) { - // Release old texture - if (node.view) { - wgpuTextureViewRelease(node.view); - } - for (auto& mip_view : node.mip_views) { - wgpuTextureViewRelease(mip_view); - } - if (node.texture) { - wgpuTextureRelease(node.texture); - } - - // Recreate with new dimensions - node.width = width; - node.height = height; - create_texture(node); - } -} - -bool NodeRegistry::has_node(const std::string& name) const { - return nodes_.find(name) != nodes_.end() || - aliases_.find(name) != aliases_.end(); -} - -void NodeRegistry::set_external_view(const std::string& name, - WGPUTextureView view) { - // Register external view (texture not owned by registry) - Node node = {}; - node.view = view; - node.texture = nullptr; // Not owned - nodes_[name] = node; -} - -void NodeRegistry::create_texture(Node& node) { - WGPUTextureFormat format; - WGPUTextureUsage usage; - - switch (node.type) { - case NodeType::U8X4_NORM: - format = WGPUTextureFormat_RGBA8Unorm; - usage = WGPUTextureUsage_RenderAttachment | WGPUTextureUsage_TextureBinding; - break; - case NodeType::F32X4: - format = WGPUTextureFormat_RGBA32Float; - usage = WGPUTextureUsage_RenderAttachment | WGPUTextureUsage_TextureBinding; - break; - case NodeType::F16X8: - format = WGPUTextureFormat_RGBA16Float; // WebGPU doesn't have 8-channel, use RGBA16 - usage = WGPUTextureUsage_RenderAttachment | WGPUTextureUsage_TextureBinding; - break; - case NodeType::DEPTH24: - format = WGPUTextureFormat_Depth24Plus; - usage = WGPUTextureUsage_RenderAttachment; - break; - case NodeType::COMPUTE_F32: - format = WGPUTextureFormat_RGBA32Float; - usage = WGPUTextureUsage_StorageBinding | WGPUTextureUsage_TextureBinding; - break; - } - - WGPUTextureDescriptor desc = {}; - desc.usage = usage; - desc.dimension = WGPUTextureDimension_2D; - desc.size = {static_cast(node.width), - static_cast(node.height), 1}; - desc.format = format; - desc.mipLevelCount = 1; - desc.sampleCount = 1; - - node.texture = wgpuDeviceCreateTexture(device_, &desc); - FATAL_CHECK(node.texture != nullptr, "Failed to create texture\n"); - - WGPUTextureViewDescriptor view_desc = {}; - view_desc.format = format; - view_desc.dimension = WGPUTextureViewDimension_2D; - view_desc.baseMipLevel = 0; - view_desc.mipLevelCount = 1; - view_desc.baseArrayLayer = 0; - view_desc.arrayLayerCount = 1; - view_desc.aspect = (node.type == NodeType::DEPTH24) - ? WGPUTextureAspect_DepthOnly - : WGPUTextureAspect_All; - - node.view = wgpuTextureCreateView(node.texture, &view_desc); - FATAL_CHECK(node.view != nullptr, "Failed to create texture view\n"); -} - -// SequenceV2 implementation - -SequenceV2::SequenceV2(const GpuContext& ctx, int width, int height) - : ctx_(ctx), width_(width), height_(height), - nodes_(ctx.device, width, height) { - uniforms_buffer_.init(ctx.device); -} - -void SequenceV2::preprocess(float seq_time, float beat_time, float beat_phase, - float audio_intensity) { - params_.resolution = {static_cast(width_), static_cast(height_)}; - params_.aspect_ratio = - static_cast(width_) / static_cast(height_); - params_.time = seq_time; - params_.beat_time = beat_time; - params_.beat_phase = beat_phase; - params_.audio_intensity = audio_intensity; - params_._pad = 0.0f; - - uniforms_buffer_.update(ctx_.queue, params_); -} - -void SequenceV2::postprocess(WGPUCommandEncoder encoder) { - (void)encoder; - // Default: No-op (last effect writes to sink directly) -} - -void SequenceV2::render_effects(WGPUCommandEncoder encoder) { - // Execute DAG in topological order (pre-sorted by compiler) - for (const auto& dag_node : effect_dag_) { - dag_node.effect->render(encoder, params_, nodes_); - } -} - -void SequenceV2::resize(int width, int height) { - width_ = width; - height_ = height; - nodes_.resize(width, height); - - // Notify effects - for (auto& dag_node : effect_dag_) { - dag_node.effect->resize(width, height); - } -} - -void SequenceV2::init_effect_nodes() { - for (auto& dag_node : effect_dag_) { - dag_node.effect->declare_nodes(nodes_); - } -} diff --git a/src/gpu/sequence_v2.h b/src/gpu/sequence_v2.h deleted file mode 100644 index 2197a82..0000000 --- a/src/gpu/sequence_v2.h +++ /dev/null @@ -1,128 +0,0 @@ -// Sequence v2: Explicit node system with DAG effect routing -// Replaces implicit framebuffer ping-pong with compile-time optimized nodes - -#pragma once - -#include "gpu/gpu.h" -#include "gpu/uniform_helper.h" -#include "util/mini_math.h" -#include -#include -#include -#include - -class EffectV2; - -enum class NodeType { - U8X4_NORM, // RGBAu8 normalized (0-1) - default Source/Sink - F32X4, // RGBA float32 - F16X8, // 8-channel float16 - DEPTH24, // Depth buffer - COMPUTE_F32, // Compute buffer -}; - -struct Node { - NodeType type; - int width; - int height; - WGPUTexture texture; - WGPUTextureView view; - std::vector mip_views; // For multi-target render -}; - -struct UniformsSequenceParams { - vec2 resolution; - float aspect_ratio; - float time; // Per-sequence relative time - float beat_time; // Musical beats - float beat_phase; // Fractional beat 0.0-1.0 - float audio_intensity; - float _pad; -}; -static_assert(sizeof(UniformsSequenceParams) == 32, - "UniformsSequenceParams must be 32 bytes for WGSL alignment"); - -class NodeRegistry { - public: - NodeRegistry(WGPUDevice device, int default_width, int default_height); - ~NodeRegistry(); - - // Declare new node with explicit type/dimensions - void declare_node(const std::string& name, NodeType type, int width, - int height); - - // Declare aliased node (ping-pong optimization) - void declare_aliased_node(const std::string& name, - const std::string& alias_of); - - // Retrieve views - WGPUTextureView get_view(const std::string& name); - std::vector - get_output_views(const std::vector& names); - - // Resize all nodes - void resize(int width, int height); - - // Check if node exists - bool has_node(const std::string& name) const; - - // Register external view (for source/sink managed externally) - void set_external_view(const std::string& name, WGPUTextureView view); - - private: - WGPUDevice device_; - int default_width_; - int default_height_; - std::map nodes_; - std::map aliases_; // name -> backing node name - - void create_texture(Node& node); -}; - -struct EffectDAGNode { - std::shared_ptr effect; - std::vector input_nodes; - std::vector output_nodes; - int execution_order; // Topologically sorted -}; - -class SequenceV2 { - public: - SequenceV2(const GpuContext& ctx, int width, int height); - virtual ~SequenceV2() = default; - - // Virtual methods (most sequences use defaults) - virtual void preprocess(float seq_time, float beat_time, float beat_phase, - float audio_intensity); - virtual void postprocess(WGPUCommandEncoder encoder); - virtual void render_effects(WGPUCommandEncoder encoder); - - void resize(int width, int height); - - // Initialize effect nodes (call at end of subclass constructor) - void init_effect_nodes(); - - // Set surface texture view for rendering (sink node) - void set_sink_view(WGPUTextureView view) { - nodes_.set_external_view("sink", view); - } - - // Set source texture view (input framebuffer) - void set_source_view(WGPUTextureView view) { - nodes_.set_external_view("source", view); - } - - // Test accessor - const std::vector& get_effect_dag() const { - return effect_dag_; - } - - protected: - const GpuContext& ctx_; - int width_; - int height_; - NodeRegistry nodes_; - std::vector effect_dag_; - UniformsSequenceParams params_; - UniformBuffer uniforms_buffer_; -}; diff --git a/src/tests/gpu/test_demo_effects.cc b/src/tests/gpu/test_demo_effects.cc index 02aee78..7bba831 100644 --- a/src/tests/gpu/test_demo_effects.cc +++ b/src/tests/gpu/test_demo_effects.cc @@ -14,7 +14,7 @@ #include // Helper: Test v2 effect construction -static int test_effect_v2(const char* name, std::shared_ptr effect) { +static int test_effect(const char* name, std::shared_ptr effect) { fprintf(stdout, " Testing %s...\n", name); if (!effect) { @@ -27,7 +27,7 @@ static int test_effect_v2(const char* name, std::shared_ptr effect) { } // Test all available v2 effects -static void test_v2_effects() { +static void test_effects() { fprintf(stdout, "Testing V2 effects...\n"); WebGPUTestFixture fixture; @@ -36,40 +36,40 @@ static void test_v2_effects() { return; } - std::vector>> effects = { - {"PassthroughEffectV2", - std::make_shared( + std::vector>> effects = { + {"PassthroughEffect", + std::make_shared( fixture.ctx(), std::vector{"source"}, std::vector{"sink"})}, - {"GaussianBlurEffectV2", - std::make_shared( + {"GaussianBlurEffect", + std::make_shared( fixture.ctx(), std::vector{"source"}, std::vector{"sink"})}, - {"PlaceholderEffectV2", - std::make_shared( + {"PlaceholderEffect", + std::make_shared( fixture.ctx(), std::vector{"source"}, std::vector{"sink"})}, - {"HeptagonEffectV2", - std::make_shared( + {"HeptagonEffect", + std::make_shared( fixture.ctx(), std::vector{"source"}, std::vector{"sink"})}, - {"ParticlesEffectV2", - std::make_shared( + {"ParticlesEffect", + std::make_shared( fixture.ctx(), std::vector{"source"}, std::vector{"sink"})}, - {"RotatingCubeEffectV2", - std::make_shared( + {"RotatingCubeEffect", + std::make_shared( fixture.ctx(), std::vector{"source"}, std::vector{"sink"})}, - {"Hybrid3DEffectV2", - std::make_shared( + {"Hybrid3DEffect", + std::make_shared( fixture.ctx(), std::vector{"source"}, std::vector{"sink"})}, }; int passed = 0; for (const auto& [name, effect] : effects) { - passed += test_effect_v2(name, effect); + passed += test_effect(name, effect); } fprintf(stdout, " ✓ %d/%zu V2 effects tested\n", passed, effects.size()); @@ -81,7 +81,7 @@ int main() { extern void InitShaderComposer(); InitShaderComposer(); - test_v2_effects(); + test_effects(); fprintf(stdout, "=== All Tests Passed ===\n"); return 0; diff --git a/src/tests/gpu/test_effect_base.cc b/src/tests/gpu/test_effect_base.cc index ddccad4..8b0e6b2 100644 --- a/src/tests/gpu/test_effect_base.cc +++ b/src/tests/gpu/test_effect_base.cc @@ -1,13 +1,13 @@ // This file is part of the 64k demo project. -// It tests the EffectV2/SequenceV2 lifecycle using headless rendering. +// It tests the Effect/Sequence lifecycle using headless rendering. // Verifies effect initialization and basic rendering. #include "../common/effect_test_helpers.h" #include "../common/offscreen_render_target.h" #include "../common/webgpu_test_fixture.h" -#include "effects/passthrough_effect_v2.h" -#include "gpu/effect_v2.h" -#include "gpu/sequence_v2.h" +#include "effects/passthrough_effect.h" +#include "gpu/effect.h" +#include "gpu/sequence.h" #include #include #include @@ -80,19 +80,19 @@ static void test_effect_construction() { return; } - // Create PassthroughEffectV2 (simple effect) - auto effect = std::make_shared( + // Create PassthroughEffect (simple effect) + auto effect = std::make_shared( fixture.ctx(), std::vector{"source"}, std::vector{"sink"}); assert(effect != nullptr && "Effect should be constructed"); - fprintf(stdout, " ✓ PassthroughEffectV2 constructed\n"); + fprintf(stdout, " ✓ PassthroughEffect constructed\n"); } // Test 4: Effect added to sequence DAG static void test_effect_in_sequence() { - fprintf(stdout, "Testing effect in SequenceV2 DAG...\n"); + fprintf(stdout, "Testing effect in Sequence DAG...\n"); WebGPUTestFixture fixture; if (!fixture.init()) { @@ -101,10 +101,10 @@ static void test_effect_in_sequence() { } // Create minimal sequence with one effect - class TestSequence : public SequenceV2 { + class TestSequence : public Sequence { public: - TestSequence(const GpuContext& ctx, int w, int h) : SequenceV2(ctx, w, h) { - auto effect = std::make_shared( + TestSequence(const GpuContext& ctx, int w, int h) : Sequence(ctx, w, h) { + auto effect = std::make_shared( ctx, std::vector{"source"}, std::vector{"sink"}); @@ -133,10 +133,10 @@ static void test_sequence_render() { OffscreenRenderTarget target(fixture.instance(), fixture.device(), 256, 256); - class TestSequence : public SequenceV2 { + class TestSequence : public Sequence { public: - TestSequence(const GpuContext& ctx, int w, int h) : SequenceV2(ctx, w, h) { - auto effect = std::make_shared( + TestSequence(const GpuContext& ctx, int w, int h) : Sequence(ctx, w, h) { + auto effect = std::make_shared( ctx, std::vector{"source"}, std::vector{"sink"}); @@ -176,15 +176,15 @@ static void test_sequence_time_params() { return; } - class TestSequence : public SequenceV2 { + class TestSequence : public Sequence { public: - TestSequence(const GpuContext& ctx, int w, int h) : SequenceV2(ctx, w, h) { + TestSequence(const GpuContext& ctx, int w, int h) : Sequence(ctx, w, h) { init_effect_nodes(); } void preprocess(float seq_time, float beat_time, float beat_phase, float audio_intensity) override { - SequenceV2::preprocess(seq_time, beat_time, beat_phase, audio_intensity); + Sequence::preprocess(seq_time, beat_time, beat_phase, audio_intensity); last_time = seq_time; } diff --git a/src/tests/gpu/test_sequence.cc b/src/tests/gpu/test_sequence.cc new file mode 100644 index 0000000..337381a --- /dev/null +++ b/src/tests/gpu/test_sequence.cc @@ -0,0 +1,184 @@ +// Test file for Sequence v2 system +// Phase 1: Foundation tests (NodeRegistry, Sequence base class) + +#include "gpu/sequence.h" +#include "gpu/effect.h" +#include "tests/common/webgpu_test_fixture.h" +#include +#include + +// Simple test effect for DAG execution +class TestEffect : public Effect { + public: + TestEffect(const GpuContext& ctx, const std::vector& inputs, + const std::vector& outputs) + : Effect(ctx, inputs, outputs), render_called_(false) { + } + + void render(WGPUCommandEncoder encoder, const UniformsSequenceParams& params, + NodeRegistry& nodes) override { + (void)encoder; + (void)params; + (void)nodes; + render_called_ = true; + } + + bool was_render_called() const { + return render_called_; + } + + private: + bool render_called_; +}; + +// Test: NodeRegistry basic allocation +void test_node_registry_basic() { + WebGPUTestFixture fixture; + if (!fixture.init()) { + fprintf(stderr, "Skipping test_node_registry_basic (no GPU)\n"); + return; + } + + NodeRegistry registry(fixture.ctx().device, 1280, 720); + + // Declare node + registry.declare_node("test_node", NodeType::U8X4_NORM, 1280, 720); + + // Verify node exists + assert(registry.has_node("test_node")); + + // Get view (should not crash) + WGPUTextureView view = registry.get_view("test_node"); + assert(view != nullptr); + + printf("PASS: NodeRegistry basic allocation\n"); +} + +// Test: NodeRegistry aliased nodes (ping-pong optimization) +void test_node_registry_aliased() { + WebGPUTestFixture fixture; + if (!fixture.init()) { + fprintf(stderr, "Skipping test_node_registry_aliased (no GPU)\n"); + return; + } + + NodeRegistry registry(fixture.ctx().device, 1280, 720); + + // Declare backing node + registry.declare_node("frame_a", NodeType::U8X4_NORM, 1280, 720); + + // Declare aliased node + registry.declare_aliased_node("frame_b", "frame_a"); + + // Both should resolve to same view + WGPUTextureView view_a = registry.get_view("frame_a"); + WGPUTextureView view_b = registry.get_view("frame_b"); + assert(view_a == view_b); + + printf("PASS: NodeRegistry aliased nodes\n"); +} + +// Test: NodeRegistry multi-output views +void test_node_registry_multi_output() { + WebGPUTestFixture fixture; + if (!fixture.init()) { + fprintf(stderr, "Skipping test_node_registry_multi_output (no GPU)\n"); + return; + } + + NodeRegistry registry(fixture.ctx().device, 1280, 720); + + // Declare multiple nodes + registry.declare_node("output1", NodeType::U8X4_NORM, 1280, 720); + registry.declare_node("output2", NodeType::F32X4, 1280, 720); + + // Get multiple views + std::vector names = {"output1", "output2"}; + std::vector views = registry.get_output_views(names); + + assert(views.size() == 2); + assert(views[0] != nullptr); + assert(views[1] != nullptr); + + printf("PASS: NodeRegistry multi-output views\n"); +} + +// Test: Sequence default preprocess +void test_sequence_v2_preprocess() { + WebGPUTestFixture fixture; + if (!fixture.init()) { + fprintf(stderr, "Skipping test_sequence_v2_preprocess (no GPU)\n"); + return; + } + + Sequence seq(fixture.ctx(), 1280, 720); + + // Call preprocess with test values + seq.preprocess(1.0f, 4.0f, 0.5f, 0.8f); + + // No crash = success (params updated internally) + printf("PASS: Sequence preprocess\n"); +} + +// Test: Sequence DAG execution +void test_sequence_v2_dag_execution() { + WebGPUTestFixture fixture; + if (!fixture.init()) { + fprintf(stderr, "Skipping test_sequence_v2_dag_execution (no GPU)\n"); + return; + } + + // Create sequence + class TestSequence : public Sequence { + public: + TestSequence(const GpuContext& ctx) + : Sequence(ctx, 1280, 720), + effect1_(std::make_shared(ctx, std::vector{"source"}, + std::vector{"temp"})), + effect2_(std::make_shared(ctx, std::vector{"temp"}, + std::vector{"sink"})) { + // Build DAG (2 effects in sequence) + effect_dag_.push_back( + {effect1_, {"source"}, {"temp"}, 0}); + effect_dag_.push_back( + {effect2_, {"temp"}, {"sink"}, 1}); + } + + std::shared_ptr effect1_; + std::shared_ptr effect2_; + }; + + TestSequence seq(fixture.ctx()); + + // Create command encoder + WGPUCommandEncoderDescriptor enc_desc = {}; + WGPUCommandEncoder encoder = + wgpuDeviceCreateCommandEncoder(fixture.ctx().device, &enc_desc); + + // Execute DAG + seq.render_effects(encoder); + + // Verify both effects called + assert(seq.effect1_->was_render_called()); + assert(seq.effect2_->was_render_called()); + + // Cleanup + WGPUCommandBuffer cmd = wgpuCommandEncoderFinish(encoder, nullptr); + wgpuCommandBufferRelease(cmd); + wgpuCommandEncoderRelease(encoder); + + printf("PASS: Sequence DAG execution\n"); +} + +int main() { + printf("Running Sequence v2 tests...\n"); + + test_node_registry_basic(); + test_node_registry_aliased(); + test_node_registry_multi_output(); + test_sequence_v2_preprocess(); + test_sequence_v2_dag_execution(); + + printf("All Sequence v2 tests passed!\n"); + return 0; +} diff --git a/src/tests/gpu/test_sequence_e2e.cc b/src/tests/gpu/test_sequence_e2e.cc new file mode 100644 index 0000000..91a8da2 --- /dev/null +++ b/src/tests/gpu/test_sequence_e2e.cc @@ -0,0 +1,112 @@ +// End-to-end test for Sequence v2 system +// Tests compiler output instantiation and execution + +#include "gpu/sequence.h" +#include "gpu/effect.h" +#include "effects/gaussian_blur_effect.h" +#include "effects/heptagon_effect.h" +#include "effects/passthrough_effect.h" +#include "gpu/shaders.h" +#include "tests/common/webgpu_test_fixture.h" +#include +#include + +// Manually transcribed generated sequence (simulates compiler output) +// Simple 2-effect chain to validate DAG execution +class SimpleTestSequence : public Sequence { + public: + SimpleTestSequence(const GpuContext& ctx, int width, int height) + : Sequence(ctx, width, height) { + // Node declarations (source/sink already created by NodeRegistry) + nodes_.declare_node("temp", NodeType::U8X4_NORM, width_, height_); + + // Effect DAG construction (2 effects: source->temp->sink) + effect_dag_.push_back({ + .effect = std::make_shared(ctx, + std::vector{"source"}, + std::vector{"temp"}), + .input_nodes = {"source"}, + .output_nodes = {"temp"}, + .execution_order = 0 + }); + effect_dag_.push_back({ + .effect = std::make_shared(ctx, + std::vector{"temp"}, + std::vector{"sink"}), + .input_nodes = {"temp"}, + .output_nodes = {"sink"}, + .execution_order = 1 + }); + } +}; + +// Test: Instantiate and run v2 sequence +void test_sequence_v2_instantiation() { + WebGPUTestFixture fixture; + if (!fixture.init()) { + fprintf(stderr, "Skipping test (no GPU)\n"); + return; + } + + // Initialize shader composer with snippets + InitShaderComposer(); + + // Create sequence + SimpleTestSequence seq(fixture.ctx(), 1280, 720); + + // Preprocess + seq.preprocess(0.0f, 0.0f, 0.0f, 0.0f); + + // Create command encoder + WGPUCommandEncoderDescriptor enc_desc = {}; + WGPUCommandEncoder encoder = + wgpuDeviceCreateCommandEncoder(fixture.ctx().device, &enc_desc); + + // Execute DAG (should not crash) + seq.render_effects(encoder); + + // Cleanup + WGPUCommandBuffer cmd = wgpuCommandEncoderFinish(encoder, nullptr); + wgpuCommandBufferRelease(cmd); + wgpuCommandEncoderRelease(encoder); + + printf("PASS: Sequence v2 instantiation and execution\n"); +} + +// Test: Verify DAG execution order +void test_dag_execution_order() { + WebGPUTestFixture fixture; + if (!fixture.init()) { + fprintf(stderr, "Skipping test (no GPU)\n"); + return; + } + + // Initialize shader composer with snippets + InitShaderComposer(); + + SimpleTestSequence seq(fixture.ctx(), 1280, 720); + + // Verify effects are in correct order + const auto& dag = seq.get_effect_dag(); + assert(dag.size() == 2); + assert(dag[0].execution_order == 0); + assert(dag[1].execution_order == 1); + + // Verify node routing + assert(dag[0].input_nodes[0] == "source"); + assert(dag[0].output_nodes[0] == "temp"); + assert(dag[1].input_nodes[0] == "temp"); + assert(dag[1].output_nodes[0] == "sink"); + + printf("PASS: DAG execution order validated\n"); +} + +int main() { + printf("Running Sequence v2 end-to-end tests...\n"); + + test_sequence_v2_instantiation(); + test_dag_execution_order(); + + printf("All Sequence v2 e2e tests passed!\n"); + return 0; +} diff --git a/src/tests/gpu/test_sequence_v2.cc b/src/tests/gpu/test_sequence_v2.cc deleted file mode 100644 index 54b544e..0000000 --- a/src/tests/gpu/test_sequence_v2.cc +++ /dev/null @@ -1,184 +0,0 @@ -// Test file for Sequence v2 system -// Phase 1: Foundation tests (NodeRegistry, SequenceV2 base class) - -#include "gpu/sequence_v2.h" -#include "gpu/effect_v2.h" -#include "tests/common/webgpu_test_fixture.h" -#include -#include - -// Simple test effect for DAG execution -class TestEffectV2 : public EffectV2 { - public: - TestEffectV2(const GpuContext& ctx, const std::vector& inputs, - const std::vector& outputs) - : EffectV2(ctx, inputs, outputs), render_called_(false) { - } - - void render(WGPUCommandEncoder encoder, const UniformsSequenceParams& params, - NodeRegistry& nodes) override { - (void)encoder; - (void)params; - (void)nodes; - render_called_ = true; - } - - bool was_render_called() const { - return render_called_; - } - - private: - bool render_called_; -}; - -// Test: NodeRegistry basic allocation -void test_node_registry_basic() { - WebGPUTestFixture fixture; - if (!fixture.init()) { - fprintf(stderr, "Skipping test_node_registry_basic (no GPU)\n"); - return; - } - - NodeRegistry registry(fixture.ctx().device, 1280, 720); - - // Declare node - registry.declare_node("test_node", NodeType::U8X4_NORM, 1280, 720); - - // Verify node exists - assert(registry.has_node("test_node")); - - // Get view (should not crash) - WGPUTextureView view = registry.get_view("test_node"); - assert(view != nullptr); - - printf("PASS: NodeRegistry basic allocation\n"); -} - -// Test: NodeRegistry aliased nodes (ping-pong optimization) -void test_node_registry_aliased() { - WebGPUTestFixture fixture; - if (!fixture.init()) { - fprintf(stderr, "Skipping test_node_registry_aliased (no GPU)\n"); - return; - } - - NodeRegistry registry(fixture.ctx().device, 1280, 720); - - // Declare backing node - registry.declare_node("frame_a", NodeType::U8X4_NORM, 1280, 720); - - // Declare aliased node - registry.declare_aliased_node("frame_b", "frame_a"); - - // Both should resolve to same view - WGPUTextureView view_a = registry.get_view("frame_a"); - WGPUTextureView view_b = registry.get_view("frame_b"); - assert(view_a == view_b); - - printf("PASS: NodeRegistry aliased nodes\n"); -} - -// Test: NodeRegistry multi-output views -void test_node_registry_multi_output() { - WebGPUTestFixture fixture; - if (!fixture.init()) { - fprintf(stderr, "Skipping test_node_registry_multi_output (no GPU)\n"); - return; - } - - NodeRegistry registry(fixture.ctx().device, 1280, 720); - - // Declare multiple nodes - registry.declare_node("output1", NodeType::U8X4_NORM, 1280, 720); - registry.declare_node("output2", NodeType::F32X4, 1280, 720); - - // Get multiple views - std::vector names = {"output1", "output2"}; - std::vector views = registry.get_output_views(names); - - assert(views.size() == 2); - assert(views[0] != nullptr); - assert(views[1] != nullptr); - - printf("PASS: NodeRegistry multi-output views\n"); -} - -// Test: SequenceV2 default preprocess -void test_sequence_v2_preprocess() { - WebGPUTestFixture fixture; - if (!fixture.init()) { - fprintf(stderr, "Skipping test_sequence_v2_preprocess (no GPU)\n"); - return; - } - - SequenceV2 seq(fixture.ctx(), 1280, 720); - - // Call preprocess with test values - seq.preprocess(1.0f, 4.0f, 0.5f, 0.8f); - - // No crash = success (params updated internally) - printf("PASS: SequenceV2 preprocess\n"); -} - -// Test: SequenceV2 DAG execution -void test_sequence_v2_dag_execution() { - WebGPUTestFixture fixture; - if (!fixture.init()) { - fprintf(stderr, "Skipping test_sequence_v2_dag_execution (no GPU)\n"); - return; - } - - // Create sequence - class TestSequence : public SequenceV2 { - public: - TestSequence(const GpuContext& ctx) - : SequenceV2(ctx, 1280, 720), - effect1_(std::make_shared(ctx, std::vector{"source"}, - std::vector{"temp"})), - effect2_(std::make_shared(ctx, std::vector{"temp"}, - std::vector{"sink"})) { - // Build DAG (2 effects in sequence) - effect_dag_.push_back( - {effect1_, {"source"}, {"temp"}, 0}); - effect_dag_.push_back( - {effect2_, {"temp"}, {"sink"}, 1}); - } - - std::shared_ptr effect1_; - std::shared_ptr effect2_; - }; - - TestSequence seq(fixture.ctx()); - - // Create command encoder - WGPUCommandEncoderDescriptor enc_desc = {}; - WGPUCommandEncoder encoder = - wgpuDeviceCreateCommandEncoder(fixture.ctx().device, &enc_desc); - - // Execute DAG - seq.render_effects(encoder); - - // Verify both effects called - assert(seq.effect1_->was_render_called()); - assert(seq.effect2_->was_render_called()); - - // Cleanup - WGPUCommandBuffer cmd = wgpuCommandEncoderFinish(encoder, nullptr); - wgpuCommandBufferRelease(cmd); - wgpuCommandEncoderRelease(encoder); - - printf("PASS: SequenceV2 DAG execution\n"); -} - -int main() { - printf("Running Sequence v2 tests...\n"); - - test_node_registry_basic(); - test_node_registry_aliased(); - test_node_registry_multi_output(); - test_sequence_v2_preprocess(); - test_sequence_v2_dag_execution(); - - printf("All Sequence v2 tests passed!\n"); - return 0; -} diff --git a/src/tests/gpu/test_sequence_v2_e2e.cc b/src/tests/gpu/test_sequence_v2_e2e.cc deleted file mode 100644 index c015e0b..0000000 --- a/src/tests/gpu/test_sequence_v2_e2e.cc +++ /dev/null @@ -1,112 +0,0 @@ -// End-to-end test for Sequence v2 system -// Tests compiler output instantiation and execution - -#include "gpu/sequence_v2.h" -#include "gpu/effect_v2.h" -#include "effects/gaussian_blur_effect_v2.h" -#include "effects/heptagon_effect_v2.h" -#include "effects/passthrough_effect_v2.h" -#include "gpu/shaders.h" -#include "tests/common/webgpu_test_fixture.h" -#include -#include - -// Manually transcribed generated sequence (simulates compiler output) -// Simple 2-effect chain to validate DAG execution -class SimpleTestSequence : public SequenceV2 { - public: - SimpleTestSequence(const GpuContext& ctx, int width, int height) - : SequenceV2(ctx, width, height) { - // Node declarations (source/sink already created by NodeRegistry) - nodes_.declare_node("temp", NodeType::U8X4_NORM, width_, height_); - - // Effect DAG construction (2 effects: source->temp->sink) - effect_dag_.push_back({ - .effect = std::make_shared(ctx, - std::vector{"source"}, - std::vector{"temp"}), - .input_nodes = {"source"}, - .output_nodes = {"temp"}, - .execution_order = 0 - }); - effect_dag_.push_back({ - .effect = std::make_shared(ctx, - std::vector{"temp"}, - std::vector{"sink"}), - .input_nodes = {"temp"}, - .output_nodes = {"sink"}, - .execution_order = 1 - }); - } -}; - -// Test: Instantiate and run v2 sequence -void test_sequence_v2_instantiation() { - WebGPUTestFixture fixture; - if (!fixture.init()) { - fprintf(stderr, "Skipping test (no GPU)\n"); - return; - } - - // Initialize shader composer with snippets - InitShaderComposer(); - - // Create sequence - SimpleTestSequence seq(fixture.ctx(), 1280, 720); - - // Preprocess - seq.preprocess(0.0f, 0.0f, 0.0f, 0.0f); - - // Create command encoder - WGPUCommandEncoderDescriptor enc_desc = {}; - WGPUCommandEncoder encoder = - wgpuDeviceCreateCommandEncoder(fixture.ctx().device, &enc_desc); - - // Execute DAG (should not crash) - seq.render_effects(encoder); - - // Cleanup - WGPUCommandBuffer cmd = wgpuCommandEncoderFinish(encoder, nullptr); - wgpuCommandBufferRelease(cmd); - wgpuCommandEncoderRelease(encoder); - - printf("PASS: Sequence v2 instantiation and execution\n"); -} - -// Test: Verify DAG execution order -void test_dag_execution_order() { - WebGPUTestFixture fixture; - if (!fixture.init()) { - fprintf(stderr, "Skipping test (no GPU)\n"); - return; - } - - // Initialize shader composer with snippets - InitShaderComposer(); - - SimpleTestSequence seq(fixture.ctx(), 1280, 720); - - // Verify effects are in correct order - const auto& dag = seq.get_effect_dag(); - assert(dag.size() == 2); - assert(dag[0].execution_order == 0); - assert(dag[1].execution_order == 1); - - // Verify node routing - assert(dag[0].input_nodes[0] == "source"); - assert(dag[0].output_nodes[0] == "temp"); - assert(dag[1].input_nodes[0] == "temp"); - assert(dag[1].output_nodes[0] == "sink"); - - printf("PASS: DAG execution order validated\n"); -} - -int main() { - printf("Running Sequence v2 end-to-end tests...\n"); - - test_sequence_v2_instantiation(); - test_dag_execution_order(); - - printf("All Sequence v2 e2e tests passed!\n"); - return 0; -} diff --git a/tools/seq_compiler.py b/tools/seq_compiler.py new file mode 100755 index 0000000..6b72ebd --- /dev/null +++ b/tools/seq_compiler.py @@ -0,0 +1,682 @@ +#!/usr/bin/env python3 +"""Sequence Compiler - DAG-based timeline compiler with ping-pong optimization. + +Converts timeline syntax into optimized C++ Sequence subclasses. +Performs DAG validation, topological sorting, and lifetime analysis. +""" + +import argparse +import os +import re +import sys +from typing import Dict, List, Set, Tuple, Optional + +# Node type enum mapping +NODE_TYPES = { + 'u8x4_norm': 'NodeType::U8X4_NORM', + 'f32x4': 'NodeType::F32X4', + 'f16x8': 'NodeType::F16X8', + 'depth24': 'NodeType::DEPTH24', + 'compute_f32': 'NodeType::COMPUTE_F32', +} + +class NodeDecl: + def __init__(self, name: str, node_type: str): + self.name = name + self.type = node_type + +class EffectDecl: + def __init__(self, class_name: str, inputs: List[str], outputs: List[str], + start: float, end: float, priority: int, params: str): + self.class_name = class_name + self.inputs = inputs + self.outputs = outputs + self.start = start + self.end = end + self.priority = priority + self.params = params + self.execution_order = -1 + +class SequenceDecl: + def __init__(self, name: str, start_time: float, priority: int): + self.name = name + self.start_time = start_time + self.priority = priority + self.nodes: Dict[str, NodeDecl] = {} + self.assets: Set[str] = set() + self.effects: List[EffectDecl] = [] + +def parse_timeline(filename: str) -> List[SequenceDecl]: + """Parse timeline file.""" + sequences = [] + current_seq = None + + with open(filename, 'r') as f: + for line_num, line in enumerate(f, 1): + line = line.strip() + + # Skip comments and empty lines + if not line or line.startswith('#'): + continue + + # BPM directive (ignored for now) + if line.startswith('# BPM'): + continue + + # SEQUENCE start + if line.startswith('SEQUENCE'): + parts = line.split() + start_time = float(parts[1]) + priority = int(parts[2]) + name = ' '.join(parts[3:]).strip('"') if len(parts) > 3 else f"seq_{start_time}" + current_seq = SequenceDecl(name, start_time, priority) + sequences.append(current_seq) + continue + + if not current_seq: + print(f"Error: {filename}:{line_num}: Effect/Node outside SEQUENCE block", file=sys.stderr) + sys.exit(1) + + # NODE declaration + if line.startswith('NODE'): + parts = line.split() + if len(parts) < 3: + print(f"Error: {filename}:{line_num}: NODE requires name and type", file=sys.stderr) + sys.exit(1) + node_name = parts[1] + node_type = parts[2] + if node_type not in NODE_TYPES: + print(f"Error: {filename}:{line_num}: Unknown node type '{node_type}'", file=sys.stderr) + sys.exit(1) + current_seq.nodes[node_name] = NodeDecl(node_name, node_type) + continue + + # ASSET declaration + if line.startswith('ASSET'): + parts = line.split() + if len(parts) < 2: + print(f"Error: {filename}:{line_num}: ASSET requires name", file=sys.stderr) + sys.exit(1) + current_seq.assets.add(parts[1]) + continue + + # EFFECT with routing + if line.startswith('EFFECT'): + # Parse: EFFECT +/=/- ClassName inputs... -> outputs... start end [params...] + match = re.match(r'EFFECT\s+([+\-=])\s+(\w+)\s+(.+)', line) + if not match: + print(f"Error: {filename}:{line_num}: Invalid EFFECT syntax", file=sys.stderr) + sys.exit(1) + + priority_mod = match.group(1) + class_name = match.group(2) + rest = match.group(3) + + # Parse routing: inputs... -> outputs... start end [params] + if '->' not in rest: + print(f"Error: {filename}:{line_num}: EFFECT missing '->' routing", file=sys.stderr) + sys.exit(1) + + before_arrow, after_arrow = rest.split('->', 1) + inputs = before_arrow.strip().split() + + after_parts = after_arrow.strip().split() + # Find where outputs end (look for numeric start time) + outputs = [] + idx = 0 + while idx < len(after_parts): + try: + float(after_parts[idx]) + break + except ValueError: + outputs.append(after_parts[idx]) + idx += 1 + + if idx + 2 > len(after_parts): + print(f"Error: {filename}:{line_num}: EFFECT missing start/end times", file=sys.stderr) + sys.exit(1) + + start_time = float(after_parts[idx]) + end_time = float(after_parts[idx + 1]) + params = ' '.join(after_parts[idx + 2:]) if idx + 2 < len(after_parts) else '' + + # Priority calculation (relative to sequence priority) + if priority_mod == '+': + effect_priority = current_seq.priority + len(current_seq.effects) + elif priority_mod == '=': + effect_priority = current_seq.priority + len(current_seq.effects) - 1 if current_seq.effects else current_seq.priority + else: # '-' + effect_priority = current_seq.priority - 1 + + effect = EffectDecl(class_name, inputs, outputs, start_time, end_time, effect_priority, params) + current_seq.effects.append(effect) + continue + + print(f"Warning: {filename}:{line_num}: Unrecognized line: {line}", file=sys.stderr) + + return sequences + +def validate_dag(seq: SequenceDecl) -> None: + """Validate DAG: check for cycles, missing nodes, connectivity.""" + + # 1. Auto-infer nodes from effects + all_nodes = set(seq.nodes.keys()) + all_nodes.add('source') # Implicit + all_nodes.add('sink') # Implicit + + for effect in seq.effects: + for node in effect.inputs + effect.outputs: + if node not in all_nodes and node not in seq.nodes: + # Auto-infer as u8x4_norm + seq.nodes[node] = NodeDecl(node, 'u8x4_norm') + all_nodes.add(node) + + # 2. Check all referenced nodes exist + for effect in seq.effects: + for node in effect.inputs: + if node not in all_nodes: + print(f"Error: Effect {effect.class_name} references undefined input node '{node}'", file=sys.stderr) + sys.exit(1) + for node in effect.outputs: + if node not in all_nodes: + print(f"Error: Effect {effect.class_name} references undefined output node '{node}'", file=sys.stderr) + sys.exit(1) + + # 3. Check for cycles (DFS on effect graph, not node graph) + effect_visited = {} + for effect in seq.effects: + effect_visited[id(effect)] = 0 # 0=unvisited, 1=visiting, 2=visited + + # Build effect dependency graph + def get_effect_dependencies(effect: EffectDecl) -> List[EffectDecl]: + """Get effects that must execute before this one.""" + deps = [] + effect_idx = seq.effects.index(effect) + + for input_node in effect.inputs: + if input_node == 'source': + continue + # Find LAST effect before this one that produces this input + producer = None + for i in range(effect_idx - 1, -1, -1): + other = seq.effects[i] + if input_node in other.outputs: + producer = other + break + + if producer: + deps.append(producer) + return deps + + def dfs_cycle(effect: EffectDecl) -> bool: + eff_id = id(effect) + if effect_visited[eff_id] == 1: + return True # Back edge = cycle + if effect_visited[eff_id] == 2: + return False + + effect_visited[eff_id] = 1 + for dep in get_effect_dependencies(effect): + if dfs_cycle(dep): + return True + effect_visited[eff_id] = 2 + return False + + for effect in seq.effects: + if dfs_cycle(effect): + print(f"Error: Cycle detected in effect DAG involving effect '{effect.class_name}'", file=sys.stderr) + sys.exit(1) + + # 4. Check connectivity (source must reach sink) + reachable = set(['source']) + changed = True + while changed: + changed = False + for effect in seq.effects: + if any(inp in reachable for inp in effect.inputs): + for out in effect.outputs: + if out not in reachable: + reachable.add(out) + changed = True + + if 'sink' not in reachable: + print(f"Error: No path from 'source' to 'sink' in DAG", file=sys.stderr) + sys.exit(1) + +def topological_sort(seq: SequenceDecl) -> List[EffectDecl]: + """Sort effects in execution order using Kahn's algorithm.""" + + # Build dependency graph + in_degree = {} + for effect in seq.effects: + in_degree[id(effect)] = 0 + + # Count dependencies + node_producers = {} # node -> effect that produces it + for effect in seq.effects: + for output in effect.outputs: + node_producers[output] = effect + + # Calculate in-degrees + for effect in seq.effects: + for input_node in effect.inputs: + if input_node == 'source': + continue + if input_node in node_producers: + in_degree[id(effect)] += 1 + + # Find effects with no dependencies + queue = [eff for eff in seq.effects if in_degree[id(eff)] == 0] + sorted_effects = [] + + while queue: + current = queue.pop(0) + sorted_effects.append(current) + + # Mark outputs as available, decrement downstream dependencies + for output in current.outputs: + for other in seq.effects: + if output in other.inputs and id(other) != id(current): + in_degree[id(other)] -= 1 + if in_degree[id(other)] == 0: + queue.append(other) + + if len(sorted_effects) != len(seq.effects): + print(f"Error: DAG has unreachable effects (disconnected components)", file=sys.stderr) + sys.exit(1) + + # Assign execution order + for idx, effect in enumerate(sorted_effects): + effect.execution_order = idx + + return sorted_effects + +def analyze_lifetimes(seq: SequenceDecl, sorted_effects: List[EffectDecl]) -> Dict[str, Tuple[int, int]]: + """Analyze node lifetimes: (first_use, last_use) execution order indices.""" + + lifetimes = {} + + for effect in sorted_effects: + order = effect.execution_order + + for node in effect.inputs: + if node == 'source': + continue + if node not in lifetimes: + lifetimes[node] = (order, order) + else: + lifetimes[node] = (lifetimes[node][0], order) + + for node in effect.outputs: + if node == 'sink': + continue + if node not in lifetimes: + lifetimes[node] = (order, order) + else: + lifetimes[node] = (min(lifetimes[node][0], order), max(lifetimes[node][1], order)) + + return lifetimes + +def detect_ping_pong(seq: SequenceDecl, sorted_effects: List[EffectDecl]) -> Dict[str, str]: + """Detect ping-pong patterns and return alias map. + + Pattern: Effect i writes A, reads B; Effect i+1 writes B, reads A + Optimization: Alias B -> A (reuse same texture) + """ + + aliases = {} + used_nodes = set() + + # Look for adjacent alternating read/write patterns + for i in range(len(sorted_effects) - 1): + eff1 = sorted_effects[i] + eff2 = sorted_effects[i + 1] + + # Find nodes that alternate + for out1 in eff1.outputs: + if out1 in ['source', 'sink'] or out1 in used_nodes: + continue + + for in1 in eff1.inputs: + if in1 in ['source', 'sink'] or in1 in used_nodes: + continue + + # Check if eff2 writes in1 and reads out1 (alternating) + if in1 in eff2.outputs and out1 in eff2.inputs: + # Classic ping-pong: eff1 (reads in1, writes out1), eff2 (reads out1, writes in1) + # Check no other effects use these nodes + other_uses = False + for j, eff in enumerate(sorted_effects): + if j == i or j == i + 1: + continue + if out1 in eff.inputs + eff.outputs or in1 in eff.inputs + eff.outputs: + other_uses = True + break + + if not other_uses: + # Alias in1 -> out1 (in1 uses same texture as out1) + aliases[in1] = out1 + used_nodes.add(out1) + used_nodes.add(in1) + break + + return aliases + +def generate_cpp(seq: SequenceDecl, sorted_effects: List[EffectDecl], + aliases: Dict[str, str], flatten: bool = False) -> str: + """Generate C++ Sequence subclass.""" + + class_name = seq.name.replace(' ', '_').replace('-', '_') + if not class_name[0].isalpha(): + class_name = 'Seq_' + class_name + class_name += 'Sequence' + + # Generate includes + includes = set() + for effect in seq.effects: + # Convert ClassName to snake_case header + header = re.sub('([A-Z])', r'_\1', effect.class_name).lower().lstrip('_') + if header.endswith('_effect'): + header = header[:-7] # Remove _effect suffix + includes.add(f'#include "effects/{header}_effect.h"') + + cpp = f'''// Generated by seq_compiler.py +// Sequence: {seq.name} + +#include "gpu/sequence.h" +#include "gpu/effect.h" +''' + + for inc in sorted(includes): + cpp += inc + '\n' + + cpp += f''' +class {class_name} : public Sequence {{ + public: + {class_name}(const GpuContext& ctx, int width, int height) + : Sequence(ctx, width, height) {{ +''' + + # Node declarations + cpp += ' // Node declarations\n' + for node_name, node_decl in sorted(seq.nodes.items()): + if node_name in aliases: + # Aliased node + cpp += f' nodes_.declare_aliased_node("{node_name}", "{aliases[node_name]}");\n' + else: + node_type = NODE_TYPES[node_decl.type] + cpp += f' nodes_.declare_node("{node_name}", {node_type}, width_, height_);\n' + + cpp += '\n // Effect DAG construction\n' + + # Effect instantiation + for effect in sorted_effects: + inputs_str = ', '.join(f'"{inp}"' for inp in effect.inputs) + outputs_str = ', '.join(f'"{out}"' for out in effect.outputs) + + cpp += f''' effect_dag_.push_back({{ + .effect = std::make_shared<{effect.class_name}>(ctx, + std::vector{{{inputs_str}}}, + std::vector{{{outputs_str}}}), + .input_nodes = {{{inputs_str}}}, + .output_nodes = {{{outputs_str}}}, + .execution_order = {effect.execution_order} + }}); +''' + + cpp += ''' init_effect_nodes(); + } +}; +''' + + return cpp + +def main(): + parser = argparse.ArgumentParser(description='Sequence compiler with DAG optimization') + parser.add_argument('input', help='Input .seq file') + parser.add_argument('--output', '-o', help='Output .cc file', required=True) + parser.add_argument('--flatten', action='store_true', help='Generate flattened code (FINAL_STRIP mode)') + + args = parser.parse_args() + + # Parse timeline + sequences = parse_timeline(args.input) + + if not sequences: + print("Error: No sequences found in input file", file=sys.stderr) + sys.exit(1) + + # Process each sequence + all_cpp = '''// Generated by seq_compiler.py +// DO NOT EDIT + +#include "gpu/sequence.h" +#include "gpu/effect.h" + +''' + + for seq in sequences: + # Validate DAG + validate_dag(seq) + + # Topological sort + sorted_effects = topological_sort(seq) + + # Lifetime analysis + lifetimes = analyze_lifetimes(seq, sorted_effects) + + # Ping-pong detection + aliases = detect_ping_pong(seq, sorted_effects) + + # Generate C++ + cpp = generate_cpp(seq, sorted_effects, aliases, args.flatten) + all_cpp += cpp + '\n' + + # Generate sequence registry and accessors + all_cpp += ''' +// Sequence Registry +#include +#include + +struct SequenceEntry { + float start_time; + int priority; + std::unique_ptr sequence; +}; + +static std::vector g_sequences; +static bool g_initialized = false; + +void InitializeSequences(const GpuContext& ctx, int width, int height) { + if (g_initialized) return; + g_initialized = true; + +''' + + # Instantiate each sequence + for seq in sequences: + class_name = f"{seq.name}Sequence" + all_cpp += f' g_sequences.push_back({{{seq.start_time}f, {seq.priority}, std::make_unique<{class_name}>(ctx, width, height)}});\n' + + all_cpp += ''' +} + +Sequence* GetActiveSequence(float time) { + // Find active sequence (latest start_time <= current time) + Sequence* active = nullptr; + for (auto& entry : g_sequences) { + if (entry.start_time <= time) { + active = entry.sequence.get(); + } + } + return active; +} + +void RenderTimeline(WGPUCommandEncoder encoder, float time, int width, int height, + float beat_time, float audio_intensity) { + Sequence* seq = GetActiveSequence(time); + if (seq) { + seq->preprocess(time, beat_time, 0.0f, audio_intensity); + seq->render_effects(encoder); + } +} + +float GetDemoDuration() { + return 40.0f; // TODO: Calculate from sequences +} + +// Surface-based rendering with framebuffers +#include "gpu/post_process_helper.h" +#include "gpu/shaders.h" + +static WGPUTexture g_source_texture = nullptr; +static WGPUTextureView g_source_view = nullptr; +static WGPUTexture g_sink_texture = nullptr; +static WGPUTextureView g_sink_view = nullptr; +static int g_fb_width = 0; +static int g_fb_height = 0; +static UniformBuffer g_blit_uniforms; + +static void ensure_framebuffers(WGPUDevice device, int width, int height) { + if (g_source_texture && g_fb_width == width && g_fb_height == height) { + return; + } + + // Release old + if (g_source_view) wgpuTextureViewRelease(g_source_view); + if (g_source_texture) wgpuTextureRelease(g_source_texture); + if (g_sink_view) wgpuTextureViewRelease(g_sink_view); + if (g_sink_texture) wgpuTextureRelease(g_sink_texture); + + // Create new + WGPUTextureDescriptor tex_desc = {}; + tex_desc.size = {(uint32_t)width, (uint32_t)height, 1}; + tex_desc.format = WGPUTextureFormat_RGBA8Unorm; + tex_desc.usage = WGPUTextureUsage_RenderAttachment | WGPUTextureUsage_TextureBinding; + tex_desc.dimension = WGPUTextureDimension_2D; + tex_desc.mipLevelCount = 1; + tex_desc.sampleCount = 1; + + g_source_texture = wgpuDeviceCreateTexture(device, &tex_desc); + g_source_view = wgpuTextureCreateView(g_source_texture, nullptr); + g_sink_texture = wgpuDeviceCreateTexture(device, &tex_desc); + g_sink_view = wgpuTextureCreateView(g_sink_texture, nullptr); + + g_fb_width = width; + g_fb_height = height; +} + +void RenderTimeline(WGPUSurface surface, float time, int width, int height, + float beat_time, float audio_intensity) { + Sequence* seq = GetActiveSequence(time); + if (!seq) return; + + const GpuContext* ctx = gpu_get_context(); + ensure_framebuffers(ctx->device, width, height); + + // Initialize blit uniforms buffer if needed + if (!g_blit_uniforms.get().buffer) { + g_blit_uniforms.init(ctx->device); + } + + // Bind source/sink views to sequence + seq->set_source_view(g_source_view); + seq->set_sink_view(g_sink_view); + + // Update uniforms via preprocess + seq->preprocess(time, beat_time, 0.0f, audio_intensity); + + WGPUCommandEncoder encoder = wgpuDeviceCreateCommandEncoder(ctx->device, nullptr); + + // Clear source + WGPURenderPassColorAttachment clear_attach = {}; + clear_attach.view = g_source_view; +#if !defined(DEMO_CROSS_COMPILE_WIN32) + clear_attach.depthSlice = WGPU_DEPTH_SLICE_UNDEFINED; +#endif + clear_attach.loadOp = WGPULoadOp_Clear; + clear_attach.storeOp = WGPUStoreOp_Store; + clear_attach.clearValue = {0.0, 0.0, 0.0, 1.0}; + + WGPURenderPassDescriptor clear_desc = {}; + clear_desc.colorAttachmentCount = 1; + clear_desc.colorAttachments = &clear_attach; + + WGPURenderPassEncoder clear_pass = wgpuCommandEncoderBeginRenderPass(encoder, &clear_desc); + wgpuRenderPassEncoderEnd(clear_pass); + wgpuRenderPassEncoderRelease(clear_pass); + + // Render effects + seq->render_effects(encoder); + + // Blit sink to surface + WGPUSurfaceTexture surface_texture; + wgpuSurfaceGetCurrentTexture(surface, &surface_texture); + + if (surface_texture.status == WGPUSurfaceGetCurrentTextureStatus_SuccessOptimal) { + WGPURenderPassColorAttachment blit_attach = {}; + blit_attach.view = surface_texture.texture + ? wgpuTextureCreateView(surface_texture.texture, nullptr) + : nullptr; +#if !defined(DEMO_CROSS_COMPILE_WIN32) + blit_attach.depthSlice = WGPU_DEPTH_SLICE_UNDEFINED; +#endif + blit_attach.loadOp = WGPULoadOp_Clear; + blit_attach.storeOp = WGPUStoreOp_Store; + blit_attach.clearValue = {0.0, 0.0, 0.0, 1.0}; + + WGPURenderPassDescriptor blit_desc = {}; + blit_desc.colorAttachmentCount = 1; + blit_desc.colorAttachments = &blit_attach; + + static WGPURenderPipeline blit_pipeline = nullptr; + static WGPUBindGroup blit_bind_group = nullptr; + + if (!blit_pipeline) { + blit_pipeline = create_post_process_pipeline(ctx->device, + ctx->format, passthrough_shader_wgsl); + } + + // Update blit uniforms + UniformsSequenceParams blit_params = {}; + blit_params.resolution = {(float)width, (float)height}; + blit_params.aspect_ratio = (float)width / (float)height; + blit_params.time = time; + blit_params.beat_time = beat_time; + blit_params.beat_phase = 0.0f; + blit_params.audio_intensity = audio_intensity; + g_blit_uniforms.update(ctx->queue, blit_params); + + pp_update_bind_group(ctx->device, blit_pipeline, &blit_bind_group, + g_sink_view, g_blit_uniforms.get(), {nullptr, 0}); + + WGPURenderPassEncoder blit_pass = wgpuCommandEncoderBeginRenderPass(encoder, &blit_desc); + wgpuRenderPassEncoderSetPipeline(blit_pass, blit_pipeline); + wgpuRenderPassEncoderSetBindGroup(blit_pass, 0, blit_bind_group, 0, nullptr); + wgpuRenderPassEncoderDraw(blit_pass, 3, 1, 0, 0); + wgpuRenderPassEncoderEnd(blit_pass); + wgpuRenderPassEncoderRelease(blit_pass); + + if (blit_attach.view) wgpuTextureViewRelease(blit_attach.view); + } + + WGPUCommandBuffer commands = wgpuCommandEncoderFinish(encoder, nullptr); + wgpuQueueSubmit(ctx->queue, 1, &commands); + wgpuCommandBufferRelease(commands); + wgpuCommandEncoderRelease(encoder); + + wgpuSurfacePresent(surface); + if (surface_texture.texture) { + wgpuTextureRelease(surface_texture.texture); + } +} +''' + + # Write output + with open(args.output, 'w') as f: + f.write(all_cpp) + + print(f"Generated {len(sequences)} sequence(s) -> {args.output}") + +if __name__ == '__main__': + main() diff --git a/tools/seq_compiler_v2.py b/tools/seq_compiler_v2.py deleted file mode 100755 index f835295..0000000 --- a/tools/seq_compiler_v2.py +++ /dev/null @@ -1,690 +0,0 @@ -#!/usr/bin/env python3 -"""Sequence v2 Compiler - DAG-based timeline compiler with ping-pong optimization. - -Converts v2 timeline syntax into optimized C++ SequenceV2 subclasses. -Performs DAG validation, topological sorting, and lifetime analysis. -""" - -import argparse -import os -import re -import sys -from typing import Dict, List, Set, Tuple, Optional - -# Node type enum mapping -NODE_TYPES = { - 'u8x4_norm': 'NodeType::U8X4_NORM', - 'f32x4': 'NodeType::F32X4', - 'f16x8': 'NodeType::F16X8', - 'depth24': 'NodeType::DEPTH24', - 'compute_f32': 'NodeType::COMPUTE_F32', -} - -class NodeDecl: - def __init__(self, name: str, node_type: str): - self.name = name - self.type = node_type - -class EffectDecl: - def __init__(self, class_name: str, inputs: List[str], outputs: List[str], - start: float, end: float, priority: int, params: str): - self.class_name = class_name - self.inputs = inputs - self.outputs = outputs - self.start = start - self.end = end - self.priority = priority - self.params = params - self.execution_order = -1 - -class SequenceDecl: - def __init__(self, name: str, start_time: float, priority: int): - self.name = name - self.start_time = start_time - self.priority = priority - self.nodes: Dict[str, NodeDecl] = {} - self.assets: Set[str] = set() - self.effects: List[EffectDecl] = [] - -def parse_timeline(filename: str) -> List[SequenceDecl]: - """Parse v2 timeline file.""" - sequences = [] - current_seq = None - - with open(filename, 'r') as f: - for line_num, line in enumerate(f, 1): - line = line.strip() - - # Skip comments and empty lines - if not line or line.startswith('#'): - continue - - # BPM directive (ignored for now) - if line.startswith('# BPM'): - continue - - # SEQUENCE start - if line.startswith('SEQUENCE'): - parts = line.split() - start_time = float(parts[1]) - priority = int(parts[2]) - name = ' '.join(parts[3:]).strip('"') if len(parts) > 3 else f"seq_{start_time}" - current_seq = SequenceDecl(name, start_time, priority) - sequences.append(current_seq) - continue - - if not current_seq: - print(f"Error: {filename}:{line_num}: Effect/Node outside SEQUENCE block", file=sys.stderr) - sys.exit(1) - - # NODE declaration - if line.startswith('NODE'): - parts = line.split() - if len(parts) < 3: - print(f"Error: {filename}:{line_num}: NODE requires name and type", file=sys.stderr) - sys.exit(1) - node_name = parts[1] - node_type = parts[2] - if node_type not in NODE_TYPES: - print(f"Error: {filename}:{line_num}: Unknown node type '{node_type}'", file=sys.stderr) - sys.exit(1) - current_seq.nodes[node_name] = NodeDecl(node_name, node_type) - continue - - # ASSET declaration - if line.startswith('ASSET'): - parts = line.split() - if len(parts) < 2: - print(f"Error: {filename}:{line_num}: ASSET requires name", file=sys.stderr) - sys.exit(1) - current_seq.assets.add(parts[1]) - continue - - # EFFECT with routing - if line.startswith('EFFECT'): - # Parse: EFFECT +/=/- ClassName inputs... -> outputs... start end [params...] - match = re.match(r'EFFECT\s+([+\-=])\s+(\w+)\s+(.+)', line) - if not match: - print(f"Error: {filename}:{line_num}: Invalid EFFECT syntax", file=sys.stderr) - sys.exit(1) - - priority_mod = match.group(1) - class_name = match.group(2) - rest = match.group(3) - - # Parse routing: inputs... -> outputs... start end [params] - if '->' not in rest: - print(f"Error: {filename}:{line_num}: EFFECT missing '->' routing", file=sys.stderr) - sys.exit(1) - - before_arrow, after_arrow = rest.split('->', 1) - inputs = before_arrow.strip().split() - - after_parts = after_arrow.strip().split() - # Find where outputs end (look for numeric start time) - outputs = [] - idx = 0 - while idx < len(after_parts): - try: - float(after_parts[idx]) - break - except ValueError: - outputs.append(after_parts[idx]) - idx += 1 - - if idx + 2 > len(after_parts): - print(f"Error: {filename}:{line_num}: EFFECT missing start/end times", file=sys.stderr) - sys.exit(1) - - start_time = float(after_parts[idx]) - end_time = float(after_parts[idx + 1]) - params = ' '.join(after_parts[idx + 2:]) if idx + 2 < len(after_parts) else '' - - # Priority calculation (relative to sequence priority) - if priority_mod == '+': - effect_priority = current_seq.priority + len(current_seq.effects) - elif priority_mod == '=': - effect_priority = current_seq.priority + len(current_seq.effects) - 1 if current_seq.effects else current_seq.priority - else: # '-' - effect_priority = current_seq.priority - 1 - - effect = EffectDecl(class_name, inputs, outputs, start_time, end_time, effect_priority, params) - current_seq.effects.append(effect) - continue - - print(f"Warning: {filename}:{line_num}: Unrecognized line: {line}", file=sys.stderr) - - return sequences - -def validate_dag(seq: SequenceDecl) -> None: - """Validate DAG: check for cycles, missing nodes, connectivity.""" - - # 1. Auto-infer nodes from effects - all_nodes = set(seq.nodes.keys()) - all_nodes.add('source') # Implicit - all_nodes.add('sink') # Implicit - - for effect in seq.effects: - for node in effect.inputs + effect.outputs: - if node not in all_nodes and node not in seq.nodes: - # Auto-infer as u8x4_norm - seq.nodes[node] = NodeDecl(node, 'u8x4_norm') - all_nodes.add(node) - - # 2. Check all referenced nodes exist - for effect in seq.effects: - for node in effect.inputs: - if node not in all_nodes: - print(f"Error: Effect {effect.class_name} references undefined input node '{node}'", file=sys.stderr) - sys.exit(1) - for node in effect.outputs: - if node not in all_nodes: - print(f"Error: Effect {effect.class_name} references undefined output node '{node}'", file=sys.stderr) - sys.exit(1) - - # 3. Check for cycles (DFS on effect graph, not node graph) - effect_visited = {} - for effect in seq.effects: - effect_visited[id(effect)] = 0 # 0=unvisited, 1=visiting, 2=visited - - # Build effect dependency graph - def get_effect_dependencies(effect: EffectDecl) -> List[EffectDecl]: - """Get effects that must execute before this one.""" - deps = [] - effect_idx = seq.effects.index(effect) - - for input_node in effect.inputs: - if input_node == 'source': - continue - # Find LAST effect before this one that produces this input - producer = None - for i in range(effect_idx - 1, -1, -1): - other = seq.effects[i] - if input_node in other.outputs: - producer = other - break - - if producer: - deps.append(producer) - return deps - - def dfs_cycle(effect: EffectDecl) -> bool: - eff_id = id(effect) - if effect_visited[eff_id] == 1: - return True # Back edge = cycle - if effect_visited[eff_id] == 2: - return False - - effect_visited[eff_id] = 1 - for dep in get_effect_dependencies(effect): - if dfs_cycle(dep): - return True - effect_visited[eff_id] = 2 - return False - - for effect in seq.effects: - if dfs_cycle(effect): - print(f"Error: Cycle detected in effect DAG involving effect '{effect.class_name}'", file=sys.stderr) - sys.exit(1) - - # 4. Check connectivity (source must reach sink) - reachable = set(['source']) - changed = True - while changed: - changed = False - for effect in seq.effects: - if any(inp in reachable for inp in effect.inputs): - for out in effect.outputs: - if out not in reachable: - reachable.add(out) - changed = True - - if 'sink' not in reachable: - print(f"Error: No path from 'source' to 'sink' in DAG", file=sys.stderr) - sys.exit(1) - -def topological_sort(seq: SequenceDecl) -> List[EffectDecl]: - """Sort effects in execution order using Kahn's algorithm.""" - - # Build dependency graph - in_degree = {} - for effect in seq.effects: - in_degree[id(effect)] = 0 - - # Count dependencies - node_producers = {} # node -> effect that produces it - for effect in seq.effects: - for output in effect.outputs: - node_producers[output] = effect - - # Calculate in-degrees - for effect in seq.effects: - for input_node in effect.inputs: - if input_node == 'source': - continue - if input_node in node_producers: - in_degree[id(effect)] += 1 - - # Find effects with no dependencies - queue = [eff for eff in seq.effects if in_degree[id(eff)] == 0] - sorted_effects = [] - - while queue: - current = queue.pop(0) - sorted_effects.append(current) - - # Mark outputs as available, decrement downstream dependencies - for output in current.outputs: - for other in seq.effects: - if output in other.inputs and id(other) != id(current): - in_degree[id(other)] -= 1 - if in_degree[id(other)] == 0: - queue.append(other) - - if len(sorted_effects) != len(seq.effects): - print(f"Error: DAG has unreachable effects (disconnected components)", file=sys.stderr) - sys.exit(1) - - # Assign execution order - for idx, effect in enumerate(sorted_effects): - effect.execution_order = idx - - return sorted_effects - -def analyze_lifetimes(seq: SequenceDecl, sorted_effects: List[EffectDecl]) -> Dict[str, Tuple[int, int]]: - """Analyze node lifetimes: (first_use, last_use) execution order indices.""" - - lifetimes = {} - - for effect in sorted_effects: - order = effect.execution_order - - for node in effect.inputs: - if node == 'source': - continue - if node not in lifetimes: - lifetimes[node] = (order, order) - else: - lifetimes[node] = (lifetimes[node][0], order) - - for node in effect.outputs: - if node == 'sink': - continue - if node not in lifetimes: - lifetimes[node] = (order, order) - else: - lifetimes[node] = (min(lifetimes[node][0], order), max(lifetimes[node][1], order)) - - return lifetimes - -def detect_ping_pong(seq: SequenceDecl, sorted_effects: List[EffectDecl]) -> Dict[str, str]: - """Detect ping-pong patterns and return alias map. - - Pattern: Effect i writes A, reads B; Effect i+1 writes B, reads A - Optimization: Alias B -> A (reuse same texture) - """ - - aliases = {} - used_nodes = set() - - # Look for adjacent alternating read/write patterns - for i in range(len(sorted_effects) - 1): - eff1 = sorted_effects[i] - eff2 = sorted_effects[i + 1] - - # Find nodes that alternate - for out1 in eff1.outputs: - if out1 in ['source', 'sink'] or out1 in used_nodes: - continue - - for in1 in eff1.inputs: - if in1 in ['source', 'sink'] or in1 in used_nodes: - continue - - # Check if eff2 writes in1 and reads out1 (alternating) - if in1 in eff2.outputs and out1 in eff2.inputs: - # Classic ping-pong: eff1 (reads in1, writes out1), eff2 (reads out1, writes in1) - # Check no other effects use these nodes - other_uses = False - for j, eff in enumerate(sorted_effects): - if j == i or j == i + 1: - continue - if out1 in eff.inputs + eff.outputs or in1 in eff.inputs + eff.outputs: - other_uses = True - break - - if not other_uses: - # Alias in1 -> out1 (in1 uses same texture as out1) - aliases[in1] = out1 - used_nodes.add(out1) - used_nodes.add(in1) - break - - return aliases - -def generate_cpp(seq: SequenceDecl, sorted_effects: List[EffectDecl], - aliases: Dict[str, str], flatten: bool = False) -> str: - """Generate C++ SequenceV2 subclass.""" - - class_name = seq.name.replace(' ', '_').replace('-', '_') - if not class_name[0].isalpha(): - class_name = 'Seq_' + class_name - class_name += 'Sequence' - - # Generate includes - includes = set() - for effect in seq.effects: - # Convert ClassName to snake_case header - # Remove V2 suffix first if present - base_name = effect.class_name - if base_name.endswith('V2'): - base_name = base_name[:-2] - - header = re.sub('([A-Z])', r'_\1', base_name).lower().lstrip('_') - if header.endswith('_effect'): - header = header[:-7] # Remove _effect suffix - includes.add(f'#include "effects/{header}_effect_v2.h"') - - cpp = f'''// Generated by seq_compiler_v2.py -// Sequence: {seq.name} - -#include "gpu/sequence_v2.h" -#include "gpu/effect_v2.h" -''' - - for inc in sorted(includes): - cpp += inc + '\n' - - cpp += f''' -class {class_name} : public SequenceV2 {{ - public: - {class_name}(const GpuContext& ctx, int width, int height) - : SequenceV2(ctx, width, height) {{ -''' - - # Node declarations - cpp += ' // Node declarations\n' - for node_name, node_decl in sorted(seq.nodes.items()): - if node_name in aliases: - # Aliased node - cpp += f' nodes_.declare_aliased_node("{node_name}", "{aliases[node_name]}");\n' - else: - node_type = NODE_TYPES[node_decl.type] - cpp += f' nodes_.declare_node("{node_name}", {node_type}, width_, height_);\n' - - cpp += '\n // Effect DAG construction\n' - - # Effect instantiation - for effect in sorted_effects: - inputs_str = ', '.join(f'"{inp}"' for inp in effect.inputs) - outputs_str = ', '.join(f'"{out}"' for out in effect.outputs) - - # Ensure class name has V2 suffix (add if not present) - effect_class = effect.class_name if effect.class_name.endswith('V2') else effect.class_name + 'V2' - - cpp += f''' effect_dag_.push_back({{ - .effect = std::make_shared<{effect_class}>(ctx, - std::vector{{{inputs_str}}}, - std::vector{{{outputs_str}}}), - .input_nodes = {{{inputs_str}}}, - .output_nodes = {{{outputs_str}}}, - .execution_order = {effect.execution_order} - }}); -''' - - cpp += ''' init_effect_nodes(); - } -}; -''' - - return cpp - -def main(): - parser = argparse.ArgumentParser(description='Sequence v2 compiler with DAG optimization') - parser.add_argument('input', help='Input .seq file') - parser.add_argument('--output', '-o', help='Output .cc file', required=True) - parser.add_argument('--flatten', action='store_true', help='Generate flattened code (FINAL_STRIP mode)') - - args = parser.parse_args() - - # Parse timeline - sequences = parse_timeline(args.input) - - if not sequences: - print("Error: No sequences found in input file", file=sys.stderr) - sys.exit(1) - - # Process each sequence - all_cpp = '''// Generated by seq_compiler_v2.py -// DO NOT EDIT - -#include "gpu/sequence_v2.h" -#include "gpu/effect_v2.h" - -''' - - for seq in sequences: - # Validate DAG - validate_dag(seq) - - # Topological sort - sorted_effects = topological_sort(seq) - - # Lifetime analysis - lifetimes = analyze_lifetimes(seq, sorted_effects) - - # Ping-pong detection - aliases = detect_ping_pong(seq, sorted_effects) - - # Generate C++ - cpp = generate_cpp(seq, sorted_effects, aliases, args.flatten) - all_cpp += cpp + '\n' - - # Generate sequence registry and accessors - all_cpp += ''' -// V2 Sequence Registry -#include -#include - -struct SequenceV2Entry { - float start_time; - int priority; - std::unique_ptr sequence; -}; - -static std::vector g_v2_sequences; -static bool g_v2_initialized = false; - -void InitializeV2Sequences(const GpuContext& ctx, int width, int height) { - if (g_v2_initialized) return; - g_v2_initialized = true; - -''' - - # Instantiate each sequence - for seq in sequences: - class_name = f"{seq.name}Sequence" - all_cpp += f' g_v2_sequences.push_back({{{seq.start_time}f, {seq.priority}, std::make_unique<{class_name}>(ctx, width, height)}});\n' - - all_cpp += ''' -} - -SequenceV2* GetActiveV2Sequence(float time) { - // Find active sequence (latest start_time <= current time) - SequenceV2* active = nullptr; - for (auto& entry : g_v2_sequences) { - if (entry.start_time <= time) { - active = entry.sequence.get(); - } - } - return active; -} - -void RenderV2Timeline(WGPUCommandEncoder encoder, float time, int width, int height, - float beat_time, float audio_intensity) { - SequenceV2* seq = GetActiveV2Sequence(time); - if (seq) { - seq->preprocess(time, beat_time, 0.0f, audio_intensity); - seq->render_effects(encoder); - } -} - -float GetDemoDuration() { - return 40.0f; // TODO: Calculate from v2 sequences -} - -// Surface-based rendering with framebuffers -#include "gpu/post_process_helper.h" -#include "gpu/shaders.h" - -static WGPUTexture g_source_texture = nullptr; -static WGPUTextureView g_source_view = nullptr; -static WGPUTexture g_sink_texture = nullptr; -static WGPUTextureView g_sink_view = nullptr; -static int g_fb_width = 0; -static int g_fb_height = 0; -static UniformBuffer g_blit_uniforms; - -static void ensure_framebuffers(WGPUDevice device, int width, int height) { - if (g_source_texture && g_fb_width == width && g_fb_height == height) { - return; - } - - // Release old - if (g_source_view) wgpuTextureViewRelease(g_source_view); - if (g_source_texture) wgpuTextureRelease(g_source_texture); - if (g_sink_view) wgpuTextureViewRelease(g_sink_view); - if (g_sink_texture) wgpuTextureRelease(g_sink_texture); - - // Create new - WGPUTextureDescriptor tex_desc = {}; - tex_desc.size = {(uint32_t)width, (uint32_t)height, 1}; - tex_desc.format = WGPUTextureFormat_RGBA8Unorm; - tex_desc.usage = WGPUTextureUsage_RenderAttachment | WGPUTextureUsage_TextureBinding; - tex_desc.dimension = WGPUTextureDimension_2D; - tex_desc.mipLevelCount = 1; - tex_desc.sampleCount = 1; - - g_source_texture = wgpuDeviceCreateTexture(device, &tex_desc); - g_source_view = wgpuTextureCreateView(g_source_texture, nullptr); - g_sink_texture = wgpuDeviceCreateTexture(device, &tex_desc); - g_sink_view = wgpuTextureCreateView(g_sink_texture, nullptr); - - g_fb_width = width; - g_fb_height = height; -} - -void RenderV2Timeline(WGPUSurface surface, float time, int width, int height, - float beat_time, float audio_intensity) { - SequenceV2* seq = GetActiveV2Sequence(time); - if (!seq) return; - - const GpuContext* ctx = gpu_get_context(); - ensure_framebuffers(ctx->device, width, height); - - // Initialize blit uniforms buffer if needed - if (!g_blit_uniforms.get().buffer) { - g_blit_uniforms.init(ctx->device); - } - - // Bind source/sink views to sequence - seq->set_source_view(g_source_view); - seq->set_sink_view(g_sink_view); - - // Update uniforms via preprocess - seq->preprocess(time, beat_time, 0.0f, audio_intensity); - - WGPUCommandEncoder encoder = wgpuDeviceCreateCommandEncoder(ctx->device, nullptr); - - // Clear source - WGPURenderPassColorAttachment clear_attach = {}; - clear_attach.view = g_source_view; -#if !defined(DEMO_CROSS_COMPILE_WIN32) - clear_attach.depthSlice = WGPU_DEPTH_SLICE_UNDEFINED; -#endif - clear_attach.loadOp = WGPULoadOp_Clear; - clear_attach.storeOp = WGPUStoreOp_Store; - clear_attach.clearValue = {0.0, 0.0, 0.0, 1.0}; - - WGPURenderPassDescriptor clear_desc = {}; - clear_desc.colorAttachmentCount = 1; - clear_desc.colorAttachments = &clear_attach; - - WGPURenderPassEncoder clear_pass = wgpuCommandEncoderBeginRenderPass(encoder, &clear_desc); - wgpuRenderPassEncoderEnd(clear_pass); - wgpuRenderPassEncoderRelease(clear_pass); - - // Render effects - seq->render_effects(encoder); - - // Blit sink to surface - WGPUSurfaceTexture surface_texture; - wgpuSurfaceGetCurrentTexture(surface, &surface_texture); - - if (surface_texture.status == WGPUSurfaceGetCurrentTextureStatus_SuccessOptimal) { - WGPURenderPassColorAttachment blit_attach = {}; - blit_attach.view = surface_texture.texture - ? wgpuTextureCreateView(surface_texture.texture, nullptr) - : nullptr; -#if !defined(DEMO_CROSS_COMPILE_WIN32) - blit_attach.depthSlice = WGPU_DEPTH_SLICE_UNDEFINED; -#endif - blit_attach.loadOp = WGPULoadOp_Clear; - blit_attach.storeOp = WGPUStoreOp_Store; - blit_attach.clearValue = {0.0, 0.0, 0.0, 1.0}; - - WGPURenderPassDescriptor blit_desc = {}; - blit_desc.colorAttachmentCount = 1; - blit_desc.colorAttachments = &blit_attach; - - static WGPURenderPipeline blit_pipeline = nullptr; - static WGPUBindGroup blit_bind_group = nullptr; - - if (!blit_pipeline) { - blit_pipeline = create_post_process_pipeline(ctx->device, - ctx->format, passthrough_v2_shader_wgsl); - } - - // Update blit uniforms - UniformsSequenceParams blit_params = {}; - blit_params.resolution = {(float)width, (float)height}; - blit_params.aspect_ratio = (float)width / (float)height; - blit_params.time = time; - blit_params.beat_time = beat_time; - blit_params.beat_phase = 0.0f; - blit_params.audio_intensity = audio_intensity; - g_blit_uniforms.update(ctx->queue, blit_params); - - pp_update_bind_group(ctx->device, blit_pipeline, &blit_bind_group, - g_sink_view, g_blit_uniforms.get(), {nullptr, 0}); - - WGPURenderPassEncoder blit_pass = wgpuCommandEncoderBeginRenderPass(encoder, &blit_desc); - wgpuRenderPassEncoderSetPipeline(blit_pass, blit_pipeline); - wgpuRenderPassEncoderSetBindGroup(blit_pass, 0, blit_bind_group, 0, nullptr); - wgpuRenderPassEncoderDraw(blit_pass, 3, 1, 0, 0); - wgpuRenderPassEncoderEnd(blit_pass); - wgpuRenderPassEncoderRelease(blit_pass); - - if (blit_attach.view) wgpuTextureViewRelease(blit_attach.view); - } - - WGPUCommandBuffer commands = wgpuCommandEncoderFinish(encoder, nullptr); - wgpuQueueSubmit(ctx->queue, 1, &commands); - wgpuCommandBufferRelease(commands); - wgpuCommandEncoderRelease(encoder); - - wgpuSurfacePresent(surface); - if (surface_texture.texture) { - wgpuTextureRelease(surface_texture.texture); - } -} -''' - - # Write output - with open(args.output, 'w') as f: - f.write(all_cpp) - - print(f"Generated {len(sequences)} sequence(s) -> {args.output}") - -if __name__ == '__main__': - main() diff --git a/tools/test_demo.seq b/tools/test_demo.seq index fa4dae8..da5d064 100644 --- a/tools/test_demo.seq +++ b/tools/test_demo.seq @@ -2,4 +2,4 @@ # BPM 120 (set in test_demo.track) SEQUENCE 0.0 0 "test_loop" - EFFECT + HeptagonEffectV2 source -> sink 0.0 16.0 + EFFECT + HeptagonEffect source -> sink 0.0 16.0 diff --git a/workspaces/main/assets.txt b/workspaces/main/assets.txt index 9d15213..71dd7e0 100644 --- a/workspaces/main/assets.txt +++ b/workspaces/main/assets.txt @@ -33,9 +33,9 @@ SHADER_RAY_TRIANGLE, NONE, ../../common/shaders/ray_triangle.wgsl, "Ray-Triangle SHADER_MAIN, NONE, shaders/main_shader.wgsl, "Main Heptagon Shader" SHADER_PARTICLE_COMPUTE, NONE, shaders/particle_compute.wgsl, "Particle Compute Shader" SHADER_PARTICLE_RENDER, NONE, shaders/particle_render.wgsl, "Particle Render Shader" -SHADER_PARTICLE_COMPUTE_V2, NONE, shaders/particle_compute_v2.wgsl, "Particle Compute Shader V2" -SHADER_PARTICLE_RENDER_V2, NONE, shaders/particle_render_v2.wgsl, "Particle Render Shader V2" -SHADER_ROTATING_CUBE_V2, NONE, shaders/rotating_cube_v2.wgsl, "Rotating Cube Shader V2" +SHADER_PARTICLE_COMPUTE_V2, NONE, shaders/particle_compute.wgsl, "Particle Compute Shader" +SHADER_PARTICLE_RENDER_V2, NONE, shaders/particle_render.wgsl, "Particle Render Shader" +SHADER_ROTATING_CUBE_V2, NONE, shaders/rotating_cube.wgsl, "Rotating Cube Shader" SHADER_PASSTHROUGH, NONE, ../../common/shaders/passthrough.wgsl, "Passthrough Shader" SHADER_ELLIPSE, NONE, shaders/ellipse.wgsl, "Ellipse Shader" SHADER_PARTICLE_SPRAY_COMPUTE, NONE, shaders/particle_spray_compute.wgsl, "Particle Spray Compute" @@ -82,9 +82,9 @@ CIRCLE_MASK_RENDER_SHADER, NONE, shaders/circle_mask_render.wgsl, "Circle mask r MASKED_CUBE_SHADER, NONE, shaders/masked_cube.wgsl, "Masked cube shader" SHADER_SCENE1, NONE, shaders/scene1.wgsl, "Scene1 effect shader" -# --- Sequence v2 Shaders --- -SHADER_SEQUENCE_V2_UNIFORMS, NONE, ../../common/shaders/sequence_v2_uniforms.wgsl, "Sequence v2 Uniforms Snippet" +# --- Sequence Shaders --- +SHADER_SEQUENCE_V2_UNIFORMS, NONE, ../../common/shaders/sequence_uniforms.wgsl, "Sequence Uniforms Snippet" SHADER_POSTPROCESS_INLINE, NONE, ../../common/shaders/postprocess_inline.wgsl, "Inline Post-Process Functions" -SHADER_PASSTHROUGH_V2, NONE, ../../common/shaders/passthrough_v2.wgsl, "Passthrough Shader (v2)" -SHADER_GAUSSIAN_BLUR_V2, NONE, ../../common/shaders/gaussian_blur_v2.wgsl, "Gaussian Blur Shader (v2)" -SHADER_HEPTAGON_V2, NONE, ../../common/shaders/heptagon_v2.wgsl, "Heptagon Shader (v2)" +SHADER_PASSTHROUGH_V2, NONE, ../../common/shaders/passthrough.wgsl, "Passthrough Shader" +SHADER_GAUSSIAN_BLUR_V2, NONE, ../../common/shaders/gaussian_blur.wgsl, "Gaussian Blur Shader" +SHADER_HEPTAGON_V2, NONE, ../../common/shaders/heptagon.wgsl, "Heptagon Shader" diff --git a/workspaces/main/shaders/particle_compute.wgsl b/workspaces/main/shaders/particle_compute.wgsl index ae513c8..d7a24b6 100644 --- a/workspaces/main/shaders/particle_compute.wgsl +++ b/workspaces/main/shaders/particle_compute.wgsl @@ -1,3 +1,4 @@ +// Particle simulation (compute shader) - V2 struct Particle { pos: vec4, vel: vec4, @@ -5,10 +6,10 @@ struct Particle { color: vec4, }; -#include "common_uniforms" +#include "sequence_uniforms" @group(0) @binding(0) var particles: array; -@group(0) @binding(1) var uniforms: CommonUniforms; +@group(0) @binding(1) var uniforms: UniformsSequenceParams; @compute @workgroup_size(64) fn main(@builtin(global_invocation_id) id: vec3) { diff --git a/workspaces/main/shaders/particle_compute_v2.wgsl b/workspaces/main/shaders/particle_compute_v2.wgsl deleted file mode 100644 index 3683826..0000000 --- a/workspaces/main/shaders/particle_compute_v2.wgsl +++ /dev/null @@ -1,31 +0,0 @@ -// Particle simulation (compute shader) - V2 -struct Particle { - pos: vec4, - vel: vec4, - rot: vec4, - color: vec4, -}; - -#include "sequence_v2_uniforms" - -@group(0) @binding(0) var particles: array; -@group(0) @binding(1) var uniforms: UniformsSequenceParams; - -@compute @workgroup_size(64) -fn main(@builtin(global_invocation_id) id: vec3) { - let i = id.x; - if (i >= arrayLength(&particles)) { - return; - } - var p = particles[i]; - let new_pos = p.pos.xyz + p.vel.xyz * 0.016; - p.pos = vec4(new_pos, p.pos.w); - p.vel.y = p.vel.y - 0.01 * (1.0 + uniforms.audio_intensity * 5.0); - p.rot.x = p.rot.x + p.rot.y * 0.016; - if (p.pos.y < -1.5) { - p.pos.y = 1.5; - p.pos.x = (f32(i % 100u) / 50.0) - 1.0 + (uniforms.audio_intensity * 0.5); - p.vel.y = 0.0; - } - particles[i] = p; -} diff --git a/workspaces/main/shaders/particle_render.wgsl b/workspaces/main/shaders/particle_render.wgsl index 6a2b636..dd83220 100644 --- a/workspaces/main/shaders/particle_render.wgsl +++ b/workspaces/main/shaders/particle_render.wgsl @@ -1,3 +1,4 @@ +// Particle rendering (vertex + fragment) - V2 struct Particle { pos: vec4, vel: vec4, @@ -5,10 +6,10 @@ struct Particle { color: vec4, }; -#include "common_uniforms" +#include "sequence_uniforms" @group(0) @binding(0) var particles: array; -@group(0) @binding(1) var uniforms: CommonUniforms; +@group(0) @binding(1) var uniforms: UniformsSequenceParams; struct VSOut { @builtin(position) pos: vec4, diff --git a/workspaces/main/shaders/particle_render_v2.wgsl b/workspaces/main/shaders/particle_render_v2.wgsl deleted file mode 100644 index 8663658..0000000 --- a/workspaces/main/shaders/particle_render_v2.wgsl +++ /dev/null @@ -1,53 +0,0 @@ -// Particle rendering (vertex + fragment) - V2 -struct Particle { - pos: vec4, - vel: vec4, - rot: vec4, - color: vec4, -}; - -#include "sequence_v2_uniforms" - -@group(0) @binding(0) var particles: array; -@group(0) @binding(1) var uniforms: UniformsSequenceParams; - -struct VSOut { - @builtin(position) pos: vec4, - @location(0) color: vec4, - @location(1) uv: vec2, -}; - -@vertex fn vs_main(@builtin(vertex_index) vi: u32, @builtin(instance_index) ii: u32) -> VSOut { - let p = particles[ii]; - let size = 0.02 + p.pos.z * 0.01 + uniforms.audio_intensity * 0.02; - var offsets = array, 6>( - vec2(-1, -1), - vec2(1, -1), - vec2(-1, 1), - vec2(-1, 1), - vec2(1, -1), - vec2(1, 1) - ); - let offset = offsets[vi]; - let c = cos(p.rot.x); - let s = sin(p.rot.x); - let rotated_offset = vec2(offset.x * c - offset.y * s, offset.x * s + offset.y * c); - let pos = vec2(p.pos.x + rotated_offset.x * size / uniforms.aspect_ratio, p.pos.y + rotated_offset.y * size); - - // Fade based on lifetime (p.pos.w goes from 1.0 to 0.0) - let lifetime_fade = p.pos.w; - let color_with_fade = vec4(p.color.rgb * (0.5 + 0.5 * uniforms.audio_intensity), p.color.a * lifetime_fade); - - return VSOut(vec4(pos, 0.0, 1.0), color_with_fade, offset); -} - -@fragment fn fs_main(@location(0) color: vec4, @location(1) uv: vec2) -> @location(0) vec4 { - // Calculate distance from center for circular shape - let dist = length(uv); - - // Smooth circular falloff (1.0 at center, 0.0 at edge) - let circle_alpha = smoothstep(1.0, 0.5, dist); - - // Apply circular fade to alpha channel - return vec4(color.rgb, color.a * circle_alpha); -} diff --git a/workspaces/main/shaders/rotating_cube.wgsl b/workspaces/main/shaders/rotating_cube.wgsl new file mode 100644 index 0000000..d7e4cae --- /dev/null +++ b/workspaces/main/shaders/rotating_cube.wgsl @@ -0,0 +1,89 @@ +// Rotating cube shader v2 (simplified, no masking) + +struct Uniforms { + view_proj: mat4x4, + inv_view_proj: mat4x4, + camera_pos_time: vec4, + params: vec4, + resolution: vec2, + aspect_ratio: f32, + _pad: f32, +}; + +struct ObjectData { + model: mat4x4, + inv_model: mat4x4, + color: vec4, + params: vec4, +}; + +@group(0) @binding(0) var uniforms: Uniforms; +@group(0) @binding(1) var object: ObjectData; + +struct VSOut { + @builtin(position) pos: vec4, + @location(0) world_pos: vec3, + @location(1) normal: vec3, +}; + +// Cube vertices (hardcoded) +fn get_cube_vertex(vid: u32) -> vec3 { + let positions = array, 36>( + // Front face + vec3(-1, -1, 1), vec3( 1, -1, 1), vec3( 1, 1, 1), + vec3(-1, -1, 1), vec3( 1, 1, 1), vec3(-1, 1, 1), + // Back face + vec3( 1, -1, -1), vec3(-1, -1, -1), vec3(-1, 1, -1), + vec3( 1, -1, -1), vec3(-1, 1, -1), vec3( 1, 1, -1), + // Right face + vec3( 1, -1, 1), vec3( 1, -1, -1), vec3( 1, 1, -1), + vec3( 1, -1, 1), vec3( 1, 1, -1), vec3( 1, 1, 1), + // Left face + vec3(-1, -1, -1), vec3(-1, -1, 1), vec3(-1, 1, 1), + vec3(-1, -1, -1), vec3(-1, 1, 1), vec3(-1, 1, -1), + // Top face + vec3(-1, 1, 1), vec3( 1, 1, 1), vec3( 1, 1, -1), + vec3(-1, 1, 1), vec3( 1, 1, -1), vec3(-1, 1, -1), + // Bottom face + vec3(-1, -1, -1), vec3( 1, -1, -1), vec3( 1, -1, 1), + vec3(-1, -1, -1), vec3( 1, -1, 1), vec3(-1, -1, 1) + ); + return positions[vid]; +} + +fn get_cube_normal(vid: u32) -> vec3 { + let face_id = vid / 6u; + let normals = array, 6>( + vec3( 0, 0, 1), // Front + vec3( 0, 0, -1), // Back + vec3( 1, 0, 0), // Right + vec3(-1, 0, 0), // Left + vec3( 0, 1, 0), // Top + vec3( 0, -1, 0) // Bottom + ); + return normals[face_id]; +} + +@vertex fn vs_main(@builtin(vertex_index) vid: u32) -> VSOut { + let local_pos = get_cube_vertex(vid); + let local_normal = get_cube_normal(vid); + + let world_pos = object.model * vec4(local_pos, 1.0); + let world_normal = normalize((object.model * vec4(local_normal, 0.0)).xyz); + + let clip_pos = uniforms.view_proj * world_pos; + + return VSOut(clip_pos, world_pos.xyz, world_normal); +} + +@fragment fn fs_main(@location(0) world_pos: vec3, @location(1) normal: vec3) -> @location(0) vec4 { + let N = normalize(normal); + let light_dir = normalize(vec3(1.0, 1.0, 1.0)); + let diffuse = max(dot(N, light_dir), 0.0); + + let ambient = 0.3; + let lighting = ambient + diffuse * 0.7; + + let color = object.color.rgb * lighting; + return vec4(color, 1.0); +} diff --git a/workspaces/main/shaders/rotating_cube_v2.wgsl b/workspaces/main/shaders/rotating_cube_v2.wgsl deleted file mode 100644 index d7e4cae..0000000 --- a/workspaces/main/shaders/rotating_cube_v2.wgsl +++ /dev/null @@ -1,89 +0,0 @@ -// Rotating cube shader v2 (simplified, no masking) - -struct Uniforms { - view_proj: mat4x4, - inv_view_proj: mat4x4, - camera_pos_time: vec4, - params: vec4, - resolution: vec2, - aspect_ratio: f32, - _pad: f32, -}; - -struct ObjectData { - model: mat4x4, - inv_model: mat4x4, - color: vec4, - params: vec4, -}; - -@group(0) @binding(0) var uniforms: Uniforms; -@group(0) @binding(1) var object: ObjectData; - -struct VSOut { - @builtin(position) pos: vec4, - @location(0) world_pos: vec3, - @location(1) normal: vec3, -}; - -// Cube vertices (hardcoded) -fn get_cube_vertex(vid: u32) -> vec3 { - let positions = array, 36>( - // Front face - vec3(-1, -1, 1), vec3( 1, -1, 1), vec3( 1, 1, 1), - vec3(-1, -1, 1), vec3( 1, 1, 1), vec3(-1, 1, 1), - // Back face - vec3( 1, -1, -1), vec3(-1, -1, -1), vec3(-1, 1, -1), - vec3( 1, -1, -1), vec3(-1, 1, -1), vec3( 1, 1, -1), - // Right face - vec3( 1, -1, 1), vec3( 1, -1, -1), vec3( 1, 1, -1), - vec3( 1, -1, 1), vec3( 1, 1, -1), vec3( 1, 1, 1), - // Left face - vec3(-1, -1, -1), vec3(-1, -1, 1), vec3(-1, 1, 1), - vec3(-1, -1, -1), vec3(-1, 1, 1), vec3(-1, 1, -1), - // Top face - vec3(-1, 1, 1), vec3( 1, 1, 1), vec3( 1, 1, -1), - vec3(-1, 1, 1), vec3( 1, 1, -1), vec3(-1, 1, -1), - // Bottom face - vec3(-1, -1, -1), vec3( 1, -1, -1), vec3( 1, -1, 1), - vec3(-1, -1, -1), vec3( 1, -1, 1), vec3(-1, -1, 1) - ); - return positions[vid]; -} - -fn get_cube_normal(vid: u32) -> vec3 { - let face_id = vid / 6u; - let normals = array, 6>( - vec3( 0, 0, 1), // Front - vec3( 0, 0, -1), // Back - vec3( 1, 0, 0), // Right - vec3(-1, 0, 0), // Left - vec3( 0, 1, 0), // Top - vec3( 0, -1, 0) // Bottom - ); - return normals[face_id]; -} - -@vertex fn vs_main(@builtin(vertex_index) vid: u32) -> VSOut { - let local_pos = get_cube_vertex(vid); - let local_normal = get_cube_normal(vid); - - let world_pos = object.model * vec4(local_pos, 1.0); - let world_normal = normalize((object.model * vec4(local_normal, 0.0)).xyz); - - let clip_pos = uniforms.view_proj * world_pos; - - return VSOut(clip_pos, world_pos.xyz, world_normal); -} - -@fragment fn fs_main(@location(0) world_pos: vec3, @location(1) normal: vec3) -> @location(0) vec4 { - let N = normalize(normal); - let light_dir = normalize(vec3(1.0, 1.0, 1.0)); - let diffuse = max(dot(N, light_dir), 0.0); - - let ambient = 0.3; - let lighting = ambient + diffuse * 0.7; - - let color = object.color.rgb * lighting; - return vec4(color, 1.0); -} diff --git a/workspaces/main/timeline.seq b/workspaces/main/timeline.seq index b4663bb..57e1c50 100644 --- a/workspaces/main/timeline.seq +++ b/workspaces/main/timeline.seq @@ -1,97 +1,45 @@ -# Demo Timeline -# Generated by Timeline Editor +# Demo Timeline v2 # BPM 90 -SEQUENCE 0.00 0 - EFFECT - FlashCubeEffect 0.00 4.00 -# EFFECT + FlashEffect 0.00 2.00 color=1.0,0.5,0.5 decay=0.95 -# EFFECT + FadeEffect 2.00 4.00 -# EFFECT + SolarizeEffect 0.00 4.00 - EFFECT + VignetteEffect 0.00 4.00 radius=0.6 softness=0.1 - -SEQUENCE 4.00 0 "rotating cube" - EFFECT + CircleMaskEffect 0.00 4.00 0.50 - EFFECT + RotatingCubeEffect 0.00 4.00 - EFFECT + GaussianBlurEffect 1.00 4.00 strength=1.0 - -SEQUENCE 8.00 0 "Flash Cube" - EFFECT - FlashCubeEffect 0.00 4.02 - EFFECT + FlashEffect 0.00 0.40 - -SEQUENCE 12.00 1 "spray" - EFFECT + ParticleSprayEffect 0.00 2.00 - EFFECT + ParticlesEffect 2.00 4.00 - EFFECT = GaussianBlurEffect 0.00 4.00 strength=3.0 - -SEQUENCE 16.00 2 "Hybrid3D + CNN" - EFFECT + ThemeModulationEffect 0.00 4.00 - EFFECT + HeptagonEffect 0.00 4.00 - EFFECT + ParticleSprayEffect 0.00 2.00 - EFFECT = ParticlesEffect 2.00 4.00 - EFFECT + Hybrid3DEffect 0.00 4.00 - EFFECT + CNNv1Effect 0.00 4.00 layers=3 blend=.9 - -SEQUENCE 20.00 0 "CNN effect" - EFFECT + HeptagonEffect 0.00 8.00 - EFFECT + Scene1Effect 0.00 8.00 - EFFECT + CNNv1Effect 6.00 8.00 layers=3 blend=.5 - -SEQUENCE 28.00 0 "buggy" - EFFECT + HeptagonEffect 0.00 2.00 - EFFECT + FadeEffect 0.00 2.00 - -SEQUENCE 30.00 3 "Seq-8" - EFFECT + ThemeModulationEffect 0.00 10.00 - EFFECT = HeptagonEffect 0.00 10.00 - EFFECT + GaussianBlurEffect 0.00 10.00 strength=1.5 - EFFECT + ChromaAberrationEffect 0.00 10.00 offset=0.03 angle=0.785 - EFFECT + SolarizeEffect 0.00 10.00 - -SEQUENCE 40.00 2 - EFFECT - FlashCubeEffect 0.00 4.00 - EFFECT + HeptagonEffect 0.00 4.00 - EFFECT + ParticleSprayEffect 0.00 4.00 - -SEQUENCE 44.00 2 "Fade" - EFFECT - FlashCubeEffect 0.00 2.00 - EFFECT + FlashEffect 1.00 2.00 - -SEQUENCE 46.00 10 - EFFECT - FlashCubeEffect 0.00 3.00 - EFFECT + GaussianBlurEffect 0.00 3.00 - EFFECT + FlashEffect 0.00 3.00 - -SEQUENCE 49.00 1 - EFFECT + ThemeModulationEffect 0.00 8.00 - EFFECT + HeptagonEffect 0.00 8.00 - EFFECT + ParticleSprayEffect 0.00 8.00 - EFFECT + Hybrid3DEffect 0.00 8.00 - EFFECT + GaussianBlurEffect 0.00 8.00 - EFFECT + ChromaAberrationEffect 0.00 8.00 - -SEQUENCE 57.00 0 - EFFECT + ThemeModulationEffect 0.00 7.00 - EFFECT + VignetteEffect 0.00 7.00 radius=0.6 softness=0.3 - EFFECT + SolarizeEffect 0.00 7.00 - -SEQUENCE 64.00 0 - EFFECT + ThemeModulationEffect 0.00 4.00 - EFFECT + HeptagonEffect 0.00 4.00 - EFFECT + GaussianBlurEffect 0.00 4.00 - EFFECT + SolarizeEffect 0.00 4.00 - -SEQUENCE 68.00 0 "double hepta!" - EFFECT + ThemeModulationEffect 0.00 4.00 - EFFECT = HeptagonEffect 0.00 4.00 - EFFECT + Hybrid3DEffect 0.00 4.00 - EFFECT + ParticleSprayEffect 0.00 4.00 - EFFECT + HeptagonEffect 0.00 4.00 - EFFECT + ChromaAberrationEffect 0.00 4.00 - EFFECT + GaussianBlurEffect 0.00 4.00 - -SEQUENCE 72.00 0 "The End" - EFFECT + ThemeModulationEffect 0.00 7.00 - EFFECT + HeptagonEffect 0.00 7.00 - EFFECT + ChromaAberrationEffect 0.00 7.00 - EFFECT + GaussianBlurEffect 0.00 7.00 - +SEQUENCE 0.00 0 "intro" + # FlashCube (placeholder) -> Vignette -> sink + EFFECT + PlaceholderEffect source -> temp1 0.00 4.00 + EFFECT + PlaceholderEffect temp1 -> sink 0.00 4.00 + +SEQUENCE 4.00 0 "rotating_cube" + # RotatingCube -> Blur -> sink + EFFECT + RotatingCubeEffect source -> temp1 0.00 4.00 + EFFECT + GaussianBlurEffect temp1 -> sink 1.00 4.00 + +SEQUENCE 8.00 0 "flash_cube" + # FlashCube (placeholder) -> Flash (placeholder) -> sink + EFFECT - PlaceholderEffect source -> temp1 0.00 4.02 + EFFECT + PlaceholderEffect temp1 -> sink 0.00 0.40 + +SEQUENCE 12.00 1 "particles" + # Particles -> Blur -> sink + EFFECT + ParticlesEffect source -> temp1 0.00 4.00 + EFFECT = GaussianBlurEffect temp1 -> sink 0.00 4.00 + +SEQUENCE 16.00 2 "hybrid_heptagon" + # Heptagon -> Hybrid3D -> sink + EFFECT + HeptagonEffect source -> temp1 0.00 4.00 + EFFECT + Hybrid3DEffect temp1 -> sink 0.00 4.00 + +SEQUENCE 20.00 0 "heptagon_scene" + # Heptagon -> Scene1 (placeholder) -> sink + EFFECT + HeptagonEffect source -> temp1 0.00 8.00 + EFFECT + PlaceholderEffect temp1 -> sink 0.00 8.00 + +SEQUENCE 28.00 0 "fade_test" + # Heptagon -> Fade (placeholder) -> sink + EFFECT + HeptagonEffect source -> temp1 0.00 2.00 + EFFECT + PlaceholderEffect temp1 -> sink 0.00 2.00 + +SEQUENCE 30.00 3 "complex_chain" + # Theme (placeholder) -> Heptagon -> Blur -> ChromaAberration (placeholder) -> Solarize (placeholder) -> sink + EFFECT + PlaceholderEffect source -> temp1 0.00 10.00 + EFFECT = HeptagonEffect temp1 -> temp2 0.00 10.00 + EFFECT + GaussianBlurEffect temp2 -> temp3 0.00 10.00 + EFFECT + PlaceholderEffect temp3 -> temp4 0.00 10.00 + EFFECT + PlaceholderEffect temp4 -> sink 0.00 10.00 diff --git a/workspaces/main/timeline_v2.seq b/workspaces/main/timeline_v2.seq deleted file mode 100644 index 4b51023..0000000 --- a/workspaces/main/timeline_v2.seq +++ /dev/null @@ -1,45 +0,0 @@ -# Demo Timeline v2 -# BPM 90 - -SEQUENCE 0.00 0 "intro" - # FlashCube (placeholder) -> Vignette -> sink - EFFECT + PlaceholderEffect source -> temp1 0.00 4.00 - EFFECT + PlaceholderEffect temp1 -> sink 0.00 4.00 - -SEQUENCE 4.00 0 "rotating_cube" - # RotatingCube -> Blur -> sink - EFFECT + RotatingCubeEffectV2 source -> temp1 0.00 4.00 - EFFECT + GaussianBlurEffect temp1 -> sink 1.00 4.00 - -SEQUENCE 8.00 0 "flash_cube" - # FlashCube (placeholder) -> Flash (placeholder) -> sink - EFFECT - PlaceholderEffect source -> temp1 0.00 4.02 - EFFECT + PlaceholderEffect temp1 -> sink 0.00 0.40 - -SEQUENCE 12.00 1 "particles" - # Particles -> Blur -> sink - EFFECT + ParticlesEffectV2 source -> temp1 0.00 4.00 - EFFECT = GaussianBlurEffect temp1 -> sink 0.00 4.00 - -SEQUENCE 16.00 2 "hybrid_heptagon" - # Heptagon -> Hybrid3D -> sink - EFFECT + HeptagonEffect source -> temp1 0.00 4.00 - EFFECT + Hybrid3DEffectV2 temp1 -> sink 0.00 4.00 - -SEQUENCE 20.00 0 "heptagon_scene" - # Heptagon -> Scene1 (placeholder) -> sink - EFFECT + HeptagonEffect source -> temp1 0.00 8.00 - EFFECT + PlaceholderEffect temp1 -> sink 0.00 8.00 - -SEQUENCE 28.00 0 "fade_test" - # Heptagon -> Fade (placeholder) -> sink - EFFECT + HeptagonEffect source -> temp1 0.00 2.00 - EFFECT + PlaceholderEffect temp1 -> sink 0.00 2.00 - -SEQUENCE 30.00 3 "complex_chain" - # Theme (placeholder) -> Heptagon -> Blur -> ChromaAberration (placeholder) -> Solarize (placeholder) -> sink - EFFECT + PlaceholderEffect source -> temp1 0.00 10.00 - EFFECT = HeptagonEffect temp1 -> temp2 0.00 10.00 - EFFECT + GaussianBlurEffect temp2 -> temp3 0.00 10.00 - EFFECT + PlaceholderEffect temp3 -> temp4 0.00 10.00 - EFFECT + PlaceholderEffect temp4 -> sink 0.00 10.00 diff --git a/workspaces/main/workspace.cfg b/workspaces/main/workspace.cfg index feaa3f1..5eff423 100644 --- a/workspaces/main/workspace.cfg +++ b/workspaces/main/workspace.cfg @@ -5,7 +5,7 @@ version = "1.0" [build] target = "demo64k" -timeline = "timeline_v2.seq" +timeline = "timeline.seq" # music = "pop_punk_drums.track" music = "beat_test.track" assets = "assets.txt" diff --git a/workspaces/test/assets.txt b/workspaces/test/assets.txt index 8550eca..0f065d3 100644 --- a/workspaces/test/assets.txt +++ b/workspaces/test/assets.txt @@ -68,12 +68,12 @@ CIRCLE_MASK_COMPUTE_SHADER, NONE, shaders/circle_mask_compute.wgsl, "Circle mask CIRCLE_MASK_RENDER_SHADER, NONE, shaders/circle_mask_render.wgsl, "Circle mask render shader" MASKED_CUBE_SHADER, NONE, shaders/masked_cube.wgsl, "Masked cube shader" -# --- Sequence v2 Shaders --- -SHADER_SEQUENCE_V2_UNIFORMS, NONE, ../../common/shaders/sequence_v2_uniforms.wgsl, "Sequence v2 Uniforms Snippet" +# --- Sequence Shaders --- +SHADER_SEQUENCE_V2_UNIFORMS, NONE, ../../common/shaders/sequence_uniforms.wgsl, "Sequence Uniforms Snippet" SHADER_POSTPROCESS_INLINE, NONE, ../../common/shaders/postprocess_inline.wgsl, "Inline Post-Process Functions" -SHADER_PASSTHROUGH_V2, NONE, ../../common/shaders/passthrough_v2.wgsl, "Passthrough Shader (v2)" -SHADER_GAUSSIAN_BLUR_V2, NONE, ../../common/shaders/gaussian_blur_v2.wgsl, "Gaussian Blur Shader (v2)" -SHADER_HEPTAGON_V2, NONE, ../../common/shaders/heptagon_v2.wgsl, "Heptagon Shader (v2)" +SHADER_PASSTHROUGH_V2, NONE, ../../common/shaders/passthrough.wgsl, "Passthrough Shader" +SHADER_GAUSSIAN_BLUR_V2, NONE, ../../common/shaders/gaussian_blur.wgsl, "Gaussian Blur Shader" +SHADER_HEPTAGON_V2, NONE, ../../common/shaders/heptagon.wgsl, "Heptagon Shader" # --- Test Assets (for test_assets.cc) --- TEST_ASSET_1, NONE, test_assets/test_asset_1.txt, "Test static asset" -- cgit v1.2.3