diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/gpu/bind_group_builder.h | 111 | ||||
| -rw-r--r-- | src/gpu/demo_effects.h | 1 | ||||
| -rw-r--r-- | src/gpu/effects/cnn_effect.cc | 106 | ||||
| -rw-r--r-- | src/gpu/effects/post_process_helper.cc | 102 | ||||
| -rw-r--r-- | src/gpu/effects/rotating_cube_effect.cc | 23 | ||||
| -rw-r--r-- | src/gpu/effects/scene1_effect.cc | 28 | ||||
| -rw-r--r-- | src/gpu/effects/scene1_effect.h | 19 | ||||
| -rw-r--r-- | src/gpu/effects/shaders.cc | 4 | ||||
| -rw-r--r-- | src/gpu/effects/shaders.h | 1 | ||||
| -rw-r--r-- | src/gpu/pipeline_builder.h | 109 | ||||
| -rw-r--r-- | src/gpu/sampler_cache.h | 61 | ||||
| -rw-r--r-- | src/tests/gpu/test_demo_effects.cc | 1 |
12 files changed, 391 insertions, 175 deletions
diff --git a/src/gpu/bind_group_builder.h b/src/gpu/bind_group_builder.h new file mode 100644 index 0000000..d63f6e2 --- /dev/null +++ b/src/gpu/bind_group_builder.h @@ -0,0 +1,111 @@ +// WGPU bind group builder - reduces boilerplate for bind group creation +#pragma once +#include <vector> + +// Forward declarations (users must include gpu.h) +struct WGPUBindGroupLayoutEntry; +struct WGPUBindGroupEntry; +struct WGPUDeviceImpl; +typedef struct WGPUDeviceImpl* WGPUDevice; +struct WGPUBindGroupLayoutImpl; +typedef struct WGPUBindGroupLayoutImpl* WGPUBindGroupLayout; +struct WGPUBindGroupImpl; +typedef struct WGPUBindGroupImpl* WGPUBindGroup; +struct WGPUBufferImpl; +typedef struct WGPUBufferImpl* WGPUBuffer; +struct WGPUTextureViewImpl; +typedef struct WGPUTextureViewImpl* WGPUTextureView; +struct WGPUSamplerImpl; +typedef struct WGPUSamplerImpl* WGPUSampler; +typedef uint32_t WGPUShaderStageFlags; + +#include "platform/platform.h" + +class BindGroupLayoutBuilder { + std::vector<WGPUBindGroupLayoutEntry> entries_; + +public: + BindGroupLayoutBuilder& uniform(uint32_t binding, WGPUShaderStageFlags vis, size_t min_size = 0) { + WGPUBindGroupLayoutEntry e{}; + e.binding = binding; + e.visibility = vis; + e.buffer.type = WGPUBufferBindingType_Uniform; + if (min_size) e.buffer.minBindingSize = min_size; + entries_.push_back(e); + return *this; + } + + BindGroupLayoutBuilder& storage(uint32_t binding, WGPUShaderStageFlags vis, size_t min_size = 0) { + WGPUBindGroupLayoutEntry e{}; + e.binding = binding; + e.visibility = vis; + e.buffer.type = WGPUBufferBindingType_ReadOnlyStorage; + if (min_size) e.buffer.minBindingSize = min_size; + entries_.push_back(e); + return *this; + } + + BindGroupLayoutBuilder& texture(uint32_t binding, WGPUShaderStageFlags vis) { + WGPUBindGroupLayoutEntry e{}; + e.binding = binding; + e.visibility = vis; + e.texture.sampleType = WGPUTextureSampleType_Float; + e.texture.viewDimension = WGPUTextureViewDimension_2D; + entries_.push_back(e); + return *this; + } + + BindGroupLayoutBuilder& sampler(uint32_t binding, WGPUShaderStageFlags vis) { + WGPUBindGroupLayoutEntry e{}; + e.binding = binding; + e.visibility = vis; + e.sampler.type = WGPUSamplerBindingType_Filtering; + entries_.push_back(e); + return *this; + } + + WGPUBindGroupLayout build(WGPUDevice device) { + WGPUBindGroupLayoutDescriptor desc{}; + desc.entryCount = entries_.size(); + desc.entries = entries_.data(); + return wgpuDeviceCreateBindGroupLayout(device, &desc); + } +}; + +class BindGroupBuilder { + std::vector<WGPUBindGroupEntry> entries_; + +public: + BindGroupBuilder& buffer(uint32_t binding, WGPUBuffer buf, size_t size) { + WGPUBindGroupEntry e{}; + e.binding = binding; + e.buffer = buf; + e.size = size; + entries_.push_back(e); + return *this; + } + + BindGroupBuilder& texture(uint32_t binding, WGPUTextureView view) { + WGPUBindGroupEntry e{}; + e.binding = binding; + e.textureView = view; + entries_.push_back(e); + return *this; + } + + BindGroupBuilder& sampler(uint32_t binding, WGPUSampler samp) { + WGPUBindGroupEntry e{}; + e.binding = binding; + e.sampler = samp; + entries_.push_back(e); + return *this; + } + + WGPUBindGroup build(WGPUDevice device, WGPUBindGroupLayout layout) { + WGPUBindGroupDescriptor desc{}; + desc.layout = layout; + desc.entryCount = entries_.size(); + desc.entries = entries_.data(); + return wgpuDeviceCreateBindGroup(device, &desc); + } +}; diff --git a/src/gpu/demo_effects.h b/src/gpu/demo_effects.h index 72b3f65..1ccf930 100644 --- a/src/gpu/demo_effects.h +++ b/src/gpu/demo_effects.h @@ -15,6 +15,7 @@ #include "gpu/effects/theme_modulation_effect.h" // ThemeModulationEffect with full definition #include "gpu/effects/hybrid_3d_effect.h" #include "gpu/effects/flash_cube_effect.h" +#include "gpu/effects/scene1_effect.h" #include "gpu/gpu.h" #include "gpu/texture_manager.h" #include "gpu/uniform_helper.h" diff --git a/src/gpu/effects/cnn_effect.cc b/src/gpu/effects/cnn_effect.cc index 7107bea..d74187c 100644 --- a/src/gpu/effects/cnn_effect.cc +++ b/src/gpu/effects/cnn_effect.cc @@ -6,70 +6,30 @@ #include "gpu/effects/shaders.h" #include "gpu/effects/shader_composer.h" #include "gpu/effect.h" +#include "gpu/bind_group_builder.h" +#include "gpu/sampler_cache.h" +#include "gpu/pipeline_builder.h" // Create custom pipeline with 5 bindings (includes original texture) static WGPURenderPipeline create_cnn_pipeline(WGPUDevice device, WGPUTextureFormat format, const char* shader_code) { - std::string composed_shader = ShaderComposer::Get().Compose({}, shader_code); + WGPUBindGroupLayout bgl = BindGroupLayoutBuilder() + .sampler(0, WGPUShaderStage_Fragment) + .texture(1, WGPUShaderStage_Fragment) + .uniform(2, WGPUShaderStage_Vertex | WGPUShaderStage_Fragment) + .uniform(3, WGPUShaderStage_Fragment) + .texture(4, WGPUShaderStage_Fragment) + .build(device); - WGPUShaderModuleDescriptor shader_desc = {}; - WGPUShaderSourceWGSL wgsl_src = {}; - wgsl_src.chain.sType = WGPUSType_ShaderSourceWGSL; - wgsl_src.code = str_view(composed_shader.c_str()); - shader_desc.nextInChain = &wgsl_src.chain; - WGPUShaderModule shader_module = - wgpuDeviceCreateShaderModule(device, &shader_desc); + WGPURenderPipeline pipeline = RenderPipelineBuilder(device) + .shader(shader_code) + .bind_group_layout(bgl) + .format(format) + .build(); - WGPUBindGroupLayoutEntry bgl_entries[5] = {}; - bgl_entries[0].binding = 0; // sampler - bgl_entries[0].visibility = WGPUShaderStage_Fragment; - bgl_entries[0].sampler.type = WGPUSamplerBindingType_Filtering; - bgl_entries[1].binding = 1; // input texture - bgl_entries[1].visibility = WGPUShaderStage_Fragment; - bgl_entries[1].texture.sampleType = WGPUTextureSampleType_Float; - bgl_entries[1].texture.viewDimension = WGPUTextureViewDimension_2D; - bgl_entries[2].binding = 2; // uniforms - bgl_entries[2].visibility = WGPUShaderStage_Vertex | WGPUShaderStage_Fragment; - bgl_entries[2].buffer.type = WGPUBufferBindingType_Uniform; - bgl_entries[3].binding = 3; // effect params - bgl_entries[3].visibility = WGPUShaderStage_Fragment; - bgl_entries[3].buffer.type = WGPUBufferBindingType_Uniform; - bgl_entries[4].binding = 4; // original texture - bgl_entries[4].visibility = WGPUShaderStage_Fragment; - bgl_entries[4].texture.sampleType = WGPUTextureSampleType_Float; - bgl_entries[4].texture.viewDimension = WGPUTextureViewDimension_2D; - - WGPUBindGroupLayoutDescriptor bgl_desc = {}; - bgl_desc.entryCount = 5; - bgl_desc.entries = bgl_entries; - WGPUBindGroupLayout bgl = wgpuDeviceCreateBindGroupLayout(device, &bgl_desc); - - WGPUPipelineLayoutDescriptor pl_desc = {}; - pl_desc.bindGroupLayoutCount = 1; - pl_desc.bindGroupLayouts = &bgl; - WGPUPipelineLayout pl = wgpuDeviceCreatePipelineLayout(device, &pl_desc); - - WGPUColorTargetState color_target = {}; - color_target.format = 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 = pl; - pipeline_desc.vertex.module = shader_module; - pipeline_desc.vertex.entryPoint = str_view("vs_main"); - pipeline_desc.fragment = &fragment_state; - pipeline_desc.primitive.topology = WGPUPrimitiveTopology_TriangleList; - pipeline_desc.multisample.count = 1; - pipeline_desc.multisample.mask = 0xFFFFFFFF; - - return wgpuDeviceCreateRenderPipeline(device, &pipeline_desc); + wgpuBindGroupLayoutRelease(bgl); + return pipeline; } CNNEffect::CNNEffect(const GpuContext& ctx) @@ -137,29 +97,13 @@ void CNNEffect::update_bind_group(WGPUTextureView input_view) { wgpuBindGroupRelease(bind_group_); WGPUBindGroupLayout bgl = wgpuRenderPipelineGetBindGroupLayout(pipeline_, 0); - WGPUSamplerDescriptor sd = {}; - sd.magFilter = WGPUFilterMode_Linear; - sd.minFilter = WGPUFilterMode_Linear; - sd.maxAnisotropy = 1; - WGPUSampler sampler = wgpuDeviceCreateSampler(ctx_.device, &sd); - - WGPUBindGroupEntry bge[5] = {}; - bge[0].binding = 0; - bge[0].sampler = sampler; - bge[1].binding = 1; - bge[1].textureView = input_view_; - bge[2].binding = 2; - bge[2].buffer = uniforms_.get().buffer; - bge[2].size = uniforms_.get().size; - bge[3].binding = 3; - bge[3].buffer = params_buffer_.get().buffer; - bge[3].size = params_buffer_.get().size; - bge[4].binding = 4; - bge[4].textureView = original_view_ ? original_view_ : input_view_; + WGPUSampler sampler = SamplerCache::Get().get_or_create(ctx_.device, SamplerCache::linear()); - WGPUBindGroupDescriptor bgd = {}; - bgd.layout = bgl; - bgd.entryCount = 5; - bgd.entries = bge; - bind_group_ = wgpuDeviceCreateBindGroup(ctx_.device, &bgd); + bind_group_ = BindGroupBuilder() + .sampler(0, sampler) + .texture(1, input_view_) + .buffer(2, uniforms_.get().buffer, uniforms_.get().size) + .buffer(3, params_buffer_.get().buffer, params_buffer_.get().size) + .texture(4, original_view_ ? original_view_ : input_view_) + .build(ctx_.device, bgl); } diff --git a/src/gpu/effects/post_process_helper.cc b/src/gpu/effects/post_process_helper.cc index e99467f..0c339c7 100644 --- a/src/gpu/effects/post_process_helper.cc +++ b/src/gpu/effects/post_process_helper.cc @@ -5,69 +5,30 @@ #include "../demo_effects.h" #include "gpu/gpu.h" #include "gpu/effects/shader_composer.h" +#include "gpu/bind_group_builder.h" +#include "gpu/sampler_cache.h" +#include "gpu/pipeline_builder.h" #include <cstring> // Helper to create a standard post-processing pipeline WGPURenderPipeline create_post_process_pipeline(WGPUDevice device, WGPUTextureFormat format, const char* shader_code) { - std::string composed_shader = ShaderComposer::Get().Compose({}, shader_code); + WGPUBindGroupLayout bgl = BindGroupLayoutBuilder() + .sampler(PP_BINDING_SAMPLER, WGPUShaderStage_Fragment) + .texture(PP_BINDING_TEXTURE, WGPUShaderStage_Fragment) + .uniform(PP_BINDING_UNIFORMS, WGPUShaderStage_Vertex | WGPUShaderStage_Fragment) + .uniform(PP_BINDING_EFFECT_PARAMS, WGPUShaderStage_Fragment) + .build(device); - WGPUShaderModuleDescriptor shader_desc = {}; - WGPUShaderSourceWGSL wgsl_src = {}; - wgsl_src.chain.sType = WGPUSType_ShaderSourceWGSL; - wgsl_src.code = str_view(composed_shader.c_str()); - shader_desc.nextInChain = &wgsl_src.chain; - WGPUShaderModule shader_module = - wgpuDeviceCreateShaderModule(device, &shader_desc); + WGPURenderPipeline pipeline = RenderPipelineBuilder(device) + .shader(shader_code) + .bind_group_layout(bgl) + .format(format) + .build(); - WGPUBindGroupLayoutEntry bgl_entries[4] = {}; - bgl_entries[0].binding = PP_BINDING_SAMPLER; - bgl_entries[0].visibility = WGPUShaderStage_Fragment; - bgl_entries[0].sampler.type = WGPUSamplerBindingType_Filtering; - bgl_entries[1].binding = PP_BINDING_TEXTURE; - bgl_entries[1].visibility = WGPUShaderStage_Fragment; - bgl_entries[1].texture.sampleType = WGPUTextureSampleType_Float; - bgl_entries[1].texture.viewDimension = WGPUTextureViewDimension_2D; - bgl_entries[2].binding = PP_BINDING_UNIFORMS; - bgl_entries[2].visibility = WGPUShaderStage_Vertex | WGPUShaderStage_Fragment; - bgl_entries[2].buffer.type = WGPUBufferBindingType_Uniform; - - // Add an entry for effect-specific parameters - bgl_entries[3].binding = PP_BINDING_EFFECT_PARAMS; - bgl_entries[3].visibility = WGPUShaderStage_Fragment; - bgl_entries[3].buffer.type = WGPUBufferBindingType_Uniform; - - WGPUBindGroupLayoutDescriptor bgl_desc = {}; - bgl_desc.entryCount = 4; - bgl_desc.entries = bgl_entries; - WGPUBindGroupLayout bgl = wgpuDeviceCreateBindGroupLayout(device, &bgl_desc); - - WGPUPipelineLayoutDescriptor pl_desc = {}; - pl_desc.bindGroupLayoutCount = 1; - pl_desc.bindGroupLayouts = &bgl; - WGPUPipelineLayout pl = wgpuDeviceCreatePipelineLayout(device, &pl_desc); - - WGPUColorTargetState color_target = {}; - color_target.format = 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 = pl; - pipeline_desc.vertex.module = shader_module; - pipeline_desc.vertex.entryPoint = str_view("vs_main"); - pipeline_desc.fragment = &fragment_state; - pipeline_desc.primitive.topology = WGPUPrimitiveTopology_TriangleList; - pipeline_desc.multisample.count = 1; - pipeline_desc.multisample.mask = 0xFFFFFFFF; - - return wgpuDeviceCreateRenderPipeline(device, &pipeline_desc); + wgpuBindGroupLayoutRelease(bgl); + return pipeline; } // --- PostProcess Implementation Helper --- @@ -82,25 +43,16 @@ void pp_update_bind_group(WGPUDevice device, WGPURenderPipeline pipeline, if (*bind_group) wgpuBindGroupRelease(*bind_group); + WGPUBindGroupLayout bgl = wgpuRenderPipelineGetBindGroupLayout(pipeline, 0); - WGPUSamplerDescriptor sd = {}; - sd.magFilter = WGPUFilterMode_Linear; - sd.minFilter = WGPUFilterMode_Linear; - sd.maxAnisotropy = 1; - WGPUSampler sampler = wgpuDeviceCreateSampler(device, &sd); - WGPUBindGroupEntry bge[4] = {}; - bge[0].binding = PP_BINDING_SAMPLER; - bge[0].sampler = sampler; - bge[1].binding = PP_BINDING_TEXTURE; - bge[1].textureView = input_view; - bge[2].binding = PP_BINDING_UNIFORMS; - bge[2].buffer = uniforms.buffer; - bge[2].size = uniforms.size; - bge[3].binding = PP_BINDING_EFFECT_PARAMS; - bge[3].buffer = - effect_params.buffer ? effect_params.buffer : g_dummy_buffer.buffer; - bge[3].size = effect_params.buffer ? effect_params.size : g_dummy_buffer.size; - WGPUBindGroupDescriptor bgd = { - .layout = bgl, .entryCount = 4, .entries = bge}; - *bind_group = wgpuDeviceCreateBindGroup(device, &bgd); + WGPUSampler sampler = SamplerCache::Get().get_or_create(device, SamplerCache::linear()); + + *bind_group = BindGroupBuilder() + .sampler(PP_BINDING_SAMPLER, sampler) + .texture(PP_BINDING_TEXTURE, input_view) + .buffer(PP_BINDING_UNIFORMS, uniforms.buffer, uniforms.size) + .buffer(PP_BINDING_EFFECT_PARAMS, + effect_params.buffer ? effect_params.buffer : g_dummy_buffer.buffer, + effect_params.buffer ? effect_params.size : g_dummy_buffer.size) + .build(device, bgl); } diff --git a/src/gpu/effects/rotating_cube_effect.cc b/src/gpu/effects/rotating_cube_effect.cc index 8d1f05a..da973e5 100644 --- a/src/gpu/effects/rotating_cube_effect.cc +++ b/src/gpu/effects/rotating_cube_effect.cc @@ -5,16 +5,14 @@ #include "gpu/effects/rotating_cube_effect.h" #include "generated/assets.h" #include "gpu/effects/shader_composer.h" +#include "gpu/sampler_cache.h" #include "util/asset_manager_utils.h" RotatingCubeEffect::RotatingCubeEffect(const GpuContext& ctx) : Effect(ctx) { } RotatingCubeEffect::~RotatingCubeEffect() { - if (mask_sampler_) - wgpuSamplerRelease(mask_sampler_); - if (noise_sampler_) - wgpuSamplerRelease(noise_sampler_); + // Samplers owned by SamplerCache - don't release if (noise_view_) wgpuTextureViewRelease(noise_view_); if (noise_texture_) @@ -49,21 +47,8 @@ void RotatingCubeEffect::init(MainSequence* demo) { noise_texture_ = wgpuDeviceCreateTexture(ctx_.device, &tex_desc); noise_view_ = wgpuTextureCreateView(noise_texture_, nullptr); - WGPUSamplerDescriptor sampler_desc = {}; - sampler_desc.addressModeU = WGPUAddressMode_Repeat; - sampler_desc.addressModeV = WGPUAddressMode_Repeat; - sampler_desc.magFilter = WGPUFilterMode_Linear; - sampler_desc.minFilter = WGPUFilterMode_Linear; - sampler_desc.maxAnisotropy = 1; - noise_sampler_ = wgpuDeviceCreateSampler(ctx_.device, &sampler_desc); - - WGPUSamplerDescriptor mask_sampler_desc = {}; - mask_sampler_desc.addressModeU = WGPUAddressMode_ClampToEdge; - mask_sampler_desc.addressModeV = WGPUAddressMode_ClampToEdge; - mask_sampler_desc.magFilter = WGPUFilterMode_Linear; - mask_sampler_desc.minFilter = WGPUFilterMode_Linear; - mask_sampler_desc.maxAnisotropy = 1; - mask_sampler_ = wgpuDeviceCreateSampler(ctx_.device, &mask_sampler_desc); + noise_sampler_ = SamplerCache::Get().get_or_create(ctx_.device, SamplerCache::linear()); + mask_sampler_ = SamplerCache::Get().get_or_create(ctx_.device, SamplerCache::clamp()); size_t shader_size; const char* shader_code = diff --git a/src/gpu/effects/scene1_effect.cc b/src/gpu/effects/scene1_effect.cc new file mode 100644 index 0000000..a6733b7 --- /dev/null +++ b/src/gpu/effects/scene1_effect.cc @@ -0,0 +1,28 @@ +// This file is part of the 64k demo project. +// Scene1 effect - ShaderToy conversion (raymarching scene) + +#include "gpu/demo_effects.h" +#include "gpu/gpu.h" + +Scene1Effect::Scene1Effect(const GpuContext& ctx) : Effect(ctx) { + ResourceBinding bindings[] = {{uniforms_.get(), WGPUBufferBindingType_Uniform}}; + pass_ = gpu_create_render_pass(ctx_.device, ctx_.format, scene1_shader_wgsl, + bindings, 1); + pass_.vertex_count = 3; +} + +void Scene1Effect::render(WGPURenderPassEncoder pass, float t, float b, + float i, float a) { + CommonPostProcessUniforms u = { + .resolution = {(float)width_, (float)height_}, + ._pad = {0.0f, 0.0f}, + .aspect_ratio = a, + .time = t, + .beat = b, + .audio_intensity = i, + }; + uniforms_.update(ctx_.queue, u); + wgpuRenderPassEncoderSetPipeline(pass, pass_.pipeline); + wgpuRenderPassEncoderSetBindGroup(pass, 0, pass_.bind_group, 0, nullptr); + wgpuRenderPassEncoderDraw(pass, pass_.vertex_count, 1, 0, 0); +} diff --git a/src/gpu/effects/scene1_effect.h b/src/gpu/effects/scene1_effect.h new file mode 100644 index 0000000..dc5c747 --- /dev/null +++ b/src/gpu/effects/scene1_effect.h @@ -0,0 +1,19 @@ +// This file is part of the 64k demo project. +// Scene1 effect - ShaderToy conversion (raymarching scene) + +#ifndef SCENE1_EFFECT_H_ +#define SCENE1_EFFECT_H_ + +#include "gpu/effect.h" + +class Scene1Effect : public Effect { + public: + Scene1Effect(const GpuContext& ctx); + void render(WGPURenderPassEncoder pass, float time, float beat, + float intensity, float aspect_ratio) override; + + private: + RenderPass pass_; +}; + +#endif /* SCENE1_EFFECT_H_ */ diff --git a/src/gpu/effects/shaders.cc b/src/gpu/effects/shaders.cc index 6559bf5..5f78298 100644 --- a/src/gpu/effects/shaders.cc +++ b/src/gpu/effects/shaders.cc @@ -98,6 +98,10 @@ const char* solarize_shader_wgsl = SafeGetAsset(AssetId::ASSET_SHADER_SOLARIZE); +const char* scene1_shader_wgsl = + + SafeGetAsset(AssetId::ASSET_SHADER_SCENE1); + const char* distort_shader_wgsl = SafeGetAsset(AssetId::ASSET_SHADER_DISTORT); diff --git a/src/gpu/effects/shaders.h b/src/gpu/effects/shaders.h index 7acc2a6..03fa48c 100644 --- a/src/gpu/effects/shaders.h +++ b/src/gpu/effects/shaders.h @@ -15,6 +15,7 @@ extern const char* ellipse_shader_wgsl; extern const char* particle_spray_compute_wgsl; extern const char* gaussian_blur_shader_wgsl; extern const char* solarize_shader_wgsl; +extern const char* scene1_shader_wgsl; extern const char* distort_shader_wgsl; extern const char* chroma_aberration_shader_wgsl; extern const char* vignette_shader_wgsl; diff --git a/src/gpu/pipeline_builder.h b/src/gpu/pipeline_builder.h new file mode 100644 index 0000000..06b4ceb --- /dev/null +++ b/src/gpu/pipeline_builder.h @@ -0,0 +1,109 @@ +// WGPU render pipeline builder - reduces pipeline creation boilerplate +#pragma once +#include <vector> +#include <string> + +// Forward declarations (users must include gpu.h and shader_composer.h) +struct WGPUDeviceImpl; +typedef struct WGPUDeviceImpl* WGPUDevice; +struct WGPUBindGroupLayoutImpl; +typedef struct WGPUBindGroupLayoutImpl* WGPUBindGroupLayout; +struct WGPURenderPipelineImpl; +typedef struct WGPURenderPipelineImpl* WGPURenderPipeline; +struct WGPUShaderModuleImpl; +typedef struct WGPUShaderModuleImpl* WGPUShaderModule; + +#include "platform/platform.h" +#include "gpu/effects/shader_composer.h" + +class RenderPipelineBuilder { + WGPUDevice device_; + WGPURenderPipelineDescriptor desc_{}; + WGPUColorTargetState color_{}; + WGPUBlendState blend_{}; + WGPUDepthStencilState depth_{}; + std::vector<WGPUBindGroupLayout> layouts_; + std::string shader_text_; + WGPUShaderModule shader_module_ = nullptr; + bool has_blend_ = false; + bool has_depth_ = false; + +public: + explicit RenderPipelineBuilder(WGPUDevice device) : device_(device) { + desc_.primitive.topology = WGPUPrimitiveTopology_TriangleList; + desc_.primitive.cullMode = WGPUCullMode_None; + desc_.multisample.count = 1; + desc_.multisample.mask = 0xFFFFFFFF; + } + + RenderPipelineBuilder& shader(const char* wgsl, bool compose = true) { + shader_text_ = compose ? ShaderComposer::Get().Compose({}, wgsl) : wgsl; + 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); + desc_.vertex.module = shader_module_; + desc_.vertex.entryPoint = str_view("vs_main"); + return *this; + } + + RenderPipelineBuilder& bind_group_layout(WGPUBindGroupLayout layout) { + layouts_.push_back(layout); + return *this; + } + + RenderPipelineBuilder& format(WGPUTextureFormat fmt) { + color_.format = fmt; + return *this; + } + + RenderPipelineBuilder& blend_alpha() { + has_blend_ = true; + blend_.color.operation = WGPUBlendOperation_Add; + blend_.color.srcFactor = WGPUBlendFactor_SrcAlpha; + blend_.color.dstFactor = WGPUBlendFactor_OneMinusSrcAlpha; + blend_.alpha.operation = WGPUBlendOperation_Add; + blend_.alpha.srcFactor = WGPUBlendFactor_One; + blend_.alpha.dstFactor = WGPUBlendFactor_OneMinusSrcAlpha; + return *this; + } + + RenderPipelineBuilder& depth(WGPUTextureFormat depth_fmt = WGPUTextureFormat_Depth24Plus) { + has_depth_ = true; + depth_.format = depth_fmt; + depth_.depthWriteEnabled = WGPUOptionalBool_True; + depth_.depthCompare = WGPUCompareFunction_Less; + return *this; + } + + RenderPipelineBuilder& cull_back() { + desc_.primitive.cullMode = WGPUCullMode_Back; + return *this; + } + + WGPURenderPipeline build() { + color_.writeMask = WGPUColorWriteMask_All; + if (has_blend_) color_.blend = &blend_; + + WGPUFragmentState fragment{}; + fragment.module = shader_module_; + fragment.entryPoint = str_view("fs_main"); + fragment.targetCount = 1; + fragment.targets = &color_; + + WGPUPipelineLayoutDescriptor pl_desc{}; + pl_desc.bindGroupLayoutCount = layouts_.size(); + pl_desc.bindGroupLayouts = layouts_.data(); + WGPUPipelineLayout layout = wgpuDeviceCreatePipelineLayout(device_, &pl_desc); + + desc_.layout = layout; + desc_.fragment = &fragment; + if (has_depth_) desc_.depthStencil = &depth_; + + WGPURenderPipeline pipeline = wgpuDeviceCreateRenderPipeline(device_, &desc_); + wgpuPipelineLayoutRelease(layout); + return pipeline; + } +}; diff --git a/src/gpu/sampler_cache.h b/src/gpu/sampler_cache.h new file mode 100644 index 0000000..0f012a8 --- /dev/null +++ b/src/gpu/sampler_cache.h @@ -0,0 +1,61 @@ +// Sampler cache - deduplicates samplers across effects +#pragma once +#include <map> + +// Forward declarations (users must include gpu.h) +struct WGPUDeviceImpl; +typedef struct WGPUDeviceImpl* WGPUDevice; +struct WGPUSamplerImpl; +typedef struct WGPUSamplerImpl* WGPUSampler; + +#include "platform/platform.h" + +struct SamplerSpec { + WGPUAddressMode u, v; + WGPUFilterMode mag, min; + uint16_t anisotropy; + + bool operator<(const SamplerSpec& o) const { + if (u != o.u) return u < o.u; + if (v != o.v) return v < o.v; + if (mag != o.mag) return mag < o.mag; + if (min != o.min) return min < o.min; + return anisotropy < o.anisotropy; + } +}; + +class SamplerCache { + std::map<SamplerSpec, WGPUSampler> cache_; + SamplerCache() = default; + +public: + static SamplerCache& Get() { + static SamplerCache instance; + return instance; + } + + WGPUSampler get_or_create(WGPUDevice device, const SamplerSpec& spec) { + auto it = cache_.find(spec); + if (it != cache_.end()) return it->second; + + WGPUSamplerDescriptor desc{}; + desc.addressModeU = spec.u; + desc.addressModeV = spec.v; + desc.magFilter = spec.mag; + desc.minFilter = spec.min; + desc.maxAnisotropy = spec.anisotropy; + WGPUSampler sampler = wgpuDeviceCreateSampler(device, &desc); + cache_[spec] = sampler; + return sampler; + } + + // Common presets + static SamplerSpec linear() { + return {WGPUAddressMode_Repeat, WGPUAddressMode_Repeat, + WGPUFilterMode_Linear, WGPUFilterMode_Linear, 1}; + } + static SamplerSpec clamp() { + return {WGPUAddressMode_ClampToEdge, WGPUAddressMode_ClampToEdge, + WGPUFilterMode_Linear, WGPUFilterMode_Linear, 1}; + } +}; diff --git a/src/tests/gpu/test_demo_effects.cc b/src/tests/gpu/test_demo_effects.cc index 619b9c9..01e6678 100644 --- a/src/tests/gpu/test_demo_effects.cc +++ b/src/tests/gpu/test_demo_effects.cc @@ -134,6 +134,7 @@ static void test_scene_effects() { {"CircleMaskEffect", std::make_shared<CircleMaskEffect>(fixture.ctx())}, {"RotatingCubeEffect", std::make_shared<RotatingCubeEffect>(fixture.ctx())}, + {"Scene1Effect", std::make_shared<Scene1Effect>(fixture.ctx())}, }; int passed = 0; |
