100 背包掉帧 + 警告刷屏的根因与修复: 1. backpack.obj 有 44 个 mesh 部件,100 实体 × 44 = 4400 实例 打爆 4096 上限 → 每帧截断 + 告警刷屏。上限提至 16384 (1MB/slot),告警改一次性 2. Shadow pass 仍逐实体逐部件画(4400 draw,CPU 提交瓶颈)。 改为与 Forward 共用同一批 batch:Set0 绑 ObjectBuffer, lightVP 一次 push,44 draw 完成。shadow.vert 经 gl_InstanceIndex 从 SSBO 取模型矩阵 3. 遥测补全:Application::Run 每 120 帧输出 CPU 归属 (update/ui/render/total),Render 输出实例收集耗时—— 实测 100 背包 CPU 2.0ms/帧 GPU 0.6ms/帧 ≈ 500fps
1077 lines
48 KiB
C++
1077 lines
48 KiB
C++
//
|
||
// Created by atdunbg on 2026/9/20.
|
||
//
|
||
|
||
#include <Engine/Renderer/Renderer.h>
|
||
#include <Engine/Renderer/Material.h>
|
||
#include <Engine/Renderer/Model.h>
|
||
#include <Engine/Renderer/Vertex.h>
|
||
#include <Engine/ECS/Scene.h>
|
||
#include <Engine/ECS/Components.h>
|
||
#include <Engine/Assets/AssetManager.h>
|
||
#include <Engine/Vulkan/Device.h>
|
||
#include <Engine/Vulkan/MemoryAllocator.h>
|
||
#include <Engine/Vulkan/DescriptorManager.h>
|
||
#include <Engine/Vulkan/ShaderManager.h>
|
||
#include <Engine/Vulkan/Swapchain.h>
|
||
#include <Engine/Vulkan/CommandManager.h>
|
||
#include <Engine/Vulkan/SyncManager.h>
|
||
#include <Engine/Vulkan/UniformBuffer.h>
|
||
#include <Engine/Core/Window.h>
|
||
#include <Engine/Core/Log.h>
|
||
#include <Engine/ImGui/ImGuiLayer.h>
|
||
|
||
#include <glm/glm.hpp>
|
||
#include <glm/gtc/matrix_transform.hpp>
|
||
|
||
#include <array>
|
||
#include <limits>
|
||
|
||
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<u32>(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<Device>(window);
|
||
|
||
// 2. MemoryAllocator
|
||
m_Allocator = CreateScope<MemoryAllocator>(*m_Device);
|
||
|
||
// 3. DescriptorManager
|
||
m_DescriptorManager = CreateScope<DescriptorManager>(*m_Device);
|
||
|
||
// 4. Set 0: UBO + GPUDriven 实例数据(SSBO,vertex 读取)
|
||
std::vector<DescriptorBinding> 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<DescriptorBinding> texBindings = {
|
||
{0, vk::DescriptorType::eCombinedImageSampler, vk::ShaderStageFlagBits::eFragment},
|
||
};
|
||
m_TextureSetLayout = m_DescriptorManager->CreateLayout(texBindings);
|
||
|
||
// 5.5 Set 2: shadow map(fragment 采样)
|
||
std::vector<DescriptorBinding> 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<vk::DescriptorSetLayout>{m_UBOSetLayout},
|
||
sizeof(glm::mat4));
|
||
|
||
// 7. ShaderManager(VK_EXT_shader_object)
|
||
m_ShaderManager = CreateScope<ShaderManager>(*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<vk::DescriptorSetLayout>{m_UBOSetLayout},
|
||
sizeof(glm::mat4));
|
||
auto shadowFrag = m_ShaderManager->CreateFromFile(
|
||
shaderDir + "shadow.frag.spv",
|
||
vk::ShaderStageFlagBits::eFragment,
|
||
std::vector<vk::DescriptorSetLayout>{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<DescriptorBinding> 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<vk::DescriptorSetLayout>{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<vk::DescriptorSetLayout>{m_TonemapSetLayout},
|
||
kFullscreenPushSize);
|
||
auto tonemapFrag = m_ShaderManager->CreateFromFile(
|
||
shaderDir + "tonemap.frag.spv",
|
||
vk::ShaderStageFlagBits::eFragment,
|
||
std::vector<vk::DescriptorSetLayout>{m_TonemapSetLayout},
|
||
kFullscreenPushSize);
|
||
auto bloomExtractFrag = m_ShaderManager->CreateFromFile(
|
||
shaderDir + "bloom_extract.frag.spv",
|
||
vk::ShaderStageFlagBits::eFragment,
|
||
std::vector<vk::DescriptorSetLayout>{m_TonemapSetLayout},
|
||
kFullscreenPushSize);
|
||
auto bloomBlurFrag = m_ShaderManager->CreateFromFile(
|
||
shaderDir + "bloom_blur.frag.spv",
|
||
vk::ShaderStageFlagBits::eFragment,
|
||
std::vector<vk::DescriptorSetLayout>{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<Swapchain>(*m_Device, *m_Allocator, m_Window);
|
||
m_Extent = m_Swapchain->GetExtent();
|
||
|
||
// 9. Sync / Command(per-frame)
|
||
m_SyncManager = CreateScope<SyncManager>(
|
||
*m_Device, kMaxFramesInFlight, m_Swapchain->GetImageCount());
|
||
m_CommandManager = CreateScope<CommandManager>(*m_Device, kMaxFramesInFlight);
|
||
|
||
// 10. Per-frame UBO ring + GPUDriven 实例 SSBO ring
|
||
// (同一 ring 类,SSBO 只是 usage 不同;正确性保证一致)
|
||
m_UniformBuffer = CreateScope<UniformBuffer>(
|
||
*m_Device, *m_Allocator, sizeof(UniformBufferObject), kMaxFramesInFlight);
|
||
m_ObjectBuffer = CreateScope<UniformBuffer>(
|
||
*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<u32>(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<f32>(
|
||
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<RG::RenderGraph>(
|
||
*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<vk::ShaderStageFlagBits, 2> stages = {
|
||
vk::ShaderStageFlagBits::eVertex,
|
||
vk::ShaderStageFlagBits::eFragment
|
||
};
|
||
std::array<vk::ShaderEXT, 2> 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<float>(kShadowMapSize),
|
||
static_cast<float>(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<float, 4>{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<vk::ShaderStageFlagBits, 2> stages = {
|
||
vk::ShaderStageFlagBits::eVertex,
|
||
vk::ShaderStageFlagBits::eFragment
|
||
};
|
||
std::array<vk::ShaderEXT, 2> 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<float>(ctx.extent.width),
|
||
static_cast<float>(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<float, 4>{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<vk::ShaderStageFlagBits, 2> stages = {
|
||
vk::ShaderStageFlagBits::eVertex,
|
||
vk::ShaderStageFlagBits::eFragment
|
||
};
|
||
std::array<vk::ShaderEXT, 2> 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<vk::VertexInputBindingDescription2EXT, 0>{},
|
||
std::array<vk::VertexInputAttributeDescription2EXT, 0>{});
|
||
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<float>(ctx.extent.width),
|
||
static_cast<float>(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<SyncManager>(
|
||
*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<u64> 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<f32>(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<f32>(m_Extent.width) /
|
||
static_cast<f32>(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<LightComponent>([&](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<Mesh*> uniqueMeshes;
|
||
scene.Each<TransformComponent, MeshRendererComponent>(
|
||
[&](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<u32>(m_InstanceModels.size()), 0};
|
||
scene.Each<TransformComponent, MeshRendererComponent>(
|
||
[&](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<f32, std::milli>(
|
||
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<u64>::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<vk::PipelineStageFlags, 1> waitStages = {
|
||
vk::PipelineStageFlagBits::eColorAttachmentOutput
|
||
};
|
||
std::array<vk::Semaphore, 1> waitSemaphores = {
|
||
m_SyncManager->GetImageAvailable(frameSlot)
|
||
};
|
||
std::array<vk::Semaphore, 1> 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<vk::SwapchainKHR, 1> swapchains = {m_Swapchain->Get()};
|
||
std::array<vk::Semaphore, 1> 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
|