diff options
Diffstat (limited to 'cnn_v3/src/gbuffer_effect.cc')
| -rw-r--r-- | cnn_v3/src/gbuffer_effect.cc | 365 |
1 files changed, 267 insertions, 98 deletions
diff --git a/cnn_v3/src/gbuffer_effect.cc b/cnn_v3/src/gbuffer_effect.cc index 750188f..06e5e66 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,33 +67,12 @@ 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(); } @@ -154,27 +84,137 @@ 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); }; + + // 20 small cubes scattered in a [-2,2]×[-1.5,1.5]×[-1.5,1.5] volume. + static const int kNumCubes = 20; + for (int i = 0; i < kNumCubes; ++i) { + Object3D obj(ObjectType::CUBE); + obj.position = vec3(rrange(-2.0f, 2.0f), + rrange(-1.5f, 1.5f), + rrange(-1.5f, 1.5f)); + const float s = rrange(0.10f, 0.25f); + obj.scale = vec3(s, s, s); + obj.color = vec4(rrange(0.4f, 1.0f), + rrange(0.4f, 1.0f), + rrange(0.4f, 1.0f), 1.0f); + + // Random rotation axis (avoid degenerate zero-length axis). + vec3 axis = vec3(rrange(-1.0f, 1.0f), + rrange(-1.0f, 1.0f), + rrange(-1.0f, 1.0f)); + if (axis.len() < 0.01f) axis = vec3(0.0f, 1.0f, 0.0f); + axis = axis.normalize(); + const float speed = rrange(0.3f, 1.5f) * (rnd() > 0.5f ? 1.0f : -1.0f); + + scene_.add_object(obj); + cube_anims_.push_back({axis, speed}); } + + // 4 pumping spheres at fixed positions; radius modulated by audio_intensity. + static const vec3 kSpherePos[4] = { + { 0.0f, 0.0f, 0.0f}, + { 1.5f, 0.5f, -0.5f}, + {-1.5f, -0.5f, 0.5f}, + { 0.0f, 1.0f, 1.0f}, + }; + static const float kBaseSphereRadius[4] = {0.35f, 0.28f, 0.30f, 0.25f}; + for (int i = 0; i < 4; ++i) { + Object3D obj(ObjectType::SPHERE); + obj.position = kSpherePos[i]; + const float r = kBaseSphereRadius[i]; + obj.scale = vec3(r, r, r); + obj.color = vec4(0.85f, 0.60f, 0.95f, 1.0f); + const int idx = (int)scene_.objects.size(); + scene_.add_object(obj); + sphere_anims_.push_back({idx, r}); + } + + // 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; + + // 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(2.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. + lu.lights[1].direction = vec4(-0.577f, 0.577f, -0.577f, 0.0f); // norm(-1,1,-1) + 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 +226,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 +268,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 +290,66 @@ 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[4] = {}; + 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); + + WGPUBindGroupLayout shadow_bgl = + wgpuRenderPipelineGetBindGroupLayout(shadow_pipeline_.get(), 0); + + WGPUBindGroupDescriptor shadow_bg_desc = {}; + shadow_bg_desc.layout = shadow_bgl; + shadow_bg_desc.entryCount = 4; + 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 +413,7 @@ void GBufferEffect::render(WGPUCommandEncoder encoder, wgpuComputePassEncoderRelease(compute_pass); wgpuBindGroupRelease(pack_bg); - wgpuSamplerRelease(bilinear); + // bilinear is owned by sampler_ — no release here. } // ---- private helpers ---- @@ -373,7 +466,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 +485,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; @@ -462,6 +555,86 @@ 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[4] = {}; + + 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); + + WGPUBindGroupLayoutDescriptor bgl_desc = {}; + bgl_desc.entryCount = 4; + 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 +769,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. -} |
