- HitProxy 拾取:RG 管线渲染实体 id → host readback,点击帧 PickEntity 精确命中;拾取按视觉几何(Model 联合包围球射线求交), 与 Collider 解耦(UE/Unity 编辑器语义),命中打印实体名+碰撞体参数 - 选中描边(UE 语义):SelectionMask pass(R8Unorm 完整投影轮廓, 无深度测试透墙可见)→ Outline pass(边缘检测 + alpha 叠加, 必须注册在 Tonemap 后);edgeCut 贴视口边缘;widthPx=6 可调 - shader object 实证修复:VUID-08879 同程序 set layouts 超集一致、 VUID-10065 push block 4×float 平铺、新 pass 析构销毁 VkShaderEXT - RG:SideEffect() 保活"产出无 RG 内消费者"的 pass 链 - Editor:层次面板点击选中、Inspector 姿态编辑、Stats 面板拾取信息 - 相机 Update 改 const(姿态写入 Transform 引用,Camera 本身无状态变更)
267 lines
13 KiB
C++
267 lines
13 KiB
C++
//
|
||
// Created by atdunbg on 2026/9/20.
|
||
//
|
||
// Renderer:渲染门面 + 渲染侧组合根。
|
||
//
|
||
// 分工(对照 UE/Unity 的渲染架构):
|
||
// Renderer —— 装配模块、声明 RG 资源、注册 pass 对象、
|
||
// 每帧编排(翻译场景 → 写 buffer → 驱动 RG → 提交呈现)。
|
||
// 不理解场景细节,不含 pass 实现。
|
||
// SceneRenderer —— 场景 → FrameSceneData 的翻译层(相机/灯光/剔除,
|
||
// 对应 UE FSceneRenderer / Unity CullingResults)。
|
||
// Passes::* —— 各 pass 的声明与录制(对应 Unity ScriptableRenderPass)。
|
||
// 新增效果 = 新增 pass 类 + BuildGraph 注册一行。
|
||
//
|
||
// 对外只暴露两个入口:Render(Scene&, Assets&) 和给 App 层调参的访问器。
|
||
//
|
||
|
||
#ifndef HELLOVULKAN_RENDERER_H
|
||
#define HELLOVULKAN_RENDERER_H
|
||
|
||
#pragma once
|
||
|
||
#include <Engine/Core/Types.h>
|
||
#include <Engine/Core/FrameContext.h>
|
||
#include <Engine/Renderer/SceneTypes.h>
|
||
#include <Engine/RenderGraph/RenderGraph.h>
|
||
#include <vulkan/vulkan.hpp>
|
||
#include <array>
|
||
#include <optional>
|
||
#include <vector>
|
||
|
||
namespace Engine {
|
||
|
||
class Window;
|
||
class Device;
|
||
class MemoryAllocator;
|
||
class DescriptorManager;
|
||
class ShaderManager;
|
||
class Swapchain;
|
||
class SyncManager;
|
||
class CommandManager;
|
||
class UniformBuffer;
|
||
class SceneRenderer;
|
||
class VulkanAssetUploader;
|
||
class Cubemap;
|
||
class IblGenerator;
|
||
class ImGuiLayer;
|
||
class TracyGpuProfiler;
|
||
struct PhysicsDebugFrame; // PhysicsDebugTypes.h(物理调试图元契约)
|
||
|
||
namespace ECS { class Scene; }
|
||
namespace Assets { class AssetManager; class IAssetUploader; }
|
||
namespace Passes {
|
||
class RenderPass; class SceneGpu; class FullscreenGpu;
|
||
class PostProcessParams; class PhysicsDebugPass; class HitProxyPass;
|
||
}
|
||
|
||
class Renderer {
|
||
public:
|
||
explicit Renderer(Window& window);
|
||
~Renderer();
|
||
|
||
Renderer(const Renderer&) = delete;
|
||
Renderer& operator=(const Renderer&) = delete;
|
||
|
||
void Render(ECS::Scene& scene, Assets::AssetManager& assets, f32 dt);
|
||
|
||
// ---- 组合根(Application)装配其他模块时需要的访问器 ----
|
||
Device& GetDevice() const { return *m_Device; }
|
||
MemoryAllocator& GetAllocator() const { return *m_Allocator; }
|
||
DescriptorManager& GetDescriptorManager() const { return *m_DescriptorManager; }
|
||
vk::DescriptorSetLayout GetTextureSetLayout() const; // 实现在 cpp(SceneGpu 成员)
|
||
vk::Format GetSwapchainFormat() const;
|
||
u32 GetSwapchainImageCount() const;
|
||
// 资产上传服务(渲染层对资产层提供的 IAssetUploader 实现)
|
||
Assets::IAssetUploader& GetAssetUploader() const;
|
||
|
||
// 可选叠加层(ImGui):不设置则 RG 里的 ImGui pass 为空转。
|
||
// 生命周期:层必须先于 Renderer 析构(Application 成员序保证)。
|
||
void SetImGuiLayer(ImGuiLayer* layer)
|
||
{
|
||
m_ImGuiLayer = layer;
|
||
if (layer) RefreshSceneTextures(); // 后端就绪:注册场景纹理
|
||
}
|
||
|
||
// 物理调试数据源(PhysicsWorld 每帧收集的图元缓冲;不拥有)。
|
||
// Application 装配时注入;nullptr = 无物理调试
|
||
void SetPhysicsDebugSource(const PhysicsDebugFrame* src)
|
||
{
|
||
m_PhysicsDebug = src;
|
||
}
|
||
|
||
// ---- 场景显示纹理(tonemap 输出,ImGui::Image 可采样显示)----
|
||
// 按 m_CurrentFrame 取当帧槽位的描述符(UI 构建段与紧随的
|
||
// Render 同槽位)。无 ImGui 层时返回 nullptr
|
||
void* GetSceneTexture() const;
|
||
// 渲染分辨率(PresentTarget 尺寸,等比适配用)
|
||
vk::Extent2D GetRenderExtent() const { return m_Extent; }
|
||
|
||
// ---- 后处理参数(ImGui 可调,转发到 pass 共享的 PostProcessParams)----
|
||
f32 GetExposure() const;
|
||
void SetExposure(f32 e);
|
||
bool GetUseACES() const;
|
||
void SetUseACES(bool b);
|
||
f32 GetBloomThreshold() const;
|
||
void SetBloomThreshold(f32 v);
|
||
f32 GetBloomStrength() const;
|
||
void SetBloomStrength(f32 v);
|
||
|
||
// ---- 特性开关 / 调试视图(转发到 SceneRenderer,每帧落
|
||
// FrameSceneData 契约,pass 只读;ImGui 直接对接)----
|
||
bool GetShadowsEnabled() const;
|
||
void SetShadowsEnabled(bool b);
|
||
bool GetTAAEnabled() const;
|
||
void SetTAAEnabled(bool b);
|
||
bool GetBloomEnabled() const;
|
||
void SetBloomEnabled(bool b);
|
||
bool GetLodEnabled() const;
|
||
void SetLodEnabled(bool b); // 关 = 全部强制 LOD0
|
||
int GetDebugView() const;
|
||
void SetDebugView(int v); // Passes::DebugView 枚举值
|
||
// 调试视图名表(下标 = DebugView 枚举值,ImGui combo 直接枚举)
|
||
const std::vector<const char*>& GetDebugViewNames() const;
|
||
|
||
// ---- 动态环境切换(资产归 Assets 层,Renderer 只消费)----
|
||
// env 传 AssetManager::GetEnvironment(ref) 的指针;nullptr 回中性兜底。
|
||
// 内部 waitIdle 后统一换绑(引用环境描述符的 pass 全部同步);
|
||
// compute 预积分已把生成成本压到毫秒级,waitIdle 尾巴 ≈ 单帧
|
||
void SetEnvironment(const Cubemap* env);
|
||
|
||
// pass 级 GPU 耗时(最近完成帧,毫秒;与 GetPassGpuMs 下标对应)
|
||
const std::vector<std::string>& GetPassNames() const { return m_PassNames; }
|
||
const std::vector<f32>& GetPassGpuMs() const { return m_PassGpuMs; }
|
||
|
||
// 双视图剔除后的实例数(遥测)
|
||
u32 GetMainInstanceCount() const;
|
||
u32 GetShadowInstanceCount() const;
|
||
bool GetInstancesTruncated() const; // 实例预算溢出(遥测红字)
|
||
f32 GetCollectMs() const; // CPU 收集+剔除段耗时
|
||
|
||
// ---- 显示模式(Editor 视口 ↔ 全屏直出;帧首同步生效)----
|
||
int GetDisplayMode() const { return m_DesiredDisplayMode; }
|
||
void SetDisplayMode(int mode); // cpp 内定义(SceneRenderer 前置声明)
|
||
// 当帧场景数据(只读直通)。CPU 消费如视口拾取:反投影矩阵必须
|
||
// 与渲染同一台相机同一投影(含 [0,1] 深度语义),禁止自行重算
|
||
const FrameSceneData& GetFrameSceneData() const; // cpp 内定义
|
||
// Editor 视口的期望渲染尺寸(像素;UI 面板每帧上报,帧首统一生效)
|
||
void SetViewportSize(u32 w, u32 h)
|
||
{
|
||
m_DesiredViewport = vk::Extent2D{w, h};
|
||
}
|
||
|
||
// ---- Editor 视口拾取(UE 式 GPU HitProxy:id pass + 点击 readback)----
|
||
// UI 段点击时调用:下一帧 HitProxy 渲染 + 请求像素拷贝(Vulkan
|
||
// NDC,y 向下,与渲染同构)
|
||
void RequestPick(const glm::vec2& ndc);
|
||
// UI 段轮询:拷贝结果就绪则取走(一次性;nullopt = 暂无结果)。
|
||
// 返回 kPickMiss = 未命中(天空/空隙),有效值 = 实体位模式。
|
||
// 内部 waitIdle 保证提交完成(点击低频,单帧 hitch 不可感知)
|
||
std::optional<u32> TakePickResult();
|
||
|
||
// ---- Editor 选中描边(UE 编辑器选中轮廓)----
|
||
// UI 段选中变化时调用(bits = 实体位模式,0 = 清选中);
|
||
// 下一帧 SelectionMask 只画该实体,Outline 在其轮廓叠加描边色
|
||
void SetSelectedEntity(u32 entityBits);
|
||
|
||
// 垂直同步开关(present mode 切换会重建 swapchain),
|
||
// 只应在帧 UI 构建段调用(Render 之前)
|
||
void SetVSync(bool vsync) { m_DesiredVSync = vsync; } // 帧首同步生效
|
||
|
||
// 帧首同步:窗口 resize / 视口尺寸 / 显示模式 / vsync 意图统一在
|
||
// UI 构建之前落定(waitIdle → 重编译 → 重绑描述符)。必须先于
|
||
// UI 构建段调用:本帧 draw data 引用的描述符与物理资源由此保证
|
||
// 全程新鲜(resize 中途换描述符会绑出已销毁句柄)
|
||
void BeginFrameSync();
|
||
private:
|
||
void BuildGraph(); // 声明 RG 资源 + 注册 pass 对象
|
||
void HandleResize(bool recreateSwapchain);
|
||
void ReadGpuTimes(u32 frameSlot);
|
||
void RefreshSceneTextures(); // 物理 view → ImGui 描述符(重建后刷新)
|
||
// 期望渲染分辨率:Editor 视口 = 面板尺寸(未上报时回退 swapchain);
|
||
// Direct = swapchain 尺寸
|
||
vk::Extent2D DesiredRenderExtent() const;
|
||
|
||
Window& m_Window;
|
||
|
||
// ---- 底层模块(析构序:后声明者先析构;引用他人者先析构)----
|
||
Scope<Device> m_Device;
|
||
Scope<MemoryAllocator> m_Allocator;
|
||
Scope<DescriptorManager> m_DescriptorManager;
|
||
Scope<ShaderManager> m_ShaderManager;
|
||
Scope<UniformBuffer> m_UniformBuffer; // per-slot UBO ring
|
||
Scope<UniformBuffer> m_ObjectBuffer; // GPUDriven 实例矩阵 ring(SSBO)
|
||
Scope<UniformBuffer> m_PrevObjectBuffer; // 上一帧实例矩阵 ring(TAA velocity)
|
||
Scope<UniformBuffer> m_SkinOffsetBuffer; // 实例→palette 基址 ring(SSBO,与实例序平行)
|
||
Scope<UniformBuffer> m_SkinPaletteBuffer; // 骨骼蒙皮矩阵 palette ring(SSBO,帧首 bump)
|
||
Scope<UniformBuffer> m_EntityIdBuffer; // 实例→实体位模式 ring(SSBO,HitProxy 拾取)
|
||
Scope<Passes::SceneGpu> m_SceneGpu; // 场景类 pass 共享装配
|
||
Scope<Passes::FullscreenGpu> m_FullscreenGpu; // 全屏 pass 共享装配
|
||
// 物理调试图元 ring VB(eVertexBuffer;per-slot fence 语义复用
|
||
// UniformBuffer——PhysicsDebugPass::PrepareFrame 上传,pass 内绑定)
|
||
Scope<UniformBuffer> m_PhysicsLineVB;
|
||
Scope<UniformBuffer> m_PhysicsTriVB;
|
||
// 环境预积分生成端(compute 链),先于 m_Environment/m_Passes 构造
|
||
Scope<IblGenerator> m_Ibl;
|
||
// 中性兜底环境(IBL 四件套):Application 未加载/加载失败时保底;
|
||
// 声明在 m_Passes 之前 → 析构晚于 pass(引用安全)
|
||
Scope<Cubemap> m_Environment;
|
||
Scope<Swapchain> m_Swapchain;
|
||
Scope<SyncManager> m_SyncManager;
|
||
Scope<CommandManager> m_CommandManager;
|
||
Scope<SceneRenderer> m_SceneRenderer; // 场景翻译层(先于 pass 构造)
|
||
Scope<RG::RenderGraph> m_RenderGraph;
|
||
Scope<VulkanAssetUploader> m_AssetUploader;
|
||
|
||
// pass 对象(注册序;执行序由 RG 拓扑排序决定)
|
||
std::vector<Scope<Passes::RenderPass>> m_Passes;
|
||
|
||
// ---- RenderGraph 资源句柄 ----
|
||
RG::TextureHandle m_Backbuffer{};
|
||
RG::TextureHandle m_PresentTarget{}; // tonemap 输出(offscreen,ImGui 采样)
|
||
RG::TextureHandle m_Depth{};
|
||
std::array<RG::TextureHandle, kMaxCascades> m_ShadowMaps{}; // CSM 级联
|
||
RG::TextureHandle m_HDRColor{}; // Forward 输出(线性 HDR),TAA 归一
|
||
RG::TextureHandle m_Velocity{}; // Forward 输出(clip 空间运动向量)
|
||
RG::TextureHandle m_HitProxyId{}; // 拾取 id 目标(R32Uint,片元写实体位模式)
|
||
RG::TextureHandle m_SelectionMask{}; // 选中描边 mask(R8Unorm,选中像素=1)
|
||
RG::TextureHandle m_TAAColor{}; // TAA 累积输出(自引用历史),后处理源
|
||
RG::TextureHandle m_BloomA{}; // 半分辨率:提取 + 垂直模糊结果
|
||
RG::TextureHandle m_BloomB{}; // 半分辨率:水平模糊中间结果
|
||
RG::TextureHandle m_TemporalDiff{}; // 半分辨率:帧历史差异(调试)
|
||
|
||
// pass 共享的可调参数(Renderer 持有,App 层访问器转发)
|
||
Scope<Passes::PostProcessParams> m_PostParams;
|
||
|
||
// 可选叠加层(不拥有;Application 持有)
|
||
ImGuiLayer* m_ImGuiLayer = nullptr;
|
||
|
||
// 物理调试(非拥有指针:pass 在 m_Passes 内,数据源由 Application 注入)
|
||
Passes::PhysicsDebugPass* m_PhysicsDebugPass = nullptr;
|
||
const PhysicsDebugFrame* m_PhysicsDebug = nullptr;
|
||
// HitProxy 拾取(非拥有指针:pass 在 m_Passes 内)
|
||
Passes::HitProxyPass* m_HitProxyPass = nullptr;
|
||
// GPU timestamp(pass 级耗时,ImGui 可读)
|
||
vk::QueryPool m_QueryPool;
|
||
u32 m_QueriesPerFrame = 0; // 每帧槽位的查询数 = pass 数 × 2
|
||
f32 m_TimestampPeriod = 1.0f; // ns/tick(设备属性缓存)
|
||
std::vector<std::string> m_PassNames; // 执行序 pass 名
|
||
std::vector<f32> m_PassGpuMs; // 最近一帧各 pass 耗时
|
||
bool m_TimesPrimed[2] = {false, false}; // 每槽位首帧跳过读取
|
||
|
||
// Tracy GPU 时间线(pass zone;TRACY_ENABLE 关闭时空实现)
|
||
Scope<TracyGpuProfiler> m_TracyProfiler;
|
||
|
||
vk::Extent2D m_Extent{};
|
||
u32 m_CurrentFrame = 0;
|
||
|
||
// ---- 帧首同步的意图缓冲(UI 段写入,下一帧帧首落定)----
|
||
int m_DesiredDisplayMode = kDisplayDirect; // 默认直接渲染, 视口渲染需要手动指定一下
|
||
vk::Extent2D m_DesiredViewport{}; // Editor 视口渲染分辨率(面板上报)
|
||
bool m_DesiredVSync = true;
|
||
};
|
||
|
||
} // namespace Engine
|
||
|
||
#endif //HELLOVULKAN_RENDERER_H
|