// // Created by atdunbg on 2026/9/20. // #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace Engine { using namespace ECS; // TransformComponent / Entity 等直接使用 namespace { struct Light { glm::vec4 direction; // xyz = 方向,w 未用 glm::vec4 color; // rgb = 颜色,a = 强度 }; struct UniformBufferObject { glm::mat4 view; glm::mat4 proj; glm::mat4 lightSpace; // 阴影光源 VP(写入与采样共用同一矩阵) glm::vec4 ambientColor; Light lights[4]; }; constexpr u32 kUboBinding = 0; constexpr u32 kObjectBinding = 1; constexpr u32 kUBOSetIndex = 0; constexpr u32 kTextureSetIndex = 1; constexpr u32 kShadowSetIndex = 2; constexpr u32 kMaxLights = 4; constexpr u32 kShadowMapSize = 2048; // 全屏 pass 通用 push(16B):所有全屏 pass(Tonemap/Bloom 系) // 统一声明与推送,规避 VUID-08878 的"range 定义必须一致" struct FullscreenPush { f32 exposure; f32 param0; // 各 pass 自定语义(tonemap: aces 开关) f32 param1; f32 param2; }; constexpr u32 kFullscreenPushSize = static_cast(sizeof(FullscreenPush)); // 光源正交包围盒(覆盖当前场景;后续按场景 AABB 自适应) constexpr f32 kLightOrthoExtent = 15.0f; constexpr f32 kLightDistance = 30.0f; constexpr f32 kLightNear = 1.0f; constexpr f32 kLightFar = 80.0f; } Renderer::Renderer(Window& window) : m_Window(window) { // 1. Device m_Device = CreateScope(window); // 2. MemoryAllocator m_Allocator = CreateScope(*m_Device); // 3. DescriptorManager m_DescriptorManager = CreateScope(*m_Device); // 4. Set 0: UBO + GPUDriven 实例数据(SSBO,vertex 读取) std::vector uboBindings = { {kUboBinding, vk::DescriptorType::eUniformBuffer, vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment}, {kObjectBinding, vk::DescriptorType::eStorageBuffer, vk::ShaderStageFlagBits::eVertex}, }; m_UBOSetLayout = m_DescriptorManager->CreateLayout(uboBindings); // 5. Set 1: texture std::vector texBindings = { {0, vk::DescriptorType::eCombinedImageSampler, vk::ShaderStageFlagBits::eFragment}, }; m_TextureSetLayout = m_DescriptorManager->CreateLayout(texBindings); // 5.5 Set 2: shadow map(fragment 采样) std::vector shadowBindings = { {0, vk::DescriptorType::eCombinedImageSampler, vk::ShaderStageFlagBits::eFragment}, }; m_ShadowSetLayout = m_DescriptorManager->CreateLayout(shadowBindings); // 6. PipelineLayout:三个 set;forward 无 push(GPUDriven 经 SSBO) m_PipelineLayout = m_DescriptorManager->CreatePipelineLayout( {m_UBOSetLayout, m_TextureSetLayout, m_ShadowSetLayout}, 0); // Shadow pass layout:Set0 = UBO/ObjectBuffer(实例矩阵),push = lightVP m_ShadowPipelineLayout = m_DescriptorManager->CreatePipelineLayout( std::vector{m_UBOSetLayout}, sizeof(glm::mat4)); // 7. ShaderManager(VK_EXT_shader_object) m_ShaderManager = CreateScope(*m_Device); std::string shaderDir = "shaders/"; // forward vert/frag 均无 push(range 定义一致:都没有,VUID-08878) auto vert = m_ShaderManager->CreateFromFile( shaderDir + "simple_shader.vert.spv", vk::ShaderStageFlagBits::eVertex, {m_UBOSetLayout, m_TextureSetLayout, m_ShadowSetLayout}, 0); auto frag = m_ShaderManager->CreateFromFile( shaderDir + "simple_shader.frag.spv", vk::ShaderStageFlagBits::eFragment, {m_UBOSetLayout, m_TextureSetLayout, m_ShadowSetLayout}, 0); m_VertShader = vert.Handle; m_FragShader = frag.Handle; // shadow shader:depth-only,无 set auto shadowVert = m_ShaderManager->CreateFromFile( shaderDir + "shadow.vert.spv", vk::ShaderStageFlagBits::eVertex, std::vector{m_UBOSetLayout}, sizeof(glm::mat4)); auto shadowFrag = m_ShaderManager->CreateFromFile( shaderDir + "shadow.frag.spv", vk::ShaderStageFlagBits::eFragment, std::vector{m_UBOSetLayout}, sizeof(glm::mat4)); m_ShadowVertShader = shadowVert.Handle; m_ShadowFragShader = shadowFrag.Handle; if (!m_VertShader || !m_FragShader || !m_ShadowVertShader || !m_ShadowFragShader) { ENGINE_FATAL("Failed to create shader objects"); } // 阴影采样器:clamp-to-border 白色 = 光视锥之外无阴影(省 shader 出界分支) vk::SamplerCreateInfo shadowSamplerInfo; shadowSamplerInfo.setMagFilter(vk::Filter::eLinear) .setMinFilter(vk::Filter::eLinear) .setMipmapMode(vk::SamplerMipmapMode::eNearest) .setAddressModeU(vk::SamplerAddressMode::eClampToBorder) .setAddressModeV(vk::SamplerAddressMode::eClampToBorder) .setAddressModeW(vk::SamplerAddressMode::eClampToBorder) .setBorderColor(vk::BorderColor::eFloatOpaqueWhite) .setUnnormalizedCoordinates(VK_FALSE) .setCompareEnable(VK_FALSE); m_ShadowSampler = m_Device->GetDevice().createSampler(shadowSamplerInfo); // 7.5 全屏后处理装配(Tonemap + Bloom 共用一套 layout/sampler): // Set0 = {HDR 颜色图, Bloom 图},push = 16B 通用结构 std::vector fullscreenBindings = { {0, vk::DescriptorType::eCombinedImageSampler, vk::ShaderStageFlagBits::eFragment}, {1, vk::DescriptorType::eCombinedImageSampler, vk::ShaderStageFlagBits::eFragment}, }; m_TonemapSetLayout = m_DescriptorManager->CreateLayout(fullscreenBindings); m_TonemapPipelineLayout = m_DescriptorManager->CreatePipelineLayout( std::vector{m_TonemapSetLayout}, kFullscreenPushSize); m_TonemapSampler = m_Device->GetDevice().createSampler( vk::SamplerCreateInfo{} .setMagFilter(vk::Filter::eLinear) .setMinFilter(vk::Filter::eLinear) .setMipmapMode(vk::SamplerMipmapMode::eNearest) .setAddressModeU(vk::SamplerAddressMode::eClampToEdge) .setAddressModeV(vk::SamplerAddressMode::eClampToEdge) .setAddressModeW(vk::SamplerAddressMode::eClampToEdge)); auto tonemapVert = m_ShaderManager->CreateFromFile( shaderDir + "tonemap.vert.spv", vk::ShaderStageFlagBits::eVertex, std::vector{m_TonemapSetLayout}, kFullscreenPushSize); auto tonemapFrag = m_ShaderManager->CreateFromFile( shaderDir + "tonemap.frag.spv", vk::ShaderStageFlagBits::eFragment, std::vector{m_TonemapSetLayout}, kFullscreenPushSize); auto bloomExtractFrag = m_ShaderManager->CreateFromFile( shaderDir + "bloom_extract.frag.spv", vk::ShaderStageFlagBits::eFragment, std::vector{m_TonemapSetLayout}, kFullscreenPushSize); auto bloomBlurFrag = m_ShaderManager->CreateFromFile( shaderDir + "bloom_blur.frag.spv", vk::ShaderStageFlagBits::eFragment, std::vector{m_TonemapSetLayout}, kFullscreenPushSize); m_TonemapVertShader = tonemapVert.Handle; m_TonemapFragShader = tonemapFrag.Handle; m_BloomExtractFrag = bloomExtractFrag.Handle; m_BloomBlurFrag = bloomBlurFrag.Handle; if (!m_TonemapVertShader || !m_TonemapFragShader || !m_BloomExtractFrag || !m_BloomBlurFrag) { ENGINE_FATAL("Failed to create fullscreen shader objects"); } // 8. Swapchain(只管呈现;深度图由 RenderGraph 创建) m_Swapchain = CreateScope(*m_Device, *m_Allocator, m_Window); m_Extent = m_Swapchain->GetExtent(); // 9. Sync / Command(per-frame) m_SyncManager = CreateScope( *m_Device, kMaxFramesInFlight, m_Swapchain->GetImageCount()); m_CommandManager = CreateScope(*m_Device, kMaxFramesInFlight); // 10. Per-frame UBO ring + GPUDriven 实例 SSBO ring // (同一 ring 类,SSBO 只是 usage 不同;正确性保证一致) m_UniformBuffer = CreateScope( *m_Device, *m_Allocator, sizeof(UniformBufferObject), kMaxFramesInFlight); m_ObjectBuffer = CreateScope( *m_Device, *m_Allocator, sizeof(glm::mat4) * kMaxInstances, kMaxFramesInFlight, vk::BufferUsageFlagBits::eStorageBuffer); m_UBODescriptorSets.resize(kMaxFramesInFlight); m_ShadowDescriptorSets.resize(kMaxFramesInFlight); m_TonemapDescriptorSets.resize(kMaxFramesInFlight); m_BloomExtractSets.resize(kMaxFramesInFlight); m_BloomBlurHSets.resize(kMaxFramesInFlight); m_BloomBlurVSets.resize(kMaxFramesInFlight); for (u32 slot = 0; slot < kMaxFramesInFlight; ++slot) { m_UBODescriptorSets[slot] = m_DescriptorManager->AllocateDescriptorSet(m_UBOSetLayout); m_DescriptorManager->UpdateUniformBuffer( m_UBODescriptorSets[slot], kUboBinding, m_UniformBuffer->Get(slot), m_UniformBuffer->GetSize()); m_DescriptorManager->UpdateStorageBuffer( m_UBODescriptorSets[slot], kObjectBinding, m_ObjectBuffer->Get(slot), m_ObjectBuffer->GetSize()); m_ShadowDescriptorSets[slot] = m_DescriptorManager->AllocateDescriptorSet(m_ShadowSetLayout); m_TonemapDescriptorSets[slot] = m_DescriptorManager->AllocateDescriptorSet(m_TonemapSetLayout); m_BloomExtractSets[slot] = m_DescriptorManager->AllocateDescriptorSet(m_TonemapSetLayout); m_BloomBlurHSets[slot] = m_DescriptorManager->AllocateDescriptorSet(m_TonemapSetLayout); m_BloomBlurVSets[slot] = m_DescriptorManager->AllocateDescriptorSet(m_TonemapSetLayout); } // 11. RenderGraph:声明 + 编译(场景在 Render(Scene&) 时按帧绑定) BuildGraph(); // Compile 已分配物理镜像:把各槽位的阴影图/HDR view 绑进描述符 UpdateShadowDescriptors(); UpdateFullscreenDescriptors(); // 12. GPU timestamp:每 pass 两枚(前后),按帧槽位分区 m_QueriesPerFrame = static_cast(m_RenderGraph->GetPassCount()) * 2; m_PassNames = m_RenderGraph->GetPassNames(); m_PassGpuMs.assign(m_RenderGraph->GetPassCount(), 0.0f); vk::QueryPoolCreateInfo qpInfo; qpInfo.setQueryType(vk::QueryType::eTimestamp) .setQueryCount(m_QueriesPerFrame * kMaxFramesInFlight); m_QueryPool = m_Device->GetDevice().createQueryPool(qpInfo); m_RenderGraph->SetQueryPool(m_QueryPool, m_QueriesPerFrame); m_TimestampPeriod = static_cast( m_Device->GetPhysicalDevice().getProperties().limits.timestampPeriod); ENGINE_INFO("Renderer initialized"); } Renderer::~Renderer() { m_Device->GetDevice().waitIdle(); const auto& vkDevice = m_Device->GetDevice(); if (m_VertShader) { vkDevice.destroyShaderEXT(m_VertShader); } if (m_FragShader) { vkDevice.destroyShaderEXT(m_FragShader); } if (m_ShadowVertShader) { vkDevice.destroyShaderEXT(m_ShadowVertShader); } if (m_ShadowFragShader) { vkDevice.destroyShaderEXT(m_ShadowFragShader); } if (m_TonemapVertShader) { vkDevice.destroyShaderEXT(m_TonemapVertShader); } if (m_TonemapFragShader) { vkDevice.destroyShaderEXT(m_TonemapFragShader); } if (m_BloomExtractFrag) { vkDevice.destroyShaderEXT(m_BloomExtractFrag); } if (m_BloomBlurFrag) { vkDevice.destroyShaderEXT(m_BloomBlurFrag); } vkDevice.destroySampler(m_ShadowSampler); vkDevice.destroySampler(m_TonemapSampler); if (m_QueryPool) { vkDevice.destroyQueryPool(m_QueryPool); } vkDevice.destroyPipelineLayout(m_PipelineLayout); vkDevice.destroyPipelineLayout(m_ShadowPipelineLayout); vkDevice.destroyPipelineLayout(m_TonemapPipelineLayout); vkDevice.destroyDescriptorSetLayout(m_UBOSetLayout); vkDevice.destroyDescriptorSetLayout(m_TextureSetLayout); vkDevice.destroyDescriptorSetLayout(m_ShadowSetLayout); vkDevice.destroyDescriptorSetLayout(m_TonemapSetLayout); } vk::Format Renderer::GetSwapchainFormat() const { return m_Swapchain->GetFormat(); } u32 Renderer::GetSwapchainImageCount() const { return m_Swapchain->GetImageCount(); } void Renderer::BuildGraph() { m_RenderGraph = CreateScope( *m_Device, *m_Allocator, kMaxFramesInFlight); // 导入 swapchain 颜色附件 m_Backbuffer = m_RenderGraph->ImportSwapchain( "Backbuffer", m_Swapchain->GetFormat()); // 深度图:目标尺寸,格式由 Device 能力决定 const vk::Format depthFormat = m_Device->FindSupportedFormat( {vk::Format::eD32Sfloat, vk::Format::eD32SfloatS8Uint, vk::Format::eD24UnormS8Uint}, vk::ImageTiling::eOptimal, vk::FormatFeatureFlagBits::eDepthStencilAttachment); m_Depth = m_RenderGraph->CreateTexture( "MainDepth", RG::TextureDesc{depthFormat}); // 阴影图:固定尺寸正交深度图(D32 采样) m_ShadowMap = m_RenderGraph->CreateTexture( "ShadowMap", RG::TextureDesc{vk::Format::eD32Sfloat, kShadowMapSize, kShadowMapSize}); // HDR 颜色图:Forward 的输出(线性光照值),Tonemap 显式完成 // 显示变换后写入 Backbuffer。对齐 UE/Unity 管线: // SceneColor(HDR) → tonemapper → UI(UI 在 tonemap 之后叠加) m_HDRColor = m_RenderGraph->CreateTexture( "HDRColor", RG::TextureDesc{vk::Format::eR16G16B16A16Sfloat}); // Bloom ping-pong:半分辨率(sizeScale 相对目标尺寸,resize 自动跟随) m_BloomA = m_RenderGraph->CreateTexture( "BloomA", RG::TextureDesc{vk::Format::eR16G16B16A16Sfloat, 0, 0, 0.5f}); m_BloomB = m_RenderGraph->CreateTexture( "BloomB", RG::TextureDesc{vk::Format::eR16G16B16A16Sfloat, 0, 0, 0.5f}); // ---- Shadow pass:光源视角写深度(所有渲染体都写入,不做实体级开关—— // castShadow 应由材质/实体数据驱动剔除,而非 pass 逻辑特判)---- m_RenderGraph->AddPass("Shadow", [this](RG::RenderGraphContext& ctx) { const vk::CommandBuffer cmd = ctx.cmd; vk::RenderingAttachmentInfo depthAttachment; depthAttachment.setImageView(ctx.View(m_ShadowMap)) .setImageLayout(vk::ImageLayout::eDepthAttachmentOptimal) .setLoadOp(vk::AttachmentLoadOp::eClear) .setStoreOp(vk::AttachmentStoreOp::eStore) .setClearValue(vk::ClearValue( vk::ClearDepthStencilValue(1.0f, 0))); vk::RenderingInfo renderingInfo; renderingInfo.setRenderArea({{0, 0}, {kShadowMapSize, kShadowMapSize}}) .setLayerCount(1) .setPDepthAttachment(&depthAttachment); cmd.beginRendering(renderingInfo); std::array stages = { vk::ShaderStageFlagBits::eVertex, vk::ShaderStageFlagBits::eFragment }; std::array shaders = {m_ShadowVertShader, m_ShadowFragShader}; cmd.bindShadersEXT(stages, shaders); // ---- 动态状态(depth-only)---- cmd.setRasterizerDiscardEnable(VK_FALSE); cmd.setDepthTestEnable(VK_TRUE); cmd.setDepthWriteEnable(VK_TRUE); cmd.setDepthCompareOp(vk::CompareOp::eLess); cmd.setDepthBoundsTestEnable(VK_FALSE); cmd.setStencilTestEnable(VK_FALSE); // 深度 bias 压平 shadow acne(NDC 量级) cmd.setDepthBiasEnable(VK_TRUE); cmd.setDepthBias(0.0025f, 0.0f, 0.003f); cmd.setPolygonModeEXT(vk::PolygonMode::eFill); cmd.setCullMode(vk::CullModeFlagBits::eNone); cmd.setFrontFace(vk::FrontFace::eCounterClockwise); cmd.setPrimitiveTopology(vk::PrimitiveTopology::eTriangleList); cmd.setPrimitiveRestartEnable(VK_FALSE); auto binding = Vertex::GetBindingDescription(); auto attributes = Vertex::GetAttributeDescriptions(); cmd.setVertexInputEXT(binding, attributes); cmd.setRasterizationSamplesEXT(vk::SampleCountFlagBits::e1); vk::SampleMask sampleMask = 0xFFFFFFFF; cmd.setSampleMaskEXT(vk::SampleCountFlagBits::e1, sampleMask); cmd.setAlphaToCoverageEnableEXT(VK_FALSE); cmd.setAlphaToOneEnableEXT(VK_FALSE); vk::Viewport viewport(0.0f, 0.0f, static_cast(kShadowMapSize), static_cast(kShadowMapSize), 0.0f, 1.0f); cmd.setViewportWithCount(viewport); vk::Rect2D scissor({0, 0}, {kShadowMapSize, kShadowMapSize}); cmd.setScissorWithCount(scissor); // ---- 绘制:GPUDriven——与 Forward 共用同一批 batch ---- // lightVP 全 pass 只 push 一次;模型矩阵经实例索引从 // ObjectBuffer 取(与 Forward 同一份数据) cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, m_ShadowPipelineLayout, 0, m_UBODescriptorSets[ctx.frame.frameSlot], {}); // stage 必须覆盖 layout range 声明的全部 stage(VUID-01796) cmd.pushConstants(m_ShadowPipelineLayout, vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment, 0, sizeof(glm::mat4), &m_LightSpace); for (const auto& batch : m_InstanceBatches) { batch.mesh->Draw(cmd, batch.count, batch.first); } cmd.endRendering(); }) .Write(m_ShadowMap, RG::ResourceUse::DepthAttachment); // ---- Forward pass:直接渲染到 swapchain ---- // 场景数据每帧经 m_BoundScene 从 ECS 读取 m_RenderGraph->AddPass("Forward", [this](RG::RenderGraphContext& ctx) { const vk::CommandBuffer cmd = ctx.cmd; vk::ClearValue clearValue; clearValue.color = vk::ClearColorValue( std::array{0.1f, 0.1f, 0.1f, 1.0f}); vk::RenderingAttachmentInfo colorAttachment; colorAttachment.setImageView(ctx.View(m_HDRColor)) .setImageLayout(vk::ImageLayout::eColorAttachmentOptimal) .setLoadOp(vk::AttachmentLoadOp::eClear) .setStoreOp(vk::AttachmentStoreOp::eStore) .setClearValue(clearValue); vk::RenderingAttachmentInfo depthAttachment; depthAttachment.setImageView(ctx.View(m_Depth)) .setImageLayout(vk::ImageLayout::eDepthAttachmentOptimal) .setLoadOp(vk::AttachmentLoadOp::eClear) .setStoreOp(vk::AttachmentStoreOp::eDontCare) .setClearValue(vk::ClearValue( vk::ClearDepthStencilValue(1.0f, 0))); vk::RenderingInfo renderingInfo; renderingInfo.setRenderArea({{0, 0}, ctx.extent}) .setLayerCount(1) .setColorAttachments(colorAttachment) .setPDepthAttachment(&depthAttachment); cmd.beginRendering(renderingInfo); // 绑定 shader object std::array stages = { vk::ShaderStageFlagBits::eVertex, vk::ShaderStageFlagBits::eFragment }; std::array shaders = {m_VertShader, m_FragShader}; cmd.bindShadersEXT(stages, shaders); // Set 0:本帧槽位的 UBO;Set 2:本帧槽位的阴影图 cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, m_PipelineLayout, kUBOSetIndex, m_UBODescriptorSets[ctx.frame.frameSlot], {}); cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, m_PipelineLayout, kShadowSetIndex, m_ShadowDescriptorSets[ctx.frame.frameSlot], {}); // ---- 动态状态 ---- cmd.setRasterizerDiscardEnable(VK_FALSE); cmd.setDepthTestEnable(VK_TRUE); cmd.setDepthWriteEnable(VK_TRUE); cmd.setDepthCompareOp(vk::CompareOp::eLess); cmd.setDepthBoundsTestEnable(VK_FALSE); cmd.setStencilTestEnable(VK_FALSE); cmd.setDepthBiasEnable(VK_FALSE); cmd.setPolygonModeEXT(vk::PolygonMode::eFill); cmd.setCullMode(vk::CullModeFlagBits::eNone); cmd.setFrontFace(vk::FrontFace::eCounterClockwise); cmd.setPrimitiveTopology(vk::PrimitiveTopology::eTriangleList); cmd.setPrimitiveRestartEnable(VK_FALSE); auto binding = Vertex::GetBindingDescription(); auto attributes = Vertex::GetAttributeDescriptions(); cmd.setVertexInputEXT(binding, attributes); cmd.setRasterizationSamplesEXT(vk::SampleCountFlagBits::e1); vk::SampleMask sampleMask = 0xFFFFFFFF; cmd.setSampleMaskEXT(vk::SampleCountFlagBits::e1, sampleMask); cmd.setAlphaToCoverageEnableEXT(VK_FALSE); cmd.setAlphaToOneEnableEXT(VK_FALSE); vk::Viewport viewport(0.0f, 0.0f, static_cast(ctx.extent.width), static_cast(ctx.extent.height), 0.0f, 1.0f); cmd.setViewportWithCount(viewport); vk::Rect2D scissor({0, 0}, ctx.extent); cmd.setScissorWithCount(scissor); vk::Bool32 blendEnable = VK_FALSE; cmd.setColorBlendEnableEXT(0, blendEnable); vk::ColorBlendEquationEXT blendEquation; blendEquation.setSrcColorBlendFactor(vk::BlendFactor::eOne) .setDstColorBlendFactor(vk::BlendFactor::eZero) .setColorBlendOp(vk::BlendOp::eAdd) .setSrcAlphaBlendFactor(vk::BlendFactor::eOne) .setDstAlphaBlendFactor(vk::BlendFactor::eZero) .setAlphaBlendOp(vk::BlendOp::eAdd); cmd.setColorBlendEquationEXT(0, blendEquation); vk::ColorComponentFlags colorWriteMask = vk::ColorComponentFlagBits::eR | vk::ColorComponentFlagBits::eG | vk::ColorComponentFlagBits::eB | vk::ColorComponentFlagBits::eA; cmd.setColorWriteMaskEXT(0, colorWriteMask); // ---- 绘制:GPUDriven 按 mesh 分 batch ---- // 实例矩阵已在 Render() 里写入 ObjectBuffer(per-slot SSBO); // 同 mesh 的 N 个实体合并为一次 drawIndexed(instanceCount=N), // draw 数 = 唯一 mesh 数,与实体数量解耦 for (const auto& batch : m_InstanceBatches) { cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, m_PipelineLayout, kTextureSetIndex, batch.mesh->GetMaterial()->GetDescriptorSet(), {}); batch.mesh->Draw(cmd, batch.count, batch.first); } cmd.endRendering(); }) .Write(m_HDRColor, RG::ResourceUse::ColorAttachment) .Write(m_Depth, RG::ResourceUse::DepthAttachment) .Read(m_ShadowMap, RG::ResourceUse::ShaderRead, vk::ShaderStageFlagBits::eFragment); // ---- 全屏 pass 公共录制:动态状态/draw 全一致,仅附件 view、 // 输入 set、push 值不同(Extract/BlurH/BlurV/Tonemap 复用)---- auto recordFullscreen = [this](RG::RenderGraphContext& ctx, vk::ImageView view, vk::ShaderEXT frag, const vk::DescriptorSet& set, const FullscreenPush& push) { const vk::CommandBuffer cmd = ctx.cmd; vk::ClearValue clearValue; clearValue.color = vk::ClearColorValue( std::array{0.0f, 0.0f, 0.0f, 1.0f}); vk::RenderingAttachmentInfo colorAttachment; colorAttachment.setImageView(view) .setImageLayout(vk::ImageLayout::eColorAttachmentOptimal) .setLoadOp(vk::AttachmentLoadOp::eClear) .setStoreOp(vk::AttachmentStoreOp::eStore) .setClearValue(clearValue); vk::RenderingInfo renderingInfo; renderingInfo.setRenderArea({{0, 0}, ctx.extent}) .setLayerCount(1) .setColorAttachments(colorAttachment); cmd.beginRendering(renderingInfo); std::array stages = { vk::ShaderStageFlagBits::eVertex, vk::ShaderStageFlagBits::eFragment }; std::array shaders = { m_TonemapVertShader, frag}; cmd.bindShadersEXT(stages, shaders); cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics, m_TonemapPipelineLayout, 0, set, {}); // ---- 动态状态(无深度附件、无顶点输入)---- cmd.setRasterizerDiscardEnable(VK_FALSE); cmd.setDepthTestEnable(VK_FALSE); cmd.setDepthWriteEnable(VK_FALSE); cmd.setDepthBoundsTestEnable(VK_FALSE); cmd.setStencilTestEnable(VK_FALSE); cmd.setDepthBiasEnable(VK_FALSE); cmd.setPolygonModeEXT(vk::PolygonMode::eFill); cmd.setCullMode(vk::CullModeFlagBits::eNone); cmd.setFrontFace(vk::FrontFace::eCounterClockwise); cmd.setPrimitiveTopology(vk::PrimitiveTopology::eTriangleList); cmd.setPrimitiveRestartEnable(VK_FALSE); cmd.setVertexInputEXT( std::array{}, std::array{}); cmd.setRasterizationSamplesEXT(vk::SampleCountFlagBits::e1); vk::SampleMask sampleMask = 0xFFFFFFFF; cmd.setSampleMaskEXT(vk::SampleCountFlagBits::e1, sampleMask); cmd.setAlphaToCoverageEnableEXT(VK_FALSE); cmd.setAlphaToOneEnableEXT(VK_FALSE); vk::Viewport viewport(0.0f, 0.0f, static_cast(ctx.extent.width), static_cast(ctx.extent.height), 0.0f, 1.0f); cmd.setViewportWithCount(viewport); cmd.setScissorWithCount(vk::Rect2D({0, 0}, ctx.extent)); cmd.pushConstants(m_TonemapPipelineLayout, vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment, 0, kFullscreenPushSize, &push); cmd.draw(3, 1, 0, 0); cmd.endRendering(); }; // ---- Bloom:Extract(HDR 软阈值)→ BlurH → BlurV,全部半分辨率 ---- m_RenderGraph->AddPass("BloomExtract", [this, recordFullscreen](RG::RenderGraphContext& ctx) { recordFullscreen(ctx, ctx.View(m_BloomA), m_BloomExtractFrag, m_BloomExtractSets[ctx.frame.frameSlot], FullscreenPush{0.0f, m_BloomThreshold, 0.0f, 0.0f}); }) .Read(m_HDRColor, RG::ResourceUse::ShaderRead, vk::ShaderStageFlagBits::eFragment) .Write(m_BloomA, RG::ResourceUse::ColorAttachment); m_RenderGraph->AddPass("BloomBlurH", [this, recordFullscreen](RG::RenderGraphContext& ctx) { recordFullscreen(ctx, ctx.View(m_BloomB), m_BloomBlurFrag, m_BloomBlurHSets[ctx.frame.frameSlot], FullscreenPush{0.0f, 1.0f / ctx.extent.width, 0.0f, 0.0f}); }) .Read(m_BloomA, RG::ResourceUse::ShaderRead, vk::ShaderStageFlagBits::eFragment) .Write(m_BloomB, RG::ResourceUse::ColorAttachment); m_RenderGraph->AddPass("BloomBlurV", [this, recordFullscreen](RG::RenderGraphContext& ctx) { recordFullscreen(ctx, ctx.View(m_BloomA), m_BloomBlurFrag, m_BloomBlurVSets[ctx.frame.frameSlot], FullscreenPush{0.0f, 0.0f, 1.0f / ctx.extent.height, 0.0f}); }) .Read(m_BloomB, RG::ResourceUse::ShaderRead, vk::ShaderStageFlagBits::eFragment) .Write(m_BloomA, RG::ResourceUse::ColorAttachment); // ---- Tonemap pass:HDR(+bloom)→ 显示空间,全屏三角无顶点缓冲 ---- // 显示变换在此显式建模(此前隐式依赖 Srgb 附件,是设计欠账)。 // Backbuffer 从此只接收"显示空间"颜色:tonemap 直接写、ImGui 叠加。 m_RenderGraph->AddPass("Tonemap", [this, recordFullscreen](RG::RenderGraphContext& ctx) { recordFullscreen(ctx, ctx.View(m_Backbuffer), m_TonemapFragShader, m_TonemapDescriptorSets[ctx.frame.frameSlot], FullscreenPush{m_Exposure, m_UseACES ? 1.0f : 0.0f, m_BloomStrength, 0.0f}); }) .Write(m_Backbuffer, RG::ResourceUse::ColorAttachment) .Read(m_HDRColor, RG::ResourceUse::ShaderRead, vk::ShaderStageFlagBits::eFragment) .Read(m_BloomA, RG::ResourceUse::ShaderRead, vk::ShaderStageFlagBits::eFragment); // ---- ImGui pass:叠加层(loadOp=Load,不清屏)---- // 框架只负责把 draw data 录进 cmd;UI 内容全部由派生类 // 在 OnRenderOverlay() 里构建,框架自身不画任何 ImGui 控件。 // 未启用叠加层时空转(begin/end 一次空 rendering region)。 m_RenderGraph->AddPass("ImGui", [this](RG::RenderGraphContext& ctx) { const vk::CommandBuffer cmd = ctx.cmd; vk::RenderingAttachmentInfo colorAttachment; colorAttachment.setImageView(ctx.View(m_Backbuffer)) .setImageLayout(vk::ImageLayout::eColorAttachmentOptimal) .setLoadOp(vk::AttachmentLoadOp::eLoad) // 关键:叠加 .setStoreOp(vk::AttachmentStoreOp::eStore); vk::RenderingInfo renderingInfo; renderingInfo.setRenderArea({{0, 0}, ctx.extent}) .setLayerCount(1) .setColorAttachments(colorAttachment); cmd.beginRendering(renderingInfo); if (m_ImGuiLayer) { m_ImGuiLayer->RenderDrawData(cmd); } cmd.endRendering(); }) .Write(m_Backbuffer, RG::ResourceUse::ColorAttachment); // ---- Present pass:transition 到 ePresentSrcKHR ---- m_RenderGraph->AddPass("Present", [](RG::RenderGraphContext&) { // present 的 layout 转换由 RG 的 barrier 完成, // 实际 vkQueuePresentKHR 由 Renderer 驱动。 }) .Read(m_Backbuffer, RG::ResourceUse::Present); m_RenderGraph->SetTargetExtent(m_Extent); // 触发首次 Compile } void Renderer::HandleResize() { m_Device->GetDevice().waitIdle(); m_Swapchain->Recreate(m_Window); m_Extent = m_Swapchain->GetExtent(); // image 数量可能变化 m_SyncManager = CreateScope( *m_Device, kMaxFramesInFlight, m_Swapchain->GetImageCount()); // RG 重编译:物理镜像按新尺寸轮换 m_RenderGraph->SetTargetExtent(m_Extent); // 重编译后物理镜像可能变化,描述符重新绑定 UpdateShadowDescriptors(); UpdateFullscreenDescriptors(); } void Renderer::UpdateShadowDescriptors() { // 物理镜像按帧槽位各一份:每个槽位的描述符绑各自的 view for (u32 slot = 0; slot < kMaxFramesInFlight; ++slot) { m_DescriptorManager->UpdateTexture( m_ShadowDescriptorSets[slot], 0, m_ShadowSampler, m_RenderGraph->GetPhysicalView(m_ShadowMap, slot)); } } void Renderer::UpdateFullscreenDescriptors() { // 全屏 pass 的输入绑定(per-slot 物理镜像): // Tonemap = {0: HDRColor, 1: BloomA} // Extract = {0: HDRColor} (binding1 不用,可不写) // BlurH = {0: BloomA} // BlurV = {0: BloomB} for (u32 slot = 0; slot < kMaxFramesInFlight; ++slot) { const vk::ImageView hdr = m_RenderGraph->GetPhysicalView(m_HDRColor, slot); const vk::ImageView bloomA = m_RenderGraph->GetPhysicalView(m_BloomA, slot); const vk::ImageView bloomB = m_RenderGraph->GetPhysicalView(m_BloomB, slot); m_DescriptorManager->UpdateTexture( m_TonemapDescriptorSets[slot], 0, m_TonemapSampler, hdr); m_DescriptorManager->UpdateTexture( m_TonemapDescriptorSets[slot], 1, m_TonemapSampler, bloomA); m_DescriptorManager->UpdateTexture( m_BloomExtractSets[slot], 0, m_TonemapSampler, hdr); m_DescriptorManager->UpdateTexture( m_BloomBlurHSets[slot], 0, m_TonemapSampler, bloomA); m_DescriptorManager->UpdateTexture( m_BloomBlurVSets[slot], 0, m_TonemapSampler, bloomB); } } void Renderer::ReadGpuTimes(u32 frameSlot) { if (!m_QueryPool || m_QueriesPerFrame == 0) return; // 每槽位首帧:该区间的查询尚未被 reset/写入,跳过一次读取 if (!m_TimesPrimed[frameSlot]) { m_TimesPrimed[frameSlot] = true; return; } const u32 passCount = m_QueriesPerFrame / 2; std::vector ticks(m_QueriesPerFrame, 0); // fence 已等待本槽位上一轮完成,结果必然可用(阻塞式 + 64bit) const vk::Result r = m_Device->GetDevice().getQueryPoolResults( m_QueryPool, frameSlot * m_QueriesPerFrame, m_QueriesPerFrame, sizeof(u64) * m_QueriesPerFrame, ticks.data(), sizeof(u64), vk::QueryResultFlagBits::e64 | vk::QueryResultFlagBits::eWait); if (r != vk::Result::eSuccess) return; for (u32 i = 0; i < passCount; ++i) { const u64 start = ticks[i * 2]; const u64 end = ticks[i * 2 + 1]; m_PassGpuMs[i] = (end >= start) ? static_cast(end - start) * m_TimestampPeriod / 1.0e6f : 0.0f; } // 控制台遥测:每 120 次读取(≈60 帧 @2 slot)输出一行 static u32 telemetryCount = 0; if (++telemetryCount % 120 == 1) { std::string line = "GPU times:"; for (u32 i = 0; i < passCount; ++i) { line += " " + m_PassNames[i] + "=" + std::to_string(m_PassGpuMs[i]).substr(0, 5) + "ms"; } line += " collect=" + std::to_string(m_CollectMs).substr(0, 5) + "ms"; ENGINE_INFO("{}", line); } } void Renderer::Render(ECS::Scene& scene, Assets::AssetManager& assets) { m_BoundScene = &scene; m_BoundAssets = &assets; if (m_Window.WasResized()) { m_Window.ResetResizedFlag(); HandleResize(); } const u32 frameSlot = m_CurrentFrame; const auto& vkDevice = m_Device->GetDevice(); // 1. UBO:从 ECS 场景组装(primary 相机 + 灯光 + 环境色) auto* cameraComp = scene.GetPrimaryCamera(); if (!cameraComp) { // 无相机:跳过本帧(正常场景必有相机,这里只是安全网) return; } const Camera& camera = cameraComp->camera; float aspect = static_cast(m_Extent.width) / static_cast(m_Extent.height); UniformBufferObject ubo{}; ubo.view = camera.GetView(); ubo.proj = camera.GetProjection(aspect, 0.1f, 100.0f); ubo.ambientColor = scene.GetSettings().ambientColor; u32 lightCount = 0; glm::vec3 shadowDir{0.0f, -1.0f, 0.0f}; // 无灯兜底:头顶正射 scene.Each([&](Entity, LightComponent& light) { if (lightCount >= kMaxLights) return; glm::vec3 dir = glm::length(light.direction) > 0.0f ? glm::normalize(light.direction) : glm::vec3(0.0f); ubo.lights[lightCount].direction = glm::vec4(dir, 0.0f); ubo.lights[lightCount].color = light.color; // 第一个灯作为阴影光源 if (lightCount == 0) shadowDir = dir; ++lightCount; }); for (u32 i = lightCount; i < kMaxLights; ++i) { ubo.lights[i].direction = glm::vec4(0.0f); ubo.lights[i].color = glm::vec4(0.0f); } // ---- 阴影光空间矩阵:写入(Shadow pass)与采样(Forward UBO)共用, // 从根上排除任何"旋转/镜像不一致"的可能 ---- // 光源位置在光方向的反方向远处,看向场景中心。 { const glm::vec3 lightPos = -shadowDir * kLightDistance; // up 与视线平行时叉积退化(NaN),切换 up const glm::vec3 up = glm::abs(shadowDir.y) > 0.999f ? glm::vec3(0.0f, 0.0f, 1.0f) : glm::vec3(0.0f, 1.0f, 0.0f); const glm::mat4 lightView = glm::lookAt( lightPos, glm::vec3(0.0f), up); glm::mat4 lightProj = glm::ortho( -kLightOrthoExtent, kLightOrthoExtent, -kLightOrthoExtent, kLightOrthoExtent, kLightNear, kLightFar); // Vulkan clip 深度 [0,1]:z' = 0.5*z + 0.5*w。 // ortho 的 w_clip = 1,偏移折算进 z 的平移分量(与 perspective 公式不同!) lightProj[2][2] *= 0.5f; lightProj[3][2] = lightProj[3][2] * 0.5f + 0.5f; m_LightSpace = lightProj * lightView; ubo.lightSpace = m_LightSpace; } // 此时本槽位的 fence 尚未 reset——先等它(保证 GPU 已完成本槽位上一轮), // 再写 UBO,写-读竞争关闭。 m_SyncManager->WaitFence(frameSlot); ReadGpuTimes(frameSlot); // fence 已等:上一轮该槽位的查询已就绪 m_UniformBuffer->Update(frameSlot, &ubo, sizeof(ubo)); m_SyncManager->ResetFence(frameSlot); // ---- GPUDriven:收集实例数据(按 mesh 聚合 batch)---- // 两趟遍历:先收集唯一 mesh(保持首现序),再逐 batch 追加实例矩阵, // 保证每个 batch 的矩阵区间连续(draw 用 firstInstance 指向起点)。 // TODO: 实体量大时改哈希聚合 + 单趟填充 const auto collectStart = std::chrono::steady_clock::now(); m_InstanceBatches.clear(); m_InstanceModels.clear(); bool instanceTruncated = false; std::vector uniqueMeshes; scene.Each( [&](Entity, TransformComponent&, MeshRendererComponent& mr) { Model* model = assets.GetModel(mr.model); if (!model) return; for (auto& mesh : model->GetMeshes()) { if (std::find(uniqueMeshes.begin(), uniqueMeshes.end(), mesh.get()) == uniqueMeshes.end()) { uniqueMeshes.push_back(mesh.get()); } } }); for (Mesh* meshPtr : uniqueMeshes) { InstanceBatch batch{meshPtr, static_cast(m_InstanceModels.size()), 0}; scene.Each( [&](Entity, TransformComponent& t, MeshRendererComponent& mr) { Model* model = assets.GetModel(mr.model); if (!model) return; for (auto& m : model->GetMeshes()) { if (m.get() != meshPtr) continue; if (m_InstanceModels.size() >= kMaxInstances) { instanceTruncated = true; return; } m_InstanceModels.push_back(t.world); ++batch.count; } }); if (batch.count > 0) { m_InstanceBatches.push_back(batch); } } if (instanceTruncated) { // 只告警一次(每帧告警会刷屏) static bool warned = false; if (!warned) { warned = true; ENGINE_WARN("GPUDriven: instance buffer full ({}), entities truncated", kMaxInstances); } } if (!m_InstanceModels.empty()) { m_ObjectBuffer->Update(frameSlot, m_InstanceModels.data(), m_InstanceModels.size() * sizeof(glm::mat4)); } m_CollectMs = std::chrono::duration( std::chrono::steady_clock::now() - collectStart).count(); // 2. acquire swapchain image u32 imageIndex = 0; try { const auto result = vkDevice.acquireNextImageKHR( m_Swapchain->Get(), std::numeric_limits::max(), m_SyncManager->GetImageAvailable(frameSlot)); if (result.result == vk::Result::eErrorOutOfDateKHR) { HandleResize(); return; } imageIndex = result.value; } catch (const vk::OutOfDateKHRError&) { HandleResize(); return; } // 3. 录制:FrameContext + RenderGraph FrameContext frame; frame.frameSlot = frameSlot; frame.imageIndex = imageIndex; frame.cmd = m_CommandManager->BeginFrame(frameSlot); m_RenderGraph->SetImportedImage( m_Backbuffer, m_Swapchain->GetImages()[imageIndex], m_Swapchain->GetImageViews()[imageIndex]); m_RenderGraph->Execute(frame); m_CommandManager->EndFrame(frameSlot); // 4. 提交 std::array waitStages = { vk::PipelineStageFlagBits::eColorAttachmentOutput }; std::array waitSemaphores = { m_SyncManager->GetImageAvailable(frameSlot) }; std::array signalSemaphores = { m_SyncManager->GetRenderFinished(imageIndex) }; vk::SubmitInfo submitInfo; submitInfo.setCommandBuffers(frame.cmd) .setWaitSemaphores(waitSemaphores) .setWaitDstStageMask(waitStages) .setSignalSemaphores(signalSemaphores); m_Device->GetGraphicsQueue().submit( submitInfo, m_SyncManager->GetFrameFence(frameSlot)); // 5. 呈现 std::array swapchains = {m_Swapchain->Get()}; std::array presentWait = { m_SyncManager->GetRenderFinished(imageIndex) }; vk::PresentInfoKHR presentInfo; presentInfo.setImageIndices(imageIndex) .setSwapchains(swapchains) .setWaitSemaphores(presentWait); try { auto result = m_Device->GetPresentQueue().presentKHR(presentInfo); if (result == vk::Result::eSuboptimalKHR) { HandleResize(); } } catch (const vk::OutOfDateKHRError&) { HandleResize(); } m_CurrentFrame = (m_CurrentFrame + 1) % kMaxFramesInFlight; } } // namespace Engine