summaryrefslogtreecommitdiff
path: root/src/gpu/gpu.cc
diff options
context:
space:
mode:
authorskal <pascal.massimino@gmail.com>2026-01-31 00:47:55 +0100
committerskal <pascal.massimino@gmail.com>2026-01-31 00:47:55 +0100
commit91f557d8777d6185ed47927318973d515f8dcbee (patch)
tree72c6280d8ed98bc19109bc447893da41af0bcecf /src/gpu/gpu.cc
parent8e199322ea4cb51d81c29d84120e4b142f7241b5 (diff)
Implement procedural audio generation, spectral effects, and WebGPU particle system
Diffstat (limited to 'src/gpu/gpu.cc')
-rw-r--r--src/gpu/gpu.cc546
1 files changed, 416 insertions, 130 deletions
diff --git a/src/gpu/gpu.cc b/src/gpu/gpu.cc
index 4d110e7..ec9922a 100644
--- a/src/gpu/gpu.cc
+++ b/src/gpu/gpu.cc
@@ -13,6 +13,7 @@
#include <cassert>
#include <cstdint>
#include <cstring>
+#include <math.h>
#include <vector>
#ifndef STRIP_ALL
@@ -26,9 +27,22 @@ static WGPUQueue g_queue = nullptr;
static WGPUSurface g_surface = nullptr;
static WGPUSurfaceConfiguration g_config = {};
-static WGPURenderPipeline g_render_pipeline = nullptr;
-static WGPUBuffer g_uniform_buffer = nullptr;
-static WGPUBindGroup g_bind_group = nullptr;
+// 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 WGPUStringView label_view(const char *str) {
#ifndef STRIP_ALL
@@ -47,6 +61,177 @@ static WGPUStringView str_view(const char *str) {
return {str, strlen(str)};
}
+// --- Helper Functions ---
+
+GpuBuffer gpu_create_buffer(size_t size, WGPUBufferUsage usage,
+ const void *data) {
+ WGPUBufferDescriptor desc = {};
+ desc.label = label_view("GpuBuffer");
+ desc.usage = usage;
+ desc.size = size;
+ desc.mappedAtCreation = (data != nullptr); // Map if we have initial data
+
+ WGPUBuffer buffer = wgpuDeviceCreateBuffer(g_device, &desc);
+
+ if (data) {
+ void *ptr = wgpuBufferGetMappedRange(buffer, 0, size);
+ memcpy(ptr, data, size);
+ wgpuBufferUnmap(buffer);
+ }
+
+ return {buffer, size};
+}
+
+RenderPass gpu_create_render_pass(const char *shader_code,
+ ResourceBinding *bindings, int num_bindings) {
+ RenderPass pass = {};
+
+ // Create Shader Module
+ WGPUShaderSourceWGSL wgsl_src = {};
+ wgsl_src.chain.sType = WGPUSType_ShaderSourceWGSL;
+ wgsl_src.code = str_view(shader_code);
+ WGPUShaderModuleDescriptor shader_desc = {};
+ shader_desc.nextInChain = &wgsl_src.chain;
+ WGPUShaderModule shader_module =
+ wgpuDeviceCreateShaderModule(g_device, &shader_desc);
+
+ // Create Bind Group Layout & Bind Group
+ std::vector<WGPUBindGroupLayoutEntry> bgl_entries;
+ std::vector<WGPUBindGroupEntry> bg_entries;
+
+ for (int i = 0; i < num_bindings; ++i) {
+ WGPUBindGroupLayoutEntry bgl_entry = {};
+ bgl_entry.binding = i;
+ bgl_entry.visibility = WGPUShaderStage_Vertex | WGPUShaderStage_Fragment;
+ bgl_entry.buffer.type = bindings[i].type;
+ bgl_entry.buffer.minBindingSize = bindings[i].buffer.size;
+ bgl_entries.push_back(bgl_entry);
+
+ WGPUBindGroupEntry bg_entry = {};
+ bg_entry.binding = i;
+ bg_entry.buffer = bindings[i].buffer.buffer;
+ bg_entry.size = bindings[i].buffer.size;
+ bg_entries.push_back(bg_entry);
+ }
+
+ WGPUBindGroupLayoutDescriptor bgl_desc = {};
+ bgl_desc.entryCount = (uint32_t)bgl_entries.size();
+ bgl_desc.entries = bgl_entries.data();
+ WGPUBindGroupLayout bind_group_layout =
+ wgpuDeviceCreateBindGroupLayout(g_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);
+
+ // Pipeline Layout
+ WGPUPipelineLayoutDescriptor pl_desc = {};
+ pl_desc.bindGroupLayoutCount = 1;
+ pl_desc.bindGroupLayouts = &bind_group_layout;
+ WGPUPipelineLayout pipeline_layout =
+ wgpuDeviceCreatePipelineLayout(g_device, &pl_desc);
+
+ // Render Pipeline
+ WGPUColorTargetState color_target = {};
+ color_target.format = g_config.format; // Use global swapchain format
+ color_target.writeMask = WGPUColorWriteMask_All;
+ color_target.blend = nullptr;
+
+ // Add additive blending for particles if it's the particle pass (hacky check
+ // based on vertex count or similar? No, let's just enable additive blending
+ // for everything for now as it looks cool for the demo, or make it
+ // configurable later. For now, simple replacement)
+ WGPUBlendState blend = {};
+ blend.color.srcFactor = WGPUBlendFactor_SrcAlpha;
+ blend.color.dstFactor = WGPUBlendFactor_One;
+ blend.color.operation = WGPUBlendOperation_Add;
+ blend.alpha.srcFactor = WGPUBlendFactor_SrcAlpha;
+ blend.alpha.dstFactor = WGPUBlendFactor_One;
+ blend.alpha.operation = WGPUBlendOperation_Add;
+ color_target.blend = &blend;
+
+ WGPUFragmentState fragment_state = {};
+ fragment_state.module = shader_module;
+ fragment_state.entryPoint = str_view("fs_main");
+ fragment_state.targetCount = 1;
+ fragment_state.targets = &color_target;
+
+ WGPURenderPipelineDescriptor pipeline_desc = {};
+ pipeline_desc.layout = pipeline_layout;
+ pipeline_desc.vertex.module = shader_module;
+ pipeline_desc.vertex.entryPoint = str_view("vs_main");
+ pipeline_desc.primitive.topology = WGPUPrimitiveTopology_TriangleList;
+ pipeline_desc.multisample.count = 1;
+ pipeline_desc.multisample.mask = 0xFFFFFFFF;
+ pipeline_desc.fragment = &fragment_state;
+
+ pass.pipeline = wgpuDeviceCreateRenderPipeline(g_device, &pipeline_desc);
+
+ return pass;
+}
+
+ComputePass gpu_create_compute_pass(const char *shader_code,
+ ResourceBinding *bindings,
+ int num_bindings) {
+ ComputePass pass = {};
+
+ WGPUShaderSourceWGSL wgsl_src = {};
+ wgsl_src.chain.sType = WGPUSType_ShaderSourceWGSL;
+ wgsl_src.code = str_view(shader_code);
+ WGPUShaderModuleDescriptor shader_desc = {};
+ shader_desc.nextInChain = &wgsl_src.chain;
+ WGPUShaderModule shader_module =
+ wgpuDeviceCreateShaderModule(g_device, &shader_desc);
+
+ std::vector<WGPUBindGroupLayoutEntry> bgl_entries;
+ std::vector<WGPUBindGroupEntry> bg_entries;
+
+ for (int i = 0; i < num_bindings; ++i) {
+ WGPUBindGroupLayoutEntry bgl_entry = {};
+ bgl_entry.binding = i;
+ bgl_entry.visibility = WGPUShaderStage_Compute;
+ bgl_entry.buffer.type = bindings[i].type;
+ bgl_entry.buffer.minBindingSize = bindings[i].buffer.size;
+ bgl_entries.push_back(bgl_entry);
+
+ WGPUBindGroupEntry bg_entry = {};
+ bg_entry.binding = i;
+ bg_entry.buffer = bindings[i].buffer.buffer;
+ bg_entry.size = bindings[i].buffer.size;
+ bg_entries.push_back(bg_entry);
+ }
+
+ WGPUBindGroupLayoutDescriptor bgl_desc = {};
+ bgl_desc.entryCount = (uint32_t)bgl_entries.size();
+ bgl_desc.entries = bgl_entries.data();
+ WGPUBindGroupLayout bind_group_layout =
+ wgpuDeviceCreateBindGroupLayout(g_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);
+
+ WGPUPipelineLayoutDescriptor pl_desc = {};
+ pl_desc.bindGroupLayoutCount = 1;
+ pl_desc.bindGroupLayouts = &bind_group_layout;
+ WGPUPipelineLayout pipeline_layout =
+ wgpuDeviceCreatePipelineLayout(g_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);
+ return pass;
+}
+
+// --- Main Init/Draw ---
+
#ifndef STRIP_ALL
static void handle_request_adapter(WGPURequestAdapterStatus status,
WGPUAdapter adapter, WGPUStringView message,
@@ -57,17 +242,6 @@ static void handle_request_adapter(WGPURequestAdapterStatus status,
printf("Request adapter failed: %.*s\n", (int)message.length, message.data);
}
}
-#else
-static void handle_request_adapter(WGPURequestAdapterStatus status,
- WGPUAdapter adapter, WGPUStringView message,
- void *userdata1, void *userdata2) {
- if (status == WGPURequestAdapterStatus_Success) {
- *((WGPUAdapter *)userdata1) = adapter;
- }
-}
-#endif
-
-#ifndef STRIP_ALL
static void handle_request_device(WGPURequestDeviceStatus status,
WGPUDevice device, WGPUStringView message,
void *userdata1, void *userdata2) {
@@ -77,7 +251,19 @@ static void handle_request_device(WGPURequestDeviceStatus status,
printf("Request device failed: %.*s\n", (int)message.length, message.data);
}
}
+static void handle_device_error(WGPUDevice const *device, WGPUErrorType type,
+ WGPUStringView message, void *userdata1,
+ void *userdata2) {
+ printf("WebGPU Error: %.*s\n", (int)message.length, message.data);
+}
#else
+static void handle_request_adapter(WGPURequestAdapterStatus status,
+ WGPUAdapter adapter, WGPUStringView message,
+ void *userdata1, void *userdata2) {
+ if (status == WGPURequestAdapterStatus_Success) {
+ *((WGPUAdapter *)userdata1) = adapter;
+ }
+}
static void handle_request_device(WGPURequestDeviceStatus status,
WGPUDevice device, WGPUStringView message,
void *userdata1, void *userdata2) {
@@ -87,15 +273,7 @@ static void handle_request_device(WGPURequestDeviceStatus status,
}
#endif
-#ifndef STRIP_ALL
-static void handle_device_error(WGPUDevice const *device, WGPUErrorType type,
- WGPUStringView message, void *userdata1,
- void *userdata2) {
- printf("WebGPU Error: %.*s\n", (int)message.length, message.data);
-}
-#endif
-
-const char *shader_wgsl_code = R"(
+const char *main_shader_wgsl = R"(
struct Uniforms {
audio_peak : f32,
aspect_ratio: f32,
@@ -139,18 +317,126 @@ fn fs_main() -> @location(0) vec4<f32> {
let g = sin(h + 2.0) * 0.9 + 0.3;
let b = sin(h + 4.0) * 0.5 + 0.5;
- // Add brightness boost on peak
let boost = uniforms.audio_peak * 0.5;
- return vec4<f32>(r + boost, g + boost, b + boost, 1.0);
+ 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);
- assert(g_instance);
-
g_surface = platform_create_wgpu_surface(g_instance);
- assert(g_surface);
WGPURequestAdapterOptions adapter_opts = {};
adapter_opts.compatibleSurface = g_surface;
@@ -159,13 +445,10 @@ void gpu_init(GLFWwindow *window) {
wgpuInstanceRequestAdapter(g_instance, &adapter_opts,
{nullptr, WGPUCallbackMode_WaitAnyOnly,
handle_request_adapter, &g_adapter, nullptr});
-
- while (!g_adapter) {
+ while (!g_adapter)
wgpuInstanceWaitAny(g_instance, 0, nullptr, 0);
- }
WGPUDeviceDescriptor device_desc = {};
- device_desc.label = label_view("Demo Device");
#ifndef STRIP_ALL
device_desc.uncapturedErrorCallbackInfo.callback = handle_device_error;
#endif
@@ -173,10 +456,8 @@ void gpu_init(GLFWwindow *window) {
wgpuAdapterRequestDevice(g_adapter, &device_desc,
{nullptr, WGPUCallbackMode_WaitAnyOnly,
handle_request_device, &g_device, nullptr});
-
- while (!g_device) {
+ while (!g_device)
wgpuInstanceWaitAny(g_instance, 0, nullptr, 0);
- }
g_queue = wgpuDeviceGetQueue(g_device);
@@ -184,77 +465,6 @@ void gpu_init(GLFWwindow *window) {
wgpuSurfaceGetCapabilities(g_surface, g_adapter, &caps);
WGPUTextureFormat swap_chain_format = caps.formats[0];
- WGPUShaderSourceWGSL wgsl_src = {};
- wgsl_src.chain.sType = WGPUSType_ShaderSourceWGSL;
- wgsl_src.code = str_view(shader_wgsl_code);
-
- WGPUShaderModuleDescriptor shader_module_desc = {};
- shader_module_desc.nextInChain = &wgsl_src.chain;
- shader_module_desc.label = label_view("Demo Shader");
-
- WGPUShaderModule shader_module =
- wgpuDeviceCreateShaderModule(g_device, &shader_module_desc);
-
- WGPUBufferDescriptor uniform_buffer_desc = {};
- uniform_buffer_desc.label = label_view("Uniform Buffer");
- uniform_buffer_desc.usage = WGPUBufferUsage_Uniform | WGPUBufferUsage_CopyDst;
- uniform_buffer_desc.size = sizeof(float) * 4; // Padding to 16 bytes
- g_uniform_buffer = wgpuDeviceCreateBuffer(g_device, &uniform_buffer_desc);
-
- WGPUBindGroupLayoutEntry bgl_entry = {};
- bgl_entry.binding = 0;
- bgl_entry.visibility = WGPUShaderStage_Vertex | WGPUShaderStage_Fragment;
- bgl_entry.buffer.type = WGPUBufferBindingType_Uniform;
- bgl_entry.buffer.minBindingSize = sizeof(float) * 3;
-
- WGPUBindGroupLayoutDescriptor bgl_desc = {};
- bgl_desc.label = label_view("Uniform Bind Group Layout");
- bgl_desc.entryCount = 1;
- bgl_desc.entries = &bgl_entry;
- WGPUBindGroupLayout bind_group_layout =
- wgpuDeviceCreateBindGroupLayout(g_device, &bgl_desc);
-
- WGPUBindGroupEntry bg_entry = {};
- bg_entry.binding = 0;
- bg_entry.buffer = g_uniform_buffer;
- bg_entry.size = sizeof(float) * 3;
-
- WGPUBindGroupDescriptor bg_desc = {};
- bg_desc.label = label_view("Uniform Bind Group");
- bg_desc.layout = bind_group_layout;
- bg_desc.entryCount = 1;
- bg_desc.entries = &bg_entry;
- g_bind_group = wgpuDeviceCreateBindGroup(g_device, &bg_desc);
-
- WGPUPipelineLayoutDescriptor pl_desc = {};
- pl_desc.label = label_view("Render Pipeline Layout");
- pl_desc.bindGroupLayoutCount = 1;
- pl_desc.bindGroupLayouts = &bind_group_layout;
- WGPUPipelineLayout pipeline_layout =
- wgpuDeviceCreatePipelineLayout(g_device, &pl_desc);
-
- WGPUColorTargetState color_target = {};
- color_target.format = swap_chain_format;
- color_target.writeMask = WGPUColorWriteMask_All;
-
- WGPUFragmentState fragment_state = {};
- fragment_state.module = shader_module;
- fragment_state.entryPoint = str_view("fs_main");
- fragment_state.targetCount = 1;
- fragment_state.targets = &color_target;
-
- WGPURenderPipelineDescriptor pipeline_desc = {};
- pipeline_desc.label = label_view("Render Pipeline");
- pipeline_desc.layout = pipeline_layout;
- pipeline_desc.vertex.module = shader_module;
- pipeline_desc.vertex.entryPoint = str_view("vs_main");
- pipeline_desc.primitive.topology = WGPUPrimitiveTopology_TriangleList;
- pipeline_desc.multisample.count = 1;
- pipeline_desc.multisample.mask = 0xFFFFFFFF;
- pipeline_desc.fragment = &fragment_state;
-
- g_render_pipeline = wgpuDeviceCreateRenderPipeline(g_device, &pipeline_desc);
-
int width, height;
glfwGetFramebufferSize(window, &width, &height);
g_config.device = g_device;
@@ -264,8 +474,63 @@ void gpu_init(GLFWwindow *window) {
g_config.height = height;
g_config.presentMode = WGPUPresentMode_Fifo;
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;
+
+ // 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_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;
}
void gpu_draw(float audio_peak, float aspect_ratio, float time) {
@@ -286,41 +551,62 @@ void gpu_draw(float audio_peak, float aspect_ratio, float time) {
float time;
float padding;
} uniforms = {audio_peak, aspect_ratio, time, 0.0f};
- wgpuQueueWriteBuffer(g_queue, g_uniform_buffer, 0, &uniforms,
+ wgpuQueueWriteBuffer(g_queue, g_uniform_buffer_struct.buffer, 0, &uniforms,
sizeof(uniforms));
WGPUCommandEncoderDescriptor encoder_desc = {};
- encoder_desc.label = label_view("Command Encoder");
WGPUCommandEncoder encoder =
wgpuDeviceCreateCommandEncoder(g_device, &encoder_desc);
- WGPURenderPassColorAttachment color_attachment = {};
- color_attachment.view = view;
- color_attachment.resolveTarget = nullptr;
- color_attachment.loadOp = WGPULoadOp_Clear;
- color_attachment.storeOp = WGPUStoreOp_Store;
+ // --- 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};
+ color_attachment.depthSlice = WGPU_DEPTH_SLICE_UNDEFINED;
+
+ WGPURenderPassDescriptor render_pass_desc = {};
+ render_pass_desc.colorAttachmentCount = 1;
+ render_pass_desc.colorAttachments = &color_attachment;
- // Background flash effect
- float flash = audio_peak * 0.6f;
- color_attachment.clearValue = {0.05 + flash, 0.1 + flash, 0.2 + flash, 1.0};
- color_attachment.depthSlice = WGPU_DEPTH_SLICE_UNDEFINED;
+ WGPURenderPassEncoder pass =
+ wgpuCommandEncoderBeginRenderPass(encoder, &render_pass_desc);
- WGPURenderPassDescriptor render_pass_desc = {};
- render_pass_desc.label = label_view("Render Pass");
- render_pass_desc.colorAttachmentCount = 1;
- render_pass_desc.colorAttachments = &color_attachment;
+ // 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);
- WGPURenderPassEncoder pass =
- wgpuCommandEncoderBeginRenderPass(encoder, &render_pass_desc);
+ // 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);
- wgpuRenderPassEncoderSetPipeline(pass, g_render_pipeline);
- wgpuRenderPassEncoderSetBindGroup(pass, 0, g_bind_group, 0, nullptr);
- // Drawing a heptagon with TriangleList: 7 triangles * 3 vertices = 21
- wgpuRenderPassEncoderDraw(pass, 21, 1, 0, 0);
- wgpuRenderPassEncoderEnd(pass);
+ wgpuRenderPassEncoderEnd(pass);
+ }
WGPUCommandBufferDescriptor cmd_desc = {};
- cmd_desc.label = label_view("Command Buffer");
WGPUCommandBuffer commands = wgpuCommandEncoderFinish(encoder, &cmd_desc);
wgpuQueueSubmit(g_queue, 1, &commands);
wgpuSurfacePresent(g_surface);