diff options
Diffstat (limited to 'src/gpu')
| -rw-r--r-- | src/gpu/demo_effects.cc | 2 | ||||
| -rw-r--r-- | src/gpu/demo_effects.h | 31 | ||||
| -rw-r--r-- | src/gpu/effect.cc | 66 | ||||
| -rw-r--r-- | src/gpu/effect.h | 14 | ||||
| -rw-r--r-- | src/gpu/effects/circle_mask_effect.cc | 189 | ||||
| -rw-r--r-- | src/gpu/effects/circle_mask_effect.h | 50 | ||||
| -rw-r--r-- | src/gpu/effects/distort_effect.cc | 28 | ||||
| -rw-r--r-- | src/gpu/effects/rotating_cube_effect.cc | 181 | ||||
| -rw-r--r-- | src/gpu/effects/rotating_cube_effect.h | 55 | ||||
| -rw-r--r-- | src/gpu/effects/shaders.cc | 4 | ||||
| -rw-r--r-- | src/gpu/effects/shaders.h | 1 | ||||
| -rw-r--r-- | src/gpu/effects/vignette_effect.cc | 36 |
12 files changed, 647 insertions, 10 deletions
diff --git a/src/gpu/demo_effects.cc b/src/gpu/demo_effects.cc index 36fd16e..069d36c 100644 --- a/src/gpu/demo_effects.cc +++ b/src/gpu/demo_effects.cc @@ -3,6 +3,8 @@ // Its content has been split into individual effect files and helper files. #include "gpu/demo_effects.h" +#include "gpu/effects/circle_mask_effect.h" +#include "gpu/effects/rotating_cube_effect.h" // Auto-generated function to populate the timeline void LoadTimeline(MainSequence& main_seq, WGPUDevice device, WGPUQueue queue, diff --git a/src/gpu/demo_effects.h b/src/gpu/demo_effects.h index 6c8729d..fabfbd2 100644 --- a/src/gpu/demo_effects.h +++ b/src/gpu/demo_effects.h @@ -9,6 +9,8 @@ #include "gpu/effects/flash_effect.h" // FlashEffect with params support #include "gpu/effects/post_process_helper.h" #include "gpu/effects/shaders.h" +#include "gpu/effects/circle_mask_effect.h" +#include "gpu/effects/rotating_cube_effect.h" #include "gpu/gpu.h" #include "gpu/texture_manager.h" #include "gpu/uniform_helper.h" @@ -119,12 +121,37 @@ class SolarizeEffect : public PostProcessEffect { void update_bind_group(WGPUTextureView input_view) override; }; -class DistortEffect : public PostProcessEffect { +// Parameters for VignetteEffect +struct VignetteParams { + float radius = 0.5f; // Radius of the clear center + float softness = 0.5f; // Softness of the vignette edge +}; + +// Uniform data for VignetteEffect +struct VignetteUniforms { + float time; // offset 0 + float beat; // offset 4 + float intensity; // offset 8 + float aspect_ratio; // offset 12 + float width; // offset 16 + float height; // offset 20 + float radius; // offset 24 + float softness; // offset 28 +}; +static_assert(sizeof(VignetteUniforms) == 32, + "VignetteUniforms must be 32 bytes for WGSL alignment"); + +class VignetteEffect : public PostProcessEffect { public: - DistortEffect(const GpuContext& ctx); + VignetteEffect(const GpuContext& ctx); + VignetteEffect(const GpuContext& ctx, const VignetteParams& params); void render(WGPURenderPassEncoder pass, float time, float beat, float intensity, float aspect_ratio) override; void update_bind_group(WGPUTextureView input_view) override; + + private: + VignetteParams params_; + UniformBuffer<VignetteUniforms> uniforms_; }; // Parameters for ChromaAberrationEffect (set at construction time) diff --git a/src/gpu/effect.cc b/src/gpu/effect.cc index df8ef2d..c2c36b4 100644 --- a/src/gpu/effect.cc +++ b/src/gpu/effect.cc @@ -5,6 +5,7 @@ #include "audio/tracker.h" #include "gpu/demo_effects.h" #include "gpu/gpu.h" +#include "util/fatal_error.h" #include <algorithm> #include <cstdio> #include <typeinfo> @@ -378,11 +379,76 @@ void MainSequence::shutdown() { wgpuTextureViewRelease(depth_view_); if (depth_texture_) wgpuTextureRelease(depth_texture_); + for (auto& [name, aux] : auxiliary_textures_) { + if (aux.view) + wgpuTextureViewRelease(aux.view); + if (aux.texture) + wgpuTextureRelease(aux.texture); + } + auxiliary_textures_.clear(); for (ActiveSequence& entry : sequences_) { entry.seq->reset(); } } +// Register a named auxiliary texture for inter-effect sharing +void MainSequence::register_auxiliary_texture(const char* name, int width, + int height) { + const std::string key(name); + + // Check if already exists + auto it = auxiliary_textures_.find(key); + if (it != auxiliary_textures_.end()) { +#if !defined(STRIP_ALL) + fprintf(stderr, "Warning: Auxiliary texture '%s' already registered\n", + name); +#endif /* !defined(STRIP_ALL) */ + return; + } + + // Create texture + const WGPUTextureDescriptor desc = { + .usage = + WGPUTextureUsage_RenderAttachment | WGPUTextureUsage_TextureBinding, + .dimension = WGPUTextureDimension_2D, + .size = {(uint32_t)width, (uint32_t)height, 1}, + .format = gpu_ctx.format, + .mipLevelCount = 1, + .sampleCount = 1, + }; + + WGPUTexture texture = wgpuDeviceCreateTexture(gpu_ctx.device, &desc); + FATAL_CHECK(!texture, "Failed to create auxiliary texture: %s\n", name); + + // Create view + const WGPUTextureViewDescriptor view_desc = { + .format = gpu_ctx.format, + .dimension = WGPUTextureViewDimension_2D, + .mipLevelCount = 1, + .arrayLayerCount = 1, + }; + + WGPUTextureView view = wgpuTextureCreateView(texture, &view_desc); + FATAL_CHECK(!view, "Failed to create auxiliary texture view: %s\n", name); + + // Store in registry + auxiliary_textures_[key] = {texture, view, width, height}; + +#if !defined(STRIP_ALL) + printf("[MainSequence] Registered auxiliary texture '%s' (%dx%d)\n", name, + width, height); +#endif /* !defined(STRIP_ALL) */ +} + +// Retrieve auxiliary texture view by name +WGPUTextureView MainSequence::get_auxiliary_view(const char* name) { + const std::string key(name); + auto it = auxiliary_textures_.find(key); + FATAL_CHECK(it == auxiliary_textures_.end(), + "Auxiliary texture not found: %s\n", name); + return it->second.view; +} + #if !defined(STRIP_ALL) void MainSequence::simulate_until(float target_time, float step_rate, float bpm) { diff --git a/src/gpu/effect.h b/src/gpu/effect.h index 6ed2c55..6fdb0f4 100644 --- a/src/gpu/effect.h +++ b/src/gpu/effect.h @@ -1,7 +1,9 @@ #pragma once #include "gpu/gpu.h" #include <algorithm> +#include <map> #include <memory> +#include <string> #include <vector> class MainSequence; @@ -112,6 +114,10 @@ class MainSequence { void resize(int width, int height); void shutdown(); + // Auxiliary texture registry for inter-effect texture sharing + void register_auxiliary_texture(const char* name, int width, int height); + WGPUTextureView get_auxiliary_view(const char* name); + #if !defined(STRIP_ALL) void simulate_until(float target_time, float step_rate, float bpm = 120.0f); #endif /* !defined(STRIP_ALL) */ @@ -139,5 +145,13 @@ class MainSequence { std::unique_ptr<Effect> passthrough_effect_; + struct AuxiliaryTexture { + WGPUTexture texture; + WGPUTextureView view; + int width; + int height; + }; + std::map<std::string, AuxiliaryTexture> auxiliary_textures_; + void create_framebuffers(int width, int height); }; diff --git a/src/gpu/effects/circle_mask_effect.cc b/src/gpu/effects/circle_mask_effect.cc new file mode 100644 index 0000000..226b603 --- /dev/null +++ b/src/gpu/effects/circle_mask_effect.cc @@ -0,0 +1,189 @@ +// This file is part of the 64k demo project. +// It implements CircleMaskEffect for auxiliary texture masking demonstration. +// Generates circular mask and renders green background outside circle. + +#include "gpu/effects/circle_mask_effect.h" +#include "generated/assets.h" + +CircleMaskEffect::CircleMaskEffect(const GpuContext& ctx, float radius) + : Effect(ctx), radius_(radius) {} + +CircleMaskEffect::~CircleMaskEffect() { + if (mask_sampler_) wgpuSamplerRelease(mask_sampler_); + if (render_bind_group_) wgpuBindGroupRelease(render_bind_group_); + if (render_pipeline_) wgpuRenderPipelineRelease(render_pipeline_); + if (compute_bind_group_) wgpuBindGroupRelease(compute_bind_group_); + if (compute_pipeline_) wgpuRenderPipelineRelease(compute_pipeline_); +} + +void CircleMaskEffect::init(MainSequence* demo) { + demo_ = demo; + + const uint32_t width = width_; + const uint32_t height = height_; + + demo_->register_auxiliary_texture("circle_mask", width, height); + + compute_uniforms_.init(ctx_.device); + render_uniforms_.init(ctx_.device); + + 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.mipmapFilter = WGPUMipmapFilterMode_Linear; + sampler_desc.maxAnisotropy = 1; + mask_sampler_ = wgpuDeviceCreateSampler(ctx_.device, &sampler_desc); + + size_t compute_size, render_size; + const char* compute_shader = (const char*)GetAsset( + AssetId::ASSET_CIRCLE_MASK_COMPUTE_SHADER, &compute_size); + const char* render_shader = (const char*)GetAsset( + AssetId::ASSET_CIRCLE_MASK_RENDER_SHADER, &render_size); + + WGPUShaderSourceWGSL compute_wgsl = {}; + compute_wgsl.chain.sType = WGPUSType_ShaderSourceWGSL; + compute_wgsl.code = str_view(compute_shader); + + WGPUShaderModuleDescriptor compute_desc = {}; + compute_desc.nextInChain = &compute_wgsl.chain; + WGPUShaderModule compute_module = wgpuDeviceCreateShaderModule(ctx_.device, &compute_desc); + + const WGPUColorTargetState compute_target = { + .format = ctx_.format, // Match auxiliary texture format + .writeMask = WGPUColorWriteMask_All, + }; + WGPUFragmentState compute_frag = {}; + compute_frag.module = compute_module; + compute_frag.entryPoint = str_view("fs_main"); + compute_frag.targetCount = 1; + compute_frag.targets = &compute_target; + WGPURenderPipelineDescriptor compute_pipeline_desc = {}; + compute_pipeline_desc.label = label_view("CircleMaskEffect_compute"); + compute_pipeline_desc.vertex.module = compute_module; + compute_pipeline_desc.vertex.entryPoint = str_view("vs_main"); + compute_pipeline_desc.primitive.topology = WGPUPrimitiveTopology_TriangleList; + compute_pipeline_desc.primitive.cullMode = WGPUCullMode_None; + compute_pipeline_desc.multisample.count = 1; + compute_pipeline_desc.multisample.mask = 0xFFFFFFFF; + compute_pipeline_desc.fragment = &compute_frag; + compute_pipeline_ = wgpuDeviceCreateRenderPipeline(ctx_.device, &compute_pipeline_desc); + wgpuShaderModuleRelease(compute_module); + + const WGPUBindGroupEntry compute_entries[] = { + {.binding = 0, .buffer = compute_uniforms_.get().buffer, + .size = sizeof(ComputeUniforms)}, + }; + const WGPUBindGroupDescriptor compute_bg_desc = { + .layout = wgpuRenderPipelineGetBindGroupLayout(compute_pipeline_, 0), + .entryCount = 1, + .entries = compute_entries, + }; + compute_bind_group_ = wgpuDeviceCreateBindGroup(ctx_.device, &compute_bg_desc); + + WGPUShaderSourceWGSL render_wgsl = {}; + render_wgsl.chain.sType = WGPUSType_ShaderSourceWGSL; + render_wgsl.code = str_view(render_shader); + + WGPUShaderModuleDescriptor render_desc = {}; + render_desc.nextInChain = &render_wgsl.chain; + WGPUShaderModule render_module = wgpuDeviceCreateShaderModule(ctx_.device, &render_desc); + + const WGPUColorTargetState render_target = { + .format = ctx_.format, + .writeMask = WGPUColorWriteMask_All, + }; + WGPUFragmentState render_frag = {}; + render_frag.module = render_module; + render_frag.entryPoint = str_view("fs_main"); + render_frag.targetCount = 1; + render_frag.targets = &render_target; + const WGPUDepthStencilState depth_stencil = { + .format = WGPUTextureFormat_Depth24Plus, + .depthWriteEnabled = WGPUOptionalBool_False, // Don't write depth + .depthCompare = WGPUCompareFunction_Always, // Always pass + }; + + WGPURenderPipelineDescriptor render_pipeline_desc = {}; + render_pipeline_desc.label = label_view("CircleMaskEffect_render"); + render_pipeline_desc.vertex.module = render_module; + render_pipeline_desc.vertex.entryPoint = str_view("vs_main"); + render_pipeline_desc.primitive.topology = WGPUPrimitiveTopology_TriangleList; + render_pipeline_desc.primitive.cullMode = WGPUCullMode_None; + render_pipeline_desc.depthStencil = &depth_stencil; + render_pipeline_desc.multisample.count = 1; + render_pipeline_desc.multisample.mask = 0xFFFFFFFF; + render_pipeline_desc.fragment = &render_frag; + render_pipeline_ = wgpuDeviceCreateRenderPipeline(ctx_.device, &render_pipeline_desc); + wgpuShaderModuleRelease(render_module); + + WGPUTextureView mask_view = demo_->get_auxiliary_view("circle_mask"); + const WGPUBindGroupEntry render_entries[] = { + {.binding = 0, .textureView = mask_view}, + {.binding = 1, .sampler = mask_sampler_}, + {.binding = 2, .buffer = render_uniforms_.get().buffer, + .size = sizeof(RenderUniforms)}, + }; + const WGPUBindGroupDescriptor render_bg_desc = { + .layout = wgpuRenderPipelineGetBindGroupLayout(render_pipeline_, 0), + .entryCount = 3, + .entries = render_entries, + }; + render_bind_group_ = wgpuDeviceCreateBindGroup(ctx_.device, &render_bg_desc); +} + +void CircleMaskEffect::compute(WGPUCommandEncoder encoder, float time, + float beat, float intensity, + float aspect_ratio) { + const uint32_t width = width_; + const uint32_t height = height_; + + const ComputeUniforms uniforms = { + .radius = radius_, + .aspect_ratio = aspect_ratio, + .width = static_cast<float>(width), + .height = static_cast<float>(height), + }; + compute_uniforms_.update(ctx_.queue, uniforms); + + WGPUTextureView mask_view = demo_->get_auxiliary_view("circle_mask"); + WGPURenderPassColorAttachment color_attachment = {}; + color_attachment.view = mask_view; + color_attachment.loadOp = WGPULoadOp_Clear; + color_attachment.storeOp = WGPUStoreOp_Store; + color_attachment.clearValue = {0.0, 0.0, 0.0, 1.0}; +#if !defined(DEMO_CROSS_COMPILE_WIN32) + color_attachment.depthSlice = WGPU_DEPTH_SLICE_UNDEFINED; +#endif + + WGPURenderPassDescriptor pass_desc = {}; + pass_desc.colorAttachmentCount = 1; + pass_desc.colorAttachments = &color_attachment; + + WGPURenderPassEncoder pass = wgpuCommandEncoderBeginRenderPass(encoder, &pass_desc); + wgpuRenderPassEncoderSetPipeline(pass, compute_pipeline_); + wgpuRenderPassEncoderSetBindGroup(pass, 0, compute_bind_group_, 0, nullptr); + wgpuRenderPassEncoderDraw(pass, 3, 1, 0, 0); + wgpuRenderPassEncoderEnd(pass); + wgpuRenderPassEncoderRelease(pass); +} + +void CircleMaskEffect::render(WGPURenderPassEncoder pass, float time, + float beat, float intensity, + float aspect_ratio) { + const uint32_t width = width_; + const uint32_t height = height_; + + const RenderUniforms uniforms = { + .width = static_cast<float>(width), + .height = static_cast<float>(height), + ._pad1 = 0.0f, + ._pad2 = 0.0f, + }; + render_uniforms_.update(ctx_.queue, uniforms); + + wgpuRenderPassEncoderSetPipeline(pass, render_pipeline_); + wgpuRenderPassEncoderSetBindGroup(pass, 0, render_bind_group_, 0, nullptr); + wgpuRenderPassEncoderDraw(pass, 3, 1, 0, 0); +} diff --git a/src/gpu/effects/circle_mask_effect.h b/src/gpu/effects/circle_mask_effect.h new file mode 100644 index 0000000..57f2389 --- /dev/null +++ b/src/gpu/effects/circle_mask_effect.h @@ -0,0 +1,50 @@ +// This file is part of the 64k demo project. +// It defines the CircleMaskEffect class for masking system demonstration. +// Creates a circular mask and renders green outside the circle. + +#ifndef CIRCLE_MASK_EFFECT_H_ +#define CIRCLE_MASK_EFFECT_H_ + +#include "gpu/effect.h" +#include "gpu/uniform_helper.h" + +class CircleMaskEffect : public Effect { + public: + CircleMaskEffect(const GpuContext& ctx, float radius = 0.4f); + ~CircleMaskEffect() override; + + void init(MainSequence* demo) override; + void compute(WGPUCommandEncoder encoder, float time, float beat, + float intensity, float aspect_ratio) override; + void render(WGPURenderPassEncoder pass, float time, float beat, + float intensity, float aspect_ratio) override; + + private: + struct ComputeUniforms { + float radius; + float aspect_ratio; + float width; + float height; + }; + + struct RenderUniforms { + float width; + float height; + float _pad1; + float _pad2; + }; + + MainSequence* demo_ = nullptr; + float radius_; + + WGPURenderPipeline compute_pipeline_ = nullptr; + WGPUBindGroup compute_bind_group_ = nullptr; + UniformBuffer<ComputeUniforms> compute_uniforms_; + + WGPURenderPipeline render_pipeline_ = nullptr; + WGPUBindGroup render_bind_group_ = nullptr; + WGPUSampler mask_sampler_ = nullptr; + UniformBuffer<RenderUniforms> render_uniforms_; +}; + +#endif /* CIRCLE_MASK_EFFECT_H_ */ diff --git a/src/gpu/effects/distort_effect.cc b/src/gpu/effects/distort_effect.cc index b7e27a7..589cdff 100644 --- a/src/gpu/effects/distort_effect.cc +++ b/src/gpu/effects/distort_effect.cc @@ -5,21 +5,33 @@ #include "gpu/gpu.h" // --- DistortEffect --- -DistortEffect::DistortEffect(const GpuContext& ctx) : PostProcessEffect(ctx) { - uniforms_ = - gpu_create_buffer(ctx_.device, sizeof(float) * 6, - WGPUBufferUsage_Uniform | WGPUBufferUsage_CopyDst); +DistortEffect::DistortEffect(const GpuContext& ctx) + : DistortEffect(ctx, DistortParams()) {} + +DistortEffect::DistEffect(const GpuContext& ctx, const DistortParams& params) + : PostProcessEffect(ctx), params_(params) { + uniforms_ = gpu_create_buffer(ctx_.device, sizeof(DistortUniforms), + WGPUBufferUsage_Uniform | WGPUBufferUsage_CopyDst); pipeline_ = create_post_process_pipeline(ctx_.device, ctx_.format, distort_shader_wgsl); } + void DistortEffect::render(WGPURenderPassEncoder pass, float t, float b, float i, float a) { - struct { - float t, b, i, a, w, h; - } u = {t, b, i, a, (float)width_, (float)height_}; + DistortUniforms u = { + .time = t, + .beat = b, + .intensity = i, + .aspect_ratio = a, + .width = (float)width_, + .height = (float)height_, + .strength = params_.strength, + .speed = params_.speed, + }; wgpuQueueWriteBuffer(ctx_.queue, uniforms_.buffer, 0, &u, sizeof(u)); PostProcessEffect::render(pass, t, b, i, a); } + void DistortEffect::update_bind_group(WGPUTextureView v) { pp_update_bind_group(ctx_.device, pipeline_, &bind_group_, v, uniforms_); -} +}
\ No newline at end of file diff --git a/src/gpu/effects/rotating_cube_effect.cc b/src/gpu/effects/rotating_cube_effect.cc new file mode 100644 index 0000000..7f590c5 --- /dev/null +++ b/src/gpu/effects/rotating_cube_effect.cc @@ -0,0 +1,181 @@ +// This file is part of the 64k demo project. +// It implements RotatingCubeEffect for bump-mapped rotating cube rendering. +// Uses auxiliary texture masking to render only inside a circular region. + +#include "gpu/effects/rotating_cube_effect.h" +#include "gpu/effects/shader_composer.h" +#include "generated/assets.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_); + if (noise_view_) wgpuTextureViewRelease(noise_view_); + if (noise_texture_) wgpuTextureRelease(noise_texture_); + if (bind_group_1_) wgpuBindGroupRelease(bind_group_1_); + if (bind_group_0_) wgpuBindGroupRelease(bind_group_0_); + if (pipeline_) wgpuRenderPipelineRelease(pipeline_); +} + +void RotatingCubeEffect::init(MainSequence* demo) { + demo_ = demo; + + uniform_buffer_ = + gpu_create_buffer(ctx_.device, sizeof(Uniforms), + WGPUBufferUsage_Uniform | WGPUBufferUsage_CopyDst); + object_buffer_ = + gpu_create_buffer(ctx_.device, sizeof(ObjectData), + WGPUBufferUsage_Storage | WGPUBufferUsage_CopyDst); + + const WGPUTextureDescriptor tex_desc = { + .usage = WGPUTextureUsage_TextureBinding | WGPUTextureUsage_RenderAttachment, + .dimension = WGPUTextureDimension_2D, + .size = {1, 1, 1}, + .format = WGPUTextureFormat_RGBA8Unorm, + .mipLevelCount = 1, + .sampleCount = 1, + }; + 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); + + size_t shader_size; + const char* shader_code = + (const char*)GetAsset(AssetId::ASSET_MASKED_CUBE_SHADER, &shader_size); + + ShaderComposer::CompositionMap composition_map; + composition_map["render/scene_query_mode"] = "render/scene_query_linear"; + composed_shader_ = ShaderComposer::Get().Compose( + {}, std::string(shader_code, shader_size), composition_map); + + WGPUShaderSourceWGSL wgsl_src = {}; + wgsl_src.chain.sType = WGPUSType_ShaderSourceWGSL; + wgsl_src.code = str_view(composed_shader_.c_str()); + + WGPUShaderModuleDescriptor shader_desc = {}; + shader_desc.nextInChain = &wgsl_src.chain; + WGPUShaderModule shader_module = + wgpuDeviceCreateShaderModule(ctx_.device, &shader_desc); + + const WGPUColorTargetState color_target = { + .format = ctx_.format, + .writeMask = WGPUColorWriteMask_All, + }; + + const WGPUDepthStencilState depth_stencil = { + .format = WGPUTextureFormat_Depth24Plus, + .depthWriteEnabled = WGPUOptionalBool_True, + .depthCompare = WGPUCompareFunction_Less, + }; + + WGPUFragmentState fragment = {}; + fragment.module = shader_module; + fragment.entryPoint = str_view("fs_main"); + fragment.targetCount = 1; + fragment.targets = &color_target; + + WGPURenderPipelineDescriptor pipeline_desc = {}; + pipeline_desc.vertex.module = shader_module; + pipeline_desc.vertex.entryPoint = str_view("vs_main"); + pipeline_desc.primitive.topology = WGPUPrimitiveTopology_TriangleList; + pipeline_desc.primitive.cullMode = WGPUCullMode_None; + pipeline_desc.depthStencil = &depth_stencil; + pipeline_desc.multisample.count = 1; + pipeline_desc.multisample.mask = 0xFFFFFFFF; + pipeline_desc.fragment = &fragment; + + pipeline_ = wgpuDeviceCreateRenderPipeline(ctx_.device, &pipeline_desc); + wgpuShaderModuleRelease(shader_module); + + const WGPUBindGroupEntry entries_0[] = { + {.binding = 0, .buffer = uniform_buffer_.buffer, .size = sizeof(Uniforms)}, + {.binding = 1, .buffer = object_buffer_.buffer, .size = sizeof(ObjectData)}, + {.binding = 3, .textureView = noise_view_}, + {.binding = 4, .sampler = noise_sampler_}, + }; + + const WGPUBindGroupDescriptor bg_desc_0 = { + .layout = wgpuRenderPipelineGetBindGroupLayout(pipeline_, 0), + .entryCount = 4, + .entries = entries_0, + }; + bind_group_0_ = wgpuDeviceCreateBindGroup(ctx_.device, &bg_desc_0); + + WGPUTextureView mask_view = demo_->get_auxiliary_view("circle_mask"); + const WGPUBindGroupEntry entries_1[] = { + {.binding = 0, .textureView = mask_view}, + {.binding = 1, .sampler = mask_sampler_}, + }; + + const WGPUBindGroupDescriptor bg_desc_1 = { + .layout = wgpuRenderPipelineGetBindGroupLayout(pipeline_, 1), + .entryCount = 2, + .entries = entries_1, + }; + bind_group_1_ = wgpuDeviceCreateBindGroup(ctx_.device, &bg_desc_1); +} + +void RotatingCubeEffect::render(WGPURenderPassEncoder pass, float time, + float beat, float intensity, + float aspect_ratio) { + rotation_ += 0.016f * 1.5f; + + const vec3 camera_pos = vec3(0, 0, 5); + const vec3 target = vec3(0, 0, 0); + const vec3 up = vec3(0, 1, 0); + + const mat4 view = mat4::look_at(camera_pos, target, up); + const float fov = 60.0f * 3.14159f / 180.0f; + const mat4 proj = mat4::perspective(fov, aspect_ratio, 0.1f, 100.0f); + const mat4 view_proj = proj * view; + + const quat rot = quat::from_axis(vec3(0.3f, 1.0f, 0.2f), rotation_); + const mat4 T = mat4::translate(vec3(0, 0, 0)); + const mat4 R = rot.to_mat(); + const mat4 S = mat4::scale(vec3(1.5f, 1.5f, 1.5f)); + const mat4 model = T * R * S; + const mat4 inv_model = model.inverse(); + + const Uniforms uniforms = { + .view_proj = view_proj, + .inv_view_proj = view_proj.inverse(), + .camera_pos_time = vec4(camera_pos.x, camera_pos.y, camera_pos.z, time), + .params = vec4(1.0f, 0.0f, 0.0f, 0.0f), + .resolution = vec2(1280.0f, 720.0f), + }; + + const ObjectData obj_data = { + .model = model, + .inv_model = inv_model, + .color = vec4(0.8f, 0.4f, 0.2f, 1.0f), + .params = vec4(1.0f, 0.0f, 0.0f, 0.0f), + }; + + wgpuQueueWriteBuffer(ctx_.queue, uniform_buffer_.buffer, 0, &uniforms, + sizeof(Uniforms)); + wgpuQueueWriteBuffer(ctx_.queue, object_buffer_.buffer, 0, &obj_data, + sizeof(ObjectData)); + + wgpuRenderPassEncoderSetPipeline(pass, pipeline_); + wgpuRenderPassEncoderSetBindGroup(pass, 0, bind_group_0_, 0, nullptr); + wgpuRenderPassEncoderSetBindGroup(pass, 1, bind_group_1_, 0, nullptr); + wgpuRenderPassEncoderDraw(pass, 36, 1, 0, 0); +} diff --git a/src/gpu/effects/rotating_cube_effect.h b/src/gpu/effects/rotating_cube_effect.h new file mode 100644 index 0000000..89b3fa6 --- /dev/null +++ b/src/gpu/effects/rotating_cube_effect.h @@ -0,0 +1,55 @@ +// This file is part of the 64k demo project. +// It defines RotatingCubeEffect for rendering a bump-mapped rotating cube. +// Uses auxiliary texture masking to render only inside a circular region. + +#ifndef ROTATING_CUBE_EFFECT_H_ +#define ROTATING_CUBE_EFFECT_H_ + +#include "gpu/effect.h" +#include "gpu/gpu.h" +#include "util/mini_math.h" +#include <string> + +class RotatingCubeEffect : public Effect { + public: + RotatingCubeEffect(const GpuContext& ctx); + ~RotatingCubeEffect() override; + + void init(MainSequence* demo) override; + void render(WGPURenderPassEncoder pass, float time, float beat, + float intensity, float aspect_ratio) override; + + private: + struct Uniforms { + mat4 view_proj; + mat4 inv_view_proj; + vec4 camera_pos_time; + vec4 params; + vec2 resolution; + vec2 padding; + }; + + struct ObjectData { + mat4 model; + mat4 inv_model; + vec4 color; + vec4 params; + }; + + MainSequence* demo_ = nullptr; + WGPURenderPipeline pipeline_ = nullptr; + WGPUBindGroup bind_group_0_ = nullptr; + WGPUBindGroup bind_group_1_ = nullptr; + GpuBuffer uniform_buffer_; + GpuBuffer object_buffer_; + WGPUTexture noise_texture_ = nullptr; + WGPUTextureView noise_view_ = nullptr; + WGPUSampler noise_sampler_ = nullptr; + WGPUSampler mask_sampler_ = nullptr; + float rotation_ = 0.0f; + + // Store composed shader to keep it alive for WebGPU + std::string composed_shader_; +}; + +#endif /* ROTATING_CUBE_EFFECT_H_ */ diff --git a/src/gpu/effects/shaders.cc b/src/gpu/effects/shaders.cc index ce60a74..2e1cfe5 100644 --- a/src/gpu/effects/shaders.cc +++ b/src/gpu/effects/shaders.cc @@ -98,3 +98,7 @@ const char* distort_shader_wgsl = const char* chroma_aberration_shader_wgsl = SafeGetAsset(AssetId::ASSET_SHADER_CHROMA_ABERRATION); + +const char* vignette_shader_wgsl = + + SafeGetAsset(AssetId::ASSET_SHADER_VIGNETTE); diff --git a/src/gpu/effects/shaders.h b/src/gpu/effects/shaders.h index f8e45ba..50b4f32 100644 --- a/src/gpu/effects/shaders.h +++ b/src/gpu/effects/shaders.h @@ -17,3 +17,4 @@ extern const char* gaussian_blur_shader_wgsl; extern const char* solarize_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/effects/vignette_effect.cc b/src/gpu/effects/vignette_effect.cc new file mode 100644 index 0000000..d3572a3 --- /dev/null +++ b/src/gpu/effects/vignette_effect.cc @@ -0,0 +1,36 @@ +// This file is part of the 64k demo project. +// It implements the VignetteEffect. + +#include "gpu/demo_effects.h" +#include "gpu/gpu.h" + +VignetteEffect::VignetteEffect(const GpuContext& ctx) + : VignetteEffect(ctx, VignetteParams()) {} + +VignetteEffect::VignetteEffect(const GpuContext& ctx, + const VignetteParams& params) + : PostProcessEffect(ctx), params_(params) { + uniforms_.init(ctx_.device); + pipeline_ = create_post_process_pipeline(ctx_.device, ctx_.format, + vignette_shader_wgsl); +} + +void VignetteEffect::render(WGPURenderPassEncoder pass, float t, float b, + float i, float a) { + VignetteUniforms u = { + .time = t, + .beat = b, + .intensity = i, + .aspect_ratio = a, + .width = (float)width_, + .height = (float)height_, + .radius = params_.radius, + .softness = params_.softness, + }; + uniforms_.update(ctx_.queue, u); + PostProcessEffect::render(pass, t, b, i, a); +} + +void VignetteEffect::update_bind_group(WGPUTextureView v) { + pp_update_bind_group(ctx_.device, pipeline_, &bind_group_, v, uniforms_.get()); +} |
