From af5d0e4c3a6cb773a4fb51ac32f4c33a7f8d8224 Mon Sep 17 00:00:00 2001 From: skal Date: Mon, 16 Feb 2026 11:54:46 +0100 Subject: 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 --- src/gpu/demo_effects.cc | 16 +- src/gpu/demo_effects.h | 50 ++--- src/gpu/effect.cc | 573 ------------------------------------------------ src/gpu/effect.h | 183 ---------------- src/gpu/gpu.cc | 45 ++-- src/gpu/gpu.h | 12 +- src/gpu/headless_gpu.cc | 4 - src/gpu/sequence_v2.cc | 25 ++- src/gpu/sequence_v2.h | 16 ++ src/gpu/stub_gpu.cc | 19 -- 10 files changed, 78 insertions(+), 865 deletions(-) delete mode 100644 src/gpu/effect.cc delete mode 100644 src/gpu/effect.h (limited to 'src/gpu') 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 -// 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 -#include -#include -#include - -// --- 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, 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& scene_effects, - std::vector& 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(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(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 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 scene_effects; - std::vector 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(width_), static_cast(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 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(width_), - static_cast(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 -#include -#include -#include -#include - -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(width_), static_cast(height_)}, - .aspect_ratio = aspect_ratio < 0.0f ? static_cast(width_) / - static_cast(height_) - : aspect_ratio, - .time = time, - .beat_time = beat_time, - .beat_phase = beat_phase, - .audio_intensity = intensity, - ._pad = 0.0f, - }; - } - - protected: - const GpuContext& ctx_; - UniformBuffer 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; - 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, 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& scene_effects, - std::vector& 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 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 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 seq; - float start_time; - int priority; - bool activated = - false; // Track if sequence has been activated for debug output - }; - std::vector 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 passthrough_effect_; - - struct AuxiliaryTexture { - WGPUTexture texture; - WGPUTextureView view; - int width; - int height; - }; - std::map 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(); - seq->init(&g_main_sequence); - seq->add_effect(std::shared_ptr(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& 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 -- cgit v1.2.3