// This file is part of the 64k demo project. // It implements the Sequence management logic. #include "effect.h" #include #include // --- Sequence Implementation --- void Sequence::init(MainSequence *demo) { for (auto &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; // Sort by priority ascending (0 draws first, 100 draws on top) 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) { for (auto &item : items_) { bool should_be_active = (seq_time >= item.start_time && seq_time < item.end_time); if (should_be_active) { if (!item.active) { item.effect->start(); item.active = true; } } else { if (item.active) { item.effect->end(); item.active = false; } } } } void Sequence::dispatch_compute(WGPUCommandEncoder encoder, float seq_time, float beat, float intensity, float aspect_ratio) { sort_items(); for (auto &item : items_) { if (item.active) { item.effect->compute(encoder, seq_time - item.start_time, beat, intensity, aspect_ratio); } } } void Sequence::dispatch_render(WGPURenderPassEncoder pass, float seq_time, float beat, float intensity, float aspect_ratio) { sort_items(); // Should be sorted already but safe to check for (auto &item : items_) { if (item.active) { item.effect->render(pass, seq_time - item.start_time, beat, intensity, aspect_ratio); } } } void Sequence::reset() { for (auto &item : items_) { if (item.active) { item.effect->end(); item.active = false; } } } // --- MainSequence Implementation --- void MainSequence::init(WGPUDevice d, WGPUQueue q, WGPUTextureFormat f) { device = d; queue = q; format = f; for (auto &entry : sequences_) { entry.seq->init(this); } } void MainSequence::add_sequence(std::shared_ptr seq, float start_time, int priority) { sequences_.push_back({seq, start_time, priority}); // Sort sequences by priority std::sort(sequences_.begin(), sequences_.end(), [](const ActiveSequence &a, const ActiveSequence &b) { return a.priority < b.priority; }); } void MainSequence::render_frame(float global_time, float beat, float peak, float aspect_ratio, WGPUSurface surface) { WGPUSurfaceTexture surface_texture; wgpuSurfaceGetCurrentTexture(surface, &surface_texture); #if defined(DEMO_CROSS_COMPILE_WIN32) #define STATUS_OPTIMAL WGPUSurfaceGetCurrentTextureStatus_Success #define STATUS_SUBOPTIMAL WGPUSurfaceGetCurrentTextureStatus_Success #else #define STATUS_OPTIMAL WGPUSurfaceGetCurrentTextureStatus_SuccessOptimal #define STATUS_SUBOPTIMAL WGPUSurfaceGetCurrentTextureStatus_SuccessSuboptimal #endif if (surface_texture.status != STATUS_OPTIMAL && surface_texture.status != STATUS_SUBOPTIMAL) { return; } WGPUTextureView view = wgpuTextureCreateView(surface_texture.texture, nullptr); WGPUCommandEncoderDescriptor encoder_desc = {}; WGPUCommandEncoder encoder = wgpuDeviceCreateCommandEncoder(device, &encoder_desc); // 1. Update & Compute Phase for (auto &entry : sequences_) { // Check if sequence is active (start_time <= global_time) // We assume sequences run until end of demo or have internal end? // User said "Sequence ... overlap". Implicitly they might have duration but here we just check start. // Let's assume they are active if time >= start. // Effects inside sequence handle duration. if (global_time >= entry.start_time) { float seq_time = global_time - entry.start_time; entry.seq->update_active_list(seq_time); // Pass generic aspect ratio 16:9 for compute? // Or wait for render. Particles compute uses it. // We can get it from surface texture size if we want? // Let's pass 1.777f for now or fetch. // gpu_draw used to pass it. We need it here. // Wait, render_frame doesn't take aspect_ratio. gpu_draw did. // I should add aspect_ratio to render_frame or calculate it from surface. } } for (auto &entry : sequences_) { if (global_time >= entry.start_time) { entry.seq->dispatch_compute(encoder, global_time - entry.start_time, beat, peak, aspect_ratio); } } // 2. Render Phase { WGPURenderPassColorAttachment color_attachment = {}; color_attachment.view = view; color_attachment.loadOp = WGPULoadOp_Clear; color_attachment.storeOp = WGPUStoreOp_Store; // Clear color logic could be dynamic or part of a "BackgroundEffect"? // For now hardcode. float flash = peak * 0.2f; color_attachment.clearValue = {0.05 + flash, 0.1 + flash, 0.2 + flash, 1.0}; #if !defined(DEMO_CROSS_COMPILE_WIN32) color_attachment.depthSlice = WGPU_DEPTH_SLICE_UNDEFINED; #endif WGPURenderPassDescriptor render_pass_desc = {}; render_pass_desc.colorAttachmentCount = 1; render_pass_desc.colorAttachments = &color_attachment; WGPURenderPassEncoder pass = wgpuCommandEncoderBeginRenderPass(encoder, &render_pass_desc); for (auto &entry : sequences_) { if (global_time >= entry.start_time) { entry.seq->dispatch_render(pass, global_time - entry.start_time, beat, peak, aspect_ratio); } } wgpuRenderPassEncoderEnd(pass); } WGPUCommandBufferDescriptor cmd_desc = {}; WGPUCommandBuffer commands = wgpuCommandEncoderFinish(encoder, &cmd_desc); wgpuQueueSubmit(queue, 1, &commands); wgpuSurfacePresent(surface); wgpuTextureViewRelease(view); wgpuTextureRelease(surface_texture.texture); } void MainSequence::shutdown() { for (auto &entry : sequences_) { entry.seq->reset(); } sequences_.clear(); }