summaryrefslogtreecommitdiff
path: root/src/gpu
diff options
context:
space:
mode:
authorskal <pascal.massimino@gmail.com>2026-02-16 11:54:46 +0100
committerskal <pascal.massimino@gmail.com>2026-02-16 11:54:46 +0100
commitaf5d0e4c3a6cb773a4fb51ac32f4c33a7f8d8224 (patch)
treea76464ca40a43d6042ed5431547008cfbe746c34 /src/gpu
parent8eeadaf0d5653c21b948103e4d328f634b739a17 (diff)
feat(sequence): complete v2 migration with DAG-based routing
Phase 4 complete: V1 system removed, v2 fully operational. Architecture Changes: - Explicit Node system with typed buffers (u8x4_norm, f32x4, depth24) - DAG effect routing with multi-input/multi-output support - Python compiler (seq_compiler_v2.py) with topological sort and ping-pong optimization - Compile-time node aliasing for framebuffer reuse V1 Removal (~4KB): - Deleted effect.h/cc base classes (1.4KB) - Deleted 19 v1 effect pairs: heptagon, particles, passthrough, gaussian_blur, solarize, scene1, chroma_aberration, vignette, hybrid_3d, flash_cube, theme_modulation, fade, flash, circle_mask, rotating_cube, sdf_test, distort, moving_ellipse, particle_spray (2.7KB) V2 Effects Ported: - PassthroughEffectV2, PlaceholderEffectV2 - GaussianBlurEffectV2 (multi-pass with temp nodes) - HeptagonEffectV2 (scene effect with dummy texture) - ParticlesEffectV2 (compute + render, format fixed) - RotatingCubeEffectV2 (3D with depth node) - Hybrid3DEffectV2 (Renderer3D integration, dummy textures for noise/sky) Compiler Features: - DAG validation (cycle detection, connectivity checks) - Topological sort for execution order - Ping-pong optimization (aliased node detection) - Surface-based and encoder-based RenderV2Timeline generation - init_effect_nodes() automatic generation Fixes Applied: - WebGPU binding layout validation (standard v2 post-process layout) - Surface format mismatch (ctx.format for blit, RGBA8Unorm for framebuffers) - Depth attachment compatibility (removed forced depth from gpu_create_render_pass) - Renderer3D texture initialization (created dummy 1x1 white textures) - ParticlesEffectV2 format (changed from ctx.format to RGBA8Unorm) - Encoder-based RenderV2Timeline (added missing preprocess() call) Testing: - 34/36 tests passing (2 v1-dependent tests disabled) - demo64k runs successfully (no crashes) - All seek positions work (--seek 12, --seek 15 validated) Documentation: - Updated PROJECT_CONTEXT.md (v2 status, reference to SEQUENCE_v2.md) - Added completion entry to COMPLETED.md TODO (Future): - Port CNN effects to v2 - Implement flatten mode (--flatten code generation) - Port remaining 10+ effects - Update HTML timeline editor for v2 (deferred) handoff(Claude): Sequence v2 migration complete, v1 removed, system operational. Phase 5 (editor) deferred per user preference. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Diffstat (limited to 'src/gpu')
-rw-r--r--src/gpu/demo_effects.cc16
-rw-r--r--src/gpu/demo_effects.h50
-rw-r--r--src/gpu/effect.cc573
-rw-r--r--src/gpu/effect.h183
-rw-r--r--src/gpu/gpu.cc45
-rw-r--r--src/gpu/gpu.h12
-rw-r--r--src/gpu/headless_gpu.cc4
-rw-r--r--src/gpu/sequence_v2.cc25
-rw-r--r--src/gpu/sequence_v2.h16
-rw-r--r--src/gpu/stub_gpu.cc19
10 files changed, 78 insertions, 865 deletions
diff --git a/src/gpu/demo_effects.cc b/src/gpu/demo_effects.cc
index 3acf287..d9c5964 100644
--- a/src/gpu/demo_effects.cc
+++ b/src/gpu/demo_effects.cc
@@ -1,16 +1,6 @@
// This file is part of the 64k demo project.
-// This file previously contained implementations of demo effects and shaders.
-// Its content has been split into individual effect files and helper files.
+// Central include for all effect implementations.
+// Individual effects are in src/effects/*_v2.{h,cc}
+// Timeline management is in src/generated/timeline.cc (v2)
#include "gpu/demo_effects.h"
-#include "effects/circle_mask_effect.h"
-#include "effects/rotating_cube_effect.h"
-
-// Auto-generated function to populate the timeline
-void LoadTimeline(MainSequence& main_seq, WGPUDevice device, WGPUQueue queue,
- WGPUTextureFormat format) {
- // This function is defined in src/generated/timeline.cc
- // and its definition should not be here.
- // This file is now essentially a placeholder that includes the main effects
- // header.
-}
diff --git a/src/gpu/demo_effects.h b/src/gpu/demo_effects.h
index 6b22f3f..beccd46 100644
--- a/src/gpu/demo_effects.h
+++ b/src/gpu/demo_effects.h
@@ -8,42 +8,30 @@
#include "3d/renderer.h"
#include "3d/scene.h"
-// Base effect classes
-#include "effect.h"
+// Base effect classes (v2)
+#include "gpu/effect_v2.h"
#include "gpu/post_process_helper.h"
+#include "gpu/sequence_v2.h"
#include "gpu/shaders.h"
#include "gpu/texture_manager.h"
#include "gpu/uniform_helper.h"
-// Individual Effect Headers
-#include "effects/chroma_aberration_effect.h"
-#include "effects/circle_mask_effect.h"
-#include "../../cnn_v1/src/cnn_v1_effect.h"
-#include "../../cnn_v2/src/cnn_v2_effect.h"
-#include "effects/distort_effect.h"
-#include "effects/fade_effect.h"
-#include "effects/flash_cube_effect.h"
-#include "effects/flash_effect.h"
-#include "effects/gaussian_blur_effect.h"
-#include "effects/heptagon_effect.h"
-#include "effects/hybrid_3d_effect.h"
-#include "effects/moving_ellipse_effect.h"
-#include "effects/particle_spray_effect.h"
-#include "effects/particles_effect.h"
-#include "effects/passthrough_effect.h"
-#include "effects/rotating_cube_effect.h"
-#include "effects/scene1_effect.h"
-#include "effects/sdf_test_effect.h"
-#include "effects/solarize_effect.h"
-#include "effects/theme_modulation_effect.h"
-#include "effects/vignette_effect.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"
+// TODO: Port CNN effects to v2
+// #include "../../cnn_v1/src/cnn_v1_effect.h"
+// #include "../../cnn_v2/src/cnn_v2_effect.h"
#include <memory>
-// Common particle definition is now in effects/particle_defs.h
-
-// Auto-generated functions from sequence compiler
-
-void LoadTimeline(MainSequence& main_seq, const GpuContext& ctx);
-
-float GetDemoDuration();
+// Auto-generated functions from sequence compiler v2
+// See generated/timeline_v2.h for:
+// - InitializeV2Sequences()
+// - GetActiveV2Sequence()
+// - RenderV2Timeline()
diff --git a/src/gpu/effect.cc b/src/gpu/effect.cc
deleted file mode 100644
index ca98ebd..0000000
--- a/src/gpu/effect.cc
+++ /dev/null
@@ -1,573 +0,0 @@
-// This file is part of the 64k demo project.
-// It implements the Sequence management logic.
-
-#include "effect.h"
-#include "audio/tracker.h"
-#include "gpu/demo_effects.h"
-#include "gpu/gpu.h"
-#include "util/fatal_error.h"
-#include <algorithm>
-#include <cstdio>
-#include <typeinfo>
-#include <vector>
-
-// --- PostProcessEffect ---
-void PostProcessEffect::render(WGPURenderPassEncoder pass,
- const CommonPostProcessUniforms& uniforms) {
- (void)uniforms; // Base implementation doesn't use uniforms
- if (pipeline_ && bind_group_) {
- wgpuRenderPassEncoderSetPipeline(pass, pipeline_);
- wgpuRenderPassEncoderSetBindGroup(pass, 0, bind_group_, 0, nullptr);
- wgpuRenderPassEncoderDraw(pass, 3, 1, 0, 0); // Fullscreen triangle
- }
-}
-
-// --- Sequence Implementation ---
-void Sequence::resize(int width, int height) {
- for (SequenceItem& item : items_) {
- item.effect->resize(width, height);
- }
-}
-
-void Sequence::init(MainSequence* demo) {
- for (SequenceItem& item : items_) {
- if (!item.effect->is_initialized) {
- item.effect->init(demo);
- item.effect->is_initialized = true;
- }
- }
-}
-
-void Sequence::add_effect(std::shared_ptr<Effect> effect, float start_time,
- float end_time, int priority) {
- items_.push_back({effect, start_time, end_time, priority, false});
- is_sorted_ = false;
-}
-
-void Sequence::sort_items() {
- if (is_sorted_)
- return;
- std::sort(items_.begin(), items_.end(),
- [](const SequenceItem& a, const SequenceItem& b) {
- return a.priority < b.priority;
- });
- is_sorted_ = true;
-}
-
-void Sequence::update_active_list(float seq_time) {
- // Check if sequence has ended (if explicit end time is set)
- const bool sequence_ended = (end_time_ >= 0.0f && seq_time >= end_time_);
-
- for (SequenceItem& item : items_) {
- bool should_be_active = !sequence_ended && (seq_time >= item.start_time &&
- seq_time < item.end_time);
-
- if (should_be_active && !item.active) {
-#if !defined(STRIP_ALL)
- Effect* effect_ptr = item.effect.get();
- const char* effect_name = typeid(*effect_ptr).name();
- printf(" [EFFECT START] <%s> (priority=%d, time=%.2f-%.2f)\n",
- effect_name, item.priority, item.start_time, item.end_time);
-#endif
- item.effect->start();
- item.active = true;
- } else if (!should_be_active && item.active) {
-#if !defined(STRIP_ALL)
- Effect* effect_ptr = item.effect.get();
- const char* effect_name = typeid(*effect_ptr).name();
- printf(" [EFFECT END] <%s> (priority=%d)\n", effect_name, item.priority);
-#endif
- item.effect->end();
- item.active = false;
- }
- }
-}
-
-void Sequence::collect_active_effects(
- std::vector<SequenceItem*>& scene_effects,
- std::vector<SequenceItem*>& post_effects) {
- sort_items();
- for (SequenceItem& item : items_) {
- if (item.active) {
- if (item.effect->is_post_process()) {
- post_effects.push_back(&item);
- } else {
- scene_effects.push_back(&item);
- }
- }
- }
-}
-
-void Sequence::reset() {
- for (SequenceItem& item : items_) {
- if (item.active) {
- item.effect->end();
- item.active = false;
- }
- }
-}
-
-// --- MainSequence Implementation ---
-
-MainSequence::MainSequence() = default;
-MainSequence::~MainSequence() = default;
-
-void MainSequence::create_framebuffers(int width, int height) {
- // In test mode, this would be skipped or mocked.
- // For now, it will only be called by the real init.
- WGPUTextureDescriptor desc = {};
- desc.usage =
- WGPUTextureUsage_RenderAttachment | WGPUTextureUsage_TextureBinding;
- desc.dimension = WGPUTextureDimension_2D;
- desc.size = {(uint32_t)width, (uint32_t)height, 1};
- desc.format = gpu_ctx.format;
- desc.mipLevelCount = 1;
- desc.sampleCount = 1;
-
- framebuffer_a_ = wgpuDeviceCreateTexture(gpu_ctx.device, &desc);
- framebuffer_b_ = wgpuDeviceCreateTexture(gpu_ctx.device, &desc);
-
- framebuffer_view_a_ =
- gpu_create_texture_view_2d(framebuffer_a_, gpu_ctx.format);
- framebuffer_view_b_ =
- gpu_create_texture_view_2d(framebuffer_b_, gpu_ctx.format);
-
- // Depth Buffer
- WGPUTextureDescriptor depth_desc = {};
- depth_desc.usage = WGPUTextureUsage_RenderAttachment;
- depth_desc.dimension = WGPUTextureDimension_2D;
- depth_desc.size = {(uint32_t)width, (uint32_t)height, 1};
- depth_desc.format = WGPUTextureFormat_Depth24Plus;
- depth_desc.mipLevelCount = 1;
- depth_desc.sampleCount = 1;
- depth_texture_ = wgpuDeviceCreateTexture(gpu_ctx.device, &depth_desc);
-
- WGPUTextureViewDescriptor depth_view_desc = {};
- depth_view_desc.format = WGPUTextureFormat_Depth24Plus;
- depth_view_desc.dimension = WGPUTextureViewDimension_2D;
- depth_view_desc.aspect = WGPUTextureAspect_DepthOnly;
- depth_view_desc.mipLevelCount = 1;
- depth_view_desc.arrayLayerCount = 1;
- depth_view_ = wgpuTextureCreateView(depth_texture_, &depth_view_desc);
-}
-
-void MainSequence::init_test(const GpuContext& ctx) {
- gpu_ctx = ctx;
- // Use sensible defaults for test dimensions
- width_ = 1280;
- height_ = 720;
-
- create_framebuffers(width_, height_);
- passthrough_effect_ = std::make_unique<PassthroughEffect>(gpu_ctx);
- passthrough_effect_->resize(width_, height_);
- // Sequences are added later in the test, so no need to iterate here.
-}
-
-void MainSequence::init(const GpuContext& ctx, int width, int height) {
- gpu_ctx = ctx;
- width_ = width;
- height_ = height;
-
- create_framebuffers(width, height);
- passthrough_effect_ = std::make_unique<PassthroughEffect>(gpu_ctx);
- passthrough_effect_->resize(width, height);
-
- for (ActiveSequence& entry : sequences_) {
- entry.seq->resize(width, height); // Set dimensions FIRST
- entry.seq->init(this); // Then init with correct dimensions
- }
-}
-
-void MainSequence::add_sequence(std::shared_ptr<Sequence> seq, float start_time,
- int priority) {
- sequences_.push_back({seq, start_time, priority});
- // If MainSequence is already initialized, init the new sequence immediately
- if (gpu_ctx.device) {
- seq->resize(width_, height_); // Set dimensions FIRST
- seq->init(this); // Then init with correct dimensions
- }
- std::sort(sequences_.begin(), sequences_.end(),
- [](const ActiveSequence& a, const ActiveSequence& b) {
- return a.priority < b.priority;
- });
-}
-
-void MainSequence::resize(int width, int height) {
- width_ = width;
- height_ = height;
- // Release old resources
- if (framebuffer_view_a_)
- wgpuTextureViewRelease(framebuffer_view_a_);
- if (framebuffer_a_)
- wgpuTextureRelease(framebuffer_a_);
- if (framebuffer_view_b_)
- wgpuTextureViewRelease(framebuffer_view_b_);
- if (framebuffer_b_)
- wgpuTextureRelease(framebuffer_b_);
- if (depth_view_)
- wgpuTextureViewRelease(depth_view_);
- if (depth_texture_)
- wgpuTextureRelease(depth_texture_);
-
- // Recreate with new size
- create_framebuffers(width, height);
-
- if (passthrough_effect_) {
- passthrough_effect_->resize(width, height);
- }
-
- // Propagate to all sequences
- for (ActiveSequence& entry : sequences_) {
- entry.seq->resize(width, height);
- }
-}
-
-void MainSequence::render_frame(float global_time, float beat_time,
- float beat_phase, float peak,
- float aspect_ratio, WGPUSurface surface) {
- WGPUCommandEncoder encoder =
- wgpuDeviceCreateCommandEncoder(gpu_ctx.device, nullptr);
-
- std::vector<SequenceItem*> scene_effects;
- std::vector<SequenceItem*> post_effects;
- for (ActiveSequence& entry : sequences_) {
- if (global_time >= entry.start_time) {
-#if !defined(STRIP_ALL)
- if (!entry.activated) {
- printf("[SEQUENCE START] priority=%d, start_time=%.2f\n",
- entry.priority, entry.start_time);
- entry.activated = true;
- }
-#endif
- float seq_time = global_time - entry.start_time;
- entry.seq->update_active_list(seq_time);
- entry.seq->collect_active_effects(scene_effects, post_effects);
- }
- }
- std::sort(scene_effects.begin(), scene_effects.end(),
- [](const SequenceItem* a, const SequenceItem* b) {
- return a->priority < b->priority;
- });
- std::sort(post_effects.begin(), post_effects.end(),
- [](const SequenceItem* a, const SequenceItem* b) {
- return a->priority < b->priority;
- });
-
- // 1. Compute
- // Construct common uniforms once (reused for all effects)
- CommonPostProcessUniforms base_uniforms = {
- .resolution = {static_cast<float>(width_), static_cast<float>(height_)},
- .aspect_ratio = aspect_ratio,
- .time = 0.0f, // Will be set per-effect
- .beat_time = beat_time,
- .beat_phase = beat_phase,
- .audio_intensity = peak,
- ._pad = 0.0f,
- };
-
- for (const SequenceItem* item : scene_effects) {
- base_uniforms.time = global_time - item->start_time;
- item->effect->compute(encoder, base_uniforms);
- }
-
- // 2. Scene Pass (to A)
- WGPURenderPassColorAttachment scene_attachment = {};
- scene_attachment.view = framebuffer_view_a_;
- scene_attachment.resolveTarget = nullptr;
- scene_attachment.loadOp = WGPULoadOp_Clear;
- scene_attachment.storeOp = WGPUStoreOp_Store;
- scene_attachment.clearValue = {0, 0, 0, 1};
-#if !defined(DEMO_CROSS_COMPILE_WIN32)
- scene_attachment.depthSlice = WGPU_DEPTH_SLICE_UNDEFINED;
-#endif /* !defined(DEMO_CROSS_COMPILE_WIN32) */
-
- WGPURenderPassDepthStencilAttachment depth_attachment = {};
- depth_attachment.view = depth_view_;
- depth_attachment.depthLoadOp = WGPULoadOp_Clear;
- depth_attachment.depthStoreOp = WGPUStoreOp_Store;
- depth_attachment.depthClearValue = 1.0f;
-
- WGPURenderPassDescriptor scene_desc = {.colorAttachmentCount = 1,
- .colorAttachments = &scene_attachment,
- .depthStencilAttachment =
- &depth_attachment};
- WGPURenderPassEncoder scene_pass =
- wgpuCommandEncoderBeginRenderPass(encoder, &scene_desc);
- wgpuRenderPassEncoderSetViewport(scene_pass, 0.0f, 0.0f, (float)width_,
- (float)height_, 0.0f, 1.0f);
- for (const SequenceItem* item : scene_effects) {
- base_uniforms.time = global_time - item->start_time;
- item->effect->render(scene_pass, base_uniforms);
- }
- wgpuRenderPassEncoderEnd(scene_pass);
-
- // 3. Post Chain
-
- // Capture framebuffer ONCE before post-processing chain
- bool needs_capture = false;
- for (const SequenceItem* item : post_effects) {
- PostProcessEffect* pp = (PostProcessEffect*)(item->effect.get());
- if (pp->needs_framebuffer_capture()) {
- needs_capture = true;
- break;
- }
- }
-
- if (needs_capture) {
- WGPUTextureView captured_view = get_auxiliary_view("captured_frame");
- if (captured_view) {
- WGPURenderPassColorAttachment capture_attachment = {};
- capture_attachment.view = captured_view;
- capture_attachment.resolveTarget = nullptr;
- capture_attachment.loadOp = WGPULoadOp_Clear;
- capture_attachment.storeOp = WGPUStoreOp_Store;
- capture_attachment.clearValue = {0.0f, 0.0f, 0.0f, 1.0f};
-#if !defined(DEMO_CROSS_COMPILE_WIN32)
- capture_attachment.depthSlice = WGPU_DEPTH_SLICE_UNDEFINED;
-#endif
- WGPURenderPassDescriptor capture_desc = {
- .colorAttachmentCount = 1, .colorAttachments = &capture_attachment};
- WGPURenderPassEncoder capture_pass =
- wgpuCommandEncoderBeginRenderPass(encoder, &capture_desc);
- wgpuRenderPassEncoderSetViewport(capture_pass, 0.0f, 0.0f, (float)width_,
- (float)height_, 0.0f, 1.0f);
-
- PostProcessEffect* passthrough =
- (PostProcessEffect*)passthrough_effect_.get();
- passthrough->update_bind_group(framebuffer_view_a_);
- base_uniforms.time = 0.0f;
- passthrough->render(capture_pass, base_uniforms);
-
- wgpuRenderPassEncoderEnd(capture_pass);
- }
- }
-
- WGPUSurfaceTexture st = {};
- WGPUTextureView final_view = nullptr;
-
- if (post_effects.empty()) {
- wgpuSurfaceGetCurrentTexture(surface, &st);
- final_view = wgpuTextureCreateView(st.texture, nullptr);
-
- // Safely cast to PostProcessEffect to call update_bind_group
- PostProcessEffect* pp_effect =
- (PostProcessEffect*)passthrough_effect_.get();
- pp_effect->update_bind_group(framebuffer_view_a_);
-
- WGPURenderPassColorAttachment final_attachment = {};
- final_attachment.view = final_view;
- final_attachment.resolveTarget = nullptr;
- final_attachment.loadOp = WGPULoadOp_Load;
- final_attachment.storeOp = WGPUStoreOp_Store;
-#if !defined(DEMO_CROSS_COMPILE_WIN32)
- final_attachment.depthSlice = WGPU_DEPTH_SLICE_UNDEFINED;
-#endif /* !defined(DEMO_CROSS_COMPILE_WIN32) */
- WGPURenderPassDescriptor final_desc = {
- .colorAttachmentCount = 1, .colorAttachments = &final_attachment};
- WGPURenderPassEncoder final_pass =
- wgpuCommandEncoderBeginRenderPass(encoder, &final_desc);
- wgpuRenderPassEncoderSetViewport(final_pass, 0.0f, 0.0f, (float)width_,
- (float)height_, 0.0f, 1.0f);
- base_uniforms.time = 0.0f;
- passthrough_effect_->render(final_pass, base_uniforms);
- wgpuRenderPassEncoderEnd(final_pass);
- } else {
- WGPUTextureView current_input = framebuffer_view_a_;
- for (size_t i = 0; i < post_effects.size(); ++i) {
- bool is_last = (i == post_effects.size() - 1);
- WGPUTextureView current_output = nullptr;
-
- if (is_last) {
- wgpuSurfaceGetCurrentTexture(surface, &st);
- final_view = wgpuTextureCreateView(st.texture, nullptr);
- current_output = final_view;
- } else {
- current_output =
- (current_input == framebuffer_view_a_ ? framebuffer_view_b_
- : framebuffer_view_a_);
- }
-
- PostProcessEffect* pp =
- (PostProcessEffect*)(post_effects[i]->effect.get());
-
- pp->update_bind_group(current_input);
-
- WGPURenderPassColorAttachment pp_attachment = {};
- pp_attachment.view = current_output;
- pp_attachment.resolveTarget = nullptr;
- pp_attachment.loadOp = WGPULoadOp_Load;
- pp_attachment.storeOp = WGPUStoreOp_Store;
-#if !defined(DEMO_CROSS_COMPILE_WIN32)
- pp_attachment.depthSlice = WGPU_DEPTH_SLICE_UNDEFINED;
-#endif /* !defined(DEMO_CROSS_COMPILE_WIN32) */
- WGPURenderPassDescriptor pp_desc = {.colorAttachmentCount = 1,
- .colorAttachments = &pp_attachment};
- WGPURenderPassEncoder pp_pass =
- wgpuCommandEncoderBeginRenderPass(encoder, &pp_desc);
- wgpuRenderPassEncoderSetViewport(pp_pass, 0.0f, 0.0f, (float)width_,
- (float)height_, 0.0f, 1.0f);
- base_uniforms.time = global_time - post_effects[i]->start_time;
- pp->render(pp_pass, base_uniforms);
- wgpuRenderPassEncoderEnd(pp_pass);
- current_input = current_output;
- }
- }
-
- WGPUCommandBuffer commands = wgpuCommandEncoderFinish(encoder, nullptr);
- wgpuQueueSubmit(gpu_ctx.queue, 1, &commands);
-
- if (st.texture) {
- wgpuTextureViewRelease(final_view);
- wgpuSurfacePresent(surface);
- wgpuTextureRelease(st.texture);
- }
-}
-
-void MainSequence::shutdown() {
- if (framebuffer_view_a_)
- wgpuTextureViewRelease(framebuffer_view_a_);
- if (framebuffer_a_)
- wgpuTextureRelease(framebuffer_a_);
- if (framebuffer_view_b_)
- wgpuTextureViewRelease(framebuffer_view_b_);
- if (framebuffer_b_)
- wgpuTextureRelease(framebuffer_b_);
- if (depth_view_)
- wgpuTextureViewRelease(depth_view_);
- if (depth_texture_)
- wgpuTextureRelease(depth_texture_);
- for (auto& [name, aux] : auxiliary_textures_) {
- if (aux.view)
- wgpuTextureViewRelease(aux.view);
- if (aux.texture)
- wgpuTextureRelease(aux.texture);
- }
- auxiliary_textures_.clear();
- for (ActiveSequence& entry : sequences_) {
- entry.seq->reset();
- }
-}
-
-// Register a named auxiliary texture for inter-effect sharing
-void MainSequence::register_auxiliary_texture(const char* name, int width,
- int height) {
- const std::string key(name);
-
- // Check if already exists (silent, idempotent registration is valid)
- auto it = auxiliary_textures_.find(key);
- if (it != auxiliary_textures_.end()) {
- return;
- }
-
- // Create texture
- const WGPUTextureDescriptor desc = {
- .usage =
- WGPUTextureUsage_RenderAttachment | WGPUTextureUsage_TextureBinding,
- .dimension = WGPUTextureDimension_2D,
- .size = {(uint32_t)width, (uint32_t)height, 1},
- .format = gpu_ctx.format,
- .mipLevelCount = 1,
- .sampleCount = 1,
- };
-
- WGPUTexture texture = wgpuDeviceCreateTexture(gpu_ctx.device, &desc);
- FATAL_CHECK(!texture, "Failed to create auxiliary texture: %s\n", name);
-
- // Create view
- WGPUTextureView view = gpu_create_texture_view_2d(texture, gpu_ctx.format);
- FATAL_CHECK(!view, "Failed to create auxiliary texture view: %s\n", name);
-
- // Store in registry
- auxiliary_textures_[key] = {texture, view, width, height};
-
-#if !defined(STRIP_ALL)
- printf("[MainSequence] Registered auxiliary texture '%s' (%dx%d)\n", name,
- width, height);
-#endif /* !defined(STRIP_ALL) */
-}
-
-// Retrieve auxiliary texture view by name
-WGPUTextureView MainSequence::get_auxiliary_view(const char* name) {
- const std::string key(name);
- auto it = auxiliary_textures_.find(key);
- FATAL_CHECK(it == auxiliary_textures_.end(),
- "Auxiliary texture not found: %s\n", name);
- return it->second.view;
-}
-
-// Resize existing auxiliary texture
-void MainSequence::resize_auxiliary_texture(const char* name, int width,
- int height) {
- const std::string key(name);
- auto it = auxiliary_textures_.find(key);
- FATAL_CHECK(it == auxiliary_textures_.end(),
- "Auxiliary texture not found for resize: %s\n", name);
-
- // Release old resources
- if (it->second.view)
- wgpuTextureViewRelease(it->second.view);
- if (it->second.texture)
- wgpuTextureRelease(it->second.texture);
-
- // Create new texture with new dimensions
- const WGPUTextureDescriptor desc = {
- .usage =
- WGPUTextureUsage_RenderAttachment | WGPUTextureUsage_TextureBinding,
- .dimension = WGPUTextureDimension_2D,
- .size = {(uint32_t)width, (uint32_t)height, 1},
- .format = gpu_ctx.format,
- .mipLevelCount = 1,
- .sampleCount = 1,
- };
-
- WGPUTexture texture = wgpuDeviceCreateTexture(gpu_ctx.device, &desc);
- FATAL_CHECK(!texture, "Failed to resize auxiliary texture: %s\n", name);
-
- // Create view
- WGPUTextureView view = gpu_create_texture_view_2d(texture, gpu_ctx.format);
- FATAL_CHECK(!view, "Failed to create resized auxiliary texture view: %s\n",
- name);
-
- // Update registry
- it->second = {texture, view, width, height};
-
-#if !defined(STRIP_ALL)
- printf("[MainSequence] Resized auxiliary texture '%s' to %dx%d\n", name,
- width, height);
-#endif /* !defined(STRIP_ALL) */
-}
-
-#if !defined(STRIP_ALL)
-void MainSequence::simulate_until(float target_time, float step_rate,
- float bpm) {
- const float aspect_ratio = 16.0f / 9.0f;
- for (float t = 0.0f; t < target_time; t += step_rate) {
- WGPUCommandEncoder encoder =
- wgpuDeviceCreateCommandEncoder(gpu_ctx.device, nullptr);
- float absolute_beat_time = t * bpm / 60.0f;
- float beat_phase = fmodf(absolute_beat_time, 1.0f);
- std::vector<SequenceItem*> scene_effects, post_effects;
- for (ActiveSequence& entry : sequences_) {
- if (t >= entry.start_time) {
- entry.seq->update_active_list(t - entry.start_time);
- entry.seq->collect_active_effects(scene_effects, post_effects);
- }
- }
- for (const SequenceItem* item : scene_effects) {
- CommonPostProcessUniforms test_uniforms = {
- .resolution = {static_cast<float>(width_),
- static_cast<float>(height_)},
- .aspect_ratio = aspect_ratio,
- .time = t - item->start_time,
- .beat_time = absolute_beat_time,
- .beat_phase = beat_phase,
- .audio_intensity = 0.0f,
- ._pad = 0.0f,
- };
- item->effect->compute(encoder, test_uniforms);
- }
- WGPUCommandBuffer commands = wgpuCommandEncoderFinish(encoder, nullptr);
- wgpuQueueSubmit(gpu_ctx.queue, 1, &commands);
- }
-}
-#endif /* !defined(STRIP_ALL) */
diff --git a/src/gpu/effect.h b/src/gpu/effect.h
deleted file mode 100644
index 30e43d1..0000000
--- a/src/gpu/effect.h
+++ /dev/null
@@ -1,183 +0,0 @@
-#pragma once
-#include "gpu/gpu.h"
-#include "gpu/post_process_helper.h"
-#include "gpu/uniform_helper.h"
-#include <algorithm>
-#include <map>
-#include <memory>
-#include <string>
-#include <vector>
-
-class MainSequence;
-class PostProcessEffect;
-
-class Effect {
- public:
- Effect(const GpuContext& ctx) : ctx_(ctx) {
- uniforms_.init(ctx.device);
- }
- virtual ~Effect() = default;
- virtual void init(MainSequence* demo) {
- (void)demo;
- }
- virtual void start() {
- }
- virtual void compute(WGPUCommandEncoder encoder,
- const CommonPostProcessUniforms& uniforms) {
- (void)encoder;
- (void)uniforms;
- }
- virtual void render(WGPURenderPassEncoder pass,
- const CommonPostProcessUniforms& uniforms) = 0;
- virtual void resize(int width, int height) {
- width_ = width;
- height_ = height;
- }
-
- virtual void end() {
- }
- bool is_initialized = false;
- virtual bool is_post_process() const {
- return false;
- }
-
- // If true, MainSequence will capture current framebuffer to "captured_frame"
- // auxiliary texture before rendering this effect
- virtual bool needs_framebuffer_capture() const {
- return false;
- }
-
- // Helper: get initialized CommonPostProcessUniforms based on current
- // dimensions If aspect_ratio < 0, computes from width_/height_
- CommonPostProcessUniforms
- get_common_uniforms(float time = 0.0f, float beat_time = 0.0f,
- float beat_phase = 0.0f, float intensity = 0.0f,
- float aspect_ratio = -1.0f) const {
- return {
- .resolution = {static_cast<float>(width_), static_cast<float>(height_)},
- .aspect_ratio = aspect_ratio < 0.0f ? static_cast<float>(width_) /
- static_cast<float>(height_)
- : aspect_ratio,
- .time = time,
- .beat_time = beat_time,
- .beat_phase = beat_phase,
- .audio_intensity = intensity,
- ._pad = 0.0f,
- };
- }
-
- protected:
- const GpuContext& ctx_;
- UniformBuffer<CommonPostProcessUniforms> uniforms_;
- int width_ = 1280;
- int height_ = 720;
-};
-
-class PostProcessEffect : public Effect {
- public:
- PostProcessEffect(const GpuContext& ctx) : Effect(ctx) {
- }
- bool is_post_process() const override {
- return true;
- }
- void compute(WGPUCommandEncoder, const CommonPostProcessUniforms&) override {
- }
- void render(WGPURenderPassEncoder pass,
- const CommonPostProcessUniforms& uniforms) override;
- virtual void update_bind_group(WGPUTextureView input_view) = 0;
-
- protected:
- WGPURenderPipeline pipeline_ = nullptr;
- WGPUBindGroup bind_group_ = nullptr;
-};
-
-struct SequenceItem {
- std::shared_ptr<Effect> effect;
- float start_time; // Relative to Sequence start
- float end_time; // Relative to Sequence start
- int priority; // Render order within sequence (higher = later/top)
- bool active;
-};
-
-class Sequence {
- public:
- int priority = 0; // Render order of this sequence (higher = later/top)
- void init(MainSequence* demo);
- void add_effect(std::shared_ptr<Effect> effect, float start_time,
- float end_time, int priority = 0);
- void update_active_list(float seq_time);
- void resize(int width, int height);
- void collect_active_effects(std::vector<SequenceItem*>& scene_effects,
- std::vector<SequenceItem*>& post_effects);
- void reset();
- void set_end_time(float end_time) {
- end_time_ = end_time;
- }
- float get_end_time() const {
- return end_time_;
- }
-
- private:
- std::vector<SequenceItem> items_;
- bool is_sorted_ = false;
- float end_time_ = -1.0f; // Optional: -1.0 means "no explicit end"
- void sort_items();
-};
-
-class MainSequence {
- public:
- MainSequence();
- ~MainSequence();
- GpuContext gpu_ctx;
-
- void init(const GpuContext& ctx, int width, int height);
- void init_test(const GpuContext& ctx);
- void add_sequence(std::shared_ptr<Sequence> seq, float start_time,
- int priority = 0);
- void render_frame(float global_time, float beat_time, float beat_phase,
- float peak, float aspect_ratio, WGPUSurface surface);
- void resize(int width, int height);
- void shutdown();
-
- // Auxiliary texture registry for inter-effect texture sharing
- void register_auxiliary_texture(const char* name, int width, int height);
- void resize_auxiliary_texture(const char* name, int width, int height);
- WGPUTextureView get_auxiliary_view(const char* name);
-
-#if !defined(STRIP_ALL)
- void simulate_until(float target_time, float step_rate, float bpm = 120.0f);
-#endif /* !defined(STRIP_ALL) */
-
- private:
- struct ActiveSequence {
- std::shared_ptr<Sequence> seq;
- float start_time;
- int priority;
- bool activated =
- false; // Track if sequence has been activated for debug output
- };
- std::vector<ActiveSequence> sequences_;
-
- int width_ = 1280;
- int height_ = 720;
-
- WGPUTexture framebuffer_a_ = nullptr;
- WGPUTextureView framebuffer_view_a_ = nullptr;
- WGPUTexture framebuffer_b_ = nullptr;
- WGPUTextureView framebuffer_view_b_ = nullptr;
-
- WGPUTexture depth_texture_ = nullptr;
- WGPUTextureView depth_view_ = nullptr;
-
- std::unique_ptr<Effect> passthrough_effect_;
-
- struct AuxiliaryTexture {
- WGPUTexture texture;
- WGPUTextureView view;
- int width;
- int height;
- };
- std::map<std::string, AuxiliaryTexture> auxiliary_textures_;
-
- void create_framebuffers(int width, int height);
-};
diff --git a/src/gpu/gpu.cc b/src/gpu/gpu.cc
index ff4def7..647833c 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 "effect.h"
+#include "generated/timeline_v2.h"
#include "gpu/shader_composer.h"
#include "gpu/shaders.h"
#include "platform/platform.h"
@@ -28,7 +28,6 @@ static WGPUSurfaceConfiguration g_config = {};
static WGPUTextureFormat g_format = WGPUTextureFormat_BGRA8Unorm;
static GpuContext g_gpu_context = {};
-static MainSequence g_main_sequence;
// --- Helper Functions ---
@@ -218,12 +217,8 @@ RenderPass gpu_create_render_pass(WGPUDevice device, WGPUTextureFormat format,
pipeline_desc.multisample.mask = 0xFFFFFFFF;
pipeline_desc.fragment = &fragment_state;
- // Depth Stencil State (Required for compatibility with MainSequence pass)
- WGPUDepthStencilState depth_stencil = {};
- depth_stencil.format = WGPUTextureFormat_Depth24Plus;
- depth_stencil.depthWriteEnabled = WGPUOptionalBool_False;
- depth_stencil.depthCompare = WGPUCompareFunction_Always;
- pipeline_desc.depthStencil = &depth_stencil;
+ // No depth stencil for v2 effects (v1 removed)
+ pipeline_desc.depthStencil = nullptr;
pass.pipeline = wgpuDeviceCreateRenderPipeline(device, &pipeline_desc);
@@ -453,14 +448,17 @@ void gpu_init(PlatformState* platform_state) {
InitShaderComposer();
- g_main_sequence.init(g_gpu_context, platform_state->width,
- platform_state->height);
+ // V2: Sequences initialized by InitializeV2Sequences() called from main
}
void gpu_draw(float audio_peak, float aspect_ratio, float time, float beat_time,
float beat_phase) {
- g_main_sequence.render_frame(time, beat_time, beat_phase, audio_peak,
- aspect_ratio, g_surface);
+ // V2: Rendering handled by RenderV2Timeline() called from main
+ (void)audio_peak;
+ (void)aspect_ratio;
+ (void)time;
+ (void)beat_time;
+ (void)beat_phase;
}
void gpu_resize(int width, int height) {
@@ -469,32 +467,17 @@ void gpu_resize(int width, int height) {
g_config.width = width;
g_config.height = height;
wgpuSurfaceConfigure(g_surface, &g_config);
- g_main_sequence.resize(width, height);
+ // V2: Resize handled by RenderV2Timeline() framebuffer recreation
}
const GpuContext* gpu_get_context() {
return &g_gpu_context;
}
-MainSequence* gpu_get_main_sequence() {
- return &g_main_sequence;
+WGPUSurface gpu_get_surface() {
+ return g_surface;
}
void gpu_shutdown() {
- g_main_sequence.shutdown();
+ // V2: Shutdown handled by sequence cleanup
}
-
-#if !defined(STRIP_ALL)
-void gpu_simulate_until(float time, float bpm) {
- g_main_sequence.simulate_until(time, 1.0f / 60.0f, bpm);
-}
-
-void gpu_add_custom_effect(Effect* effect, float start_time, float end_time,
- int priority) {
- auto seq = std::make_shared<Sequence>();
- seq->init(&g_main_sequence);
- seq->add_effect(std::shared_ptr<Effect>(effect), 0.0f, end_time - start_time,
- priority);
- g_main_sequence.add_sequence(seq, start_time, priority);
-}
-#endif
diff --git a/src/gpu/gpu.h b/src/gpu/gpu.h
index 3796517..876aa84 100644
--- a/src/gpu/gpu.h
+++ b/src/gpu/gpu.h
@@ -7,7 +7,6 @@
#include "platform/platform.h"
struct PlatformState; // Forward declaration
-class Effect; // Forward declaration
// GPU context bundling device, queue, and surface format
struct GpuContext {
@@ -39,22 +38,15 @@ struct RenderPass {
uint32_t instance_count;
};
-class MainSequence; // Forward declaration
-
void gpu_init(PlatformState* platform_state);
void gpu_draw(float audio_peak, float aspect_ratio, float time, float beat_time,
float beat_phase);
void gpu_resize(int width, int height);
void gpu_shutdown();
-const GpuContext* gpu_get_context();
-MainSequence* gpu_get_main_sequence();
+WGPUSurface gpu_get_surface();
-#if !defined(STRIP_ALL)
-void gpu_simulate_until(float time, float bpm = 120.0f);
-void gpu_add_custom_effect(Effect* effect, float start_time, float end_time,
- int priority);
-#endif
+const GpuContext* gpu_get_context();
// Placeholder for GPU performance capture.
// This define can be controlled via CMake to conditionally enable profiling
diff --git a/src/gpu/headless_gpu.cc b/src/gpu/headless_gpu.cc
index f204a78..172a916 100644
--- a/src/gpu/headless_gpu.cc
+++ b/src/gpu/headless_gpu.cc
@@ -73,10 +73,6 @@ const GpuContext* gpu_get_context() {
return &ctx;
}
-MainSequence* gpu_get_main_sequence() {
- return nullptr;
-}
-
#if !defined(STRIP_ALL)
void gpu_simulate_until(float time, float bpm) {
(void)time;
diff --git a/src/gpu/sequence_v2.cc b/src/gpu/sequence_v2.cc
index c3f9aea..9e30db0 100644
--- a/src/gpu/sequence_v2.cc
+++ b/src/gpu/sequence_v2.cc
@@ -11,7 +11,15 @@ NodeRegistry::NodeRegistry(WGPUDevice device, int default_width,
int default_height)
: device_(device), default_width_(default_width),
default_height_(default_height) {
- // Reserve source/sink as implicit nodes (managed externally by MainSequence)
+ // Create placeholder source/sink nodes (will be updated externally before rendering)
+ Node placeholder = {};
+ placeholder.type = NodeType::U8X4_NORM;
+ placeholder.width = default_width;
+ placeholder.height = default_height;
+ placeholder.texture = nullptr;
+ placeholder.view = nullptr;
+ nodes_["source"] = placeholder;
+ nodes_["sink"] = placeholder;
}
NodeRegistry::~NodeRegistry() {
@@ -107,6 +115,15 @@ bool NodeRegistry::has_node(const std::string& name) const {
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;
@@ -205,3 +222,9 @@ void SequenceV2::resize(int width, int height) {
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
index 1cc5f47..2197a82 100644
--- a/src/gpu/sequence_v2.h
+++ b/src/gpu/sequence_v2.h
@@ -66,6 +66,9 @@ class NodeRegistry {
// 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_;
@@ -96,6 +99,19 @@ class SequenceV2 {
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<EffectDAGNode>& get_effect_dag() const {
return effect_dag_;
diff --git a/src/gpu/stub_gpu.cc b/src/gpu/stub_gpu.cc
index 95e647d..d889666 100644
--- a/src/gpu/stub_gpu.cc
+++ b/src/gpu/stub_gpu.cc
@@ -63,23 +63,4 @@ const GpuContext* gpu_get_context() {
return &ctx;
}
-MainSequence* gpu_get_main_sequence() {
- return nullptr;
-}
-
-#if !defined(STRIP_ALL)
-void gpu_simulate_until(float time, float bpm) {
- (void)time;
- (void)bpm;
-}
-
-void gpu_add_custom_effect(Effect* effect, float start_time, float end_time,
- int priority) {
- (void)effect;
- (void)start_time;
- (void)end_time;
- (void)priority;
-}
-#endif
-
#endif // STRIP_EXTERNAL_LIBS