summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/audio/synth.cc10
-rw-r--r--src/audio/synth.h1
-rw-r--r--src/gpu/gpu.cc256
-rw-r--r--src/gpu/gpu.h2
-rw-r--r--src/main.cc27
-rw-r--r--src/platform.cc137
-rw-r--r--src/platform.h8
7 files changed, 416 insertions, 25 deletions
diff --git a/src/audio/synth.cc b/src/audio/synth.cc
index 451fcdb..98c12d9 100644
--- a/src/audio/synth.cc
+++ b/src/audio/synth.cc
@@ -26,10 +26,12 @@ static struct {
} g_synth_data;
static Voice g_voices[MAX_VOICES];
+static float g_current_output_peak = 0.0f; // Global peak for visualization
void synth_init() {
memset(&g_synth_data, 0, sizeof(g_synth_data));
memset(g_voices, 0, sizeof(g_voices));
+ g_current_output_peak = 0.0f;
}
void synth_shutdown() {
@@ -123,6 +125,8 @@ void synth_render(float *output_buffer, int num_frames) {
float window[WINDOW_SIZE];
hamming_window_512(window);
+ float current_peak_in_frame = 0.0f;
+
for (int i = 0; i < num_frames; ++i) {
float left_sample = 0.0f;
float right_sample = 0.0f;
@@ -165,7 +169,11 @@ void synth_render(float *output_buffer, int num_frames) {
output_buffer[i * 2] = left_sample;
output_buffer[i * 2 + 1] = right_sample;
+
+ current_peak_in_frame =
+ fmaxf(current_peak_in_frame, fmaxf(fabsf(left_sample), fabsf(right_sample)));
}
+ g_current_output_peak = current_peak_in_frame;
}
int synth_get_active_voice_count() {
@@ -177,3 +185,5 @@ int synth_get_active_voice_count() {
}
return count;
}
+
+float synth_get_output_peak() { return g_current_output_peak; }
diff --git a/src/audio/synth.h b/src/audio/synth.h
index eadde10..fe28e8d 100644
--- a/src/audio/synth.h
+++ b/src/audio/synth.h
@@ -23,3 +23,4 @@ void synth_commit_update(int spectrogram_id);
void synth_trigger_voice(int spectrogram_id, float volume, float pan);
void synth_render(float *output_buffer, int num_frames);
int synth_get_active_voice_count();
+float synth_get_output_peak();
diff --git a/src/gpu/gpu.cc b/src/gpu/gpu.cc
index e538d5d..a1da2db 100644
--- a/src/gpu/gpu.cc
+++ b/src/gpu/gpu.cc
@@ -1,8 +1,258 @@
#include "gpu.h"
+#include "platform.h"
-void gpu_init(GLFWwindow *) {
+#include <GLFW/glfw3.h>
+#include <webgpu.h>
+#include <wgpu.h>
+
+#include <algorithm>
+#include <cassert>
+#include <cstring>
+#include <iostream>
+#include <vector>
+
+static WGPUInstance g_instance = nullptr;
+static WGPUAdapter g_adapter = nullptr;
+static WGPUDevice g_device = nullptr;
+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;
+
+static WGPUStringView str_view(const char *str) {
+ if (!str) return {nullptr, 0};
+ return {str, strlen(str)};
+}
+
+static void handle_request_adapter(WGPURequestAdapterStatus status,
+ WGPUAdapter adapter, WGPUStringView message,
+ void *userdata1, void *userdata2) {
+ if (status == WGPURequestAdapterStatus_Success) {
+ *((WGPUAdapter *)userdata1) = adapter;
+ } else {
+ printf("Request adapter failed: %.*s\n", (int)message.length, message.data);
+ }
+}
+
+static void handle_request_device(WGPURequestDeviceStatus status,
+ WGPUDevice device, WGPUStringView message,
+ void *userdata1, void *userdata2) {
+ if (status == WGPURequestDeviceStatus_Success) {
+ *((WGPUDevice *)userdata1) = device;
+ } else {
+ 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);
}
-void gpu_draw() {
+
+const char *shader_wgsl_code = R"(
+struct Uniforms {
+ audio_peak : 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;
+
+ let base_scale = 0.5;
+ let pulse_scale = 0.2 * 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);
+ }
+
+ let i = tri_idx + f32(sub_idx - 1u);
+ let angle = i * 2.0 * PI / num_sides;
+ let x = scale * cos(angle);
+ let y = scale * sin(angle);
+
+ return vec4<f32>(x, y, 0.0, 1.0);
+}
+
+@fragment
+fn fs_main() -> @location(0) vec4<f32> {
+ let hue = uniforms.audio_peak * 0.5;
+ let r = sin(hue + 0.0) * 0.5 + 0.5;
+ let g = sin(hue + 2.0) * 0.5 + 0.5;
+ let b = sin(hue + 4.0) * 0.5 + 0.5;
+ return vec4<f32>(r, g, b, 1.0);
+}
+)";
+
+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;
+ adapter_opts.powerPreference = WGPUPowerPreference_HighPerformance;
+
+ wgpuInstanceRequestAdapter(
+ g_instance, &adapter_opts,
+ {nullptr, WGPUCallbackMode_WaitAnyOnly, handle_request_adapter, &g_adapter,
+ nullptr});
+
+ while (!g_adapter) {
+ wgpuInstanceWaitAny(g_instance, 0, nullptr, 0);
+ }
+
+ WGPUDeviceDescriptor device_desc = {};
+ device_desc.label = str_view("Demo Device");
+ device_desc.uncapturedErrorCallbackInfo.callback = handle_device_error;
+
+ wgpuAdapterRequestDevice(
+ g_adapter, &device_desc,
+ {nullptr, WGPUCallbackMode_WaitAnyOnly, handle_request_device, &g_device,
+ nullptr});
+
+ while (!g_device) {
+ wgpuInstanceWaitAny(g_instance, 0, nullptr, 0);
+ }
+
+ g_queue = wgpuDeviceGetQueue(g_device);
+
+ WGPUSurfaceCapabilities caps = {};
+ 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 = str_view("Demo Shader");
+
+ WGPUShaderModule shader_module =
+ wgpuDeviceCreateShaderModule(g_device, &shader_module_desc);
+
+ WGPUBufferDescriptor uniform_buffer_desc = {};
+ uniform_buffer_desc.label = str_view("Uniform Buffer");
+ uniform_buffer_desc.usage = WGPUBufferUsage_Uniform | WGPUBufferUsage_CopyDst;
+ uniform_buffer_desc.size = sizeof(float);
+ 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);
+
+ WGPUBindGroupLayoutDescriptor bgl_desc = {};
+ 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);
+
+ WGPUBindGroupDescriptor bg_desc = {};
+ 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.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.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;
+ g_config.format = swap_chain_format;
+ g_config.width = width;
+ g_config.height = height;
+ g_config.presentMode = WGPUPresentMode_Fifo;
+ g_config.alphaMode = WGPUCompositeAlphaMode_Opaque;
+
+ wgpuSurfaceConfigure(g_surface, &g_config);
}
-void gpu_shutdown() {
+
+void gpu_draw(float audio_peak) {
+ 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);
+
+ wgpuQueueWriteBuffer(g_queue, g_uniform_buffer, 0, &audio_peak, sizeof(float));
+
+ WGPUCommandEncoderDescriptor encoder_desc = {};
+ WGPUCommandEncoder encoder =
+ wgpuDeviceCreateCommandEncoder(g_device, &encoder_desc);
+
+ WGPURenderPassColorAttachment color_attachment = {};
+ color_attachment.view = view;
+ color_attachment.loadOp = WGPULoadOp_Clear;
+ color_attachment.storeOp = WGPUStoreOp_Store;
+ color_attachment.clearValue = {0.1, 0.2, 0.3, 1.0};
+
+ WGPURenderPassDescriptor render_pass_desc = {};
+ render_pass_desc.colorAttachmentCount = 1;
+ render_pass_desc.colorAttachments = &color_attachment;
+
+ WGPURenderPassEncoder pass =
+ wgpuCommandEncoderBeginRenderPass(encoder, &render_pass_desc);
+
+ 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);
+
+ 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_shutdown() {} \ No newline at end of file
diff --git a/src/gpu/gpu.h b/src/gpu/gpu.h
index d36f618..127ca42 100644
--- a/src/gpu/gpu.h
+++ b/src/gpu/gpu.h
@@ -2,5 +2,5 @@
struct GLFWwindow;
void gpu_init(GLFWwindow *window);
-void gpu_draw();
+void gpu_draw(float audio_peak);
void gpu_shutdown();
diff --git a/src/main.cc b/src/main.cc
index 56d755c..315fa10 100644
--- a/src/main.cc
+++ b/src/main.cc
@@ -26,15 +26,23 @@ void generate_tone(float *buffer, float freq) {
}
}
-int main() {
- platform_init();
+int main(int argc, char **argv) {
+ bool fullscreen_enabled = false;
+ for (int i = 1; i < argc; ++i) {
+ if (strcmp(argv[i], "--fullscreen") == 0) {
+ fullscreen_enabled = true;
+ break;
+ }
+ }
+
+ platform_init_window(fullscreen_enabled);
gpu_init(platform_get_window());
audio_init();
generate_tone(g_spec_buffer_a, 440.0f); // A4
- generate_tone(g_spec_buffer_b, 880.0f); // A5
+ generate_tone(g_spec_buffer_b, 0.0f); // A5
- Spectrogram spec = {g_spec_buffer_a, g_spec_buffer_b, SPEC_FRAMES};
+ const Spectrogram spec = {g_spec_buffer_a, g_spec_buffer_b, SPEC_FRAMES};
int tone_id = synth_register_spectrogram(&spec);
double last_beat_time = 0.0;
@@ -45,21 +53,22 @@ int main() {
double current_time = platform_get_time();
if (current_time - last_beat_time > SECONDS_PER_BEAT) {
- synth_trigger_voice(tone_id, 0.5f, 0.0f);
+ const float pan = (beat_count & 1) ? -.8 : .8;
+ synth_trigger_voice(tone_id, 0.8f, pan);
last_beat_time = current_time;
beat_count++;
- if (beat_count == 8) {
+ if (beat_count % 4 == 0) {
// Time to update the sound!
float *back_buffer = synth_begin_update(tone_id);
if (back_buffer) {
- generate_tone(back_buffer, 220.0f); // A3
+ generate_tone(back_buffer, (beat_count % 8) == 4 ? 220.0f : 480.f); // A3
synth_commit_update(tone_id);
}
- }
+ }
}
- gpu_draw();
+ gpu_draw(synth_get_output_peak());
audio_update();
}
diff --git a/src/platform.cc b/src/platform.cc
index bfb566c..09cc7ac 100644
--- a/src/platform.cc
+++ b/src/platform.cc
@@ -1,11 +1,55 @@
#include "platform.h"
+
+#ifdef _WIN32
+#define GLFW_EXPOSE_NATIVE_WIN32
+#elif defined(__APPLE__)
+#define GLFW_EXPOSE_NATIVE_COCOA
+#else
+#define GLFW_EXPOSE_NATIVE_X11
+#define GLFW_EXPOSE_NATIVE_WAYLAND
+#endif
+
#include <GLFW/glfw3.h>
+#include <GLFW/glfw3native.h>
+
+#ifdef __APPLE__
+#import <QuartzCore/CAMetalLayer.h>
+#import <AppKit/NSWindow.h>
+#import <AppKit/NSView.h>
+#endif
static GLFWwindow *window = nullptr;
+static int windowed_x, windowed_y, windowed_w, windowed_h;
+static bool g_is_fullscreen = false;
-void platform_init() {
+static void glfw_key_callback(GLFWwindow *cb_window, int key, int scancode,
+ int action, int mods) {
+ if (action == GLFW_PRESS) {
+ if (key == GLFW_KEY_ESCAPE) {
+ glfwSetWindowShouldClose(cb_window, GLFW_TRUE);
+ } else if (key == GLFW_KEY_F) {
+ platform_toggle_fullscreen();
+ }
+ }
+}
+
+void platform_init_window(bool fullscreen) {
glfwInit();
+ glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
window = glfwCreateWindow(1280, 720, "demo64k", nullptr, nullptr);
+ glfwSetKeyCallback(window, glfw_key_callback);
+
+ g_is_fullscreen = fullscreen;
+ if (fullscreen) {
+ // Save current windowed mode dimensions before going fullscreen
+ glfwGetWindowPos(window, &windowed_x, &windowed_y);
+ glfwGetWindowSize(window, &windowed_w, &windowed_h);
+
+ GLFWmonitor *monitor = glfwGetPrimaryMonitor();
+ const GLFWvidmode *mode = glfwGetVideoMode(monitor);
+ glfwSetWindowMonitor(window, monitor, 0, 0, mode->width, mode->height,
+ mode->refreshRate);
+ }
}
void platform_shutdown() {
@@ -13,18 +57,89 @@ void platform_shutdown() {
glfwTerminate();
}
-void platform_poll() {
- glfwPollEvents();
-}
+void platform_poll() { glfwPollEvents(); }
-bool platform_should_close() {
- return glfwWindowShouldClose(window);
-}
+bool platform_should_close() { return glfwWindowShouldClose(window); }
+
+void platform_toggle_fullscreen() {
+ g_is_fullscreen = !g_is_fullscreen;
+ if (g_is_fullscreen) {
+ glfwGetWindowPos(window, &windowed_x, &windowed_y);
+ glfwGetWindowSize(window, &windowed_w, &windowed_h);
-GLFWwindow *platform_get_window() {
- return window;
+ GLFWmonitor *monitor = glfwGetPrimaryMonitor();
+ const GLFWvidmode *mode = glfwGetVideoMode(monitor);
+ glfwSetWindowMonitor(window, monitor, 0, 0, mode->width, mode->height,
+ mode->refreshRate);
+ } else {
+ glfwSetWindowMonitor(window, nullptr, windowed_x, windowed_y, windowed_w,
+ windowed_h, 0);
+ }
}
-double platform_get_time() {
- return glfwGetTime();
+GLFWwindow *platform_get_window() { return window; }
+
+double platform_get_time() { return glfwGetTime(); }
+
+WGPUSurface platform_create_wgpu_surface(WGPUInstance instance) {
+#if defined(GLFW_EXPOSE_NATIVE_COCOA)
+ id metal_layer = NULL;
+ NSWindow *ns_window = glfwGetCocoaWindow(window);
+ [ns_window.contentView setWantsLayer:YES];
+ metal_layer = [CAMetalLayer layer];
+ [ns_window.contentView setLayer:metal_layer];
+
+ WGPUSurfaceSourceMetalLayer metal_src = {};
+ metal_src.chain.sType = WGPUSType_SurfaceSourceMetalLayer;
+ metal_src.layer = metal_layer;
+
+ WGPUSurfaceDescriptor surface_desc = {};
+ surface_desc.nextInChain = (const WGPUChainedStruct *)&metal_src;
+
+ return wgpuInstanceCreateSurface(instance, &surface_desc);
+
+#elif defined(GLFW_EXPOSE_NATIVE_WIN32)
+ HWND hwnd = glfwGetWin32Window(window);
+ HINSTANCE hinstance = GetModuleHandle(NULL);
+
+ WGPUSurfaceSourceWindowsHWND win_src = {};
+ win_src.chain.sType = WGPUSType_SurfaceSourceWindowsHWND;
+ win_src.hinstance = hinstance;
+ win_src.hwnd = hwnd;
+
+ WGPUSurfaceDescriptor surface_desc = {};
+ surface_desc.nextInChain = (const WGPUChainedStruct *)&win_src;
+
+ return wgpuInstanceCreateSurface(instance, &surface_desc);
+
+#elif defined(GLFW_EXPOSE_NATIVE_X11) || defined(GLFW_EXPOSE_NATIVE_WAYLAND)
+ if (glfwGetPlatform() == GLFW_PLATFORM_X11) {
+ Display *x11_display = glfwGetX11Display();
+ Window x11_window = glfwGetX11Window(window);
+
+ WGPUSurfaceSourceXlibWindow x11_src = {};
+ x11_src.chain.sType = WGPUSType_SurfaceSourceXlibWindow;
+ x11_src.display = x11_display;
+ x11_src.window = x11_window;
+
+ WGPUSurfaceDescriptor surface_desc = {};
+ surface_desc.nextInChain = (const WGPUChainedStruct *)&x11_src;
+
+ return wgpuInstanceCreateSurface(instance, &surface_desc);
+ } else if (glfwGetPlatform() == GLFW_PLATFORM_WAYLAND) {
+ struct wl_display *wayland_display = glfwGetWaylandDisplay();
+ struct wl_surface *wayland_surface = glfwGetWaylandWindow(window);
+
+ WGPUSurfaceSourceWaylandSurface wl_src = {};
+ wl_src.chain.sType = WGPUSType_SurfaceSourceWaylandSurface;
+ wl_src.display = wayland_display;
+ wl_src.surface = wayland_surface;
+
+ WGPUSurfaceDescriptor surface_desc = {};
+ surface_desc.nextInChain = (const WGPUChainedStruct *)&wl_src;
+
+ return wgpuInstanceCreateSurface(instance, &surface_desc);
+ }
+#endif
+ return nullptr;
}
diff --git a/src/platform.h b/src/platform.h
index 4d9a1f9..2146e10 100644
--- a/src/platform.h
+++ b/src/platform.h
@@ -1,9 +1,15 @@
#pragma once
+#include <webgpu.h>
+
struct GLFWwindow;
-void platform_init();
+void platform_init_window(bool fullscreen);
void platform_shutdown();
void platform_poll();
bool platform_should_close();
+void platform_toggle_fullscreen();
GLFWwindow *platform_get_window();
double platform_get_time();
+
+// Creates a WebGPU surface for the current platform window.
+WGPUSurface platform_create_wgpu_surface(WGPUInstance instance);