diff options
| -rw-r--r-- | src/audio/gen.cc | 4 | ||||
| -rw-r--r-- | src/gpu/gpu.cc | 546 | ||||
| -rw-r--r-- | src/gpu/gpu.h | 41 | ||||
| -rw-r--r-- | src/main.cc | 15 | ||||
| -rw-r--r-- | src/util/mini_math.h | 2 | ||||
| -rw-r--r-- | tools/spectool.cc | 28 |
6 files changed, 486 insertions, 150 deletions
diff --git a/src/audio/gen.cc b/src/audio/gen.cc index 3666116..8d594f2 100644 --- a/src/audio/gen.cc +++ b/src/audio/gen.cc @@ -38,8 +38,8 @@ std::vector<float> generate_note_spectrogram(const NoteParams ¶ms, } // Vibrato - float vib = - sinf(t * params.vibrato_rate * 2.0f * 3.14159f) * params.vibrato_depth; + float vib = sinf(t * params.vibrato_rate * 2.0f * 3.14159f) * + params.vibrato_depth; // Randomness float pitch_rnd = 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); diff --git a/src/gpu/gpu.h b/src/gpu/gpu.h index 00f5450..58b0307 100644 --- a/src/gpu/gpu.h +++ b/src/gpu/gpu.h @@ -3,8 +3,49 @@ // Coordinates WebGPU lifecycle and draw calls. #pragma once + +#include <webgpu.h> + struct GLFWwindow; +// Basic wrapper for WebGPU buffers +struct GpuBuffer { + WGPUBuffer buffer; + size_t size; +}; + +// Encapsulates a compute operation +struct ComputePass { + WGPUComputePipeline pipeline; + WGPUBindGroup bind_group; + uint32_t workgroup_size_x; + uint32_t workgroup_size_y; + uint32_t workgroup_size_z; +}; + +// Encapsulates a render operation +struct RenderPass { + WGPURenderPipeline pipeline; + WGPUBindGroup bind_group; + uint32_t vertex_count; + uint32_t instance_count; +}; + void gpu_init(GLFWwindow *window); void gpu_draw(float audio_peak, float aspect_ratio, float time); void gpu_shutdown(); + +// Helper functions (exposed for internal/future use) +struct ResourceBinding { + GpuBuffer buffer; + WGPUBufferBindingType type; // e.g., WGPUBufferBindingType_Uniform, + // WGPUBufferBindingType_Storage +}; + +GpuBuffer gpu_create_buffer(size_t size, WGPUBufferUsage usage, + const void *data = nullptr); +ComputePass gpu_create_compute_pass(const char *shader_code, + ResourceBinding *bindings, + int num_bindings); +RenderPass gpu_create_render_pass(const char *shader_code, + ResourceBinding *bindings, int num_bindings); diff --git a/src/main.cc b/src/main.cc index 0bc28d6..9d7c41b 100644 --- a/src/main.cc +++ b/src/main.cc @@ -62,11 +62,13 @@ int generate_melody() { srand(12345); // Fixed seed for reproducibility for (int i = 0; i < 128; ++i) { - if (i % 4 == 0) continue; // Rest on beat 1 of every bar + if (i % 4 == 0) + continue; // Rest on beat 1 of every bar NoteParams params = {}; params.base_freq = notes[rand() % num_notes]; - if (rand() % 4 == 0) params.base_freq *= 2.0f; // Occasional octave up + if (rand() % 4 == 0) + params.base_freq *= 2.0f; // Occasional octave up params.duration_sec = (rand() % 2 == 0) ? 0.2f : 0.4f; params.amplitude = 0.4f; @@ -80,12 +82,14 @@ int generate_melody() { params.amp_randomness = 0.05f; int note_frames = 0; - std::vector<float> note_data = generate_note_spectrogram(params, ¬e_frames); + std::vector<float> note_data = + generate_note_spectrogram(params, ¬e_frames); // Apply some post-processing for texture apply_spectral_noise(note_data, note_frames, 0.2f); // Add grit if (i % 2 == 0) { - apply_spectral_comb(note_data, note_frames, 10.0f, 0.8f); // Phaser-like effect + apply_spectral_comb(note_data, note_frames, 10.0f, + 0.8f); // Phaser-like effect } // Calculate offset in frames @@ -94,7 +98,8 @@ int generate_melody() { float beat_time = i * SECONDS_PER_BEAT; int frame_offset = (int)(beat_time * 32000.0f / DCT_SIZE); - paste_spectrogram(g_melody_data, &melody_frames, note_data, note_frames, frame_offset); + paste_spectrogram(g_melody_data, &melody_frames, note_data, note_frames, + frame_offset); } Spectrogram spec; diff --git a/src/util/mini_math.h b/src/util/mini_math.h index b809323..3024737 100644 --- a/src/util/mini_math.h +++ b/src/util/mini_math.h @@ -103,7 +103,7 @@ struct vec3 { struct { float x, y, z; float _; - }; // _ is padding for 16-byte alignment + }; // _ is padding for 16-byte alignment float v[4]; // Size 4 to match alignment }; vec3(float x = 0, float y = 0, float z = 0) : x(x), y(y), z(z), _(0) { diff --git a/tools/spectool.cc b/tools/spectool.cc index 7733b98..663e89e 100644 --- a/tools/spectool.cc +++ b/tools/spectool.cc @@ -155,13 +155,14 @@ int play_spec(const char *in_path) { int test_gen(const char *out_path) { printf("Generating test spectrogram -> %s\n", out_path); - + std::vector<float> track_data; int track_frames = 0; - + // Generate a simple C Major scale - float freqs[] = {261.63f, 293.66f, 329.63f, 349.23f, 392.00f, 440.00f, 493.88f, 523.25f}; - + float freqs[] = {261.63f, 293.66f, 329.63f, 349.23f, + 392.00f, 440.00f, 493.88f, 523.25f}; + srand(time(NULL)); for (int i = 0; i < 8; ++i) { @@ -177,15 +178,17 @@ int test_gen(const char *out_path) { params.harmonic_decay = 0.5f; params.pitch_randomness = 1.0f; params.amp_randomness = 0.05f; - + int note_frames = 0; - std::vector<float> note_data = generate_note_spectrogram(params, ¬e_frames); - + std::vector<float> note_data = + generate_note_spectrogram(params, ¬e_frames); + // Paste at 0.4s intervals (overlap) int offset = (int)(i * 0.4f * 32000.0f / DCT_SIZE); - paste_spectrogram(track_data, &track_frames, note_data, note_frames, offset); + paste_spectrogram(track_data, &track_frames, note_data, note_frames, + offset); } - + // Write to file (Duplicate logic, but fine for now) FILE *f_out = fopen(out_path, "wb"); if (!f_out) { @@ -202,7 +205,7 @@ int test_gen(const char *out_path) { fwrite(&header, sizeof(SpecHeader), 1, f_out); fwrite(track_data.data(), sizeof(float), track_data.size(), f_out); fclose(f_out); - + printf("Generated %d frames.\n", track_frames); return 0; } @@ -214,7 +217,8 @@ void print_usage() { "save as a spectrogram.\n"); printf( " play <input.spec> Play a spectrogram file.\n"); - printf(" test_gen <output.spec> Generate a test spectrogram.\n"); + printf(" test_gen <output.spec> Generate a test " + "spectrogram.\n"); } int main(int argc, char **argv) { @@ -233,7 +237,7 @@ int main(int argc, char **argv) { } return analyze_audio(argv[2], argv[3]); } else if (strcmp(command, "play") == 0) { - if (argc < 3) { + if (argc < 3) { printf("Error: 'play' command requires an input file.\n"); print_usage(); return 1; |
