Files
HelloVulkan/Engine/src/Renderer/Renderer.cpp
T
atdunbg 7fca721a24 feat: GPUDriven 实例化渲染——draw 数与实体数解耦
- UniformBuffer ring 支持 eStorageBuffer usage(GPUDriven 实例矩阵
  per-frame-slot,正确性保证与 UBO 一致:fence 等待后写入)
- DescriptorManager 加 UpdateStorageBuffer;池补 STORAGE_BUFFER 类型
- Set0 新增 binding1 = 实例矩阵 SSBO(vertex 读取,std430)
- Forward pass 重写:每帧从 ECS 收集实例矩阵(按 mesh 聚合 batch,
  两趟遍历保证 batch 区间连续),同 mesh 的 N 个实体合并为
  一次 drawIndexed(instanceCount=N),firstInstance 指向 batch 起点;
  vertex shader 经 gl_InstanceIndex 取模型矩阵,forward 不再用 push
- draw 数 = 唯一 mesh 数,与实体数量解耦(4096 实例上限,
  超出截断告警;聚合改哈希为后续 TODO)

验证:8 pass 全活,validation 0 error 0 warning,GPU Timings 面板
实测各 pass 毫秒,场景渲染与阴影正确
2026-09-22 19:46:17 +08:00

1063 lines
47 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//
// 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;
// Shadow pass:push constant = { lightVP, model }
constexpr u32 kShadowMapSize = 2048;
constexpr u32 kShadowPushSize = static_cast<u32>(2 * sizeof(glm::mat4));
// 全屏 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:无 set,128B push constant
m_ShadowPipelineLayout = m_DescriptorManager->CreatePipelineLayout(
std::vector<vk::DescriptorSetLayout>{}, kShadowPushSize);
// 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, {}, kShadowPushSize);
auto shadowFrag = m_ShaderManager->CreateFromFile(
shaderDir + "shadow.frag.spv",
vk::ShaderStageFlagBits::eFragment, {}, kShadowPushSize);
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 不同;正确性保证一致)
constexpr u32 kMaxInstances = 4096;
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);
// ---- 绘制:push {lightVP, model},与 Forward 用同一份 world 矩阵 ----
struct ShadowPush {
glm::mat4 lightVP;
glm::mat4 model;
};
m_BoundScene->Each<TransformComponent, MeshRendererComponent>(
[&](Entity, TransformComponent& transform,
MeshRendererComponent& meshRenderer) {
Model* model = m_BoundAssets->GetModel(meshRenderer.model);
if (!model) return;
const ShadowPush push{m_LightSpace, transform.world};
// stage 必须覆盖 layout range 声明的全部 stage(VUID-01796)
cmd.pushConstants(m_ShadowPipelineLayout,
vk::ShaderStageFlagBits::eVertex |
vk::ShaderStageFlagBits::eFragment,
0, kShadowPushSize, &push);
for (auto& mesh : model->GetMeshes()) {
mesh->Draw(cmd);
}
});
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;
}
}
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: 实体量大时改哈希聚合 + 单趟填充
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) {
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));
}
// 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