summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmake/DemoCodegen.cmake8
-rw-r--r--cmake/DemoLibraries.cmake4
-rw-r--r--src/effects/flash_effect.cc52
-rw-r--r--src/effects/flash_effect.h10
-rw-r--r--src/effects/gaussian_blur_effect.cc24
-rw-r--r--src/effects/gaussian_blur_effect.h2
-rw-r--r--src/effects/heptagon_effect.cc55
-rw-r--r--src/effects/heptagon_effect.h10
-rw-r--r--src/effects/particles_effect.cc10
-rw-r--r--src/effects/particles_effect.h1
-rw-r--r--src/effects/passthrough_effect.cc36
-rw-r--r--src/effects/passthrough_effect.h7
-rw-r--r--src/effects/peak_meter_effect.cc2
-rw-r--r--src/effects/peak_meter_effect.h1
-rw-r--r--src/effects/placeholder_effect.cc18
-rw-r--r--src/effects/placeholder_effect.h2
-rw-r--r--src/gpu/effect.cc19
-rw-r--r--src/gpu/effect.h20
-rw-r--r--src/gpu/gpu.cc35
-rw-r--r--src/gpu/gpu.h9
-rw-r--r--src/gpu/wgpu_resource.h45
-rwxr-xr-xtools/seq_compiler.py24
-rw-r--r--workspaces/test/timeline.seq6
23 files changed, 214 insertions, 186 deletions
diff --git a/cmake/DemoCodegen.cmake b/cmake/DemoCodegen.cmake
index 1e0badf..aa20a92 100644
--- a/cmake/DemoCodegen.cmake
+++ b/cmake/DemoCodegen.cmake
@@ -122,15 +122,17 @@ endfunction()
# Timeline compilation
set(DEMO_SEQ_PATH ${WORKSPACE_TIMELINE})
set(GENERATED_TIMELINE_CC ${CMAKE_CURRENT_SOURCE_DIR}/src/generated/timeline.cc)
+set(GENERATED_TIMELINE_H ${CMAKE_CURRENT_SOURCE_DIR}/src/generated/timeline.h)
+set(GENERATED_CODE ${GENERATED_TIMELINE_CC} ${GENERATED_TIMELINE_H})
add_custom_command(
- OUTPUT ${GENERATED_TIMELINE_CC}
+ OUTPUT ${GENERATED_CODE}
COMMAND ${CMAKE_COMMAND} -E make_directory ${CMAKE_CURRENT_SOURCE_DIR}/src/generated
COMMAND ${SEQ_COMPILER_CMD} ${DEMO_SEQ_PATH} --output ${GENERATED_TIMELINE_CC}
DEPENDS ${SEQ_COMPILER_DEPENDS} ${DEMO_SEQ_PATH}
src/gpu/demo_effects.h
COMMENT "Compiling demo sequence from workspace ${DEMO_WORKSPACE}..."
)
-add_custom_target(generate_timeline ALL DEPENDS ${GENERATED_TIMELINE_CC})
+add_custom_target(generate_timeline ALL DEPENDS ${GENERATED_CODE})
# Music compilation
set(TRACKER_MUSIC_PATH ${WORKSPACE_MUSIC})
@@ -255,5 +257,5 @@ pack_test_assets(test_assets ${CMAKE_CURRENT_SOURCE_DIR}/workspaces/test/assets.
# Mark generated files so CMake always checks if they need rebuilding
set_source_files_properties(${GEN_DEMO_H} ${GEN_DEMO_CC} PROPERTIES GENERATED TRUE)
set_source_files_properties(${GEN_TEST_H} ${GEN_TEST_CC} PROPERTIES GENERATED TRUE)
-set_source_files_properties(${GENERATED_TIMELINE_CC} PROPERTIES GENERATED TRUE)
+set_source_files_properties(${GENERATED_CODE} PROPERTIES GENERATED TRUE)
set_source_files_properties(${GENERATED_MUSIC_DATA_CC} PROPERTIES GENERATED TRUE)
diff --git a/cmake/DemoLibraries.cmake b/cmake/DemoLibraries.cmake
index f1891fb..39cd3f4 100644
--- a/cmake/DemoLibraries.cmake
+++ b/cmake/DemoLibraries.cmake
@@ -21,9 +21,9 @@ target_include_directories(3d PUBLIC ${CORE_INCLUDES})
add_dependencies(3d generate_demo_assets)
# GPU effects library
-add_library(gpu STATIC ${GPU_SOURCES})
+add_library(gpu STATIC ${GPU_SOURCES} ${GENERATED_TIMELINE_CC})
target_include_directories(gpu PUBLIC ${CORE_INCLUDES})
-add_dependencies(gpu generate_demo_assets)
+add_dependencies(gpu generate_demo_assets generate_timeline)
# Note: Static libraries do not strictly need to link dependencies,
# but if they did, PRIVATE would propagate to the executable.
diff --git a/src/effects/flash_effect.cc b/src/effects/flash_effect.cc
index 7baf6a2..93ff4dd 100644
--- a/src/effects/flash_effect.cc
+++ b/src/effects/flash_effect.cc
@@ -9,47 +9,15 @@
Flash::Flash(const GpuContext& ctx, const std::vector<std::string>& inputs,
const std::vector<std::string>& outputs, float start_time,
float end_time)
- : Effect(ctx, inputs, outputs, start_time, end_time), pipeline_(nullptr),
- bind_group_(nullptr), sampler_(nullptr), dummy_texture_(nullptr),
- dummy_texture_view_(nullptr) {
+ : Effect(ctx, inputs, outputs, start_time, end_time) {
HEADLESS_RETURN_IF_NULL(ctx_.device);
- uniforms_buffer_.init(ctx_.device);
- pipeline_ = create_post_process_pipeline(
- ctx_.device, WGPUTextureFormat_RGBA8Unorm, flash_shader_wgsl);
+ init_uniforms_buffer();
+ create_nearest_sampler();
+ create_dummy_scene_texture();
- // Create dummy sampler (scene effects don't use texture input)
- WGPUSamplerDescriptor sampler_desc = {};
- sampler_desc.addressModeU = WGPUAddressMode_ClampToEdge;
- sampler_desc.addressModeV = WGPUAddressMode_ClampToEdge;
- sampler_desc.magFilter = WGPUFilterMode_Nearest;
- sampler_desc.minFilter = WGPUFilterMode_Nearest;
- sampler_desc.maxAnisotropy = 1;
- sampler_ = wgpuDeviceCreateSampler(ctx_.device, &sampler_desc);
-
- // Create 1×1 dummy texture
- WGPUTextureDescriptor tex_desc = {};
- tex_desc.size = {1, 1, 1};
- tex_desc.format = WGPUTextureFormat_RGBA8Unorm;
- tex_desc.usage = WGPUTextureUsage_TextureBinding;
- tex_desc.dimension = WGPUTextureDimension_2D;
- tex_desc.mipLevelCount = 1;
- tex_desc.sampleCount = 1;
- dummy_texture_ = wgpuDeviceCreateTexture(ctx_.device, &tex_desc);
- dummy_texture_view_ = wgpuTextureCreateView(dummy_texture_, nullptr);
-}
-
-Flash::~Flash() {
- if (bind_group_)
- wgpuBindGroupRelease(bind_group_);
- if (pipeline_)
- wgpuRenderPipelineRelease(pipeline_);
- if (sampler_)
- wgpuSamplerRelease(sampler_);
- if (dummy_texture_view_)
- wgpuTextureViewRelease(dummy_texture_view_);
- if (dummy_texture_)
- wgpuTextureRelease(dummy_texture_);
+ pipeline_.set(create_post_process_pipeline(
+ ctx_.device, WGPUTextureFormat_RGBA8Unorm, flash_shader_wgsl));
}
void Flash::render(WGPUCommandEncoder encoder,
@@ -61,8 +29,8 @@ void Flash::render(WGPUCommandEncoder encoder,
uniforms_buffer_.update(ctx_.queue, params);
// Update bind group (use dummy texture for scene effect)
- pp_update_bind_group(ctx_.device, pipeline_, &bind_group_,
- dummy_texture_view_, uniforms_buffer_.get(),
+ pp_update_bind_group(ctx_.device, pipeline_.get(), bind_group_.get_address(),
+ dummy_texture_view_.get(), uniforms_buffer_.get(),
{nullptr, 0});
// Render pass
@@ -75,8 +43,8 @@ void Flash::render(WGPUCommandEncoder encoder,
WGPURenderPassEncoder pass =
wgpuCommandEncoderBeginRenderPass(encoder, &pass_desc);
- wgpuRenderPassEncoderSetPipeline(pass, pipeline_);
- wgpuRenderPassEncoderSetBindGroup(pass, 0, bind_group_, 0, nullptr);
+ wgpuRenderPassEncoderSetPipeline(pass, pipeline_.get());
+ wgpuRenderPassEncoderSetBindGroup(pass, 0, bind_group_.get(), 0, nullptr);
wgpuRenderPassEncoderDraw(pass, 3, 1, 0, 0);
wgpuRenderPassEncoderEnd(pass);
wgpuRenderPassEncoderRelease(pass);
diff --git a/src/effects/flash_effect.h b/src/effects/flash_effect.h
index 1117e0a..052957d 100644
--- a/src/effects/flash_effect.h
+++ b/src/effects/flash_effect.h
@@ -4,22 +4,18 @@
#pragma once
#include "gpu/effect.h"
#include "gpu/uniform_helper.h"
+#include "gpu/wgpu_resource.h"
class Flash : public Effect {
public:
Flash(const GpuContext& ctx, const std::vector<std::string>& inputs,
const std::vector<std::string>& outputs, float start_time,
float end_time);
- ~Flash() override;
void render(WGPUCommandEncoder encoder, const UniformsSequenceParams& params,
NodeRegistry& nodes) override;
private:
- WGPURenderPipeline pipeline_;
- WGPUBindGroup bind_group_;
- WGPUSampler sampler_;
- WGPUTexture dummy_texture_;
- WGPUTextureView dummy_texture_view_;
- UniformBuffer<UniformsSequenceParams> uniforms_buffer_;
+ RenderPipeline pipeline_;
+ BindGroup bind_group_;
};
diff --git a/src/effects/gaussian_blur_effect.cc b/src/effects/gaussian_blur_effect.cc
index 7304321..0548b4a 100644
--- a/src/effects/gaussian_blur_effect.cc
+++ b/src/effects/gaussian_blur_effect.cc
@@ -9,27 +9,15 @@ GaussianBlur::GaussianBlur(const GpuContext& ctx,
const std::vector<std::string>& inputs,
const std::vector<std::string>& outputs, float start_time,
float end_time)
- : Effect(ctx, inputs, outputs, start_time, end_time), pipeline_(nullptr), bind_group_(nullptr),
- sampler_(nullptr) {
- // Headless mode: skip GPU resource creation (compiled out in STRIP_ALL)
+ : Effect(ctx, inputs, outputs, start_time, end_time), pipeline_(nullptr), bind_group_(nullptr) {
HEADLESS_RETURN_IF_NULL(ctx_.device);
- // Create pipeline
+ init_uniforms_buffer();
+ create_linear_sampler();
+ params_buffer_.init(ctx_.device);
+
pipeline_ = create_post_process_pipeline(
ctx_.device, WGPUTextureFormat_RGBA8Unorm, gaussian_blur_shader_wgsl);
-
- // Create sampler
- WGPUSamplerDescriptor sampler_desc = {};
- sampler_desc.addressModeU = WGPUAddressMode_ClampToEdge;
- sampler_desc.addressModeV = WGPUAddressMode_ClampToEdge;
- sampler_desc.magFilter = WGPUFilterMode_Linear;
- sampler_desc.minFilter = WGPUFilterMode_Linear;
- sampler_desc.maxAnisotropy = 1;
- sampler_ = wgpuDeviceCreateSampler(ctx_.device, &sampler_desc);
-
- // Init uniform buffers
- params_buffer_.init(ctx_.device);
- uniforms_buffer_.init(ctx_.device);
}
void GaussianBlur::render(WGPUCommandEncoder encoder,
@@ -46,7 +34,7 @@ void GaussianBlur::render(WGPUCommandEncoder encoder,
// Update bind group
WGPUBindGroupEntry entries[4] = {};
entries[0].binding = PP_BINDING_SAMPLER;
- entries[0].sampler = sampler_;
+ entries[0].sampler = sampler_.get();
entries[1].binding = PP_BINDING_TEXTURE;
entries[1].textureView = input_view;
entries[2].binding = PP_BINDING_UNIFORMS;
diff --git a/src/effects/gaussian_blur_effect.h b/src/effects/gaussian_blur_effect.h
index cc9ff56..f4b8fcf 100644
--- a/src/effects/gaussian_blur_effect.h
+++ b/src/effects/gaussian_blur_effect.h
@@ -26,8 +26,6 @@ class GaussianBlur : public Effect {
private:
WGPURenderPipeline pipeline_;
WGPUBindGroup bind_group_;
- WGPUSampler sampler_;
GaussianBlurParams blur_params_;
UniformBuffer<GaussianBlurParams> params_buffer_;
- UniformBuffer<UniformsSequenceParams> uniforms_buffer_;
};
diff --git a/src/effects/heptagon_effect.cc b/src/effects/heptagon_effect.cc
index c472f2f..c9ec17c 100644
--- a/src/effects/heptagon_effect.cc
+++ b/src/effects/heptagon_effect.cc
@@ -10,50 +10,15 @@ Heptagon::Heptagon(const GpuContext& ctx,
const std::vector<std::string>& inputs,
const std::vector<std::string>& outputs, float start_time,
float end_time)
- : Effect(ctx, inputs, outputs, start_time, end_time), pipeline_(nullptr), bind_group_(nullptr),
- sampler_(nullptr) {
- // Headless mode: skip GPU resource creation (compiled out in STRIP_ALL)
+ : Effect(ctx, inputs, outputs, start_time, end_time) {
HEADLESS_RETURN_IF_NULL(ctx_.device);
- // Init uniforms
- uniforms_buffer_.init(ctx_.device);
+ init_uniforms_buffer();
+ create_nearest_sampler();
+ create_dummy_scene_texture();
- // Create pipeline (standard post-process, no depth)
- pipeline_ = create_post_process_pipeline(
- ctx_.device, WGPUTextureFormat_RGBA8Unorm, heptagon_shader_wgsl);
-
- // Create dummy sampler (scene effects don't use texture input)
- WGPUSamplerDescriptor sampler_desc = {};
- sampler_desc.addressModeU = WGPUAddressMode_ClampToEdge;
- sampler_desc.addressModeV = WGPUAddressMode_ClampToEdge;
- sampler_desc.magFilter = WGPUFilterMode_Nearest;
- sampler_desc.minFilter = WGPUFilterMode_Nearest;
- sampler_desc.maxAnisotropy = 1;
- sampler_ = wgpuDeviceCreateSampler(ctx_.device, &sampler_desc);
-
- // Create 1×1 dummy texture
- WGPUTextureDescriptor tex_desc = {};
- tex_desc.size = {1, 1, 1};
- tex_desc.format = WGPUTextureFormat_RGBA8Unorm;
- tex_desc.usage = WGPUTextureUsage_TextureBinding;
- tex_desc.dimension = WGPUTextureDimension_2D;
- tex_desc.mipLevelCount = 1;
- tex_desc.sampleCount = 1;
- dummy_texture_ = wgpuDeviceCreateTexture(ctx_.device, &tex_desc);
- dummy_texture_view_ = wgpuTextureCreateView(dummy_texture_, nullptr);
-}
-
-Heptagon::~Heptagon() {
- if (bind_group_)
- wgpuBindGroupRelease(bind_group_);
- if (pipeline_)
- wgpuRenderPipelineRelease(pipeline_);
- if (sampler_)
- wgpuSamplerRelease(sampler_);
- if (dummy_texture_view_)
- wgpuTextureViewRelease(dummy_texture_view_);
- if (dummy_texture_)
- wgpuTextureRelease(dummy_texture_);
+ pipeline_.set(create_post_process_pipeline(
+ ctx_.device, WGPUTextureFormat_RGBA8Unorm, heptagon_shader_wgsl));
}
void Heptagon::render(WGPUCommandEncoder encoder,
@@ -66,8 +31,8 @@ void Heptagon::render(WGPUCommandEncoder encoder,
uniforms_buffer_.update(ctx_.queue, params);
// Create bind group (use dummy texture for scene effect)
- pp_update_bind_group(ctx_.device, pipeline_, &bind_group_,
- dummy_texture_view_, uniforms_buffer_.get(),
+ pp_update_bind_group(ctx_.device, pipeline_.get(), bind_group_.get_address(),
+ dummy_texture_view_.get(), uniforms_buffer_.get(),
{nullptr, 0});
// Render pass
@@ -80,8 +45,8 @@ void Heptagon::render(WGPUCommandEncoder encoder,
WGPURenderPassEncoder pass =
wgpuCommandEncoderBeginRenderPass(encoder, &pass_desc);
- wgpuRenderPassEncoderSetPipeline(pass, pipeline_);
- wgpuRenderPassEncoderSetBindGroup(pass, 0, bind_group_, 0, nullptr);
+ wgpuRenderPassEncoderSetPipeline(pass, pipeline_.get());
+ wgpuRenderPassEncoderSetBindGroup(pass, 0, bind_group_.get(), 0, nullptr);
wgpuRenderPassEncoderDraw(pass, 3, 1, 0, 0); // Fullscreen triangle
wgpuRenderPassEncoderEnd(pass);
wgpuRenderPassEncoderRelease(pass);
diff --git a/src/effects/heptagon_effect.h b/src/effects/heptagon_effect.h
index ef05d79..4563e47 100644
--- a/src/effects/heptagon_effect.h
+++ b/src/effects/heptagon_effect.h
@@ -4,22 +4,18 @@
#include "gpu/effect.h"
#include "gpu/uniform_helper.h"
+#include "gpu/wgpu_resource.h"
class Heptagon : public Effect {
public:
Heptagon(const GpuContext& ctx, const std::vector<std::string>& inputs,
const std::vector<std::string>& outputs, float start_time,
float end_time);
- ~Heptagon();
void render(WGPUCommandEncoder encoder, const UniformsSequenceParams& params,
NodeRegistry& nodes) override;
private:
- WGPURenderPipeline pipeline_;
- WGPUBindGroup bind_group_;
- WGPUSampler sampler_;
- WGPUTexture dummy_texture_;
- WGPUTextureView dummy_texture_view_;
- UniformBuffer<UniformsSequenceParams> uniforms_buffer_;
+ RenderPipeline pipeline_;
+ BindGroup bind_group_;
};
diff --git a/src/effects/particles_effect.cc b/src/effects/particles_effect.cc
index d83f303..3c9feb7 100644
--- a/src/effects/particles_effect.cc
+++ b/src/effects/particles_effect.cc
@@ -12,11 +12,9 @@ Particles::Particles(const GpuContext& ctx,
const std::vector<std::string>& outputs, float start_time,
float end_time)
: Effect(ctx, inputs, outputs, start_time, end_time) {
- // Headless mode: skip GPU resource creation (compiled out in STRIP_ALL)
HEADLESS_RETURN_IF_NULL(ctx_.device);
- // Initialize uniforms
- uniforms_.init(ctx_.device);
+ init_uniforms_buffer();
// Initialize particles buffer
std::vector<Particle> init_p(NUM_PARTICLES);
@@ -49,7 +47,7 @@ Particles::Particles(const GpuContext& ctx,
// Create compute shader (particle simulation)
ResourceBinding compute_bindings[] = {
{particles_buffer_, WGPUBufferBindingType_Storage},
- {uniforms_.get(), WGPUBufferBindingType_Uniform}};
+ {uniforms_buffer_.get(), WGPUBufferBindingType_Uniform}};
compute_pass_ = gpu_create_compute_pass(ctx_.device, particle_compute_wgsl,
compute_bindings, 2);
compute_pass_.workgroup_size_x = (NUM_PARTICLES + 63) / 64;
@@ -57,7 +55,7 @@ Particles::Particles(const GpuContext& ctx,
// Create render shader (particle rendering)
ResourceBinding render_bindings[] = {
{particles_buffer_, WGPUBufferBindingType_ReadOnlyStorage},
- {uniforms_.get(), WGPUBufferBindingType_Uniform}};
+ {uniforms_buffer_.get(), WGPUBufferBindingType_Uniform}};
render_pass_ =
gpu_create_render_pass(ctx_.device, WGPUTextureFormat_RGBA8Unorm,
particle_render_wgsl, render_bindings, 2);
@@ -69,7 +67,7 @@ void Particles::render(WGPUCommandEncoder encoder,
const UniformsSequenceParams& params,
NodeRegistry& nodes) {
// Update uniforms
- uniforms_.update(ctx_.queue, params);
+ uniforms_buffer_.update(ctx_.queue, params);
// Run compute pass (particle simulation)
WGPUComputePassEncoder compute =
diff --git a/src/effects/particles_effect.h b/src/effects/particles_effect.h
index 38c60d7..e855b7b 100644
--- a/src/effects/particles_effect.h
+++ b/src/effects/particles_effect.h
@@ -30,5 +30,4 @@ class Particles : public Effect {
ComputePass compute_pass_;
RenderPass render_pass_;
GpuBuffer particles_buffer_;
- UniformBuffer<UniformsSequenceParams> uniforms_;
};
diff --git a/src/effects/passthrough_effect.cc b/src/effects/passthrough_effect.cc
index b9d9337..24eefca 100644
--- a/src/effects/passthrough_effect.cc
+++ b/src/effects/passthrough_effect.cc
@@ -9,27 +9,14 @@ Passthrough::Passthrough(const GpuContext& ctx,
const std::vector<std::string>& inputs,
const std::vector<std::string>& outputs,
float start_time, float end_time)
- : Effect(ctx, inputs, outputs, start_time, end_time), pipeline_(nullptr),
- bind_group_(nullptr), sampler_(nullptr) {
- // Headless mode: skip GPU resource creation (compiled out in STRIP_ALL)
+ : Effect(ctx, inputs, outputs, start_time, end_time) {
HEADLESS_RETURN_IF_NULL(ctx_.device);
- // Init uniform buffer
- uniforms_buffer_.init(ctx_.device);
- // Create pipeline (simple version without effect params)
- pipeline_ = create_post_process_pipeline_simple(
- ctx_.device, WGPUTextureFormat_RGBA8Unorm, passthrough_shader_wgsl);
+ init_uniforms_buffer();
+ create_linear_sampler();
- // Create sampler
- WGPUSamplerDescriptor sampler_desc = {};
- sampler_desc.addressModeU = WGPUAddressMode_ClampToEdge;
- sampler_desc.addressModeV = WGPUAddressMode_ClampToEdge;
- sampler_desc.addressModeW = WGPUAddressMode_ClampToEdge;
- sampler_desc.magFilter = WGPUFilterMode_Linear;
- sampler_desc.minFilter = WGPUFilterMode_Linear;
- sampler_desc.mipmapFilter = WGPUMipmapFilterMode_Nearest;
- sampler_desc.maxAnisotropy = 1;
- sampler_ = wgpuDeviceCreateSampler(ctx_.device, &sampler_desc);
+ pipeline_.set(create_post_process_pipeline_simple(
+ ctx_.device, WGPUTextureFormat_RGBA8Unorm, passthrough_shader_wgsl));
}
void Passthrough::render(WGPUCommandEncoder encoder,
@@ -45,7 +32,7 @@ void Passthrough::render(WGPUCommandEncoder encoder,
// Manually create bind group with only 3 entries (no effect params needed)
WGPUBindGroupEntry entries[3] = {};
entries[0].binding = PP_BINDING_SAMPLER;
- entries[0].sampler = sampler_;
+ entries[0].sampler = sampler_.get();
entries[1].binding = PP_BINDING_TEXTURE;
entries[1].textureView = input_view;
entries[2].binding = PP_BINDING_UNIFORMS;
@@ -53,14 +40,11 @@ void Passthrough::render(WGPUCommandEncoder encoder,
entries[2].size = sizeof(UniformsSequenceParams);
WGPUBindGroupDescriptor bg_desc = {};
- bg_desc.layout = wgpuRenderPipelineGetBindGroupLayout(pipeline_, 0);
+ bg_desc.layout = wgpuRenderPipelineGetBindGroupLayout(pipeline_.get(), 0);
bg_desc.entryCount = 3;
bg_desc.entries = entries;
- if (bind_group_) {
- wgpuBindGroupRelease(bind_group_);
- }
- bind_group_ = wgpuDeviceCreateBindGroup(ctx_.device, &bg_desc);
+ bind_group_.replace(wgpuDeviceCreateBindGroup(ctx_.device, &bg_desc));
// Render pass
WGPURenderPassColorAttachment color_attachment = {};
@@ -72,8 +56,8 @@ void Passthrough::render(WGPUCommandEncoder encoder,
WGPURenderPassEncoder pass =
wgpuCommandEncoderBeginRenderPass(encoder, &pass_desc);
- wgpuRenderPassEncoderSetPipeline(pass, pipeline_);
- wgpuRenderPassEncoderSetBindGroup(pass, 0, bind_group_, 0, nullptr);
+ wgpuRenderPassEncoderSetPipeline(pass, pipeline_.get());
+ wgpuRenderPassEncoderSetBindGroup(pass, 0, bind_group_.get(), 0, nullptr);
wgpuRenderPassEncoderDraw(pass, 3, 1, 0, 0); // Fullscreen triangle
wgpuRenderPassEncoderEnd(pass);
wgpuRenderPassEncoderRelease(pass);
diff --git a/src/effects/passthrough_effect.h b/src/effects/passthrough_effect.h
index f684b9b..1c60e02 100644
--- a/src/effects/passthrough_effect.h
+++ b/src/effects/passthrough_effect.h
@@ -4,6 +4,7 @@
#include "gpu/effect.h"
#include "gpu/uniform_helper.h"
+#include "gpu/wgpu_resource.h"
class Passthrough : public Effect {
public:
@@ -15,8 +16,6 @@ class Passthrough : public Effect {
NodeRegistry& nodes) override;
private:
- WGPURenderPipeline pipeline_;
- WGPUBindGroup bind_group_;
- WGPUSampler sampler_;
- UniformBuffer<UniformsSequenceParams> uniforms_buffer_;
+ RenderPipeline pipeline_;
+ BindGroup bind_group_;
};
diff --git a/src/effects/peak_meter_effect.cc b/src/effects/peak_meter_effect.cc
index d823e20..d077302 100644
--- a/src/effects/peak_meter_effect.cc
+++ b/src/effects/peak_meter_effect.cc
@@ -12,7 +12,7 @@ PeakMeter::PeakMeter(const GpuContext& ctx,
: Effect(ctx, inputs, outputs, start_time, end_time), pipeline_(nullptr), bind_group_(nullptr) {
HEADLESS_RETURN_IF_NULL(ctx_.device);
- uniforms_buffer_.init(ctx_.device);
+ init_uniforms_buffer();
const char* shader_main = R"(
struct VertexOutput {
diff --git a/src/effects/peak_meter_effect.h b/src/effects/peak_meter_effect.h
index e397a71..1786522 100644
--- a/src/effects/peak_meter_effect.h
+++ b/src/effects/peak_meter_effect.h
@@ -18,5 +18,4 @@ class PeakMeter : public Effect {
private:
WGPURenderPipeline pipeline_;
WGPUBindGroup bind_group_;
- UniformBuffer<UniformsSequenceParams> uniforms_buffer_;
};
diff --git a/src/effects/placeholder_effect.cc b/src/effects/placeholder_effect.cc
index 3367f1c..e024a6b 100644
--- a/src/effects/placeholder_effect.cc
+++ b/src/effects/placeholder_effect.cc
@@ -12,26 +12,16 @@ Placeholder::Placeholder(const GpuContext& ctx,
float start_time, float end_time,
const char* placeholder_name)
: Effect(ctx, inputs, outputs, start_time, end_time), pipeline_(nullptr),
- bind_group_(nullptr), sampler_(nullptr), name_(placeholder_name) {
- // Log once on construction
+ bind_group_(nullptr), name_(placeholder_name) {
fprintf(stderr, "TODO: %s not yet implemented, using passthrough\n", name_);
- // Headless mode: skip GPU resource creation (compiled out in STRIP_ALL)
HEADLESS_RETURN_IF_NULL(ctx_.device);
- uniforms_buffer_.init(ctx_.device);
+ init_uniforms_buffer();
+ create_linear_sampler();
+
pipeline_ = create_post_process_pipeline(
ctx_.device, WGPUTextureFormat_RGBA8Unorm, passthrough_shader_wgsl);
-
- WGPUSamplerDescriptor sampler_desc = {};
- sampler_desc.addressModeU = WGPUAddressMode_ClampToEdge;
- sampler_desc.addressModeV = WGPUAddressMode_ClampToEdge;
- sampler_desc.addressModeW = WGPUAddressMode_ClampToEdge;
- sampler_desc.magFilter = WGPUFilterMode_Linear;
- sampler_desc.minFilter = WGPUFilterMode_Linear;
- sampler_desc.mipmapFilter = WGPUMipmapFilterMode_Nearest;
- sampler_desc.maxAnisotropy = 1;
- sampler_ = wgpuDeviceCreateSampler(ctx_.device, &sampler_desc);
}
void Placeholder::render(WGPUCommandEncoder encoder,
diff --git a/src/effects/placeholder_effect.h b/src/effects/placeholder_effect.h
index 24cf3f4..72b156f 100644
--- a/src/effects/placeholder_effect.h
+++ b/src/effects/placeholder_effect.h
@@ -18,7 +18,5 @@ class Placeholder : public Effect {
private:
WGPURenderPipeline pipeline_;
WGPUBindGroup bind_group_;
- WGPUSampler sampler_;
- UniformBuffer<UniformsSequenceParams> uniforms_buffer_;
const char* name_;
};
diff --git a/src/gpu/effect.cc b/src/gpu/effect.cc
index 752dd84..d0693ec 100644
--- a/src/gpu/effect.cc
+++ b/src/gpu/effect.cc
@@ -14,6 +14,7 @@ Effect::Effect(const GpuContext& ctx, const std::vector<std::string>& inputs,
FATAL_CHECK(!outputs.empty(), "Effect must have at least one output\n");
FATAL_CHECK(start_time <= end_time, "Invalid time range: %f > %f\n",
start_time, end_time);
+ HEADLESS_RETURN_IF_NULL(ctx_.device);
}
void Effect::dispatch_render(WGPUCommandEncoder encoder,
@@ -55,3 +56,21 @@ void Effect::blit_input_to_output(WGPUCommandEncoder encoder,
wgpuCommandEncoderCopyTextureToTexture(encoder, &src_copy, &dst_copy,
&extent);
}
+
+void Effect::init_uniforms_buffer() {
+ uniforms_buffer_.init(ctx_.device);
+}
+
+void Effect::create_linear_sampler() {
+ sampler_.set(gpu_create_linear_sampler(ctx_.device));
+}
+
+void Effect::create_nearest_sampler() {
+ sampler_.set(gpu_create_nearest_sampler(ctx_.device));
+}
+
+void Effect::create_dummy_scene_texture() {
+ TextureWithView dummy = gpu_create_dummy_scene_texture(ctx_.device);
+ dummy_texture_.set(dummy.texture);
+ dummy_texture_view_.set(dummy.view);
+}
diff --git a/src/gpu/effect.h b/src/gpu/effect.h
index f5cdf2d..d47a8b7 100644
--- a/src/gpu/effect.h
+++ b/src/gpu/effect.h
@@ -6,6 +6,8 @@
#include "gpu/gpu.h"
#include "gpu/sequence.h"
+#include "gpu/uniform_helper.h"
+#include "gpu/wgpu_resource.h"
#include <string>
#include <vector>
@@ -51,6 +53,24 @@ class Effect {
int width_ = 1280;
int height_ = 720;
+ // Common resources for most effects
+ UniformBuffer<UniformsSequenceParams> uniforms_buffer_;
+ Sampler sampler_;
+ Texture dummy_texture_;
+ TextureView dummy_texture_view_;
+
+ // Helper: Initialize uniforms buffer (call in subclass constructor)
+ void init_uniforms_buffer();
+
+ // Helper: Create linear sampler (call in subclass constructor)
+ void create_linear_sampler();
+
+ // Helper: Create nearest sampler (call in subclass constructor)
+ void create_nearest_sampler();
+
+ // Helper: Create dummy texture for scene effects (call in subclass constructor)
+ void create_dummy_scene_texture();
+
private:
float start_time_;
float end_time_;
diff --git a/src/gpu/gpu.cc b/src/gpu/gpu.cc
index 2226889..e743bf7 100644
--- a/src/gpu/gpu.cc
+++ b/src/gpu/gpu.cc
@@ -124,6 +124,41 @@ WGPUTextureView gpu_create_texture_view_2d(WGPUTexture texture,
return wgpuTextureCreateView(texture, &view_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 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);
+}
+
+TextureWithView gpu_create_dummy_scene_texture(WGPUDevice device) {
+ WGPUTextureDescriptor desc = {};
+ desc.size = {1, 1, 1};
+ desc.format = WGPUTextureFormat_RGBA8Unorm;
+ desc.usage = WGPUTextureUsage_TextureBinding;
+ desc.dimension = WGPUTextureDimension_2D;
+ desc.mipLevelCount = 1;
+ desc.sampleCount = 1;
+ WGPUTexture texture = wgpuDeviceCreateTexture(device, &desc);
+ WGPUTextureView view = wgpuTextureCreateView(texture, nullptr);
+ return {texture, view};
+}
+
RenderPass gpu_create_render_pass(WGPUDevice device, WGPUTextureFormat format,
const char* shader_code,
ResourceBinding* bindings, int num_bindings) {
diff --git a/src/gpu/gpu.h b/src/gpu/gpu.h
index 9a3fd38..d6c0255 100644
--- a/src/gpu/gpu.h
+++ b/src/gpu/gpu.h
@@ -97,4 +97,11 @@ RenderPass
gpu_create_render_pass(WGPUDevice device,
WGPUTextureFormat format, // Needed for render pipeline
const char* shader_code, ResourceBinding* bindings,
- int num_bindings); \ No newline at end of file
+ int num_bindings);
+
+// Common sampler configurations
+WGPUSampler gpu_create_linear_sampler(WGPUDevice device);
+WGPUSampler gpu_create_nearest_sampler(WGPUDevice device);
+
+// Dummy 1x1 texture for scene effects (don't need texture input)
+TextureWithView gpu_create_dummy_scene_texture(WGPUDevice device); \ No newline at end of file
diff --git a/src/gpu/wgpu_resource.h b/src/gpu/wgpu_resource.h
new file mode 100644
index 0000000..e448b18
--- /dev/null
+++ b/src/gpu/wgpu_resource.h
@@ -0,0 +1,45 @@
+// WGPU Resource RAII Wrapper
+// Automatic release on destruction
+//
+// IMPORTANT: Field ordering matters for destruction order.
+// Members are destroyed in reverse declaration order.
+// Declare dependencies before dependents:
+// RenderPipeline pipeline_; // Destroyed last
+// BindGroup bind_group_; // Destroyed first (may reference pipeline)
+
+#pragma once
+#include "platform/platform.h"
+#include "util/fatal_error.h"
+
+template<typename T, void(*Release)(T)>
+class WGPUResource {
+ public:
+ WGPUResource() : ptr_(nullptr) {}
+ ~WGPUResource() { if (ptr_) Release(ptr_); }
+
+ void set(T ptr) {
+ FATAL_ASSERT(ptr_ == nullptr);
+ ptr_ = ptr;
+ }
+
+ void replace(T ptr) {
+ if (ptr_) Release(ptr_);
+ ptr_ = ptr;
+ }
+
+ T get() const { return ptr_; }
+ T* get_address() { return &ptr_; }
+
+ private:
+ T ptr_;
+ WGPUResource(const WGPUResource&) = delete;
+ WGPUResource& operator=(const WGPUResource&) = delete;
+};
+
+using BindGroup = WGPUResource<WGPUBindGroup, wgpuBindGroupRelease>;
+using RenderPipeline = WGPUResource<WGPURenderPipeline, wgpuRenderPipelineRelease>;
+using ComputePipeline = WGPUResource<WGPUComputePipeline, wgpuComputePipelineRelease>;
+using Sampler = WGPUResource<WGPUSampler, wgpuSamplerRelease>;
+using Texture = WGPUResource<WGPUTexture, wgpuTextureRelease>;
+using TextureView = WGPUResource<WGPUTextureView, wgpuTextureViewRelease>;
+using Buffer = WGPUResource<WGPUBuffer, wgpuBufferRelease>;
diff --git a/tools/seq_compiler.py b/tools/seq_compiler.py
index dec3ab1..18d5a1f 100755
--- a/tools/seq_compiler.py
+++ b/tools/seq_compiler.py
@@ -710,11 +710,31 @@ void RenderTimeline(WGPUSurface surface, float time, int width, int height,
}
'''
- # Write output
+ # Write C++ output
with open(args.output, 'w') as f:
f.write(all_cpp)
- print(f"Generated {len(sequences)} sequence(s) -> {args.output}")
+ # Write C++ header
+ header_path = os.path.splitext(args.output)[0] + ".h"
+ header_content = f'''// Generated by seq_compiler.py
+// DO NOT EDIT
+#pragma once
+
+#include "gpu/gpu.h"
+#include "gpu/sequence.h"
+
+void InitializeSequences(const GpuContext& ctx, int width, int height);
+Sequence* GetActiveSequence(float time);
+void RenderTimeline(WGPUCommandEncoder encoder, float time, int width, int height,
+ float beat_time, float audio_intensity);
+void RenderTimeline(WGPUSurface surface, float time, int width, int height,
+ float beat_time, float audio_intensity);
+float GetDemoDuration();
+'''
+ with open(header_path, 'w') as f:
+ f.write(header_content)
+
+ print(f"Generated {len(sequences)} sequence(s) -> {args.output} and {header_path}")
if __name__ == '__main__':
main()
diff --git a/workspaces/test/timeline.seq b/workspaces/test/timeline.seq
index 2ae7e8e..37cc2fa 100644
--- a/workspaces/test/timeline.seq
+++ b/workspaces/test/timeline.seq
@@ -4,7 +4,9 @@
SEQUENCE 0.0 0 "MainLoop"
EFFECT + Hybrid3D source -> temp1 0.00 4.00
-EFFECT + GaussianBlur temp1 -> sink 0.00 4.00
+EFFECT + GaussianBlur temp1 -> temp2 0.00 4.00
+EFFECT + PeakMeter temp2 -> sink 0.00 4.00
SEQUENCE 4.0 0 "MainLoop"
-EFFECT + Heptagon source -> sink 0.0 16.0
+EFFECT + Heptagon source -> temp1 0.0 16.0
+EFFECT + PeakMeter temp1 -> sink 0.0 16.0