// This file is part of the 64k demo project. // It implements the core Renderer3D class logic. #include "3d/renderer.h" #include "generated/assets.h" #include "gpu/effects/shader_composer.h" #include "util/asset_manager.h" #include "util/mesh_utils.h" #include #include #include #include #if !defined(STRIP_ALL) bool Renderer3D::s_debug_enabled_ = false; #endif void Renderer3D::init(WGPUDevice device, WGPUQueue queue, WGPUTextureFormat format) { device_ = device; queue_ = queue; format_ = format; 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; default_sampler_ = wgpuDeviceCreateSampler(device_, &sampler_desc); create_default_resources(); create_pipeline(); create_skybox_pipeline(); #if !defined(STRIP_ALL) visual_debug_.init(device_, format_); #endif } void Renderer3D::shutdown() { #if !defined(STRIP_ALL) visual_debug_.shutdown(); #endif if (default_sampler_) wgpuSamplerRelease(default_sampler_); if (pipeline_) wgpuRenderPipelineRelease(pipeline_); if (pipeline_no_bvh_) wgpuRenderPipelineRelease(pipeline_no_bvh_); if (bind_group_) wgpuBindGroupRelease(bind_group_); if (mesh_pipeline_) wgpuRenderPipelineRelease(mesh_pipeline_); if (skybox_pipeline_) wgpuRenderPipelineRelease(skybox_pipeline_); if (skybox_bind_group_) wgpuBindGroupRelease(skybox_bind_group_); if (global_uniform_buffer_) wgpuBufferRelease(global_uniform_buffer_); if (object_storage_buffer_) wgpuBufferRelease(object_storage_buffer_); if (bvh_storage_buffer_) wgpuBufferRelease(bvh_storage_buffer_); if (depth_view_) wgpuTextureViewRelease(depth_view_); if (depth_texture_) wgpuTextureRelease(depth_texture_); // Clear mesh cache for (auto& pair : mesh_cache_) { if (pair.second.vertex_buffer) wgpuBufferRelease(pair.second.vertex_buffer); if (pair.second.index_buffer) wgpuBufferRelease(pair.second.index_buffer); } mesh_cache_.clear(); } void Renderer3D::resize(int width, int height) { if (width == width_ && height == height_) return; width_ = width; height_ = height; if (depth_view_) wgpuTextureViewRelease(depth_view_); if (depth_texture_) wgpuTextureRelease(depth_texture_); WGPUTextureDescriptor desc = {}; desc.usage = WGPUTextureUsage_RenderAttachment; desc.dimension = WGPUTextureDimension_2D; desc.size = {(uint32_t)width, (uint32_t)height, 1}; desc.format = WGPUTextureFormat_Depth24Plus; desc.mipLevelCount = 1; desc.sampleCount = 1; depth_texture_ = wgpuDeviceCreateTexture(device_, &desc); WGPUTextureViewDescriptor view_desc = {}; view_desc.format = WGPUTextureFormat_Depth24Plus; view_desc.dimension = WGPUTextureViewDimension_2D; view_desc.aspect = WGPUTextureAspect_DepthOnly; view_desc.arrayLayerCount = 1; view_desc.mipLevelCount = 1; depth_view_ = wgpuTextureCreateView(depth_texture_, &view_desc); } void Renderer3D::create_default_resources() { global_uniform_buffer_ = gpu_create_buffer(device_, sizeof(GlobalUniforms), WGPUBufferUsage_Uniform | WGPUBufferUsage_CopyDst, nullptr) .buffer; object_storage_buffer_ = gpu_create_buffer(device_, sizeof(ObjectData) * kMaxObjects, WGPUBufferUsage_Storage | WGPUBufferUsage_CopyDst, nullptr) .buffer; bvh_storage_buffer_ = gpu_create_buffer( device_, sizeof(BVHNode) * kMaxObjects * 2, // Capacity for a full tree WGPUBufferUsage_Storage | WGPUBufferUsage_CopyDst, nullptr) .buffer; } void Renderer3D::create_pipeline() { // Shader Modules std::string sdf_shader_source = ShaderComposer::Get().Compose(std::vector{}, AssetId::ASSET_SHADER_RENDER_SCENE_QUERY_BVH); WGPUShaderModuleDescriptor sdf_shader_desc = platform_create_shader_module_descriptor(sdf_shader_source.c_str()); WGPUShaderModule sdf_shader_module = wgpuDeviceCreateShaderModule(device_, &sdf_shader_desc); std::string sdf_no_bvh_shader_source = ShaderComposer::Get().Compose(std::vector{}, AssetId::ASSET_SHADER_RENDER_SCENE_QUERY_LINEAR); WGPUShaderModuleDescriptor sdf_no_bvh_shader_desc = platform_create_shader_module_descriptor( sdf_no_bvh_shader_source.c_str()); WGPUShaderModule sdf_no_bvh_shader_module = wgpuDeviceCreateShaderModule(device_, &sdf_no_bvh_shader_desc); // Mesh Shader Module std::string mesh_shader_source = ShaderComposer::Get().Compose(std::vector{}, AssetId::ASSET_SHADER_RENDER_SHADOWS); // Use SHADOWS shader WGPUShaderModuleDescriptor mesh_shader_desc = platform_create_shader_module_descriptor(mesh_shader_source.c_str()); WGPUShaderModule mesh_shader_module = wgpuDeviceCreateShaderModule(device_, &mesh_shader_desc); // Bind Group Layouts and Pipelines (SDF) std::vector bgl_entries; // Uniforms bgl_entries.push_back({.binding = 0, .visibility = WGPUShaderStage_Vertex | WGPUShaderStage_Fragment, .buffer = {.type = WGPUBufferBindingType_Uniform, .hasDynamicOffset = false, .minBindingSize = sizeof(GlobalUniforms)}}); // Object Storage bgl_entries.push_back( {.binding = 1, .visibility = WGPUShaderStage_Vertex | WGPUShaderStage_Fragment, .buffer = {.type = WGPUBufferBindingType_ReadOnlyStorage, .hasDynamicOffset = false, .minBindingSize = sizeof(ObjectData) * kMaxObjects}}); // BVH Storage (Conditional for BVH pipeline) bgl_entries.push_back( {.binding = 2, .visibility = WGPUShaderStage_Vertex | WGPUShaderStage_Fragment, .buffer = {.type = WGPUBufferBindingType_ReadOnlyStorage, .hasDynamicOffset = false, .minBindingSize = sizeof(BVHNode) * kMaxObjects * 2}}); // Noise Texture bgl_entries.push_back({.binding = 3, .visibility = WGPUShaderStage_Fragment, .texture = {.sampleType = WGPUTextureSampleType_Float, .viewDimension = WGPUTextureViewDimension_2D, .multisampled = false}}); // Default Sampler bgl_entries.push_back({.binding = 4, .visibility = WGPUShaderStage_Fragment, .sampler = {.type = WGPUSamplerBindingType_Filtering}}); // Skybox Texture bgl_entries.push_back({.binding = 5, .visibility = WGPUShaderStage_Fragment, .texture = {.sampleType = WGPUTextureSampleType_Float, .viewDimension = WGPUTextureViewDimension_2D, .multisampled = false}}); WGPUBindGroupLayoutDescriptor bgl_desc = {}; bgl_desc.entryCount = (uint32_t)bgl_entries.size(); bgl_desc.entries = bgl_entries.data(); WGPUBindGroupLayout bind_group_layout = wgpuDeviceCreateBindGroupLayout(device_, &bgl_desc); WGPUPipelineLayoutDescriptor pl_desc = {}; pl_desc.bindGroupLayoutCount = 1; pl_desc.bindGroupLayouts = &bind_group_layout; WGPUPipelineLayout pipeline_layout = wgpuDeviceCreatePipelineLayout(device_, &pl_desc); // Depth Stencil State WGPUDepthStencilState depth_stencil = {}; depth_stencil.format = WGPUTextureFormat_Depth24Plus; depth_stencil.depthWriteEnabled = WGPUOptionalBool_True; depth_stencil.depthCompare = WGPUCompareFunction_Less; // Configure common color target state WGPUColorTargetState color_target = {}; color_target.format = format_; color_target.writeMask = WGPUColorWriteMask_All; // Alpha blending for meshes/particles, but not SDFs color_target.blend = nullptr; // Explicitly set to nullptr WGPUFragmentState fragment_state = {}; fragment_state.module = sdf_shader_module; fragment_state.entryPoint = str_view("fs_main"); fragment_state.targetCount = 1; fragment_state.targets = &color_target; // Create SDF pipeline (with BVH) WGPURenderPipelineDescriptor pipeline_desc = {}; pipeline_desc.layout = pipeline_layout; pipeline_desc.vertex.module = sdf_shader_module; pipeline_desc.vertex.entryPoint = str_view("vs_main"); pipeline_desc.primitive.topology = WGPUPrimitiveTopology_TriangleList; pipeline_desc.multisample.count = 1; pipeline_desc.multisample.mask = 0xFFFFFFFF; pipeline_desc.fragment = &fragment_state; PLATFORM_SET_PIPELINE_DEPTH_STENCIL(pipeline_desc, &depth_stencil); pipeline_ = wgpuDeviceCreateRenderPipeline(device_, &pipeline_desc); // Create SDF pipeline (without BVH) fragment_state.module = sdf_no_bvh_shader_module; // Use linear shader WGPURenderPipelineDescriptor pipeline_no_bvh_desc = pipeline_desc; pipeline_no_bvh_desc.vertex.module = sdf_no_bvh_shader_module; pipeline_no_bvh_desc.fragment = &fragment_state; PLATFORM_SET_PIPELINE_DEPTH_STENCIL(pipeline_no_bvh_desc, &depth_stencil); pipeline_no_bvh_ = wgpuDeviceCreateRenderPipeline(device_, &pipeline_no_bvh_desc); // Configure blend state for mesh/particle pipeline WGPUBlendState mesh_blend_state = {}; mesh_blend_state.color.srcFactor = WGPUBlendFactor_SrcAlpha; mesh_blend_state.color.dstFactor = WGPUBlendFactor_OneMinusSrcAlpha; mesh_blend_state.color.operation = WGPUBlendOperation_Add; mesh_blend_state.alpha.srcFactor = WGPUBlendFactor_One; mesh_blend_state.alpha.dstFactor = WGPUBlendFactor_OneMinusSrcAlpha; mesh_blend_state.alpha.operation = WGPUBlendOperation_Add; color_target.blend = &mesh_blend_state; // Enable blending for meshes // Create Mesh pipeline WGPURenderPipelineDescriptor mesh_pipeline_desc = {}; mesh_pipeline_desc.layout = pipeline_layout; mesh_pipeline_desc.vertex.module = mesh_shader_module; mesh_pipeline_desc.vertex.entryPoint = str_view("vs_main"); mesh_pipeline_desc.primitive.topology = WGPUPrimitiveTopology_TriangleList; mesh_pipeline_desc.multisample.count = 1; mesh_pipeline_desc.multisample.mask = 0xFFFFFFFF; mesh_pipeline_desc.fragment = &fragment_state; // Reuse fragment state // Override fragment module to mesh_shader_module fragment_state.module = mesh_shader_module; mesh_pipeline_desc.fragment = &fragment_state; PLATFORM_SET_PIPELINE_DEPTH_STENCIL(mesh_pipeline_desc, &depth_stencil); // Vertex attributes for mesh WGPUVertexAttribute vert_attrs[3] = {}; // Position vert_attrs[0].shaderLocation = 0; vert_attrs[0].offset = 0; vert_attrs[0].format = WGPUVertexFormat_Float32x3; // Normal vert_attrs[1].shaderLocation = 1; vert_attrs[1].offset = sizeof(vec3); vert_attrs[1].format = WGPUVertexFormat_Float32x3; // UV vert_attrs[2].shaderLocation = 2; vert_attrs[2].offset = sizeof(vec3) * 2; vert_attrs[2].format = WGPUVertexFormat_Float32x2; WGPUVertexBufferLayout vert_buffer_layout = {}; vert_buffer_layout.arrayStride = sizeof(MeshVertex); vert_buffer_layout.stepMode = WGPUVertexStepMode_Vertex; vert_buffer_layout.attributeCount = 3; vert_buffer_layout.attributes = vert_attrs; mesh_pipeline_desc.vertex.bufferCount = 1; mesh_pipeline_desc.vertex.buffers = &vert_buffer_layout; mesh_pipeline_ = wgpuDeviceCreateRenderPipeline(device_, &mesh_pipeline_desc); // Release shader modules as they are now part of the pipelines wgpuShaderModuleRelease(sdf_shader_module); wgpuShaderModuleRelease(sdf_no_bvh_shader_module); wgpuShaderModuleRelease(mesh_shader_module); wgpuPipelineLayoutRelease(pipeline_layout); wgpuBindGroupLayoutRelease(bind_group_layout); } void Renderer3D::create_skybox_pipeline() { std::string skybox_shader_source = ShaderComposer::Get().Compose(std::vector{}, AssetId::ASSET_SHADER_RENDER_SHADOWS); // Use SHADOWS shader WGPUShaderModuleDescriptor skybox_shader_desc = platform_create_shader_module_descriptor(skybox_shader_source.c_str()); WGPUShaderModule skybox_shader_module = wgpuDeviceCreateShaderModule(device_, &skybox_shader_desc); WGPUBindGroupLayoutEntry bgl_entries[3] = {}; // Skybox Texture bgl_entries[0].binding = 0; bgl_entries[0].visibility = WGPUShaderStage_Fragment; bgl_entries[0].texture = {.sampleType = WGPUTextureSampleType_Float, .viewDimension = WGPUTextureViewDimension_2D, .multisampled = false}; // Sampler bgl_entries[1].binding = 1; bgl_entries[1].visibility = WGPUShaderStage_Fragment; bgl_entries[1].sampler = {.type = WGPUSamplerBindingType_Filtering}; // Global Uniforms bgl_entries[2].binding = 2; bgl_entries[2].visibility = WGPUShaderStage_Vertex; bgl_entries[2].buffer = {.type = WGPUBufferBindingType_Uniform, .hasDynamicOffset = false, .minBindingSize = sizeof(GlobalUniforms)}; WGPUBindGroupLayoutDescriptor bgl_desc = {}; bgl_desc.entryCount = 3; bgl_desc.entries = bgl_entries; WGPUBindGroupLayout bind_group_layout = wgpuDeviceCreateBindGroupLayout(device_, &bgl_desc); WGPUPipelineLayoutDescriptor pl_desc = {}; pl_desc.bindGroupLayoutCount = 1; pl_desc.bindGroupLayouts = &bind_group_layout; WGPUPipelineLayout pipeline_layout = wgpuDeviceCreatePipelineLayout(device_, &pl_desc); WGPUColorTargetState color_target = {}; color_target.format = format_; color_target.writeMask = WGPUColorWriteMask_All; color_target.blend = nullptr; // No blending for skybox WGPUFragmentState fragment_state = {}; fragment_state.module = skybox_shader_module; fragment_state.entryPoint = str_view("fs_main"); fragment_state.targetCount = 1; fragment_state.targets = &color_target; WGPURenderPipelineDescriptor pipeline_desc = {}; pipeline_desc.layout = pipeline_layout; pipeline_desc.vertex.module = skybox_shader_module; pipeline_desc.vertex.entryPoint = str_view("vs_main"); pipeline_desc.primitive.topology = WGPUPrimitiveTopology_TriangleList; pipeline_desc.multisample.count = 1; pipeline_desc.multisample.mask = 0xFFFFFFFF; pipeline_desc.fragment = &fragment_state; // Skybox depth state (clear and write depth for objects) WGPUDepthStencilState depth_stencil = {}; depth_stencil.format = WGPUTextureFormat_Depth24Plus; depth_stencil.depthWriteEnabled = WGPUOptionalBool_False; depth_stencil.depthCompare = WGPUCompareFunction_Always; PLATFORM_SET_PIPELINE_DEPTH_STENCIL(pipeline_desc, &depth_stencil); skybox_pipeline_ = wgpuDeviceCreateRenderPipeline(device_, &pipeline_desc); wgpuShaderModuleRelease(skybox_shader_module); wgpuPipelineLayoutRelease(pipeline_layout); wgpuBindGroupLayoutRelease(bind_group_layout); } void Renderer3D::set_noise_texture(WGPUTextureView noise_view) { noise_texture_view_ = noise_view; } void Renderer3D::set_sky_texture(WGPUTextureView sky_view) { sky_texture_view_ = sky_view; } void Renderer3D::add_debug_aabb(const vec3& min, const vec3& max, const vec3& color) { #if !defined(STRIP_ALL) visual_debug_.add_aabb(min, max, color); #endif } void Renderer3D::update_uniforms(const Scene& scene, const Camera& camera, float time) { GlobalUniforms globals; globals.view_proj = camera.get_projection_matrix() * camera.get_view_matrix(); globals.inv_view_proj = globals.view_proj.inverse(); globals.camera_pos_time = vec4(camera.position.x, camera.position.y, camera.position.z, time); globals.params = vec4((float)std::min((size_t)kMaxObjects, scene.objects.size()), 0.0f, 0.0f, 0.0f); globals.resolution = vec2((float)width_, (float)height_); globals.padding = vec2(0.0f, 0.0f); wgpuQueueWriteBuffer(queue_, global_uniform_buffer_, 0, &globals, sizeof(GlobalUniforms)); std::vector obj_data; for (const auto& obj : scene.objects) { ObjectData data; data.model = obj.get_model_matrix(); // Calculate Inverse for point transformation data.inv_model = data.model.inverse(); data.color = obj.color; float type_id = 0.0f; switch (obj.type) { case ObjectType::SPHERE: type_id = 1.0f; break; case ObjectType::BOX: type_id = 2.0f; break; case ObjectType::CUBE: type_id = 2.0f; break; // CUBE is same as BOX for shader case ObjectType::TORUS: type_id = 3.0f; break; case ObjectType::PLANE: type_id = 4.0f; break; case ObjectType::MESH: type_id = 5.0f; break; default: type_id = 0.0f; break; } data.params = vec4(type_id, obj.local_extent.x, obj.local_extent.y, obj.local_extent.z); obj_data.push_back(data); if (obj_data.size() >= kMaxObjects) break; } if (!obj_data.empty()) { wgpuQueueWriteBuffer(queue_, object_storage_buffer_, 0, obj_data.data(), obj_data.size() * sizeof(ObjectData)); } // Build and upload BVH (always uploaded, used by BVH pipeline) BVHBuilder::build(cpu_bvh_, scene.objects); if (!cpu_bvh_.nodes.empty()) { wgpuQueueWriteBuffer(queue_, bvh_storage_buffer_, 0, cpu_bvh_.nodes.data(), cpu_bvh_.nodes.size() * sizeof(BVHNode)); } } void Renderer3D::draw(WGPURenderPassEncoder pass, const Scene& scene, const Camera& camera, float time) { update_uniforms(scene, camera, time); // Lazy Bind Group creation if (bind_group_) wgpuBindGroupRelease(bind_group_); std::vector bg_entries; { WGPUBindGroupEntry e = {}; e.binding = 0; e.buffer = global_uniform_buffer_; e.size = sizeof(GlobalUniforms); bg_entries.push_back(e); } { WGPUBindGroupEntry e = {}; e.binding = 1; e.buffer = object_storage_buffer_; e.size = sizeof(ObjectData) * kMaxObjects; bg_entries.push_back(e); } if (bvh_enabled_) { WGPUBindGroupEntry e = {}; e.binding = 2; e.buffer = bvh_storage_buffer_; e.size = sizeof(BVHNode) * kMaxObjects * 2; bg_entries.push_back(e); } { WGPUBindGroupEntry e = {}; e.binding = 3; e.textureView = noise_texture_view_; bg_entries.push_back(e); } { WGPUBindGroupEntry e = {}; e.binding = 4; e.sampler = default_sampler_; bg_entries.push_back(e); } { WGPUBindGroupEntry e = {}; e.binding = 5; e.textureView = sky_texture_view_ ? sky_texture_view_ : noise_texture_view_; bg_entries.push_back(e); } // Select the correct pipeline and bind group layout WGPURenderPipeline current_pipeline = bvh_enabled_ ? pipeline_ : pipeline_no_bvh_; WGPUBindGroupLayout current_layout = wgpuRenderPipelineGetBindGroupLayout(current_pipeline, 0); WGPUBindGroupDescriptor bg_desc = {}; bg_desc.layout = current_layout; bg_desc.entryCount = (uint32_t)bg_entries.size(); bg_desc.entries = bg_entries.data(); bind_group_ = wgpuDeviceCreateBindGroup(device_, &bg_desc); wgpuBindGroupLayoutRelease(current_layout); wgpuRenderPassEncoderSetPipeline(pass, current_pipeline); wgpuRenderPassEncoderSetBindGroup(pass, 0, bind_group_, 0, nullptr); uint32_t instance_count = (uint32_t)std::min((size_t)kMaxObjects, scene.objects.size()); if (instance_count > 0) { wgpuRenderPassEncoderSetPipeline(pass, current_pipeline); wgpuRenderPassEncoderSetBindGroup(pass, 0, bind_group_, 0, nullptr); wgpuRenderPassEncoderDraw(pass, 36, instance_count, 0, 0); // Mesh pass if (mesh_pipeline_) { wgpuRenderPassEncoderSetPipeline(pass, mesh_pipeline_); // Bind group is the same layout wgpuRenderPassEncoderSetBindGroup(pass, 0, bind_group_, 0, nullptr); for (uint32_t i = 0; i < instance_count; ++i) { const auto& obj = scene.objects[i]; if (obj.type == ObjectType::MESH) { const MeshGpuData* mesh = temp_mesh_override_ ? temp_mesh_override_ : get_or_create_mesh(obj.mesh_asset_id); if (mesh) { wgpuRenderPassEncoderSetVertexBuffer(pass, 0, mesh->vertex_buffer, 0, WGPU_WHOLE_SIZE); wgpuRenderPassEncoderSetIndexBuffer(pass, mesh->index_buffer, WGPUIndexFormat_Uint32, 0, WGPU_WHOLE_SIZE); wgpuRenderPassEncoderDrawIndexed(pass, mesh->num_indices, 1, 0, 0, i); } } } } } #if !defined(STRIP_ALL) if (s_debug_enabled_) { for (const auto& obj : scene.objects) { vec3 extent = obj.local_extent; if (obj.type == ObjectType::TORUS) { extent = vec3(1.5f, 0.5f, 1.5f); } else if (obj.type == ObjectType::MESH) { MeshAsset mesh = GetMeshAsset(obj.mesh_asset_id); if (mesh.num_indices > 0) { visual_debug_.add_mesh_wireframe( obj.get_model_matrix(), mesh.num_vertices, mesh.vertices, mesh.num_indices, mesh.indices, vec3(0.0f, 1.0f, 1.0f)); // Cyan wireframe } } else { extent = vec3(1.0f, 1.0f, 1.0f); } if (obj.type != ObjectType::MESH) { visual_debug_.add_box(obj.get_model_matrix(), extent, vec3(1.0f, 1.0f, 0.0f)); // Yellow boxes } } // Calculate ViewProj matrix for the debug renderer mat4 view_proj = camera.get_projection_matrix() * camera.get_view_matrix(); visual_debug_.render(pass, view_proj); } #endif } void Renderer3D::render(const Scene& scene, const Camera& camera, float time, WGPUTextureView target_view, WGPUTextureView depth_view_opt) { WGPUTextureView depth_view = depth_view_opt ? depth_view_opt : depth_view_; if (!depth_view) return; WGPUCommandEncoder encoder = wgpuDeviceCreateCommandEncoder(device_, nullptr); // --- Pass 1: Render Skybox (Clears color, clears and stores depth) --- bool skybox_rendered = false; if (sky_texture_view_ && skybox_pipeline_) { WGPURenderPassColorAttachment sky_color_attachment = {}; gpu_init_color_attachment(sky_color_attachment, target_view); sky_color_attachment.loadOp = WGPULoadOp_Clear; // Clear to black sky_color_attachment.storeOp = WGPUStoreOp_Store; sky_color_attachment.clearValue = {0.0f, 0.0f, 0.0f, 1.0f}; WGPURenderPassDepthStencilAttachment sky_depth_attachment = {}; sky_depth_attachment.view = depth_view; sky_depth_attachment.depthLoadOp = WGPULoadOp_Clear; // Clear depth sky_depth_attachment.depthStoreOp = WGPUStoreOp_Store; // Store cleared depth sky_depth_attachment.depthClearValue = 1.0f; // Farthest possible depth WGPURenderPassDescriptor sky_pass_desc = {}; sky_pass_desc.colorAttachmentCount = 1; sky_pass_desc.colorAttachments = &sky_color_attachment; sky_pass_desc.depthStencilAttachment = &sky_depth_attachment; WGPURenderPassEncoder sky_pass = wgpuCommandEncoderBeginRenderPass(encoder, &sky_pass_desc); wgpuRenderPassEncoderSetViewport(sky_pass, 0.0f, 0.0f, (float)width_, (float)height_, 0.0f, 1.0f); if (skybox_bind_group_) wgpuBindGroupRelease(skybox_bind_group_); WGPUBindGroupEntry bg_entries[3] = {}; bg_entries[0].binding = 0; bg_entries[0].textureView = sky_texture_view_; bg_entries[1].binding = 1; bg_entries[1].sampler = default_sampler_; bg_entries[2].binding = 2; bg_entries[2].buffer = global_uniform_buffer_; bg_entries[2].size = sizeof(GlobalUniforms); WGPUBindGroupDescriptor bg_desc = {}; bg_desc.layout = wgpuRenderPipelineGetBindGroupLayout(skybox_pipeline_, 0); bg_desc.entryCount = 3; bg_desc.entries = bg_entries; skybox_bind_group_ = wgpuDeviceCreateBindGroup(device_, &bg_desc); wgpuBindGroupLayoutRelease(bg_desc.layout); wgpuRenderPassEncoderSetPipeline(sky_pass, skybox_pipeline_); wgpuRenderPassEncoderSetBindGroup(sky_pass, 0, skybox_bind_group_, 0, nullptr); wgpuRenderPassEncoderDraw(sky_pass, 3, 1, 0, 0); // Draw a full-screen quad wgpuRenderPassEncoderEnd(sky_pass); wgpuRenderPassEncoderRelease(sky_pass); skybox_rendered = true; } // --- Pass 2: Render Scene Objects (Loads depth, writes depth) --- WGPURenderPassColorAttachment obj_color_attachment = {}; gpu_init_color_attachment(obj_color_attachment, target_view); obj_color_attachment.loadOp = skybox_rendered ? WGPULoadOp_Load : WGPULoadOp_Clear; // Load or Clear obj_color_attachment.storeOp = WGPUStoreOp_Store; obj_color_attachment.clearValue = {0.05f, 0.05f, 0.05f, 1.0f}; // Dark gray if no sky WGPURenderPassDepthStencilAttachment obj_depth_attachment = {}; obj_depth_attachment.view = depth_view; obj_depth_attachment.depthLoadOp = skybox_rendered ? WGPULoadOp_Load : WGPULoadOp_Clear; // Load or Clear obj_depth_attachment.depthStoreOp = WGPUStoreOp_Store; // Store object depth obj_depth_attachment.depthClearValue = 1.0f; WGPURenderPassDescriptor obj_pass_desc = {}; obj_pass_desc.colorAttachmentCount = 1; obj_pass_desc.colorAttachments = &obj_color_attachment; obj_pass_desc.depthStencilAttachment = &obj_depth_attachment; WGPURenderPassEncoder obj_pass = wgpuCommandEncoderBeginRenderPass(encoder, &obj_pass_desc); wgpuRenderPassEncoderSetViewport(obj_pass, 0.0f, 0.0f, (float)width_, (float)height_, 0.0f, 1.0f); draw(obj_pass, scene, camera, time); wgpuRenderPassEncoderEnd(obj_pass); wgpuRenderPassEncoderRelease(obj_pass); WGPUCommandBuffer commands = wgpuCommandEncoderFinish(encoder, nullptr); wgpuQueueSubmit(queue_, 1, &commands); wgpuCommandBufferRelease(commands); wgpuCommandEncoderRelease(encoder); }