summaryrefslogtreecommitdiff
path: root/src/gpu/gpu.cc
diff options
context:
space:
mode:
authorskal <pascal.massimino@gmail.com>2026-01-31 15:36:45 +0100
committerskal <pascal.massimino@gmail.com>2026-01-31 15:36:45 +0100
commit72c0cbcb38732b698d33d52459fed67af56ee2ec (patch)
tree83cabb089d76c7dda49ac786ad829b6f37d17bbf /src/gpu/gpu.cc
parente96f282d77bd114493c8d9097c7fb7eaaad2338b (diff)
feat: Implement Sequence and Effect system for demo choreography
Refactors the rendering pipeline into a modular Sequence/Effect system. 'MainSequence' manages the final frame rendering and a list of 'Sequence' layers. 'Sequence' manages a timeline of 'Effect' objects (start/end/priority). Concrete effects (Heptagon, Particles) are moved to 'demo_effects.cc'. Updates main loop to pass beat and aspect ratio.
Diffstat (limited to 'src/gpu/gpu.cc')
-rw-r--r--src/gpu/gpu.cc358
1 files changed, 27 insertions, 331 deletions
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