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 --- tools/seq_compiler_v2.py | 166 ++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 149 insertions(+), 17 deletions(-) (limited to 'tools/seq_compiler_v2.py') diff --git a/tools/seq_compiler_v2.py b/tools/seq_compiler_v2.py index 9918a92..f835295 100755 --- a/tools/seq_compiler_v2.py +++ b/tools/seq_compiler_v2.py @@ -432,7 +432,8 @@ class {class_name} : public SequenceV2 {{ }}); ''' - cpp += ''' } + cpp += ''' init_effect_nodes(); + } }; ''' @@ -482,7 +483,6 @@ def main(): # Generate sequence registry and accessors all_cpp += ''' // V2 Sequence Registry -#include "gpu/effect.h" #include #include @@ -524,28 +524,160 @@ void RenderV2Timeline(WGPUCommandEncoder encoder, float time, int width, int hei float beat_time, float audio_intensity) { SequenceV2* seq = GetActiveV2Sequence(time); if (seq) { - UniformsSequenceParams params = {}; - params.resolution = {(float)width, (float)height}; - params.aspect_ratio = (float)width / (float)height; - params.time = time; - params.beat_time = beat_time; - params.beat_phase = 0.0f; - params.audio_intensity = audio_intensity; - + seq->preprocess(time, beat_time, 0.0f, audio_intensity); seq->render_effects(encoder); } } -// V1 compatibility stub -void LoadTimeline(MainSequence& main_seq, const GpuContext& ctx) { - // V1 stub - v2 sequences initialized separately - (void)main_seq; - (void)ctx; -} - 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 -- cgit v1.2.3