From 5d20c892dedce7bc7486acbd72fbd35da69e413e Mon Sep 17 00:00:00 2001 From: skal Date: Wed, 20 May 2026 22:44:44 +0200 Subject: fix: code review cleanup — bugs, dead code, factorization (-167 lines) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bugs: - B1: fix dead tempo debug (prev_tempo captured after assignment) - B2: fix ReloadAssetsFromFile leak for disk-loaded assets; simplify DropAsset - B3: fix get_free_pool_slot leak (unregister synth + free data on reuse) - B4: volatile -> std::atomic with acquire/release in miniaudio_backend, synth - B5: fix unaligned reads in scene_loader (memcpy-based read_f32/read_u32) - B6: fix shader module + BGL + pipeline layout leaks in gpu.cc, pipeline_builder Dead code: - D1: remove unused particle_defs.h - D3: remove create_post_process_pipeline_simple (zero callers) - D4: remove empty gpu_draw() - D5: remove write-only Hybrid3D::initialized_ - D6: remove legacy pending buffer path in audio.cc Factorization: - F1: Effect::run_fullscreen_pass() replaces boilerplate in 5 effects - F2: particle_common.wgsl snippet, #include in 3 WGSL shaders - F3: gpu_create_shader_module() helper, used in 3 call sites - F5: get_world_aabb() shared between bvh.cc and physics.cc - F6: samples_to_seconds() replaces 6 inline expressions - F7: gpu_create_linear/nearest_sampler use SamplerCache; add nearest() preset 37/37 tests passing. handoff(Claude): code review batch — all items verified, no regressions. --- src/3d/bvh.cc | 16 +++---- src/3d/bvh.h | 3 ++ src/3d/physics.cc | 26 +---------- src/3d/scene_loader.cc | 77 +++++++++++++++----------------- src/app/main.cc | 4 +- src/audio/audio.cc | 59 +++--------------------- src/audio/backend/miniaudio_backend.cc | 20 +++++---- src/audio/backend/miniaudio_backend.h | 8 ++-- src/audio/ring_buffer.h | 5 +++ src/audio/synth.cc | 51 +++++++++++---------- src/audio/tracker.cc | 10 ++++- src/effects/hybrid3_d_effect.cc | 2 - src/effects/hybrid3_d_effect.h | 1 - src/effects/particle_compute.wgsl | 8 +--- src/effects/particle_defs.h | 13 ------ src/effects/particle_render.wgsl | 8 +--- src/effects/particle_spray_compute.wgsl | 8 +--- src/effects/peak_meter_effect.cc | 17 +------ src/effects/placeholder_effect.cc | 15 +------ src/effects/scene1_effect.cc | 15 +------ src/effects/scene2_effect.cc | 15 +------ src/effects/shaders.cc | 2 + src/gpu/effect.cc | 25 +++++++++++ src/gpu/effect.h | 7 +++ src/gpu/gpu.cc | 71 +++++++++++++---------------- src/gpu/gpu.h | 7 ++- src/gpu/gpu_headless.cc | 9 ---- src/gpu/gpu_stub.cc | 9 ---- src/gpu/pipeline_builder.cc | 11 +++-- src/gpu/post_process_helper.cc | 34 +++----------- src/gpu/post_process_helper.h | 5 --- src/gpu/sampler_cache.h | 4 ++ src/gpu/wgsl_effect.cc | 17 +------ src/shaders/compute/particle_common.wgsl | 7 +++ src/util/asset_manager.cc | 36 +++++++-------- workspaces/main/assets.txt | 1 + 36 files changed, 233 insertions(+), 393 deletions(-) delete mode 100644 src/effects/particle_defs.h create mode 100644 src/shaders/compute/particle_common.wgsl diff --git a/src/3d/bvh.cc b/src/3d/bvh.cc index 5f7abef..129016c 100644 --- a/src/3d/bvh.cc +++ b/src/3d/bvh.cc @@ -4,14 +4,6 @@ #include "3d/bvh.h" #include -namespace { - -struct ObjectInfo { - int index; - AABB aabb; - vec3 centroid; -}; - AABB get_world_aabb(const Object3D& obj) { BoundingVolume local = obj.get_local_bounds(); mat4 model = obj.get_model_matrix(); @@ -35,6 +27,14 @@ AABB get_world_aabb(const Object3D& obj) { return world; } +namespace { + +struct ObjectInfo { + int index; + AABB aabb; + vec3 centroid; +}; + int build_recursive(std::vector& nodes, std::vector& obj_info, int start, int end) { int node_idx = (int)nodes.size(); diff --git a/src/3d/bvh.h b/src/3d/bvh.h index 97e9a06..af4b152 100644 --- a/src/3d/bvh.h +++ b/src/3d/bvh.h @@ -63,6 +63,9 @@ class BVH { void query(const AABB& box, std::vector& out_indices) const; }; +// Compute world-space AABB by transforming local bounds corners +AABB get_world_aabb(const Object3D& obj); + class BVHBuilder { public: static void build(BVH& out_bvh, const std::vector& objects); diff --git a/src/3d/physics.cc b/src/3d/physics.cc index 2aa101d..db27e95 100644 --- a/src/3d/physics.cc +++ b/src/3d/physics.cc @@ -6,31 +6,7 @@ #include "3d/sdf_cpu.h" #include -namespace { -// Helper to get world AABB (copied from bvh.cc or shared) -AABB get_world_aabb(const Object3D& obj) { - BoundingVolume local = obj.get_local_bounds(); - mat4 model = obj.get_model_matrix(); - - vec3 corners[8] = { - {local.min.x, local.min.y, local.min.z}, - {local.max.x, local.min.y, local.min.z}, - {local.min.x, local.max.y, local.min.z}, - {local.max.x, local.max.y, local.min.z}, - {local.min.x, local.min.y, local.max.z}, - {local.max.x, local.min.y, local.max.z}, - {local.min.x, local.max.y, local.max.z}, - {local.max.x, local.max.y, local.max.z}, - }; - - AABB world; - for (int i = 0; i < 8; ++i) { - vec4 p = model * vec4(corners[i].x, corners[i].y, corners[i].z, 1.0f); - world.expand(p.xyz()); - } - return world; -} -} // namespace +// get_world_aabb() is declared in bvh.h float PhysicsSystem::sample_sdf(const Object3D& obj, vec3 world_p) { mat4 inv_model = obj.get_model_matrix().inverse(); diff --git a/src/3d/scene_loader.cc b/src/3d/scene_loader.cc index eb20954..d5c1879 100644 --- a/src/3d/scene_loader.cc +++ b/src/3d/scene_loader.cc @@ -11,6 +11,18 @@ namespace SceneLoader { +// Safe unaligned read helpers +static inline uint32_t read_u32(const uint8_t* p) { + uint32_t v; + std::memcpy(&v, p, sizeof(v)); + return v; +} +static inline float read_f32(const uint8_t* p) { + float v; + std::memcpy(&v, p, sizeof(v)); + return v; +} + bool LoadScene(Scene& scene, const uint8_t* data, size_t size) { if (!data || size < 16) { // Header size check printf("SceneLoader: Data too small\n"); @@ -25,11 +37,11 @@ bool LoadScene(Scene& scene, const uint8_t* data, size_t size) { size_t offset = 4; - uint32_t num_objects = *(const uint32_t*)(data + offset); + uint32_t num_objects = read_u32(data + offset); offset += 4; - uint32_t num_cameras = *(const uint32_t*)(data + offset); + uint32_t num_cameras = read_u32(data + offset); offset += 4; - uint32_t num_lights = *(const uint32_t*)(data + offset); + uint32_t num_lights = read_u32(data + offset); offset += 4; // printf("SceneLoader: Loading %d objects, %d cameras, %d lights\n", @@ -45,49 +57,33 @@ bool LoadScene(Scene& scene, const uint8_t* data, size_t size) { if (offset + 4 > size) return false; - uint32_t type_val = *(const uint32_t*)(data + offset); + uint32_t type_val = read_u32(data + offset); offset += 4; ObjectType type = (ObjectType)type_val; if (offset + 12 + 16 + 12 + 16 > size) return false; // Transforms + Color - float px = *(const float*)(data + offset); - offset += 4; - float py = *(const float*)(data + offset); - offset += 4; - float pz = *(const float*)(data + offset); - offset += 4; + float px = read_f32(data + offset); offset += 4; + float py = read_f32(data + offset); offset += 4; + float pz = read_f32(data + offset); offset += 4; vec3 pos(px, py, pz); - float rx = *(const float*)(data + offset); - offset += 4; - float ry = *(const float*)(data + offset); - offset += 4; - float rz = *(const float*)(data + offset); - offset += 4; - float rw = *(const float*)(data + offset); - offset += 4; + float rx = read_f32(data + offset); offset += 4; + float ry = read_f32(data + offset); offset += 4; + float rz = read_f32(data + offset); offset += 4; + float rw = read_f32(data + offset); offset += 4; quat rot(rx, ry, rz, rw); - float sx = *(const float*)(data + offset); - offset += 4; - float sy = *(const float*)(data + offset); - offset += 4; - float sz = *(const float*)(data + offset); - offset += 4; + float sx = read_f32(data + offset); offset += 4; + float sy = read_f32(data + offset); offset += 4; + float sz = read_f32(data + offset); offset += 4; vec3 scale(sx, sy, sz); - // Color components (cr, cg, cb, ca) - float cr = *(const float*)(data + offset); - offset += 4; - float cg = *(const float*)(data + offset); - offset += 4; - float cb = *(const float*)(data + offset); - offset += 4; - // Read ca, advance offset AFTER reading ca, then construct color - float ca = *(const float*)(data + offset); - offset += 4; // Offset is now after ca + float cr = read_f32(data + offset); offset += 4; + float cg = read_f32(data + offset); offset += 4; + float cb = read_f32(data + offset); offset += 4; + float ca = read_f32(data + offset); offset += 4; vec4 color(cr, cg, cb, ca); // Plane Distance (if type == PLANE) @@ -96,7 +92,7 @@ bool LoadScene(Scene& scene, const uint8_t* data, size_t size) { // Check bounds before reading plane_distance if (offset + 4 > size) return false; - plane_distance = *(const float*)(data + offset); + plane_distance = read_f32(data + offset); offset += 4; // Advance offset after reading plane_distance } @@ -105,7 +101,7 @@ bool LoadScene(Scene& scene, const uint8_t* data, size_t size) { // either after ca (if not PLANE) or after plane_distance (if PLANE). if (offset + 4 > size) return false; - uint32_t name_len = *(const uint32_t*)(data + offset); + uint32_t name_len = read_u32(data + offset); offset += 4; AssetId mesh_id = (AssetId)0; // Default or INVALID (if 0 is invalid) @@ -131,12 +127,9 @@ bool LoadScene(Scene& scene, const uint8_t* data, size_t size) { // Physics properties if (offset + 4 + 4 + 4 > size) return false; - float mass = *(const float*)(data + offset); - offset += 4; - float restitution = *(const float*)(data + offset); - offset += 4; - uint32_t is_static_u32 = *(const uint32_t*)(data + offset); - offset += 4; + float mass = read_f32(data + offset); offset += 4; + float restitution = read_f32(data + offset); offset += 4; + uint32_t is_static_u32 = read_u32(data + offset); offset += 4; bool is_static = (is_static_u32 != 0); // Create Object3D diff --git a/src/app/main.cc b/src/app/main.cc index c85877b..4cddc8a 100644 --- a/src/app/main.cc +++ b/src/app/main.cc @@ -142,6 +142,8 @@ int main(int argc, char** argv) { static float g_last_audio_time = 0.0f; auto fill_audio_buffer = [&](float audio_dt, double physical_time) { + const float prev_tempo = g_tempo_scale; + // Calculate tempo scale if --tempo flag enabled if (tempo_test_enabled) { const float t = (float)physical_time; @@ -161,8 +163,6 @@ int main(int argc, char** argv) { g_tempo_scale = 1.0f; // No tempo variation } - const float prev_tempo = g_tempo_scale; - #if !defined(STRIP_ALL) // Debug output when tempo changes significantly if (fabsf(g_tempo_scale - prev_tempo) > 0.05f) { diff --git a/src/audio/audio.cc b/src/audio/audio.cc index 3b98452..91dd05b 100644 --- a/src/audio/audio.cc +++ b/src/audio/audio.cc @@ -28,12 +28,6 @@ static void clip_samples(float* buf, int count) { // Global ring buffer for audio streaming static AudioRingBuffer g_ring_buffer; -// Pending write buffer for partially written samples -// Maximum size: one chunk (533 frames @ 60fps = 1066 samples stereo) -#define MAX_PENDING_SAMPLES 2048 -static float g_pending_buffer[MAX_PENDING_SAMPLES]; -static int g_pending_samples = 0; // How many samples are waiting to be written - // Global backend pointer for audio abstraction static AudioBackend* g_audio_backend = nullptr; static MiniaudioBackend g_default_backend; @@ -56,9 +50,6 @@ void audio_init() { // In production code, use AudioEngine::init() which manages initialization // order. - // Clear pending buffer - g_pending_samples = 0; - // Use default backend if none set if (g_audio_backend == nullptr) { g_audio_backend = &g_default_backend; @@ -74,8 +65,7 @@ float audio_get_required_prefill_time() { bool audio_is_prefilled() { const int buffered = g_ring_buffer.available_read(); - const float buffered_time = - (float)buffered / (RING_BUFFER_SAMPLE_RATE * RING_BUFFER_CHANNELS); + const float buffered_time = samples_to_seconds(buffered); const float required = audio_get_required_prefill_time(); return buffered_time >= (required - 0.001f); // 1ms tolerance } @@ -89,9 +79,7 @@ void audio_start() { #if !defined(STRIP_ALL) if (!audio_is_prefilled()) { const int buffered = g_ring_buffer.available_read(); - const float buffered_ms = (float)buffered / - (RING_BUFFER_SAMPLE_RATE * RING_BUFFER_CHANNELS) * - 1000.0f; + const float buffered_ms = samples_to_seconds(buffered) * 1000.0f; printf("WARNING: Audio buffer not pre-filled (%.1fms < %.1fms)\n", buffered_ms, audio_get_required_prefill_time() * 1000.0f); } @@ -116,40 +104,9 @@ void audio_render_ahead(float music_time, float dt, float target_fill) { // Keep rendering small chunks until buffer is full enough while (true) { - // First, try to flush any pending samples from previous partial writes - if (g_pending_samples > 0) { - const int written = - g_ring_buffer.write(g_pending_buffer, g_pending_samples); - - if (written > 0) { - // Some or all samples were written - // Move remaining samples to front of buffer - const int remaining = g_pending_samples - written; - if (remaining > 0) { - for (int i = 0; i < remaining; ++i) { - g_pending_buffer[i] = g_pending_buffer[written + i]; - } - } - g_pending_samples = remaining; - - // Notify backend (for testing/tracking) -#if !defined(STRIP_ALL) - if (g_audio_backend != nullptr) { - g_audio_backend->on_frames_rendered(written / RING_BUFFER_CHANNELS); - } -#endif - } - - // If still have pending samples, buffer is full - wait for consumption - if (g_pending_samples > 0) - break; - } - // Check current buffer state const int buffered_samples = g_ring_buffer.available_read(); - const float buffered_time = - (float)buffered_samples / - (RING_BUFFER_SAMPLE_RATE * RING_BUFFER_CHANNELS); + const float buffered_time = samples_to_seconds(buffered_samples); // Stop if buffer is full enough if (buffered_time >= target_lookahead) @@ -238,20 +195,16 @@ float audio_get_playback_time() { (float)(elapsed * RING_BUFFER_SAMPLE_RATE * RING_BUFFER_CHANNELS); const float total_samples = (float)last_callback_samples + interpolated_samples; - return total_samples / (RING_BUFFER_SAMPLE_RATE * RING_BUFFER_CHANNELS); + return samples_to_seconds((int64_t)total_samples); } } // Fallback: coarse ring buffer time (before first callback or no backend) - const int64_t total_samples = g_ring_buffer.get_total_read(); - return (float)total_samples / - (RING_BUFFER_SAMPLE_RATE * RING_BUFFER_CHANNELS); + return samples_to_seconds(g_ring_buffer.get_total_read()); } float audio_get_render_time() { - const int64_t total_samples = g_ring_buffer.get_total_written(); - return (float)total_samples / - (RING_BUFFER_SAMPLE_RATE * RING_BUFFER_CHANNELS); + return samples_to_seconds(g_ring_buffer.get_total_written()); } float audio_get_realtime_peak() { diff --git a/src/audio/backend/miniaudio_backend.cc b/src/audio/backend/miniaudio_backend.cc index 312d36e..8871d10 100644 --- a/src/audio/backend/miniaudio_backend.cc +++ b/src/audio/backend/miniaudio_backend.cc @@ -12,11 +12,11 @@ // Real-time peak measured at actual playback time // Updated in audio_callback when samples are read from ring buffer -volatile float MiniaudioBackend::realtime_peak_ = 0.0f; +std::atomic MiniaudioBackend::realtime_peak_{0.0f}; // Smooth playback time interpolation -volatile double MiniaudioBackend::last_callback_time_ = 0.0; -volatile int64_t MiniaudioBackend::last_callback_samples_ = 0; +std::atomic MiniaudioBackend::last_callback_time_{0.0}; +std::atomic MiniaudioBackend::last_callback_samples_{0}; // Static callback for miniaudio (C API requirement) void MiniaudioBackend::audio_callback(ma_device* pDevice, void* pOutput, @@ -148,8 +148,10 @@ void MiniaudioBackend::audio_callback(ma_device* pDevice, void* pOutput, // Update smooth playback time tracking (absolute time, no epoch needed) struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); - last_callback_time_ = ts.tv_sec + ts.tv_nsec / 1e9; - last_callback_samples_ = ring_buffer->get_total_read(); + last_callback_time_.store(ts.tv_sec + ts.tv_nsec / 1e9, + std::memory_order_release); + last_callback_samples_.store(ring_buffer->get_total_read(), + std::memory_order_release); #if defined(DEBUG_LOG_RING_BUFFER) if (actually_read < samples_to_read) { @@ -280,11 +282,11 @@ void MiniaudioBackend::shutdown() { } float MiniaudioBackend::get_realtime_peak() { - return realtime_peak_; + return realtime_peak_.load(std::memory_order_acquire); } void MiniaudioBackend::get_callback_state(double* out_time, - int64_t* out_samples) { - *out_time = last_callback_time_; - *out_samples = last_callback_samples_; + int64_t* out_samples) { + *out_time = last_callback_time_.load(std::memory_order_acquire); + *out_samples = last_callback_samples_.load(std::memory_order_acquire); } diff --git a/src/audio/backend/miniaudio_backend.h b/src/audio/backend/miniaudio_backend.h index 953a0c0..01fa790 100644 --- a/src/audio/backend/miniaudio_backend.h +++ b/src/audio/backend/miniaudio_backend.h @@ -6,6 +6,8 @@ #include "../audio_backend.h" #include "miniaudio.h" +#include +#include // Production audio backend using miniaudio library // Manages real hardware audio device and playback @@ -31,11 +33,11 @@ class MiniaudioBackend : public AudioBackend { // Real-time peak measured at actual playback time (not pre-buffer) // Updated in audio_callback when samples are read from ring buffer - static volatile float realtime_peak_; + static std::atomic realtime_peak_; // Smooth playback time interpolation (updated in callback) - static volatile double last_callback_time_; // Absolute CLOCK_MONOTONIC time - static volatile int64_t last_callback_samples_; + static std::atomic last_callback_time_; // Absolute CLOCK_MONOTONIC time + static std::atomic last_callback_samples_; // Static callback required by miniaudio C API static void audio_callback(ma_device* pDevice, void* pOutput, diff --git a/src/audio/ring_buffer.h b/src/audio/ring_buffer.h index 1a21542..ba8cb49 100644 --- a/src/audio/ring_buffer.h +++ b/src/audio/ring_buffer.h @@ -19,6 +19,11 @@ RING_BUFFER_CHANNELS) / \ 1000) +// Convert sample count to seconds (accounting for stereo channels) +inline float samples_to_seconds(int64_t samples) { + return (float)samples / (RING_BUFFER_SAMPLE_RATE * RING_BUFFER_CHANNELS); +} + class AudioRingBuffer { public: AudioRingBuffer(); diff --git a/src/audio/synth.cc b/src/audio/synth.cc index 0161385..d584c62 100644 --- a/src/audio/synth.cc +++ b/src/audio/synth.cc @@ -5,7 +5,9 @@ #include "synth.h" #include "audio/dct.h" #include "audio/ola.h" +#include "audio/ring_buffer.h" #include "util/debug.h" +#include #include #include // For memset @@ -32,18 +34,18 @@ struct Voice { int start_sample_offset; // Samples to wait before producing audio output - const volatile float* active_spectral_data; + const float* active_spectral_data; }; static struct { Spectrogram spectrograms[MAX_SPECTROGRAMS]; - const volatile float* active_spectrogram_data[MAX_SPECTROGRAMS]; + std::atomic active_spectrogram_data[MAX_SPECTROGRAMS]; bool spectrogram_registered[MAX_SPECTROGRAMS]; } g_synth_data; static Voice g_voices[MAX_VOICES]; -static volatile float g_current_output_peak = - 0.0f; // Global peak for visualization +static std::atomic g_current_output_peak{ + 0.0f}; // Global peak for visualization static float g_tempo_scale = 1.0f; // Playback speed multiplier #if !defined(STRIP_ALL) @@ -53,7 +55,7 @@ static float g_elapsed_time_sec = 0.0f; // Tracks elapsed time for event hooks 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; + g_current_output_peak.store(0.0f, std::memory_order_relaxed); #if !defined(STRIP_ALL) g_elapsed_time_sec = 0.0f; #endif /* !defined(STRIP_ALL) */ @@ -108,7 +110,8 @@ int synth_register_spectrogram(const Spectrogram* spec) { for (int i = 0; i < MAX_SPECTROGRAMS; ++i) { if (!g_synth_data.spectrogram_registered[i]) { g_synth_data.spectrograms[i] = *spec; - g_synth_data.active_spectrogram_data[i] = spec->spectral_data_a; + g_synth_data.active_spectrogram_data[i].store( + spec->spectral_data_a, std::memory_order_release); g_synth_data.spectrogram_registered[i] = true; return i; } @@ -128,8 +131,9 @@ float* synth_begin_update(int spectrogram_id) { return nullptr; } - const volatile float* active_ptr = - g_synth_data.active_spectrogram_data[spectrogram_id]; + const float* active_ptr = + g_synth_data.active_spectrogram_data[spectrogram_id].load( + std::memory_order_acquire); if (active_ptr == g_synth_data.spectrograms[spectrogram_id].spectral_data_a) { return (float*)(g_synth_data.spectrograms[spectrogram_id].spectral_data_b); @@ -144,18 +148,17 @@ void synth_commit_update(int spectrogram_id) { return; } - const volatile float* old_active_ptr = - g_synth_data.active_spectrogram_data[spectrogram_id]; + const float* old_active_ptr = + g_synth_data.active_spectrogram_data[spectrogram_id].load( + std::memory_order_acquire); const float* new_active_ptr = (old_active_ptr == g_synth_data.spectrograms[spectrogram_id].spectral_data_a) ? g_synth_data.spectrograms[spectrogram_id].spectral_data_b : g_synth_data.spectrograms[spectrogram_id].spectral_data_a; - // Atomic swap using GCC/Clang builtins for thread safety - __atomic_store_n( - (const float**)&g_synth_data.active_spectrogram_data[spectrogram_id], - new_active_ptr, __ATOMIC_RELEASE); + g_synth_data.active_spectrogram_data[spectrogram_id].store( + new_active_ptr, std::memory_order_release); } void synth_trigger_voice(int spectrogram_id, float volume, float pan, @@ -215,7 +218,8 @@ void synth_trigger_voice(int spectrogram_id, float volume, float pan, v.fractional_pos = 0.0f; v.start_sample_offset = start_offset_samples; v.active_spectral_data = - g_synth_data.active_spectrogram_data[spectrogram_id]; + g_synth_data.active_spectrogram_data[spectrogram_id].load( + std::memory_order_acquire); #if !defined(STRIP_ALL) // Notify backend of voice trigger event (for testing/tracking) @@ -233,7 +237,7 @@ void synth_trigger_voice(int spectrogram_id, float volume, float pan, void synth_render(float* output_buffer, int num_frames) { // Faster decay for more responsive visuals - g_current_output_peak *= 0.90f; + float peak = g_current_output_peak.load(std::memory_order_relaxed) * 0.90f; for (int i = 0; i < num_frames; ++i) { float left_sample = 0.0f; @@ -258,7 +262,8 @@ void synth_render(float* output_buffer, int num_frames) { // Fetch the latest active spectrogram pointer for this voice v.active_spectral_data = - g_synth_data.active_spectrogram_data[v.spectrogram_id]; + g_synth_data.active_spectrogram_data[v.spectrogram_id].load( + std::memory_order_acquire); const float* spectral_frame = (const float*)v.active_spectral_data + (v.current_spectral_frame * DCT_SIZE); @@ -286,14 +291,14 @@ void synth_render(float* output_buffer, int num_frames) { output_buffer[i * 2 + 1] = right_sample; // Update the peak with the new max (attack) - g_current_output_peak = fmaxf( - g_current_output_peak, fmaxf(fabsf(left_sample), fabsf(right_sample))); + peak = fmaxf(peak, fmaxf(fabsf(left_sample), fabsf(right_sample))); } + g_current_output_peak.store(peak, std::memory_order_release); + #if !defined(STRIP_ALL) - // Update elapsed time for event tracking (32000 Hz sample rate) - const float sample_rate = 32000.0f; - g_elapsed_time_sec += (float)num_frames / sample_rate; + // Update elapsed time for event tracking + g_elapsed_time_sec += (float)num_frames / RING_BUFFER_SAMPLE_RATE; #endif /* !defined(STRIP_ALL) */ } @@ -308,5 +313,5 @@ int synth_get_active_voice_count() { } float synth_get_output_peak() { - return g_current_output_peak; + return g_current_output_peak.load(std::memory_order_acquire); } diff --git a/src/audio/tracker.cc b/src/audio/tracker.cc index 95b8022..a511a62 100644 --- a/src/audio/tracker.cc +++ b/src/audio/tracker.cc @@ -255,9 +255,17 @@ static int get_free_pool_slot() { } // If all slots are active, reuse the oldest one (round-robin) - // This automatically handles cleanup of old patterns + // Clean up the old slot before reuse const int slot = g_next_pool_slot; g_next_pool_slot = (g_next_pool_slot + 1) % MAX_SPECTROGRAMS; + + if (g_spec_pool[slot].synth_id >= 0) { + synth_unregister_spectrogram(g_spec_pool[slot].synth_id); + } + delete[] g_spec_pool[slot].data; + g_spec_pool[slot].data = nullptr; + g_spec_pool[slot].synth_id = -1; + g_spec_pool[slot].active = false; return slot; } diff --git a/src/effects/hybrid3_d_effect.cc b/src/effects/hybrid3_d_effect.cc index 33a2d73..bef82b4 100644 --- a/src/effects/hybrid3_d_effect.cc +++ b/src/effects/hybrid3_d_effect.cc @@ -42,8 +42,6 @@ Hybrid3D::Hybrid3D(const GpuContext& ctx, renderer_.set_noise_texture(dummy_texture_view_); renderer_.set_sky_texture(dummy_texture_view_); - initialized_ = true; - // Setup simple scene (1 center cube + 8 surrounding objects) scene_.clear(); Object3D center(ObjectType::BOX); diff --git a/src/effects/hybrid3_d_effect.h b/src/effects/hybrid3_d_effect.h index 13fd7df..88047dd 100644 --- a/src/effects/hybrid3_d_effect.h +++ b/src/effects/hybrid3_d_effect.h @@ -24,7 +24,6 @@ class Hybrid3D : public Effect { Renderer3D renderer_; Scene scene_; Camera camera_; - bool initialized_ = false; std::string depth_node_; WGPUTexture dummy_texture_; WGPUTextureView dummy_texture_view_; diff --git a/src/effects/particle_compute.wgsl b/src/effects/particle_compute.wgsl index f3e8051..148a2c3 100644 --- a/src/effects/particle_compute.wgsl +++ b/src/effects/particle_compute.wgsl @@ -1,11 +1,5 @@ // Particle simulation (compute shader) - V2 -struct Particle { - pos: vec4f, - vel: vec4f, - rot: vec4f, - color: vec4f, -}; - +#include "particle_common" #include "sequence_uniforms" @group(0) @binding(0) var particles: array; diff --git a/src/effects/particle_defs.h b/src/effects/particle_defs.h deleted file mode 100644 index dcbb830..0000000 --- a/src/effects/particle_defs.h +++ /dev/null @@ -1,13 +0,0 @@ -// This file is part of the 64k demo project. -// It defines common structures for particle-based effects. - -#pragma once - -static const int NUM_PARTICLES = 10000; - -struct Particle { - float pos[4]; - float vel[4]; - float rot[4]; - float color[4]; -}; diff --git a/src/effects/particle_render.wgsl b/src/effects/particle_render.wgsl index ef0db42..66b0b9c 100644 --- a/src/effects/particle_render.wgsl +++ b/src/effects/particle_render.wgsl @@ -1,11 +1,5 @@ // Particle rendering (vertex + fragment) - V2 -struct Particle { - pos: vec4f, - vel: vec4f, - rot: vec4f, - color: vec4f, -}; - +#include "particle_common" #include "sequence_uniforms" @group(0) @binding(0) var particles: array; diff --git a/src/effects/particle_spray_compute.wgsl b/src/effects/particle_spray_compute.wgsl index 7bdae88..84a51f4 100644 --- a/src/effects/particle_spray_compute.wgsl +++ b/src/effects/particle_spray_compute.wgsl @@ -1,10 +1,4 @@ -struct Particle { - pos: vec4f, - vel: vec4f, - rot: vec4f, - color: vec4f, -}; - +#include "particle_common" #include "common_uniforms" @group(0) @binding(0) var particles: array; diff --git a/src/effects/peak_meter_effect.cc b/src/effects/peak_meter_effect.cc index d462fa0..c2ef42e 100644 --- a/src/effects/peak_meter_effect.cc +++ b/src/effects/peak_meter_effect.cc @@ -78,19 +78,6 @@ void PeakMeter::render(WGPUCommandEncoder encoder, pp_update_bind_group(ctx_.device, pipeline_, &bind_group_, input_view, uniforms_buffer_.get(), {nullptr, 0}); - WGPURenderPassColorAttachment color_attachment = {}; - gpu_init_color_attachment(color_attachment, output_view); - color_attachment.loadOp = WGPULoadOp_Load; - - WGPURenderPassDescriptor pass_desc = {}; - pass_desc.colorAttachmentCount = 1; - pass_desc.colorAttachments = &color_attachment; - - WGPURenderPassEncoder pass = - wgpuCommandEncoderBeginRenderPass(encoder, &pass_desc); - wgpuRenderPassEncoderSetPipeline(pass, pipeline_); - wgpuRenderPassEncoderSetBindGroup(pass, 0, bind_group_, 0, nullptr); - wgpuRenderPassEncoderDraw(pass, 3, 1, 0, 0); - wgpuRenderPassEncoderEnd(pass); - wgpuRenderPassEncoderRelease(pass); + run_fullscreen_pass(encoder, pipeline_, bind_group_, output_view, + WGPULoadOp_Load); } diff --git a/src/effects/placeholder_effect.cc b/src/effects/placeholder_effect.cc index beb5f33..4221a74 100644 --- a/src/effects/placeholder_effect.cc +++ b/src/effects/placeholder_effect.cc @@ -32,18 +32,5 @@ void Placeholder::render(WGPUCommandEncoder encoder, pp_update_bind_group(ctx_.device, pipeline_, &bind_group_, input_view, uniforms_buffer_.get(), {nullptr, 0}); - WGPURenderPassColorAttachment color_attachment = {}; - gpu_init_color_attachment(color_attachment, output_view); - - WGPURenderPassDescriptor pass_desc = {}; - pass_desc.colorAttachmentCount = 1; - pass_desc.colorAttachments = &color_attachment; - - WGPURenderPassEncoder pass = - wgpuCommandEncoderBeginRenderPass(encoder, &pass_desc); - wgpuRenderPassEncoderSetPipeline(pass, pipeline_); - wgpuRenderPassEncoderSetBindGroup(pass, 0, bind_group_, 0, nullptr); - wgpuRenderPassEncoderDraw(pass, 3, 1, 0, 0); - wgpuRenderPassEncoderEnd(pass); - wgpuRenderPassEncoderRelease(pass); + run_fullscreen_pass(encoder, pipeline_, bind_group_, output_view); } diff --git a/src/effects/scene1_effect.cc b/src/effects/scene1_effect.cc index 0aae94a..bf99fc7 100644 --- a/src/effects/scene1_effect.cc +++ b/src/effects/scene1_effect.cc @@ -49,18 +49,5 @@ void Scene1::render(WGPUCommandEncoder encoder, dummy_texture_view_.get(), uniforms_buffer_.get(), camera_params_.get()); - WGPURenderPassColorAttachment color_attachment = {}; - gpu_init_color_attachment(color_attachment, output_view); - - WGPURenderPassDescriptor pass_desc = {}; - pass_desc.colorAttachmentCount = 1; - pass_desc.colorAttachments = &color_attachment; - - WGPURenderPassEncoder pass = - wgpuCommandEncoderBeginRenderPass(encoder, &pass_desc); - wgpuRenderPassEncoderSetPipeline(pass, pipeline_.get()); - wgpuRenderPassEncoderSetBindGroup(pass, 0, bind_group_.get(), 0, nullptr); - wgpuRenderPassEncoderDraw(pass, 3, 1, 0, 0); // Fullscreen triangle - wgpuRenderPassEncoderEnd(pass); - wgpuRenderPassEncoderRelease(pass); + run_fullscreen_pass(encoder, pipeline_.get(), bind_group_.get(), output_view); } diff --git a/src/effects/scene2_effect.cc b/src/effects/scene2_effect.cc index 8c05574..92e5ecd 100644 --- a/src/effects/scene2_effect.cc +++ b/src/effects/scene2_effect.cc @@ -30,18 +30,5 @@ void Scene2Effect::render(WGPUCommandEncoder encoder, dummy_texture_view_.get(), uniforms_buffer_.get(), {nullptr, 0}); - WGPURenderPassColorAttachment color_attachment = {}; - gpu_init_color_attachment(color_attachment, output_view); - - WGPURenderPassDescriptor pass_desc = {}; - pass_desc.colorAttachmentCount = 1; - pass_desc.colorAttachments = &color_attachment; - - WGPURenderPassEncoder pass = - wgpuCommandEncoderBeginRenderPass(encoder, &pass_desc); - wgpuRenderPassEncoderSetPipeline(pass, pipeline_.get()); - wgpuRenderPassEncoderSetBindGroup(pass, 0, bind_group_.get(), 0, nullptr); - wgpuRenderPassEncoderDraw(pass, 3, 1, 0, 0); - wgpuRenderPassEncoderEnd(pass); - wgpuRenderPassEncoderRelease(pass); + run_fullscreen_pass(encoder, pipeline_.get(), bind_group_.get(), output_view); } diff --git a/src/effects/shaders.cc b/src/effects/shaders.cc index 8b625ee..7ca66fa 100644 --- a/src/effects/shaders.cc +++ b/src/effects/shaders.cc @@ -61,6 +61,8 @@ void InitShaderComposer() { AssetId::ASSET_SHADER_RENDER_NTSC_COMMON); register_if_exists("render/fullscreen_vs", AssetId::ASSET_SHADER_RENDER_FULLSCREEN_VS); + register_if_exists("particle_common", + AssetId::ASSET_SHADER_COMPUTE_PARTICLE_COMMON); register_if_exists("render/fullscreen_uv_vs", AssetId::ASSET_SHADER_RENDER_FULLSCREEN_UV_VS); register_if_exists("math/color", AssetId::ASSET_SHADER_MATH_COLOR); diff --git a/src/gpu/effect.cc b/src/gpu/effect.cc index 1257090..2e93a11 100644 --- a/src/gpu/effect.cc +++ b/src/gpu/effect.cc @@ -2,6 +2,7 @@ #include "gpu/effect.h" #include "gpu/gpu.h" +#include "gpu/sampler_cache.h" #include "gpu/sequence.h" #include "util/fatal_error.h" @@ -72,6 +73,30 @@ std::string Effect::find_downstream_output( return ""; } +void Effect::run_fullscreen_pass(WGPUCommandEncoder encoder, + WGPURenderPipeline pipeline, + WGPUBindGroup bind_group, + WGPUTextureView output_view, + WGPULoadOp load_op) { + HEADLESS_RETURN_IF_NULL(encoder); + + WGPURenderPassColorAttachment color_attachment = {}; + gpu_init_color_attachment(color_attachment, output_view); + color_attachment.loadOp = load_op; + + WGPURenderPassDescriptor pass_desc = {}; + pass_desc.colorAttachmentCount = 1; + pass_desc.colorAttachments = &color_attachment; + + WGPURenderPassEncoder pass = + wgpuCommandEncoderBeginRenderPass(encoder, &pass_desc); + wgpuRenderPassEncoderSetPipeline(pass, pipeline); + wgpuRenderPassEncoderSetBindGroup(pass, 0, bind_group, 0, nullptr); + wgpuRenderPassEncoderDraw(pass, 3, 1, 0, 0); + wgpuRenderPassEncoderEnd(pass); + wgpuRenderPassEncoderRelease(pass); +} + void Effect::create_linear_sampler() { sampler_.set(gpu_create_linear_sampler(ctx_.device)); } diff --git a/src/gpu/effect.h b/src/gpu/effect.h index 70ba9be..47dd3c2 100644 --- a/src/gpu/effect.h +++ b/src/gpu/effect.h @@ -79,6 +79,13 @@ class Effect { // consumer). Returns "" if no such effect exists or it has no outputs. std::string find_downstream_output(const std::vector& dag) const; + // Helper: Run a fullscreen triangle pass (pipeline + bind_group → output) + static void run_fullscreen_pass(WGPUCommandEncoder encoder, + WGPURenderPipeline pipeline, + WGPUBindGroup bind_group, + WGPUTextureView output_view, + WGPULoadOp load_op = WGPULoadOp_Clear); + // Helper: Create linear sampler (call in subclass constructor if needed) void create_linear_sampler(); diff --git a/src/gpu/gpu.cc b/src/gpu/gpu.cc index 0be5afe..f5f1515 100644 --- a/src/gpu/gpu.cc +++ b/src/gpu/gpu.cc @@ -5,6 +5,7 @@ #include "gpu.h" #include "effects/shaders.h" #include "generated/timeline.h" +#include "gpu/sampler_cache.h" #include "gpu/shader_composer.h" #include "platform/platform.h" @@ -124,26 +125,28 @@ WGPUTextureView gpu_create_texture_view_2d(WGPUTexture texture, return wgpuTextureCreateView(texture, &view_desc); } +WGPUShaderModule gpu_create_shader_module(WGPUDevice device, + const char* wgsl_code, + const char* label) { + WGPUShaderSourceWGSL wgsl_src = {}; + wgsl_src.chain.sType = WGPUSType_ShaderSourceWGSL; + wgsl_src.code = str_view(wgsl_code); + WGPUShaderModuleDescriptor desc = {}; + desc.label = label_view(label); + desc.nextInChain = &wgsl_src.chain; + return wgpuDeviceCreateShaderModule(device, &desc); +} + WGPUSampler gpu_create_linear_sampler(WGPUDevice device) { - WGPUSamplerDescriptor desc = {}; - desc.addressModeU = WGPUAddressMode_ClampToEdge; - desc.addressModeV = WGPUAddressMode_ClampToEdge; - desc.addressModeW = WGPUAddressMode_ClampToEdge; - desc.magFilter = WGPUFilterMode_Linear; - desc.minFilter = WGPUFilterMode_Linear; - desc.mipmapFilter = WGPUMipmapFilterMode_Nearest; - desc.maxAnisotropy = 1; - return wgpuDeviceCreateSampler(device, &desc); + WGPUSampler s = SamplerCache::Get().get_or_create(device, SamplerCache::clamp()); + if (s) wgpuSamplerAddRef(s); // Caller owns a reference (for RAII wrappers) + return s; } WGPUSampler gpu_create_nearest_sampler(WGPUDevice device) { - WGPUSamplerDescriptor desc = {}; - desc.addressModeU = WGPUAddressMode_ClampToEdge; - desc.addressModeV = WGPUAddressMode_ClampToEdge; - desc.magFilter = WGPUFilterMode_Nearest; - desc.minFilter = WGPUFilterMode_Nearest; - desc.maxAnisotropy = 1; - return wgpuDeviceCreateSampler(device, &desc); + WGPUSampler s = SamplerCache::Get().get_or_create(device, SamplerCache::nearest()); + if (s) wgpuSamplerAddRef(s); // Caller owns a reference (for RAII wrappers) + return s; } TextureWithView gpu_create_dummy_scene_texture(WGPUDevice device) { @@ -167,15 +170,8 @@ RenderPass gpu_create_render_pass(WGPUDevice device, WGPUTextureFormat format, // Compose shader to resolve #include directives std::string composed_shader = ShaderComposer::Get().Compose({}, shader_code); - // Create Shader Module - WGPUShaderSourceWGSL wgsl_src = {}; - wgsl_src.chain.sType = WGPUSType_ShaderSourceWGSL; - wgsl_src.code = str_view(composed_shader.c_str()); - WGPUShaderModuleDescriptor shader_desc = {}; - shader_desc.label = label_view("render_shader"); - shader_desc.nextInChain = &wgsl_src.chain; WGPUShaderModule shader_module = - wgpuDeviceCreateShaderModule(device, &shader_desc); + gpu_create_shader_module(device, composed_shader.c_str(), "render_shader"); // Create Bind Group Layout & Bind Group std::vector bgl_entries; @@ -257,6 +253,10 @@ RenderPass gpu_create_render_pass(WGPUDevice device, WGPUTextureFormat format, pass.pipeline = wgpuDeviceCreateRenderPipeline(device, &pipeline_desc); + wgpuShaderModuleRelease(shader_module); + wgpuPipelineLayoutRelease(pipeline_layout); + wgpuBindGroupLayoutRelease(bind_group_layout); + return pass; } @@ -268,14 +268,8 @@ ComputePass gpu_create_compute_pass(WGPUDevice device, const char* shader_code, // Compose shader to resolve #include directives std::string composed_shader = ShaderComposer::Get().Compose({}, shader_code); - WGPUShaderSourceWGSL wgsl_src = {}; - wgsl_src.chain.sType = WGPUSType_ShaderSourceWGSL; - wgsl_src.code = str_view(composed_shader.c_str()); - WGPUShaderModuleDescriptor shader_desc = {}; - shader_desc.label = label_view("compute_shader"); - shader_desc.nextInChain = &wgsl_src.chain; WGPUShaderModule shader_module = - wgpuDeviceCreateShaderModule(device, &shader_desc); + gpu_create_shader_module(device, composed_shader.c_str(), "compute_shader"); std::vector bgl_entries; std::vector bg_entries; @@ -319,6 +313,11 @@ ComputePass gpu_create_compute_pass(WGPUDevice device, const char* shader_code, pipeline_desc.compute.entryPoint = str_view("main"); pass.pipeline = wgpuDeviceCreateComputePipeline(device, &pipeline_desc); + + wgpuShaderModuleRelease(shader_module); + wgpuPipelineLayoutRelease(pipeline_layout); + wgpuBindGroupLayoutRelease(bind_group_layout); + return pass; } @@ -439,16 +438,6 @@ void gpu_init(PlatformState* platform_state) { } -void gpu_draw(float audio_peak, float aspect_ratio, float time, float beat_time, - float beat_phase) { - // Rendering is driven externally via the sequence pipeline - (void)audio_peak; - (void)aspect_ratio; - (void)time; - (void)beat_time; - (void)beat_phase; -} - void gpu_resize(int width, int height) { if (width <= 0 || height <= 0) return; diff --git a/src/gpu/gpu.h b/src/gpu/gpu.h index bbced41..c5d0123 100644 --- a/src/gpu/gpu.h +++ b/src/gpu/gpu.h @@ -34,8 +34,6 @@ struct RenderPass { }; void gpu_init(PlatformState* platform_state); -void gpu_draw(float audio_peak, float aspect_ratio, float time, float beat_time, - float beat_phase); void gpu_resize(int width, int height); void gpu_shutdown(); @@ -92,6 +90,11 @@ gpu_create_render_pass(WGPUDevice device, const char* shader_code, ResourceBinding* bindings, int num_bindings); +// Create a shader module from WGSL source code +WGPUShaderModule gpu_create_shader_module(WGPUDevice device, + const char* wgsl_code, + const char* label = "shader"); + // Common sampler configurations WGPUSampler gpu_create_linear_sampler(WGPUDevice device); WGPUSampler gpu_create_nearest_sampler(WGPUDevice device); diff --git a/src/gpu/gpu_headless.cc b/src/gpu/gpu_headless.cc index 547ca18..c60da00 100644 --- a/src/gpu/gpu_headless.cc +++ b/src/gpu/gpu_headless.cc @@ -48,15 +48,6 @@ void gpu_init(PlatformState* platform_state) { } } -void gpu_draw(float audio_peak, float aspect_ratio, float time, float beat_time, - float beat_phase) { - (void)audio_peak; - (void)aspect_ratio; - (void)time; - (void)beat_time; - (void)beat_phase; -} - void gpu_resize(int width, int height) { (void)width; (void)height; diff --git a/src/gpu/gpu_stub.cc b/src/gpu/gpu_stub.cc index d889666..246c3a6 100644 --- a/src/gpu/gpu_stub.cc +++ b/src/gpu/gpu_stub.cc @@ -41,15 +41,6 @@ void gpu_init(PlatformState* platform_state) { (void)platform_state; } -void gpu_draw(float audio_peak, float aspect_ratio, float time, float beat_time, - float beat_phase) { - (void)audio_peak; - (void)aspect_ratio; - (void)time; - (void)beat_time; - (void)beat_phase; -} - void gpu_resize(int width, int height) { (void)width; (void)height; diff --git a/src/gpu/pipeline_builder.cc b/src/gpu/pipeline_builder.cc index 2d9ec07..acd2ae9 100644 --- a/src/gpu/pipeline_builder.cc +++ b/src/gpu/pipeline_builder.cc @@ -1,5 +1,6 @@ // WGPU render pipeline builder - implementation #include "gpu/pipeline_builder.h" +#include "gpu/gpu.h" #include "util/fatal_error.h" RenderPipelineBuilder::RenderPipelineBuilder(WGPUDevice device) @@ -15,12 +16,8 @@ RenderPipelineBuilder& RenderPipelineBuilder::shader(const char* wgsl, shader_text_ = compose ? ShaderComposer::Get().Compose({}, wgsl) : wgsl; if (device_ == nullptr) return *this; - WGPUShaderSourceWGSL wgsl_src{}; - wgsl_src.chain.sType = WGPUSType_ShaderSourceWGSL; - wgsl_src.code = str_view(shader_text_.c_str()); - WGPUShaderModuleDescriptor shader_desc{}; - shader_desc.nextInChain = &wgsl_src.chain; - shader_module_ = wgpuDeviceCreateShaderModule(device_, &shader_desc); + shader_module_ = + gpu_create_shader_module(device_, shader_text_.c_str(), "pipeline_shader"); desc_.vertex.module = shader_module_; desc_.vertex.entryPoint = str_view("vs_main"); return *this; @@ -90,5 +87,7 @@ WGPURenderPipeline RenderPipelineBuilder::build() { WGPURenderPipeline pipeline = wgpuDeviceCreateRenderPipeline(device_, &desc_); wgpuPipelineLayoutRelease(layout); + if (shader_module_) + wgpuShaderModuleRelease(shader_module_); return pipeline; } diff --git a/src/gpu/post_process_helper.cc b/src/gpu/post_process_helper.cc index 79fda20..871a238 100644 --- a/src/gpu/post_process_helper.cc +++ b/src/gpu/post_process_helper.cc @@ -39,33 +39,13 @@ WGPURenderPipeline create_post_process_pipeline(WGPUDevice device, return pipeline; } -// Helper to create a simple post-processing pipeline (no effect params) -WGPURenderPipeline -create_post_process_pipeline_simple(WGPUDevice device, WGPUTextureFormat format, - const char* shader_code) { - // Headless mode: skip pipeline creation (compiled out in STRIP_ALL) - HEADLESS_RETURN_VAL_IF_NULL(device, nullptr); - - WGPUBindGroupLayout bgl = - BindGroupLayoutBuilder() - .sampler(PP_BINDING_SAMPLER, WGPUShaderStage_Fragment) - .texture(PP_BINDING_TEXTURE, WGPUShaderStage_Fragment) - .uniform(PP_BINDING_UNIFORMS, - WGPUShaderStage_Vertex | WGPUShaderStage_Fragment) - .build(device); - - const std::string composed_shader = - ShaderComposer::Get().Compose({}, shader_code); - - WGPURenderPipeline pipeline = RenderPipelineBuilder(device) - .shader(composed_shader.c_str()) - .bind_group_layout(bgl) - .format(format) - .build(); - - wgpuBindGroupLayoutRelease(bgl); - return pipeline; -} +// NOTE: create_post_process_pipeline_simple() was removed (zero callers). +// If a 3-binding pipeline is needed in the future, add a `bool use_effect_params` +// parameter to create_post_process_pipeline() instead. +// Example: +// WGPURenderPipeline p = create_post_process_pipeline(device, format, code); +// // Then pass {nullptr, 0} as effect_params to pp_update_bind_group — +// // the dummy buffer fallback handles it automatically. void gpu_upload_mat4(WGPUQueue queue, WGPUBuffer buffer, size_t offset, const mat4& m) { diff --git a/src/gpu/post_process_helper.h b/src/gpu/post_process_helper.h index 178a4d5..8f2bd21 100644 --- a/src/gpu/post_process_helper.h +++ b/src/gpu/post_process_helper.h @@ -17,11 +17,6 @@ WGPURenderPipeline create_post_process_pipeline(WGPUDevice device, WGPUTextureFormat format, const char* shader_code); -// No effect params, 3 bindings only -WGPURenderPipeline create_post_process_pipeline_simple(WGPUDevice device, - WGPUTextureFormat format, - const char* shader_code); - void pp_update_bind_group(WGPUDevice device, WGPURenderPipeline pipeline, WGPUBindGroup* bind_group, WGPUTextureView input_view, GpuBuffer uniforms, GpuBuffer effect_params); diff --git a/src/gpu/sampler_cache.h b/src/gpu/sampler_cache.h index 0a2afa0..562db29 100644 --- a/src/gpu/sampler_cache.h +++ b/src/gpu/sampler_cache.h @@ -51,4 +51,8 @@ class SamplerCache { return {WGPUAddressMode_ClampToEdge, WGPUAddressMode_ClampToEdge, WGPUFilterMode_Linear, WGPUFilterMode_Linear, 1}; } + static SamplerSpec nearest() { + return {WGPUAddressMode_ClampToEdge, WGPUAddressMode_ClampToEdge, + WGPUFilterMode_Nearest, WGPUFilterMode_Nearest, 1}; + } }; diff --git a/src/gpu/wgsl_effect.cc b/src/gpu/wgsl_effect.cc index 0ce4730..1cb0ecb 100644 --- a/src/gpu/wgsl_effect.cc +++ b/src/gpu/wgsl_effect.cc @@ -36,19 +36,6 @@ void WgslEffect::render(WGPUCommandEncoder encoder, input_view, uniforms_buffer_.get(), params_buffer_.get()); - WGPURenderPassColorAttachment color_attachment = {}; - gpu_init_color_attachment(color_attachment, output_view); - color_attachment.loadOp = load_op_; - - WGPURenderPassDescriptor pass_desc = {}; - pass_desc.colorAttachmentCount = 1; - pass_desc.colorAttachments = &color_attachment; - - WGPURenderPassEncoder pass = - wgpuCommandEncoderBeginRenderPass(encoder, &pass_desc); - wgpuRenderPassEncoderSetPipeline(pass, pipeline_.get()); - wgpuRenderPassEncoderSetBindGroup(pass, 0, bind_group_.get(), 0, nullptr); - wgpuRenderPassEncoderDraw(pass, 3, 1, 0, 0); - wgpuRenderPassEncoderEnd(pass); - wgpuRenderPassEncoderRelease(pass); + run_fullscreen_pass(encoder, pipeline_.get(), bind_group_.get(), output_view, + load_op_); } diff --git a/src/shaders/compute/particle_common.wgsl b/src/shaders/compute/particle_common.wgsl new file mode 100644 index 0000000..0c36be8 --- /dev/null +++ b/src/shaders/compute/particle_common.wgsl @@ -0,0 +1,7 @@ +// Shared particle struct for compute and render shaders +struct Particle { + pos: vec4f, + vel: vec4f, + rot: vec4f, + color: vec4f, +}; diff --git a/src/util/asset_manager.cc b/src/util/asset_manager.cc index 264ddda..82c07be 100644 --- a/src/util/asset_manager.cc +++ b/src/util/asset_manager.cc @@ -245,29 +245,25 @@ void DropAsset(AssetId asset_id, const uint8_t* asset) { return; // Invalid asset_id } - // Check if the asset is in cache and is procedural, and if the pointer - // matches. This prevents accidentally freeing static data or freeing twice. - if (g_asset_cache[index].data == asset && - (g_asset_cache[index].type == AssetType::PROC || - g_asset_cache[index].type == AssetType::PROC_GPU)) { - delete[] g_asset_cache[index].data; - g_asset_cache[index] = {}; // Zero out the struct to force re-generation - } - // Heap-allocated decompressed buffer (compression != NONE): cache owns it. - if (g_asset_cache[index].data == asset && - g_asset_cache[index].compression != AssetCompression::NONE) { - delete[] g_asset_cache[index].data; - g_asset_cache[index] = {}; + // Check if the asset is in cache and the pointer matches. + // This prevents accidentally freeing static data or freeing twice. + if (g_asset_cache[index].data != asset) { + return; // Pointer mismatch — not our allocation } + + // Heap-owned: procedural, compressed, or disk-loaded (debug) + if (g_asset_cache[index].type == AssetType::PROC || + g_asset_cache[index].type == AssetType::PROC_GPU || + g_asset_cache[index].compression != AssetCompression::NONE #if !defined(STRIP_ALL) - if (g_asset_cache[index].data == asset && - (g_asset_cache[index].type == AssetType::SPEC || - g_asset_cache[index].type == AssetType::MP3 || - g_asset_cache[index].type == AssetType::WGSL)) { + || g_asset_cache[index].type == AssetType::SPEC || + g_asset_cache[index].type == AssetType::MP3 || + g_asset_cache[index].type == AssetType::WGSL +#endif + ) { delete[] g_asset_cache[index].data; g_asset_cache[index] = {}; } -#endif // For static assets, no dynamic memory to free. } @@ -283,7 +279,9 @@ bool ReloadAssetsFromFile(const char* config_path) { const AssetRecord& e = g_asset_cache[i]; if (e.data && (e.type == AssetType::PROC || e.type == AssetType::PROC_GPU || - e.compression != AssetCompression::NONE)) { + e.compression != AssetCompression::NONE || + e.type == AssetType::SPEC || e.type == AssetType::MP3 || + e.type == AssetType::WGSL)) { delete[] e.data; } g_asset_cache[i] = {}; diff --git a/workspaces/main/assets.txt b/workspaces/main/assets.txt index b96b4c4..2b752d5 100644 --- a/workspaces/main/assets.txt +++ b/workspaces/main/assets.txt @@ -84,6 +84,7 @@ SHADER_MATH_NORMAL, WGSL, ../../src/shaders/math/normal.wgsl, "Octahedral normal SHADER_RENDER_RAYMARCHING, WGSL, ../../src/shaders/render/raymarching.wgsl, "Raymarching Functions" SHADER_RENDER_RAYMARCHING_ID, WGSL, ../../src/shaders/render/raymarching_id.wgsl, "Raymarching-ID Functions" SHADER_VIGNETTE, WGSL, ../../src/effects/vignette.wgsl, "Vignette Shader" +SHADER_COMPUTE_PARTICLE_COMMON, WGSL, ../../src/shaders/compute/particle_common.wgsl, "Particle Struct Snippet" SHADER_COMPUTE_GEN_NOISE, WGSL, ../../src/shaders/compute/gen_noise.wgsl, "GPU Noise Compute Shader" SHADER_COMPUTE_GEN_PERLIN, WGSL, ../../src/shaders/compute/gen_perlin.wgsl, "GPU Perlin Noise Compute Shader" SHADER_COMPUTE_GEN_GRID, WGSL, ../../src/shaders/compute/gen_grid.wgsl, "GPU Grid Compute Shader" -- cgit v1.2.3