summaryrefslogtreecommitdiff
path: root/cnn_v3/src
diff options
context:
space:
mode:
Diffstat (limited to 'cnn_v3/src')
-rw-r--r--cnn_v3/src/gbuf_deferred_effect.cc140
-rw-r--r--cnn_v3/src/gbuf_deferred_effect.h23
-rw-r--r--cnn_v3/src/gbuf_view_effect.cc4
-rw-r--r--cnn_v3/src/gbuffer_effect.cc369
-rw-r--r--cnn_v3/src/gbuffer_effect.h59
5 files changed, 477 insertions, 118 deletions
diff --git a/cnn_v3/src/gbuf_deferred_effect.cc b/cnn_v3/src/gbuf_deferred_effect.cc
new file mode 100644
index 0000000..de6bd29
--- /dev/null
+++ b/cnn_v3/src/gbuf_deferred_effect.cc
@@ -0,0 +1,140 @@
+// GBufDeferredEffect — simple deferred render: albedo * shadow from packed G-buffer.
+
+#include "gbuf_deferred_effect.h"
+#include "gpu/gpu.h"
+#include "gpu/shader_composer.h"
+#include "util/fatal_error.h"
+
+extern const char* gbuf_deferred_wgsl;
+
+struct GBufDeferredUniforms {
+ float resolution[2];
+};
+static_assert(sizeof(GBufDeferredUniforms) == 8, "GBufDeferredUniforms must be 8 bytes");
+
+static WGPUBindGroupLayoutEntry bgl_uint_tex(uint32_t binding) {
+ WGPUBindGroupLayoutEntry e = {};
+ e.binding = binding;
+ e.visibility = WGPUShaderStage_Fragment;
+ e.texture.sampleType = WGPUTextureSampleType_Uint;
+ e.texture.viewDimension = WGPUTextureViewDimension_2D;
+ return e;
+}
+
+static WGPUBindGroupLayoutEntry bgl_uniform(uint32_t binding, uint64_t min_size) {
+ WGPUBindGroupLayoutEntry e = {};
+ e.binding = binding;
+ e.visibility = WGPUShaderStage_Fragment;
+ e.buffer.type = WGPUBufferBindingType_Uniform;
+ e.buffer.minBindingSize = min_size;
+ return e;
+}
+
+GBufDeferredEffect::GBufDeferredEffect(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) {
+ HEADLESS_RETURN_IF_NULL(ctx_.device);
+
+ WGPUBindGroupLayoutEntry entries[3] = {
+ bgl_uint_tex(0),
+ bgl_uint_tex(1),
+ bgl_uniform(2, sizeof(GBufDeferredUniforms)),
+ };
+ WGPUBindGroupLayoutDescriptor bgl_desc = {};
+ bgl_desc.entryCount = 3;
+ bgl_desc.entries = entries;
+ WGPUBindGroupLayout bgl = wgpuDeviceCreateBindGroupLayout(ctx_.device, &bgl_desc);
+
+ WGPUPipelineLayoutDescriptor pl_desc = {};
+ pl_desc.bindGroupLayoutCount = 1;
+ pl_desc.bindGroupLayouts = &bgl;
+ WGPUPipelineLayout pl = wgpuDeviceCreatePipelineLayout(ctx_.device, &pl_desc);
+
+ WGPUShaderSourceWGSL wgsl_src = {};
+ wgsl_src.chain.sType = WGPUSType_ShaderSourceWGSL;
+ const std::string composed = ShaderComposer::Get().Compose({}, gbuf_deferred_wgsl);
+ wgsl_src.code = str_view(composed.c_str());
+ WGPUShaderModuleDescriptor shader_desc = {};
+ shader_desc.nextInChain = &wgsl_src.chain;
+ WGPUShaderModule shader = wgpuDeviceCreateShaderModule(ctx_.device, &shader_desc);
+
+ WGPUColorTargetState target = {};
+ target.format = WGPUTextureFormat_RGBA8Unorm;
+ target.writeMask = WGPUColorWriteMask_All;
+
+ WGPUFragmentState frag = {};
+ frag.module = shader;
+ frag.entryPoint = str_view("fs_main");
+ frag.targetCount = 1;
+ frag.targets = &target;
+
+ WGPURenderPipelineDescriptor pipe_desc = {};
+ pipe_desc.layout = pl;
+ pipe_desc.vertex.module = shader;
+ pipe_desc.vertex.entryPoint = str_view("vs_main");
+ pipe_desc.fragment = &frag;
+ pipe_desc.primitive.topology = WGPUPrimitiveTopology_TriangleList;
+ pipe_desc.multisample.count = 1;
+ pipe_desc.multisample.mask = UINT32_MAX;
+
+ pipeline_.set(wgpuDeviceCreateRenderPipeline(ctx_.device, &pipe_desc));
+
+ wgpuShaderModuleRelease(shader);
+ wgpuPipelineLayoutRelease(pl);
+ wgpuBindGroupLayoutRelease(bgl);
+}
+
+void GBufDeferredEffect::render(WGPUCommandEncoder encoder,
+ const UniformsSequenceParams& params,
+ NodeRegistry& nodes) {
+ WGPUTextureView feat0_view = nodes.get_view(input_nodes_[0]);
+ WGPUTextureView feat1_view = nodes.get_view(input_nodes_[1]);
+ WGPUTextureView output_view = nodes.get_view(output_nodes_[0]);
+
+ // Upload resolution uniform into the base class uniforms buffer (first 8 bytes).
+ GBufDeferredUniforms u;
+ u.resolution[0] = params.resolution.x;
+ u.resolution[1] = params.resolution.y;
+ wgpuQueueWriteBuffer(ctx_.queue, uniforms_buffer_.get().buffer, 0,
+ &u, sizeof(u));
+
+ WGPUBindGroupLayout bgl =
+ wgpuRenderPipelineGetBindGroupLayout(pipeline_.get(), 0);
+
+ WGPUBindGroupEntry bg_entries[3] = {};
+ bg_entries[0].binding = 0;
+ bg_entries[0].textureView = feat0_view;
+ bg_entries[1].binding = 1;
+ bg_entries[1].textureView = feat1_view;
+ bg_entries[2].binding = 2;
+ bg_entries[2].buffer = uniforms_buffer_.get().buffer;
+ bg_entries[2].size = sizeof(GBufDeferredUniforms);
+
+ WGPUBindGroupDescriptor bg_desc = {};
+ bg_desc.layout = bgl;
+ bg_desc.entryCount = 3;
+ bg_desc.entries = bg_entries;
+ bind_group_.replace(wgpuDeviceCreateBindGroup(ctx_.device, &bg_desc));
+ wgpuBindGroupLayoutRelease(bgl);
+
+ WGPURenderPassColorAttachment color_att = {};
+ color_att.view = output_view;
+ color_att.loadOp = WGPULoadOp_Clear;
+ color_att.storeOp = WGPUStoreOp_Store;
+ color_att.clearValue = {0.0f, 0.0f, 0.0f, 1.0f};
+ color_att.depthSlice = WGPU_DEPTH_SLICE_UNDEFINED;
+
+ WGPURenderPassDescriptor pass_desc = {};
+ pass_desc.colorAttachmentCount = 1;
+ pass_desc.colorAttachments = &color_att;
+
+ 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);
+}
diff --git a/cnn_v3/src/gbuf_deferred_effect.h b/cnn_v3/src/gbuf_deferred_effect.h
new file mode 100644
index 0000000..4daf13d
--- /dev/null
+++ b/cnn_v3/src/gbuf_deferred_effect.h
@@ -0,0 +1,23 @@
+// GBufDeferredEffect — simple deferred render from packed G-buffer.
+// Inputs: feat_tex0, feat_tex1 (rgba32uint). Output: albedo * shadow (rgba8unorm).
+
+#pragma once
+#include "gpu/effect.h"
+#include "gpu/uniform_helper.h"
+#include "gpu/wgpu_resource.h"
+
+class GBufDeferredEffect : public Effect {
+ public:
+ GBufDeferredEffect(const GpuContext& ctx,
+ const std::vector<std::string>& inputs,
+ const std::vector<std::string>& outputs,
+ float start_time, float end_time);
+
+ void render(WGPUCommandEncoder encoder,
+ const UniformsSequenceParams& params,
+ NodeRegistry& nodes) override;
+
+ private:
+ RenderPipeline pipeline_;
+ BindGroup bind_group_;
+};
diff --git a/cnn_v3/src/gbuf_view_effect.cc b/cnn_v3/src/gbuf_view_effect.cc
index 180919d..ccf80b0 100644
--- a/cnn_v3/src/gbuf_view_effect.cc
+++ b/cnn_v3/src/gbuf_view_effect.cc
@@ -10,6 +10,7 @@
#endif
#include "gpu/gpu.h"
+#include "gpu/shader_composer.h"
#include "util/asset_manager.h"
#include "util/fatal_error.h"
@@ -63,7 +64,8 @@ GBufViewEffect::GBufViewEffect(const GpuContext& ctx,
// Shader module
WGPUShaderSourceWGSL wgsl_src = {};
wgsl_src.chain.sType = WGPUSType_ShaderSourceWGSL;
- wgsl_src.code = str_view(gbuf_view_wgsl);
+ const std::string composed = ShaderComposer::Get().Compose({}, gbuf_view_wgsl);
+ wgsl_src.code = str_view(composed.c_str());
WGPUShaderModuleDescriptor shader_desc = {};
shader_desc.nextInChain = &wgsl_src.chain;
WGPUShaderModule shader =
diff --git a/cnn_v3/src/gbuffer_effect.cc b/cnn_v3/src/gbuffer_effect.cc
index 750188f..b059915 100644
--- a/cnn_v3/src/gbuffer_effect.cc
+++ b/cnn_v3/src/gbuffer_effect.cc
@@ -14,6 +14,7 @@
// For standalone use outside the asset system, the caller must ensure the WGSL
// source strings are available. They are declared here as weak-linkable externs.
extern const char* gbuf_raster_wgsl;
+extern const char* gbuf_shadow_wgsl;
extern const char* gbuf_pack_wgsl;
// Maximum number of objects the G-buffer supports per frame.
@@ -41,53 +42,6 @@ struct GBufGlobalUniforms {
static_assert(sizeof(GBufGlobalUniforms) == sizeof(float) * 44,
"GBufGlobalUniforms must be 176 bytes");
-// Helper: create a 1×1 placeholder texture of a given format cleared to `value`.
-static WGPUTexture create_placeholder_tex(WGPUDevice device,
- WGPUTextureFormat format,
- float value) {
- WGPUTextureDescriptor desc = {};
- desc.usage = (WGPUTextureUsage)(WGPUTextureUsage_TextureBinding |
- WGPUTextureUsage_CopyDst);
- desc.dimension = WGPUTextureDimension_2D;
- desc.size = {1, 1, 1};
- desc.format = format;
- desc.mipLevelCount = 1;
- desc.sampleCount = 1;
- WGPUTexture tex = wgpuDeviceCreateTexture(device, &desc);
- return tex;
-}
-
-// Helper: write a single RGBA float pixel to a texture via queue.
-static void write_placeholder_pixel(WGPUQueue queue, WGPUTexture tex,
- float r, float g, float b, float a) {
- const float data[4] = {r, g, b, a};
- WGPUTexelCopyTextureInfo dst = {};
- dst.texture = tex;
- dst.mipLevel = 0;
- dst.origin = {0, 0, 0};
- dst.aspect = WGPUTextureAspect_All;
-
- WGPUTexelCopyBufferLayout layout = {};
- layout.offset = 0;
- layout.bytesPerRow = 16; // 4 × sizeof(float)
- layout.rowsPerImage = 1;
-
- const WGPUExtent3D extent = {1, 1, 1};
- wgpuQueueWriteTexture(queue, &dst, data, sizeof(data), &layout, &extent);
-}
-
-// Create bilinear sampler.
-static WGPUSampler create_bilinear_sampler(WGPUDevice device) {
- WGPUSamplerDescriptor desc = {};
- desc.addressModeU = WGPUAddressMode_ClampToEdge;
- desc.addressModeV = WGPUAddressMode_ClampToEdge;
- desc.magFilter = WGPUFilterMode_Linear;
- desc.minFilter = WGPUFilterMode_Linear;
- desc.mipmapFilter = WGPUMipmapFilterMode_Linear;
- desc.maxAnisotropy = 1;
- return wgpuDeviceCreateSampler(device, &desc);
-}
-
// ---- GBufferEffect ----
GBufferEffect::GBufferEffect(const GpuContext& ctx,
@@ -104,9 +58,6 @@ GBufferEffect::GBufferEffect(const GpuContext& ctx,
node_depth_ = prefix + "_depth";
node_shadow_ = prefix + "_shadow";
node_transp_ = prefix + "_transp";
- node_feat0_ = outputs.size() > 0 ? outputs[0] : prefix + "_feat0";
- node_feat1_ = outputs.size() > 1 ? outputs[1] : prefix + "_feat1";
-
// Allocate GPU buffers for scene data.
global_uniforms_buf_ =
gpu_create_buffer(ctx_.device, sizeof(GBufGlobalUniforms),
@@ -116,34 +67,15 @@ GBufferEffect::GBufferEffect(const GpuContext& ctx,
// Resolution uniform for pack shader.
pack_res_uniform_.init(ctx_.device);
+ lights_uniform_.init(ctx_.device);
- // Placeholder shadow (1.0 = fully lit) and transp (0.0 = opaque) textures.
- shadow_placeholder_tex_.set(
- create_placeholder_tex(ctx_.device, WGPUTextureFormat_RGBA32Float, 1.0f));
- write_placeholder_pixel(ctx_.queue,
- shadow_placeholder_tex_.get(), 1.0f, 0.0f, 0.0f, 1.0f);
-
- transp_placeholder_tex_.set(
- create_placeholder_tex(ctx_.device, WGPUTextureFormat_RGBA32Float, 0.0f));
- write_placeholder_pixel(ctx_.queue,
- transp_placeholder_tex_.get(), 0.0f, 0.0f, 0.0f, 1.0f);
-
- WGPUTextureViewDescriptor vd = {};
- vd.format = WGPUTextureFormat_RGBA32Float;
- vd.dimension = WGPUTextureViewDimension_2D;
- vd.baseMipLevel = 0;
- vd.mipLevelCount = 1;
- vd.baseArrayLayer = 0;
- vd.arrayLayerCount = 1;
- vd.aspect = WGPUTextureAspect_All;
-
- shadow_placeholder_view_.set(
- wgpuTextureCreateView(shadow_placeholder_tex_.get(), &vd));
- transp_placeholder_view_.set(
- wgpuTextureCreateView(transp_placeholder_tex_.get(), &vd));
+ create_linear_sampler();
create_raster_pipeline();
+ create_shadow_pipeline();
create_pack_pipeline();
+
+ set_scene();
}
void GBufferEffect::declare_nodes(NodeRegistry& registry) {
@@ -154,27 +86,123 @@ void GBufferEffect::declare_nodes(NodeRegistry& registry) {
registry.declare_node(node_transp_, NodeType::GBUF_R8, -1, -1);
// feat_tex0 / feat_tex1 are the declared output_nodes_ — they get registered
// by the sequence infrastructure; declare them here as well if not already.
- if (!registry.has_node(node_feat0_)) {
- registry.declare_node(node_feat0_, NodeType::GBUF_RGBA32UINT, -1, -1);
+ if (!registry.has_node(output_nodes_[0])) {
+ registry.declare_node(output_nodes_[0], NodeType::GBUF_RGBA32UINT, -1, -1);
+ }
+ if (!registry.has_node(output_nodes_[1])) {
+ registry.declare_node(output_nodes_[1], NodeType::GBUF_RGBA32UINT, -1, -1);
}
- if (!registry.has_node(node_feat1_)) {
- registry.declare_node(node_feat1_, NodeType::GBUF_RGBA32UINT, -1, -1);
+}
+
+void GBufferEffect::set_scene() {
+ scene_.clear();
+ cube_anims_.clear();
+ sphere_anims_.clear();
+
+ // Deterministic pseudo-random (xorshift32).
+ uint32_t seed = 0xBEEF1234u;
+ auto rnd = [&]() -> float {
+ seed ^= seed << 13;
+ seed ^= seed >> 17;
+ seed ^= seed << 5;
+ return (float)(seed >> 8) / 16777216.0f; // [0, 1)
+ };
+ auto rrange = [&](float lo, float hi) { return lo + rnd() * (hi - lo); };
+
+ // 2 large cubes.
+ // 2 large static cubes for shadow debugging.
+ {
+ Object3D obj(ObjectType::CUBE);
+ obj.position = vec3(-1.0f, 0.0f, 0.0f);
+ obj.scale = vec3(0.6f, 0.6f, 0.6f);
+ obj.color = vec4(0.9f, 0.5f, 0.3f, 1.0f);
+ scene_.add_object(obj);
+ cube_anims_.push_back({{0.0f, 1.0f, 0.0f}, 0.0f});
}
+ {
+ Object3D obj(ObjectType::SPHERE);
+ obj.position = vec3(1.0f, 0.0f, 0.0f);
+ const float r = 0.9f;
+ obj.scale = vec3(r, r, r);
+ obj.color = vec4(0.3f, 0.6f, 0.9f, 1.0f);
+ const int idx = (int)scene_.objects.size();
+ scene_.add_object(obj);
+ sphere_anims_.push_back({idx, r});
+ }
+
+ // (sphere removed for shadow debugging)
+
+ // Camera: above and in front of the scene, looking at origin.
+ camera_.set_look_at(vec3(0.0f, 2.5f, 6.0f),
+ vec3(0.0f, 0.0f, 0.0f),
+ vec3(0.0f, 1.0f, 0.0f));
+ camera_.fov_y_rad = 0.7854f; // 45°
+ camera_.near_plane = 0.1f;
+ camera_.far_plane = 20.0f;
+ // aspect_ratio is updated each frame from params.resolution.
+
+ scene_ready_ = true;
}
-void GBufferEffect::set_scene(const Scene* scene, const Camera* camera) {
- scene_ = scene;
- camera_ = camera;
+static void clear_r8_node(WGPUCommandEncoder encoder, WGPUTextureView view,
+ float value) {
+ WGPURenderPassColorAttachment att = {};
+ att.view = view;
+ att.loadOp = WGPULoadOp_Clear;
+ att.storeOp = WGPUStoreOp_Store;
+ att.clearValue = {value, value, value, value};
+ att.depthSlice = WGPU_DEPTH_SLICE_UNDEFINED;
+ WGPURenderPassDescriptor pd = {};
+ pd.colorAttachmentCount = 1;
+ pd.colorAttachments = &att;
+ WGPURenderPassEncoder p = wgpuCommandEncoderBeginRenderPass(encoder, &pd);
+ wgpuRenderPassEncoderEnd(p);
+ wgpuRenderPassEncoderRelease(p);
}
void GBufferEffect::render(WGPUCommandEncoder encoder,
const UniformsSequenceParams& params,
NodeRegistry& nodes) {
- if (!scene_ || !camera_) {
+ if (!scene_ready_) {
return;
}
- upload_scene_data(*scene_, *camera_, params.time);
+ // Update camera aspect ratio from current resolution.
+ camera_.aspect_ratio = params.aspect_ratio;
+
+ // Slowly orbit around the scene.
+ const float angle = params.time * 0.3f;
+ const float R = 6.0f;
+ camera_.set_look_at(vec3(R * sinf(angle), 2.5f, R * cosf(angle)),
+ vec3(0.0f, 0.0f, 0.0f),
+ vec3(0.0f, 1.0f, 0.0f));
+
+ // Animate cubes: axis-angle rotation driven by physical time.
+ for (int i = 0; i < (int)cube_anims_.size(); ++i) {
+ const CubeAnim& a = cube_anims_[(size_t)i];
+ scene_.objects[(size_t)i].rotation =
+ quat::from_axis(a.axis, params.time * a.speed);
+ }
+ // Pump spheres: scale with audio_intensity.
+ for (const SphereAnim& a : sphere_anims_) {
+ const float r = a.base_radius * (1.0f + params.audio_intensity * 0.8f);
+ scene_.objects[(size_t)a.obj_idx].scale = vec3(r, r, r);
+ }
+
+ // Upload two directional lights.
+ {
+ GBufLightsUniforms lu = {};
+ lu.params = vec4(1.0f, 0.0f, 0.0f, 0.0f);
+ // Key: warm sun, upper-right-front.
+ lu.lights[0].direction = vec4(0.408f, 0.816f, 0.408f, 0.0f); // norm(1,2,1)
+ lu.lights[0].color = vec4(1.00f, 0.92f, 0.78f, 1.0f);
+ // Fill: cool sky, upper-left-back. (disabled for debugging)
+ // lu.lights[1].direction = vec4(-0.577f, 0.577f, -0.577f, 0.0f);
+ // lu.lights[1].color = vec4(0.40f, 0.45f, 0.80f, 0.4f);
+ lights_uniform_.update(ctx_.queue, lu);
+ }
+
+ upload_scene_data(scene_, camera_, params.time);
// Update resolution uniform for pack shader.
GBufResUniforms res_uni;
@@ -186,8 +214,8 @@ void GBufferEffect::render(WGPUCommandEncoder encoder,
WGPUTextureView albedo_view = nodes.get_view(node_albedo_);
WGPUTextureView normal_mat_view = nodes.get_view(node_normal_mat_);
WGPUTextureView depth_view = nodes.get_view(node_depth_);
- WGPUTextureView feat0_view = nodes.get_view(node_feat0_);
- WGPUTextureView feat1_view = nodes.get_view(node_feat1_);
+ WGPUTextureView feat0_view = nodes.get_view(output_nodes_[0]);
+ WGPUTextureView feat1_view = nodes.get_view(output_nodes_[1]);
// prev_cnn: first input node if available, else dummy.
WGPUTextureView prev_view = nullptr;
@@ -228,8 +256,8 @@ void GBufferEffect::render(WGPUCommandEncoder encoder,
raster_pass_desc.depthStencilAttachment = &depth_attachment;
const int num_objects =
- (int)(scene_->objects.size() < (size_t)kGBufMaxObjects
- ? scene_->objects.size()
+ (int)(scene_.objects.size() < (size_t)kGBufMaxObjects
+ ? scene_.objects.size()
: (size_t)kGBufMaxObjects);
if (num_objects > 0 && raster_pipeline_.get() != nullptr) {
@@ -250,13 +278,69 @@ void GBufferEffect::render(WGPUCommandEncoder encoder,
wgpuRenderPassEncoderRelease(raster_pass);
}
- // Pass 2: SDF raymarching — TODO (placeholder: shadow=1, transp=0 already set)
- // Pass 3: Lighting/shadow — TODO
+ // --- Pass 2: SDF shadow raymarching ---
+ if (shadow_pipeline_.get() != nullptr) {
+ WGPUBindGroupEntry shadow_entries[5] = {};
+ shadow_entries[0].binding = 0;
+ shadow_entries[0].buffer = global_uniforms_buf_.buffer;
+ shadow_entries[0].size = sizeof(GBufGlobalUniforms);
+
+ shadow_entries[1].binding = 1;
+ shadow_entries[1].buffer = objects_buf_.buffer;
+ shadow_entries[1].size = (size_t)objects_buf_capacity_ * sizeof(GBufObjectData);
+
+ shadow_entries[2].binding = 2;
+ shadow_entries[2].textureView = depth_view;
+
+ shadow_entries[3].binding = 3;
+ shadow_entries[3].buffer = lights_uniform_.get().buffer;
+ shadow_entries[3].size = sizeof(GBufLightsUniforms);
+
+ shadow_entries[4].binding = 4;
+ shadow_entries[4].textureView = normal_mat_view;
+
+ WGPUBindGroupLayout shadow_bgl =
+ wgpuRenderPipelineGetBindGroupLayout(shadow_pipeline_.get(), 0);
+
+ WGPUBindGroupDescriptor shadow_bg_desc = {};
+ shadow_bg_desc.layout = shadow_bgl;
+ shadow_bg_desc.entryCount = 5;
+ shadow_bg_desc.entries = shadow_entries;
+
+ WGPUBindGroup shadow_bg =
+ wgpuDeviceCreateBindGroup(ctx_.device, &shadow_bg_desc);
+ wgpuBindGroupLayoutRelease(shadow_bgl);
+
+ WGPURenderPassColorAttachment shadow_att = {};
+ shadow_att.view = nodes.get_view(node_shadow_);
+ shadow_att.loadOp = WGPULoadOp_Clear;
+ shadow_att.storeOp = WGPUStoreOp_Store;
+ shadow_att.clearValue = {1.0f, 1.0f, 1.0f, 1.0f};
+ shadow_att.depthSlice = WGPU_DEPTH_SLICE_UNDEFINED;
+
+ WGPURenderPassDescriptor shadow_pass_desc = {};
+ shadow_pass_desc.colorAttachmentCount = 1;
+ shadow_pass_desc.colorAttachments = &shadow_att;
+
+ WGPURenderPassEncoder shadow_pass =
+ wgpuCommandEncoderBeginRenderPass(encoder, &shadow_pass_desc);
+ wgpuRenderPassEncoderSetPipeline(shadow_pass, shadow_pipeline_.get());
+ wgpuRenderPassEncoderSetBindGroup(shadow_pass, 0, shadow_bg, 0, nullptr);
+ wgpuRenderPassEncoderDraw(shadow_pass, 3, 1, 0, 0);
+ wgpuRenderPassEncoderEnd(shadow_pass);
+ wgpuRenderPassEncoderRelease(shadow_pass);
+ wgpuBindGroupRelease(shadow_bg);
+ } else {
+ // Fallback: clear to 1.0 (fully lit) if pipeline not ready.
+ clear_r8_node(encoder, nodes.get_view(node_shadow_), 1.0f);
+ }
+
+ // Pass 3: Transparency — TODO (deferred; opaque scenes only)
+ clear_r8_node(encoder, nodes.get_view(node_transp_), 0.0f);
// --- Pass 4: Pack compute ---
// Rebuild pack bind group with current node views.
- // Construct a temporary bilinear sampler for this pass.
- WGPUSampler bilinear = create_bilinear_sampler(ctx_.device);
+ WGPUSampler bilinear = sampler_.get();
// Get texture views from nodes.
// shadow / transp are GBUF_R8 nodes; use their views.
@@ -320,7 +404,7 @@ void GBufferEffect::render(WGPUCommandEncoder encoder,
wgpuComputePassEncoderRelease(compute_pass);
wgpuBindGroupRelease(pack_bg);
- wgpuSamplerRelease(bilinear);
+ // bilinear is owned by sampler_ — no release here.
}
// ---- private helpers ----
@@ -346,7 +430,8 @@ void GBufferEffect::upload_scene_data(const Scene& scene,
: (size_t)kGBufMaxObjects);
const mat4 view = camera.get_view_matrix();
- const mat4 proj = camera.get_projection_matrix();
+ mat4 proj = camera.get_projection_matrix();
+ proj.m[5] = -proj.m[5]; // undo post-process Y flip: G-buffer uses integer reads
const mat4 vp = proj * view;
GBufGlobalUniforms gu = {};
@@ -373,7 +458,7 @@ void GBufferEffect::upload_scene_data(const Scene& scene,
d.model = m;
d.inv_model = m.inverse();
d.color = obj.color;
- d.params = vec4(0.0f, 0.0f, 0.0f, 0.0f);
+ d.params = vec4((float)(int)obj.type, 0.0f, 0.0f, 0.0f);
obj_data.push_back(d);
}
wgpuQueueWriteBuffer(ctx_.queue, objects_buf_.buffer, 0,
@@ -392,7 +477,7 @@ void GBufferEffect::create_raster_pipeline() {
}
const std::string composed =
- ShaderComposer::Get().Compose({"common_uniforms"}, src);
+ ShaderComposer::Get().Compose({}, src);
WGPUShaderSourceWGSL wgsl_src = {};
wgsl_src.chain.sType = WGPUSType_ShaderSourceWGSL;
@@ -450,8 +535,9 @@ void GBufferEffect::create_raster_pipeline() {
pipe_desc.vertex.entryPoint = str_view("vs_main");
pipe_desc.fragment = &frag;
pipe_desc.depthStencil = &ds;
- pipe_desc.primitive.topology = WGPUPrimitiveTopology_TriangleList;
- pipe_desc.primitive.cullMode = WGPUCullMode_Back;
+ pipe_desc.primitive.topology = WGPUPrimitiveTopology_TriangleList;
+ pipe_desc.primitive.cullMode = WGPUCullMode_Back;
+ pipe_desc.primitive.frontFace = WGPUFrontFace_CCW; // standard (no Y flip)
pipe_desc.multisample.count = 1;
pipe_desc.multisample.mask = 0xFFFFFFFF;
@@ -462,6 +548,91 @@ void GBufferEffect::create_raster_pipeline() {
wgpuShaderModuleRelease(shader);
}
+void GBufferEffect::create_shadow_pipeline() {
+ HEADLESS_RETURN_IF_NULL(ctx_.device);
+
+ const char* src = gbuf_shadow_wgsl;
+ if (!src) {
+ return;
+ }
+
+ const std::string composed = ShaderComposer::Get().Compose({}, src);
+
+ WGPUShaderSourceWGSL wgsl_src = {};
+ wgsl_src.chain.sType = WGPUSType_ShaderSourceWGSL;
+ wgsl_src.code = str_view(composed.c_str());
+
+ WGPUShaderModuleDescriptor shader_desc = {};
+ shader_desc.nextInChain = &wgsl_src.chain;
+ WGPUShaderModule shader = wgpuDeviceCreateShaderModule(ctx_.device, &shader_desc);
+
+ // BGL: B0=GlobalUniforms, B1=ObjectsBuffer, B2=texture_depth_2d, B3=GBufLightsUniforms
+ WGPUBindGroupLayoutEntry bgl_entries[5] = {};
+
+ bgl_entries[0].binding = 0;
+ bgl_entries[0].visibility =
+ (WGPUShaderStage)(WGPUShaderStage_Vertex | WGPUShaderStage_Fragment);
+ bgl_entries[0].buffer.type = WGPUBufferBindingType_Uniform;
+ bgl_entries[0].buffer.minBindingSize = sizeof(GBufGlobalUniforms);
+
+ bgl_entries[1].binding = 1;
+ bgl_entries[1].visibility = WGPUShaderStage_Fragment;
+ bgl_entries[1].buffer.type = WGPUBufferBindingType_ReadOnlyStorage;
+ bgl_entries[1].buffer.minBindingSize = sizeof(GBufObjectData);
+
+ bgl_entries[2].binding = 2;
+ bgl_entries[2].visibility = WGPUShaderStage_Fragment;
+ bgl_entries[2].texture.sampleType = WGPUTextureSampleType_Depth;
+ bgl_entries[2].texture.viewDimension = WGPUTextureViewDimension_2D;
+
+ bgl_entries[3].binding = 3;
+ bgl_entries[3].visibility = WGPUShaderStage_Fragment;
+ bgl_entries[3].buffer.type = WGPUBufferBindingType_Uniform;
+ bgl_entries[3].buffer.minBindingSize = sizeof(GBufLightsUniforms);
+
+ bgl_entries[4].binding = 4;
+ 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(ctx_.device, &bgl_desc);
+
+ WGPUPipelineLayoutDescriptor pl_desc = {};
+ pl_desc.bindGroupLayoutCount = 1;
+ pl_desc.bindGroupLayouts = &bgl;
+ WGPUPipelineLayout pl = wgpuDeviceCreatePipelineLayout(ctx_.device, &pl_desc);
+
+ // Color target: RGBA8Unorm (NodeType::GBUF_R8)
+ WGPUColorTargetState color_target = {};
+ color_target.format = WGPUTextureFormat_RGBA8Unorm;
+ color_target.writeMask = WGPUColorWriteMask_All;
+
+ WGPUFragmentState frag = {};
+ frag.module = shader;
+ frag.entryPoint = str_view("fs_main");
+ frag.targetCount = 1;
+ frag.targets = &color_target;
+
+ WGPURenderPipelineDescriptor pipe_desc = {};
+ pipe_desc.layout = pl;
+ pipe_desc.vertex.module = shader;
+ pipe_desc.vertex.entryPoint = str_view("vs_main");
+ pipe_desc.fragment = &frag;
+ pipe_desc.primitive.topology = WGPUPrimitiveTopology_TriangleList;
+ pipe_desc.primitive.cullMode = WGPUCullMode_None;
+ pipe_desc.multisample.count = 1;
+ pipe_desc.multisample.mask = 0xFFFFFFFF;
+
+ shadow_pipeline_.set(wgpuDeviceCreateRenderPipeline(ctx_.device, &pipe_desc));
+
+ wgpuPipelineLayoutRelease(pl);
+ wgpuBindGroupLayoutRelease(bgl);
+ wgpuShaderModuleRelease(shader);
+}
+
void GBufferEffect::create_pack_pipeline() {
HEADLESS_RETURN_IF_NULL(ctx_.device);
@@ -596,7 +767,3 @@ void GBufferEffect::update_raster_bind_group(NodeRegistry& nodes) {
wgpuBindGroupLayoutRelease(bgl);
}
-void GBufferEffect::update_pack_bind_group(NodeRegistry& nodes) {
- (void)nodes;
- // Pack bind group is rebuilt inline in render() to use current node views.
-}
diff --git a/cnn_v3/src/gbuffer_effect.h b/cnn_v3/src/gbuffer_effect.h
index 42fb0ec..13d394d 100644
--- a/cnn_v3/src/gbuffer_effect.h
+++ b/cnn_v3/src/gbuffer_effect.h
@@ -10,6 +10,7 @@
#include "gpu/uniform_helper.h"
#include "gpu/wgpu_resource.h"
#include "util/mini_math.h"
+#include <vector>
// Uniform for the pack compute shader
struct GBufResUniforms {
@@ -20,6 +21,20 @@ struct GBufResUniforms {
static_assert(sizeof(GBufResUniforms) == 16,
"GBufResUniforms must be 16 bytes");
+// Single directional light: direction points *toward* the light source (world space).
+struct GBufLight {
+ vec4 direction; // xyz = normalized direction toward light, w = unused
+ vec4 color; // rgb = color, a = intensity
+};
+static_assert(sizeof(GBufLight) == 32, "GBufLight must be 32 bytes");
+
+struct GBufLightsUniforms {
+ GBufLight lights[2];
+ vec4 params; // x = num_lights
+};
+static_assert(sizeof(GBufLightsUniforms) == 80,
+ "GBufLightsUniforms must be 80 bytes");
+
class GBufferEffect : public Effect {
public:
GBufferEffect(const GpuContext& ctx, const std::vector<std::string>& inputs,
@@ -31,47 +46,59 @@ class GBufferEffect : public Effect {
void render(WGPUCommandEncoder encoder, const UniformsSequenceParams& params,
NodeRegistry& nodes) override;
- void set_scene(const Scene* scene, const Camera* camera);
+ // Populate the internal scene with ~20 rotating cubes and a few pumping
+ // spheres. Must be called once before the first render().
+ void set_scene();
private:
+ // Per-cube animation state (axis-angle rotation)
+ struct CubeAnim {
+ vec3 axis;
+ float speed; // radians/second, may be negative
+ };
+ // Per-sphere animation state (radius driven by audio_intensity)
+ struct SphereAnim {
+ int obj_idx; // index into scene_.objects
+ float base_radius;
+ };
+
// Internal G-buffer node names
std::string node_albedo_;
std::string node_normal_mat_;
std::string node_depth_;
std::string node_shadow_;
std::string node_transp_;
- std::string node_feat0_;
- std::string node_feat1_;
- const Scene* scene_ = nullptr;
- const Camera* camera_ = nullptr;
+ // Owned scene and camera — populated by set_scene()
+ Scene scene_;
+ Camera camera_;
+ bool scene_ready_ = false;
+
+ std::vector<CubeAnim> cube_anims_;
+ std::vector<SphereAnim> sphere_anims_;
// Pass 1: MRT rasterization pipeline
RenderPipeline raster_pipeline_;
BindGroup raster_bind_group_;
+ // Pass 2: SDF shadow pipeline
+ RenderPipeline shadow_pipeline_;
+
// Pass 4: Pack compute pipeline
ComputePipeline pack_pipeline_;
- BindGroup pack_bind_group_;
- UniformBuffer<GBufResUniforms> pack_res_uniform_;
-
- // Placeholder textures for shadow/transp (white/black cleared once)
- Texture shadow_placeholder_tex_;
- TextureView shadow_placeholder_view_;
- Texture transp_placeholder_tex_;
- TextureView transp_placeholder_view_;
+ UniformBuffer<GBufResUniforms> pack_res_uniform_;
+ UniformBuffer<GBufLightsUniforms> lights_uniform_;
// GPU-side object data buffers (global uniforms + objects storage)
- // These mirror the layout expected by gbuf_raster.wgsl
GpuBuffer global_uniforms_buf_;
GpuBuffer objects_buf_;
- int objects_buf_capacity_ = 0; // number of ObjectData slots allocated
+ int objects_buf_capacity_ = 0;
void create_raster_pipeline();
+ void create_shadow_pipeline();
void create_pack_pipeline();
void update_raster_bind_group(NodeRegistry& nodes);
- void update_pack_bind_group(NodeRegistry& nodes);
void upload_scene_data(const Scene& scene, const Camera& camera,
float time);