diff options
Diffstat (limited to 'src/gpu')
| -rw-r--r-- | src/gpu/demo_effects.cc | 314 | ||||
| -rw-r--r-- | src/gpu/demo_effects.h | 40 | ||||
| -rw-r--r-- | src/gpu/effect.cc | 203 | ||||
| -rw-r--r-- | src/gpu/effect.h | 117 | ||||
| -rw-r--r-- | src/gpu/gpu.cc | 358 | ||||
| -rw-r--r-- | src/gpu/gpu.h | 10 |
6 files changed, 707 insertions, 335 deletions
diff --git a/src/gpu/demo_effects.cc b/src/gpu/demo_effects.cc new file mode 100644 index 0000000..5fc7c15 --- /dev/null +++ b/src/gpu/demo_effects.cc @@ -0,0 +1,314 @@ +// This file is part of the 64k demo project. +// It implements the concrete effects used in the demo. + +#include "demo_effects.h" +#include <cmath> +#include <cstdlib> +#include <cstring> +#include <vector> + +static const int NUM_PARTICLES = 10000; + +struct Particle { + float pos[4]; // x, y, z, life + float vel[4]; // vx, vy, vz, padding + float rot[4]; // angle, speed, padding, padding + float color[4]; // r, g, b, a +}; + +const char *main_shader_wgsl = R"( +struct Uniforms { + audio_peak : f32, + aspect_ratio: f32, + time: f32, +}; + +@group(0) @binding(0) var<uniform> uniforms : Uniforms; + +@vertex +fn vs_main(@builtin(vertex_index) vertex_index: u32) -> @builtin(position) vec4<f32> { + let PI = 3.14159265; + let num_sides = 7.0; + + // Pulse scale based on audio peak + let base_scale = 0.5; + let pulse_scale = 0.3 * uniforms.audio_peak; + let scale = base_scale + pulse_scale; + + let tri_idx = f32(vertex_index / 3u); + let sub_idx = vertex_index % 3u; + + if (sub_idx == 0u) { + return vec4<f32>(0.0, 0.0, 0.0, 1.0); + } + + // Apply rotation based on time + let rotation = uniforms.time * 0.5; + let i = tri_idx + f32(sub_idx - 1u); + let angle = i * 2.0 * PI / num_sides + rotation; + let x = scale * cos(angle) / uniforms.aspect_ratio; + let y = scale * sin(angle); + + return vec4<f32>(x, y, 0.0, 1.0); +} + +@fragment +fn fs_main() -> @location(0) vec4<f32> { + // Dynamic color shifting based on time and responsiveness to peak + let h = uniforms.time * 2.0 + uniforms.audio_peak * 3.0; + let r = sin(h + 0.0) * 0.5 + 0.5; + let g = sin(h + 2.0) * 0.9 + 0.3; + let b = sin(h + 4.0) * 0.5 + 0.5; + + let boost = uniforms.audio_peak * 0.5; + return vec4<f32>(r + boost, g + boost, b + boost, 0.5); // Alpha 0.5 for blending +} +)"; + +const char *particle_compute_wgsl = R"( +struct Particle { + pos : vec4<f32>, + vel : vec4<f32>, + rot : vec4<f32>, + color : vec4<f32>, +}; + +struct Uniforms { + audio_peak : f32, + aspect_ratio: f32, + time: f32, +}; + +@group(0) @binding(0) var<storage, read_write> particles : array<Particle>; +@group(0) @binding(1) var<uniform> uniforms : Uniforms; + +@compute @workgroup_size(64) +fn main(@builtin(global_invocation_id) GlobalInvocationID : vec3<u32>) { + let index = GlobalInvocationID.x; + if (index >= arrayLength(&particles)) { + return; + } + + var p = particles[index]; + + // Update Position + p.pos.x = p.pos.x + p.vel.x * 0.016; + p.pos.y = p.pos.y + p.vel.y * 0.016; + p.pos.z = p.pos.z + p.vel.z * 0.016; + + // Gravity / Audio attraction + p.vel.y = p.vel.y - 0.01 * (1.0 + uniforms.audio_peak * 5.0); + + // Rotate + p.rot.x = p.rot.x + p.rot.y * 0.016; + + // Reset if out of bounds + if (p.pos.y < -1.5) { + p.pos.y = 1.5; + p.pos.x = (f32(index % 100u) / 50.0) - 1.0 + (uniforms.audio_peak * 0.5); + p.vel.y = 0.0; + p.vel.x = (f32(index % 10u) - 5.0) * 0.1; + } + + particles[index] = p; +} +)"; + +const char *particle_render_wgsl = R"( +struct Particle { + pos : vec4<f32>, + vel : vec4<f32>, + rot : vec4<f32>, + color : vec4<f32>, +}; + +struct Uniforms { + audio_peak : f32, + aspect_ratio: f32, + time: f32, +}; + +@group(0) @binding(0) var<storage, read> particles : array<Particle>; +@group(0) @binding(1) var<uniform> uniforms : Uniforms; + +struct VertexOutput { + @builtin(position) Position : vec4<f32>, + @location(0) Color : vec4<f32>, +}; + +@vertex +fn vs_main(@builtin(vertex_index) vertex_index : u32, @builtin(instance_index) instance_index : u32) -> VertexOutput { + let p = particles[instance_index]; + + // Simple quad expansion + let size = 0.02 + p.pos.z * 0.01 + uniforms.audio_peak * 0.02; + + // Vertex ID 0..5 for 2 triangles (Quad) + // 0 1 2, 2 1 3 (Strip-like order manually mapped) + var offsets = array<vec2<f32>, 6>( + vec2<f32>(-1.0, -1.0), + vec2<f32>( 1.0, -1.0), + vec2<f32>(-1.0, 1.0), + vec2<f32>(-1.0, 1.0), + vec2<f32>( 1.0, -1.0), + vec2<f32>( 1.0, 1.0) + ); + + let offset = offsets[vertex_index]; + + // Rotate + let c = cos(p.rot.x); + let s = sin(p.rot.x); + let rot_x = offset.x * c - offset.y * s; + let rot_y = offset.x * s + offset.y * c; + + let x = p.pos.x + rot_x * size / uniforms.aspect_ratio; + let y = p.pos.y + rot_y * size; + + var output : VertexOutput; + output.Position = vec4<f32>(x, y, 0.0, 1.0); + output.Color = p.color * (0.5 + 0.5 * uniforms.audio_peak); + return output; +} + +@fragment +fn fs_main(@location(0) Color : vec4<f32>) -> @location(0) vec4<f32> { + return Color; +} +)"; + +// --- HeptagonEffect --- + +HeptagonEffect::HeptagonEffect(WGPUDevice device, WGPUQueue queue, + WGPUTextureFormat format) + : queue_(queue) { + uniforms_ = gpu_create_buffer( + device, sizeof(float) * 4, + WGPUBufferUsage_Uniform | WGPUBufferUsage_CopyDst, nullptr); + + ResourceBinding bindings[] = {{uniforms_, WGPUBufferBindingType_Uniform}}; + pass_ = gpu_create_render_pass(device, format, main_shader_wgsl, bindings, 1); + pass_.vertex_count = 21; +} + +void HeptagonEffect::render(WGPURenderPassEncoder pass, float time, float beat, + float intensity, float aspect_ratio) { + struct { + float audio_peak; + float aspect_ratio; + float time; + float padding; + } u = {intensity, aspect_ratio, time, 0.0f}; + + wgpuQueueWriteBuffer(queue_, uniforms_.buffer, 0, &u, sizeof(u)); + + wgpuRenderPassEncoderSetPipeline(pass, pass_.pipeline); + wgpuRenderPassEncoderSetBindGroup(pass, 0, pass_.bind_group, 0, nullptr); + wgpuRenderPassEncoderDraw(pass, pass_.vertex_count, 1, 0, 0); +} + +// --- ParticlesEffect --- + +ParticlesEffect::ParticlesEffect(WGPUDevice device, WGPUQueue queue, + WGPUTextureFormat format) + : queue_(queue) { + uniforms_ = gpu_create_buffer( + device, sizeof(float) * 4, + WGPUBufferUsage_Uniform | WGPUBufferUsage_CopyDst, nullptr); + + std::vector<Particle> initial_particles(NUM_PARTICLES); + for (int i = 0; i < NUM_PARTICLES; ++i) { + initial_particles[i].pos[0] = ((float)(rand() % 100) / 50.0f) - 1.0f; + initial_particles[i].pos[1] = ((float)(rand() % 100) / 50.0f) - 1.0f; + initial_particles[i].pos[2] = 0.0f; + initial_particles[i].pos[3] = 1.0f; + + initial_particles[i].vel[0] = 0.0f; + initial_particles[i].vel[1] = 0.0f; + + initial_particles[i].rot[0] = 0.0f; + initial_particles[i].rot[1] = ((float)(rand() % 10) / 100.0f); + + initial_particles[i].color[0] = (float)(rand() % 10) / 10.0f; + initial_particles[i].color[1] = (float)(rand() % 10) / 10.0f; + initial_particles[i].color[2] = 1.0f; + initial_particles[i].color[3] = 1.0f; + } + + particles_buffer_ = gpu_create_buffer( + device, sizeof(Particle) * NUM_PARTICLES, + (WGPUBufferUsage)(WGPUBufferUsage_Storage | WGPUBufferUsage_CopyDst | + WGPUBufferUsage_Vertex), + initial_particles.data()); + + ResourceBinding compute_bindings[] = { + {particles_buffer_, WGPUBufferBindingType_Storage}, + {uniforms_, WGPUBufferBindingType_Uniform}}; + compute_pass_ = gpu_create_compute_pass(device, particle_compute_wgsl, + compute_bindings, 2); + compute_pass_.workgroup_size_x = (NUM_PARTICLES + 63) / 64; + compute_pass_.workgroup_size_y = 1; + compute_pass_.workgroup_size_z = 1; + + ResourceBinding render_bindings[] = { + {particles_buffer_, WGPUBufferBindingType_ReadOnlyStorage}, + {uniforms_, WGPUBufferBindingType_Uniform}}; + render_pass_ = gpu_create_render_pass(device, format, particle_render_wgsl, + render_bindings, 2); + render_pass_.vertex_count = 6; + render_pass_.instance_count = NUM_PARTICLES; +} + +void ParticlesEffect::compute(WGPUCommandEncoder encoder, float time, + float beat, float intensity, float aspect_ratio) { + struct { + float audio_peak; + float aspect_ratio; + float time; + float padding; + } u = {intensity, aspect_ratio, time, 0.0f}; + + wgpuQueueWriteBuffer(queue_, uniforms_.buffer, 0, &u, sizeof(u)); + + WGPUComputePassDescriptor compute_desc = {}; + WGPUComputePassEncoder pass = + wgpuCommandEncoderBeginComputePass(encoder, &compute_desc); + wgpuComputePassEncoderSetPipeline(pass, compute_pass_.pipeline); + wgpuComputePassEncoderSetBindGroup(pass, 0, compute_pass_.bind_group, 0, + nullptr); + wgpuComputePassEncoderDispatchWorkgroups(pass, compute_pass_.workgroup_size_x, + 1, 1); + wgpuComputePassEncoderEnd(pass); +} + +void ParticlesEffect::render(WGPURenderPassEncoder pass, float time, float beat, + float intensity, float aspect_ratio) { + // Update uniforms again? Technically redundant if compute happened same frame. + // But safer if render is called without compute (e.g. debugging). + struct { + float audio_peak; + float aspect_ratio; + float time; + float padding; + } u = {intensity, aspect_ratio, time, 0.0f}; + + wgpuQueueWriteBuffer(queue_, uniforms_.buffer, 0, &u, sizeof(u)); + + wgpuRenderPassEncoderSetPipeline(pass, render_pass_.pipeline); + wgpuRenderPassEncoderSetBindGroup(pass, 0, render_pass_.bind_group, 0, + nullptr); + wgpuRenderPassEncoderDraw(pass, render_pass_.vertex_count, + render_pass_.instance_count, 0, 0); +} + +std::shared_ptr<Sequence> create_demo_sequence(WGPUDevice device, + WGPUQueue queue, + WGPUTextureFormat format) { + auto seq = std::make_shared<Sequence>(); + // Overlap them for now to replicate original behavior + seq->add_effect(std::make_shared<HeptagonEffect>(device, queue, format), 0.0f, + 1000.0f); + seq->add_effect(std::make_shared<ParticlesEffect>(device, queue, format), + 0.0f, 1000.0f); + return seq; +} diff --git a/src/gpu/demo_effects.h b/src/gpu/demo_effects.h new file mode 100644 index 0000000..81321d9 --- /dev/null +++ b/src/gpu/demo_effects.h @@ -0,0 +1,40 @@ +// This file is part of the 64k demo project. +// It declares the concrete effects used in the demo. + +#pragma once +#include "effect.h" +#include "gpu.h" +#include <memory> + +class HeptagonEffect : public Effect { +public: + HeptagonEffect(WGPUDevice device, WGPUQueue queue, WGPUTextureFormat format); + void render(WGPURenderPassEncoder pass, float time, float beat, + float intensity, float aspect_ratio) override; + +private: + WGPUQueue queue_; + RenderPass pass_; + GpuBuffer uniforms_; +}; + +class ParticlesEffect : public Effect { +public: + ParticlesEffect(WGPUDevice device, WGPUQueue queue, WGPUTextureFormat format); + void compute(WGPUCommandEncoder encoder, float time, float beat, + float intensity, float aspect_ratio) override; + void render(WGPURenderPassEncoder pass, float time, float beat, + float intensity, float aspect_ratio) override; + +private: + WGPUQueue queue_; + ComputePass compute_pass_; + RenderPass render_pass_; + GpuBuffer particles_buffer_; + GpuBuffer uniforms_; +}; + +// Factory +std::shared_ptr<Sequence> create_demo_sequence(WGPUDevice device, + WGPUQueue queue, + WGPUTextureFormat format); diff --git a/src/gpu/effect.cc b/src/gpu/effect.cc new file mode 100644 index 0000000..cfef7f9 --- /dev/null +++ b/src/gpu/effect.cc @@ -0,0 +1,203 @@ +// This file is part of the 64k demo project. +// It implements the Sequence management logic. + +#include "effect.h" +#include <algorithm> +#include <cstdio> + +// --- 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> 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<Sequence> 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(); +}
\ No newline at end of file diff --git a/src/gpu/effect.h b/src/gpu/effect.h new file mode 100644 index 0000000..50b2d72 --- /dev/null +++ b/src/gpu/effect.h @@ -0,0 +1,117 @@ +// This file is part of the 64k demo project. +// It defines the Effect interface and Sequence management system. +// Used for choreographing visual effects. + +#pragma once + +#include <vector> +#include <memory> +#include <algorithm> + +#if defined(DEMO_CROSS_COMPILE_WIN32) +#include <webgpu/webgpu.h> +#else +#include <webgpu.h> +#endif + +class MainSequence; + +// Abstract base class for all visual effects +class Effect { +public: + virtual ~Effect() = default; + + // One-time setup (load assets, create buffers). + // Idempotent: safe to call multiple times if effect is shared. + 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; +}; + +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); + + // Add an effect to the sequence. + // start_time, end_time: Relative to sequence start. + // priority: Drawing order within this sequence. + void add_effect(std::shared_ptr<Effect> effect, float start_time, + float end_time, int priority = 0); + + // Updates active state of effects based on sequence-local time. + // seq_time: Time relative to sequence start. + void update_active_list(float seq_time); + + // Calls compute() on all active effects (sorted by priority). + void dispatch_compute(WGPUCommandEncoder encoder, float seq_time, float beat, + float intensity, float aspect_ratio); + + // Calls render() on all active effects (sorted by priority). + void dispatch_render(WGPURenderPassEncoder pass, float seq_time, float beat, + float intensity, float aspect_ratio); + + void reset(); + +private: + std::vector<SequenceItem> items_; + bool is_sorted_ = false; + void sort_items(); +}; + +class MainSequence { +public: + WGPUDevice device; + WGPUQueue queue; + WGPUTextureFormat format; + + void init(WGPUDevice device, WGPUQueue queue, WGPUTextureFormat format); + + // Add a sequence to the demo. + // start_time: Global time when this sequence starts. + // priority: Layering order (higher = on top). + void add_sequence(std::shared_ptr<Sequence> 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(); + +private: + struct ActiveSequence { + std::shared_ptr<Sequence> seq; + float start_time; + int priority; + }; + std::vector<ActiveSequence> sequences_; +}; diff --git a/src/gpu/gpu.cc b/src/gpu/gpu.cc index 1d3c24f..0655bb3 100644 --- a/src/gpu/gpu.cc +++ b/src/gpu/gpu.cc @@ -4,6 +4,8 @@ #include "gpu.h" #include "platform.h" +#include "demo_effects.h" +#include "effect.h" #include <GLFW/glfw3.h> #include <math.h> @@ -91,33 +93,19 @@ static WGPUQueue g_queue = nullptr; static WGPUSurface g_surface = nullptr; static WGPUSurfaceConfiguration g_config = {}; -// We keep the main render pass as a global for now -static RenderPass g_main_pass; -static GpuBuffer g_uniform_buffer_struct; - -// Particle System Globals -static ComputePass g_particle_compute_pass; -static RenderPass g_particle_render_pass; -static GpuBuffer g_particle_buffer; -static const int NUM_PARTICLES = 10000; - -struct Particle { - float pos[4]; // x, y, z, life - float vel[4]; // vx, vy, vz, padding - float rot[4]; // angle, speed, padding, padding - float color[4]; // r, g, b, a -}; +static MainSequence g_main_sequence; // --- Helper Functions --- -GpuBuffer gpu_create_buffer(size_t size, uint32_t usage, const void *data) { +GpuBuffer gpu_create_buffer(WGPUDevice device, size_t size, uint32_t usage, + const void *data) { WGPUBufferDescriptor desc = {}; desc.label = label_view("GpuBuffer"); desc.usage = (WGPUBufferUsage)usage; // Cast for C++ strictness with enums desc.size = size; desc.mappedAtCreation = (data != nullptr); // Map if we have initial data - WGPUBuffer buffer = wgpuDeviceCreateBuffer(g_device, &desc); + WGPUBuffer buffer = wgpuDeviceCreateBuffer(device, &desc); if (data) { void *ptr = wgpuBufferGetMappedRange(buffer, 0, size); @@ -128,7 +116,8 @@ GpuBuffer gpu_create_buffer(size_t size, uint32_t usage, const void *data) { return {buffer, size}; } -RenderPass gpu_create_render_pass(const char *shader_code, +RenderPass gpu_create_render_pass(WGPUDevice device, WGPUTextureFormat format, + const char *shader_code, ResourceBinding *bindings, int num_bindings) { RenderPass pass = {}; @@ -139,7 +128,7 @@ RenderPass gpu_create_render_pass(const char *shader_code, WGPUShaderModuleDescriptor shader_desc = {}; shader_desc.nextInChain = &wgsl_src.chain; WGPUShaderModule shader_module = - wgpuDeviceCreateShaderModule(g_device, &shader_desc); + wgpuDeviceCreateShaderModule(device, &shader_desc); // Create Bind Group Layout & Bind Group std::vector<WGPUBindGroupLayoutEntry> bgl_entries; @@ -164,24 +153,24 @@ RenderPass gpu_create_render_pass(const char *shader_code, bgl_desc.entryCount = (uint32_t)bgl_entries.size(); bgl_desc.entries = bgl_entries.data(); WGPUBindGroupLayout bind_group_layout = - wgpuDeviceCreateBindGroupLayout(g_device, &bgl_desc); + wgpuDeviceCreateBindGroupLayout(device, &bgl_desc); WGPUBindGroupDescriptor bg_desc = {}; bg_desc.layout = bind_group_layout; bg_desc.entryCount = (uint32_t)bg_entries.size(); bg_desc.entries = bg_entries.data(); - pass.bind_group = wgpuDeviceCreateBindGroup(g_device, &bg_desc); + pass.bind_group = wgpuDeviceCreateBindGroup(device, &bg_desc); // Pipeline Layout WGPUPipelineLayoutDescriptor pl_desc = {}; pl_desc.bindGroupLayoutCount = 1; pl_desc.bindGroupLayouts = &bind_group_layout; WGPUPipelineLayout pipeline_layout = - wgpuDeviceCreatePipelineLayout(g_device, &pl_desc); + wgpuDeviceCreatePipelineLayout(device, &pl_desc); // Render Pipeline WGPUColorTargetState color_target = {}; - color_target.format = g_config.format; // Use global swapchain format + color_target.format = format; // Use passed format color_target.writeMask = WGPUColorWriteMask_All; color_target.blend = nullptr; @@ -210,12 +199,12 @@ RenderPass gpu_create_render_pass(const char *shader_code, pipeline_desc.multisample.mask = 0xFFFFFFFF; pipeline_desc.fragment = &fragment_state; - pass.pipeline = wgpuDeviceCreateRenderPipeline(g_device, &pipeline_desc); + pass.pipeline = wgpuDeviceCreateRenderPipeline(device, &pipeline_desc); return pass; } -ComputePass gpu_create_compute_pass(const char *shader_code, +ComputePass gpu_create_compute_pass(WGPUDevice device, const char *shader_code, ResourceBinding *bindings, int num_bindings) { ComputePass pass = {}; @@ -226,7 +215,7 @@ ComputePass gpu_create_compute_pass(const char *shader_code, WGPUShaderModuleDescriptor shader_desc = {}; shader_desc.nextInChain = &wgsl_src.chain; WGPUShaderModule shader_module = - wgpuDeviceCreateShaderModule(g_device, &shader_desc); + wgpuDeviceCreateShaderModule(device, &shader_desc); std::vector<WGPUBindGroupLayoutEntry> bgl_entries; std::vector<WGPUBindGroupEntry> bg_entries; @@ -250,26 +239,26 @@ ComputePass gpu_create_compute_pass(const char *shader_code, bgl_desc.entryCount = (uint32_t)bgl_entries.size(); bgl_desc.entries = bgl_entries.data(); WGPUBindGroupLayout bind_group_layout = - wgpuDeviceCreateBindGroupLayout(g_device, &bgl_desc); + wgpuDeviceCreateBindGroupLayout(device, &bgl_desc); WGPUBindGroupDescriptor bg_desc = {}; bg_desc.layout = bind_group_layout; bg_desc.entryCount = (uint32_t)bg_entries.size(); bg_desc.entries = bg_entries.data(); - pass.bind_group = wgpuDeviceCreateBindGroup(g_device, &bg_desc); + pass.bind_group = wgpuDeviceCreateBindGroup(device, &bg_desc); WGPUPipelineLayoutDescriptor pl_desc = {}; pl_desc.bindGroupLayoutCount = 1; pl_desc.bindGroupLayouts = &bind_group_layout; WGPUPipelineLayout pipeline_layout = - wgpuDeviceCreatePipelineLayout(g_device, &pl_desc); + wgpuDeviceCreatePipelineLayout(device, &pl_desc); WGPUComputePipelineDescriptor pipeline_desc = {}; pipeline_desc.layout = pipeline_layout; pipeline_desc.compute.module = shader_module; pipeline_desc.compute.entryPoint = str_view("main"); - pass.pipeline = wgpuDeviceCreateComputePipeline(g_device, &pipeline_desc); + pass.pipeline = wgpuDeviceCreateComputePipeline(device, &pipeline_desc); return pass; } @@ -364,169 +353,6 @@ static void handle_request_device(WGPURequestDeviceStatus status, #endif #endif -// ... (Shaders omitted for brevity, they are unchanged) ... - -const char *main_shader_wgsl = R"( -struct Uniforms { - audio_peak : f32, - aspect_ratio: f32, - time: f32, -}; - -@group(0) @binding(0) var<uniform> uniforms : Uniforms; - -@vertex -fn vs_main(@builtin(vertex_index) vertex_index: u32) -> @builtin(position) vec4<f32> { - let PI = 3.14159265; - let num_sides = 7.0; - - // Pulse scale based on audio peak - let base_scale = 0.5; - let pulse_scale = 0.3 * uniforms.audio_peak; - let scale = base_scale + pulse_scale; - - let tri_idx = f32(vertex_index / 3u); - let sub_idx = vertex_index % 3u; - - if (sub_idx == 0u) { - return vec4<f32>(0.0, 0.0, 0.0, 1.0); - } - - // Apply rotation based on time - let rotation = uniforms.time * 0.5; - let i = tri_idx + f32(sub_idx - 1u); - let angle = i * 2.0 * PI / num_sides + rotation; - let x = scale * cos(angle) / uniforms.aspect_ratio; - let y = scale * sin(angle); - - return vec4<f32>(x, y, 0.0, 1.0); -} - -@fragment -fn fs_main() -> @location(0) vec4<f32> { - // Dynamic color shifting based on time and responsiveness to peak - let h = uniforms.time * 2.0 + uniforms.audio_peak * 3.0; - let r = sin(h + 0.0) * 0.5 + 0.5; - let g = sin(h + 2.0) * 0.9 + 0.3; - let b = sin(h + 4.0) * 0.5 + 0.5; - - let boost = uniforms.audio_peak * 0.5; - return vec4<f32>(r + boost, g + boost, b + boost, 0.5); // Alpha 0.5 for blending -} -)"; - -const char *particle_compute_wgsl = R"( -struct Particle { - pos : vec4<f32>, - vel : vec4<f32>, - rot : vec4<f32>, - color : vec4<f32>, -}; - -struct Uniforms { - audio_peak : f32, - aspect_ratio: f32, - time: f32, -}; - -@group(0) @binding(0) var<storage, read_write> particles : array<Particle>; -@group(0) @binding(1) var<uniform> uniforms : Uniforms; - -@compute @workgroup_size(64) -fn main(@builtin(global_invocation_id) GlobalInvocationID : vec3<u32>) { - let index = GlobalInvocationID.x; - if (index >= arrayLength(&particles)) { - return; - } - - var p = particles[index]; - - // Update Position - p.pos.x = p.pos.x + p.vel.x * 0.016; - p.pos.y = p.pos.y + p.vel.y * 0.016; - p.pos.z = p.pos.z + p.vel.z * 0.016; - - // Gravity / Audio attraction - p.vel.y = p.vel.y - 0.01 * (1.0 + uniforms.audio_peak * 5.0); - - // Rotate - p.rot.x = p.rot.x + p.rot.y * 0.016; - - // Reset if out of bounds - if (p.pos.y < -1.5) { - p.pos.y = 1.5; - p.pos.x = (f32(index % 100u) / 50.0) - 1.0 + (uniforms.audio_peak * 0.5); - p.vel.y = 0.0; - p.vel.x = (f32(index % 10u) - 5.0) * 0.1; - } - - particles[index] = p; -} -)"; - -const char *particle_render_wgsl = R"( -struct Particle { - pos : vec4<f32>, - vel : vec4<f32>, - rot : vec4<f32>, - color : vec4<f32>, -}; - -struct Uniforms { - audio_peak : f32, - aspect_ratio: f32, - time: f32, -}; - -@group(0) @binding(0) var<storage, read> particles : array<Particle>; -@group(0) @binding(1) var<uniform> uniforms : Uniforms; - -struct VertexOutput { - @builtin(position) Position : vec4<f32>, - @location(0) Color : vec4<f32>, -}; - -@vertex -fn vs_main(@builtin(vertex_index) vertex_index : u32, @builtin(instance_index) instance_index : u32) -> VertexOutput { - let p = particles[instance_index]; - - // Simple quad expansion - let size = 0.02 + p.pos.z * 0.01 + uniforms.audio_peak * 0.02; - - // Vertex ID 0..5 for 2 triangles (Quad) - // 0 1 2, 2 1 3 (Strip-like order manually mapped) - var offsets = array<vec2<f32>, 6>( - vec2<f32>(-1.0, -1.0), - vec2<f32>( 1.0, -1.0), - vec2<f32>(-1.0, 1.0), - vec2<f32>(-1.0, 1.0), - vec2<f32>( 1.0, -1.0), - vec2<f32>( 1.0, 1.0) - ); - - let offset = offsets[vertex_index]; - - // Rotate - let c = cos(p.rot.x); - let s = sin(p.rot.x); - let rot_x = offset.x * c - offset.y * s; - let rot_y = offset.x * s + offset.y * c; - - let x = p.pos.x + rot_x * size / uniforms.aspect_ratio; - let y = p.pos.y + rot_y * size; - - var output : VertexOutput; - output.Position = vec4<f32>(x, y, 0.0, 1.0); - output.Color = p.color * (0.5 + 0.5 * uniforms.audio_peak); - return output; -} - -@fragment -fn fs_main(@location(0) Color : vec4<f32>) -> @location(0) vec4<f32> { - return Color; -} -)"; - void gpu_init(GLFWwindow *window) { g_instance = wgpuCreateInstance(nullptr); g_surface = platform_create_wgpu_surface(g_instance); @@ -589,146 +415,16 @@ void gpu_init(GLFWwindow *window) { g_config.alphaMode = WGPUCompositeAlphaMode_Opaque; wgpuSurfaceConfigure(g_surface, &g_config); - // Initialize Uniforms - g_uniform_buffer_struct = gpu_create_buffer( - sizeof(float) * 4, WGPUBufferUsage_Uniform | WGPUBufferUsage_CopyDst, - nullptr); - - // Initialize Main Pass - ResourceBinding main_bindings[] = { - {g_uniform_buffer_struct, WGPUBufferBindingType_Uniform}}; - g_main_pass = gpu_create_render_pass(main_shader_wgsl, main_bindings, 1); - g_main_pass.vertex_count = 21; + g_main_sequence.init(g_device, g_queue, g_config.format); - // Initialize Particles - std::vector<Particle> initial_particles(NUM_PARTICLES); - for (int i = 0; i < NUM_PARTICLES; ++i) { - initial_particles[i].pos[0] = ((float)(rand() % 100) / 50.0f) - 1.0f; - initial_particles[i].pos[1] = ((float)(rand() % 100) / 50.0f) - 1.0f; - initial_particles[i].pos[2] = 0.0f; - initial_particles[i].pos[3] = 1.0f; - - initial_particles[i].vel[0] = 0.0f; - initial_particles[i].vel[1] = 0.0f; - - initial_particles[i].rot[0] = 0.0f; - initial_particles[i].rot[1] = ((float)(rand() % 10) / 100.0f); - - initial_particles[i].color[0] = (float)(rand() % 10) / 10.0f; - initial_particles[i].color[1] = (float)(rand() % 10) / 10.0f; - initial_particles[i].color[2] = 1.0f; - initial_particles[i].color[3] = 1.0f; - } - - g_particle_buffer = gpu_create_buffer( - sizeof(Particle) * NUM_PARTICLES, - (WGPUBufferUsage)(WGPUBufferUsage_Storage | WGPUBufferUsage_CopyDst | - WGPUBufferUsage_Vertex), - initial_particles.data()); - - // Initialize Particle Compute Pass - ResourceBinding compute_bindings[] = { - {g_particle_buffer, WGPUBufferBindingType_Storage}, - {g_uniform_buffer_struct, WGPUBufferBindingType_Uniform}}; - g_particle_compute_pass = - gpu_create_compute_pass(particle_compute_wgsl, compute_bindings, 2); - g_particle_compute_pass.workgroup_size_x = (NUM_PARTICLES + 63) / 64; - g_particle_compute_pass.workgroup_size_y = 1; - g_particle_compute_pass.workgroup_size_z = 1; - - // Initialize Particle Render Pass - ResourceBinding render_bindings[] = { - {g_particle_buffer, WGPUBufferBindingType_ReadOnlyStorage}, - {g_uniform_buffer_struct, WGPUBufferBindingType_Uniform}}; - g_particle_render_pass = - gpu_create_render_pass(particle_render_wgsl, render_bindings, 2); - g_particle_render_pass.vertex_count = 6; - g_particle_render_pass.instance_count = NUM_PARTICLES; + auto seq = create_demo_sequence(g_device, g_queue, g_config.format); + g_main_sequence.add_sequence(seq, 0.0f, 0); } -void gpu_draw(float audio_peak, float aspect_ratio, float time) { - WGPUSurfaceTexture surface_texture; - wgpuSurfaceGetCurrentTexture(g_surface, &surface_texture); - if (surface_texture.status != - WGPUSurfaceGetCurrentTextureStatus_SuccessOptimal && - surface_texture.status != - WGPUSurfaceGetCurrentTextureStatus_SuccessSuboptimal) - return; - - WGPUTextureView view = - wgpuTextureCreateView(surface_texture.texture, nullptr); - - struct { - float audio_peak; - float aspect_ratio; - float time; - float padding; - } uniforms = {audio_peak, aspect_ratio, time, 0.0f}; - wgpuQueueWriteBuffer(g_queue, g_uniform_buffer_struct.buffer, 0, &uniforms, - sizeof(uniforms)); - - WGPUCommandEncoderDescriptor encoder_desc = {}; - WGPUCommandEncoder encoder = - wgpuDeviceCreateCommandEncoder(g_device, &encoder_desc); - - // --- Compute Pass --- - { - WGPUComputePassDescriptor compute_desc = {}; - compute_desc.label = label_view("Particle Compute"); - WGPUComputePassEncoder compute_pass = - wgpuCommandEncoderBeginComputePass(encoder, &compute_desc); - wgpuComputePassEncoderSetPipeline(compute_pass, - g_particle_compute_pass.pipeline); - wgpuComputePassEncoderSetBindGroup( - compute_pass, 0, g_particle_compute_pass.bind_group, 0, nullptr); - wgpuComputePassEncoderDispatchWorkgroups( - compute_pass, g_particle_compute_pass.workgroup_size_x, 1, 1); - wgpuComputePassEncoderEnd(compute_pass); - } - - // --- Render Pass --- - { - WGPURenderPassColorAttachment color_attachment = {}; - color_attachment.view = view; - color_attachment.loadOp = WGPULoadOp_Clear; - color_attachment.storeOp = WGPUStoreOp_Store; - float flash = audio_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); - - // Draw Main Object - wgpuRenderPassEncoderSetPipeline(pass, g_main_pass.pipeline); - wgpuRenderPassEncoderSetBindGroup(pass, 0, g_main_pass.bind_group, 0, - nullptr); - wgpuRenderPassEncoderDraw(pass, g_main_pass.vertex_count, 1, 0, 0); - - // Draw Particles - wgpuRenderPassEncoderSetPipeline(pass, g_particle_render_pass.pipeline); - wgpuRenderPassEncoderSetBindGroup( - pass, 0, g_particle_render_pass.bind_group, 0, nullptr); - wgpuRenderPassEncoderDraw(pass, g_particle_render_pass.vertex_count, - g_particle_render_pass.instance_count, 0, 0); - - wgpuRenderPassEncoderEnd(pass); - } - - WGPUCommandBufferDescriptor cmd_desc = {}; - WGPUCommandBuffer commands = wgpuCommandEncoderFinish(encoder, &cmd_desc); - wgpuQueueSubmit(g_queue, 1, &commands); - wgpuSurfacePresent(g_surface); - - wgpuTextureViewRelease(view); - wgpuTextureRelease(surface_texture.texture); +void gpu_draw(float audio_peak, float aspect_ratio, float time, float beat) { + g_main_sequence.render_frame(time, beat, audio_peak, aspect_ratio, g_surface); } void gpu_shutdown() { -} + g_main_sequence.shutdown(); +}
\ No newline at end of file diff --git a/src/gpu/gpu.h b/src/gpu/gpu.h index a78c433..f9c322c 100644 --- a/src/gpu/gpu.h +++ b/src/gpu/gpu.h @@ -32,7 +32,7 @@ struct RenderPass { }; void gpu_init(GLFWwindow *window); -void gpu_draw(float audio_peak, float aspect_ratio, float time); +void gpu_draw(float audio_peak, float aspect_ratio, float time, float beat); void gpu_shutdown(); // Helper functions (exposed for internal/future use) @@ -42,10 +42,12 @@ struct ResourceBinding { // WGPUBufferBindingType_Storage }; -GpuBuffer gpu_create_buffer(size_t size, uint32_t usage, +GpuBuffer gpu_create_buffer(WGPUDevice device, size_t size, uint32_t usage, const void *data = nullptr); -ComputePass gpu_create_compute_pass(const char *shader_code, +ComputePass gpu_create_compute_pass(WGPUDevice device, const char *shader_code, ResourceBinding *bindings, int num_bindings); -RenderPass gpu_create_render_pass(const char *shader_code, +RenderPass gpu_create_render_pass(WGPUDevice device, + WGPUTextureFormat format, // Needed for render pipeline + const char *shader_code, ResourceBinding *bindings, int num_bindings); |
