// This file is part of the 64k demo project. // It defines the Effect interface and Sequence management system. #pragma once #include #include #include #if defined(DEMO_CROSS_COMPILE_WIN32) #include #else #include #endif class MainSequence; class PostProcessEffect; // Abstract base class for all visual effects class Effect { public: virtual ~Effect() = default; // One-time setup (load assets, create buffers). virtual void init(MainSequence *demo) { (void)demo; } // Called when the effect starts playing in a sequence segment. virtual void start() { } // Dispatch compute shaders. virtual void compute(WGPUCommandEncoder encoder, float time, float beat, float intensity, float aspect_ratio) { (void)encoder; (void)time; (void)beat; (void)intensity; (void)aspect_ratio; } // Record render commands. virtual void render(WGPURenderPassEncoder pass, float time, float beat, float intensity, float aspect_ratio) = 0; // Called when the effect finishes in a sequence segment. virtual void end() { } bool is_initialized = false; virtual bool is_post_process() const { return false; } }; // Base class for all post-processing effects class PostProcessEffect : public Effect { public: bool is_post_process() const override { return true; } // Post-process effects don't have a compute phase by default void compute(WGPUCommandEncoder, float, float, float, float) override { } // Fullscreen quad render void render(WGPURenderPassEncoder pass, float time, float beat, float intensity, float aspect_ratio) override; // Called by MainSequence to update which texture this effect reads from 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); // Add an effect to the sequence. void add_effect(std::shared_ptr effect, float start_time, float end_time, int priority = 0); // Updates active state of effects based on sequence-local time. void update_active_list(float seq_time); // Gathers active effects into lists for processing. void collect_active_effects(std::vector &scene_effects, std::vector &post_effects); void reset(); private: std::vector items_; bool is_sorted_ = false; void sort_items(); }; class MainSequence { public: MainSequence(); ~MainSequence(); // Defined in .cc to handle unique_ptr to incomplete type WGPUDevice device; WGPUQueue queue; WGPUTextureFormat format; void init(WGPUDevice device, WGPUQueue queue, WGPUTextureFormat format, int width, int height); // Add a sequence to the demo. void add_sequence(std::shared_ptr seq, float start_time, int priority = 0); // Renders the full frame: updates sequences, runs compute, runs render pass. void render_frame(float global_time, float beat, float peak, float aspect_ratio, WGPUSurface surface); void shutdown(); #ifndef STRIP_ALL // Fast-forwards the simulation (updates & compute) without rendering. void simulate_until(float target_time, float step_rate); #endif private: struct ActiveSequence { std::shared_ptr seq; float start_time; int priority; }; std::vector sequences_; // Framebuffers for post-processing WGPUTexture framebuffer_a_ = nullptr; WGPUTextureView framebuffer_view_a_ = nullptr; WGPUTexture framebuffer_b_ = nullptr; WGPUTextureView framebuffer_view_b_ = nullptr; // Default passthrough effect for blitting std::unique_ptr passthrough_effect_; void create_framebuffers(int width, int height); };