Compare commits
4
Commits
4fd88248fc
...
41e4cc85d5
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
41e4cc85d5 | ||
|
|
908e688178 | ||
|
|
601fd8997c | ||
|
|
952ba8135d |
@@ -15,6 +15,7 @@
|
|||||||
#include <Engine/ECS/EntityHandle.h>
|
#include <Engine/ECS/EntityHandle.h>
|
||||||
#include <Engine/ECS/Components.h>
|
#include <Engine/ECS/Components.h>
|
||||||
#include <Engine/Assets/AssetManager.h>
|
#include <Engine/Assets/AssetManager.h>
|
||||||
|
#include <Engine/Renderer/Renderer.h>
|
||||||
|
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
|
|
||||||
@@ -23,6 +24,8 @@
|
|||||||
|
|
||||||
#include <GLFW/glfw3.h>
|
#include <GLFW/glfw3.h>
|
||||||
|
|
||||||
|
#include "Engine/ImGui/ImGuiLayer.h"
|
||||||
|
|
||||||
using namespace Engine;
|
using namespace Engine;
|
||||||
using namespace Engine::ECS;
|
using namespace Engine::ECS;
|
||||||
|
|
||||||
@@ -114,6 +117,8 @@ protected:
|
|||||||
{
|
{
|
||||||
if (!m_OverlayVisible) return;
|
if (!m_OverlayVisible) return;
|
||||||
|
|
||||||
|
ImGui::ShowDemoWindow();
|
||||||
|
|
||||||
ImGui::Begin("Scene Debug");
|
ImGui::Begin("Scene Debug");
|
||||||
|
|
||||||
// 阴影光源方向:拖动滑条实时看影子变化
|
// 阴影光源方向:拖动滑条实时看影子变化
|
||||||
@@ -126,6 +131,38 @@ protected:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Tonemap + Bloom 参数(HDR 后处理输出端)
|
||||||
|
auto& renderer = GetRenderer();
|
||||||
|
ImGui::Separator();
|
||||||
|
ImGui::Text("Tonemap");
|
||||||
|
f32 exposure = renderer.GetExposure();
|
||||||
|
if (ImGui::SliderFloat("exposure", &exposure, 0.1f, 4.0f)) {
|
||||||
|
renderer.SetExposure(exposure);
|
||||||
|
}
|
||||||
|
bool aces = renderer.GetUseACES();
|
||||||
|
if (ImGui::Checkbox("ACES", &aces)) {
|
||||||
|
renderer.SetUseACES(aces);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::Text("Bloom");
|
||||||
|
f32 threshold = renderer.GetBloomThreshold();
|
||||||
|
if (ImGui::SliderFloat("threshold", &threshold, 0.0f, 2.0f)) {
|
||||||
|
renderer.SetBloomThreshold(threshold);
|
||||||
|
}
|
||||||
|
f32 strength = renderer.GetBloomStrength();
|
||||||
|
if (ImGui::SliderFloat("strength", &strength, 0.0f, 2.0f)) {
|
||||||
|
renderer.SetBloomStrength(strength);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pass 级 GPU 耗时(RG timestamp)
|
||||||
|
ImGui::Separator();
|
||||||
|
ImGui::Text("GPU Timings");
|
||||||
|
const auto& names = renderer.GetPassNames();
|
||||||
|
const auto& times = renderer.GetPassGpuMs();
|
||||||
|
for (size_t i = 0; i < names.size() && i < times.size(); ++i) {
|
||||||
|
ImGui::Text(" %-12s %.3f ms", names[i].c_str(), times[i]);
|
||||||
|
}
|
||||||
|
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
ImGui::Text("fps: %.1f frame: %llu",
|
ImGui::Text("fps: %.1f frame: %llu",
|
||||||
1.0f / GetDeltaTime(),
|
1.0f / GetDeltaTime(),
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ set(IMGUI_SOURCES
|
|||||||
${IMGUI_DIR}/imgui_draw.cpp
|
${IMGUI_DIR}/imgui_draw.cpp
|
||||||
${IMGUI_DIR}/imgui_tables.cpp
|
${IMGUI_DIR}/imgui_tables.cpp
|
||||||
${IMGUI_DIR}/imgui_widgets.cpp
|
${IMGUI_DIR}/imgui_widgets.cpp
|
||||||
|
${IMGUI_DIR}/imgui_demo.cpp # ShowDemoWindow 的实现(调试期保留)
|
||||||
${IMGUI_DIR}/backends/imgui_impl_glfw.cpp
|
${IMGUI_DIR}/backends/imgui_impl_glfw.cpp
|
||||||
${IMGUI_DIR}/backends/imgui_impl_vulkan.cpp
|
${IMGUI_DIR}/backends/imgui_impl_vulkan.cpp
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -33,6 +33,17 @@ namespace Engine {
|
|||||||
using EventCallbackFn = std::function<void(const Event&)>;
|
using EventCallbackFn = std::function<void(const Event&)>;
|
||||||
void SetEventCallback(EventCallbackFn callback) { m_EventCallback = std::move(callback); }
|
void SetEventCallback(EventCallbackFn callback) { m_EventCallback = std::move(callback); }
|
||||||
|
|
||||||
|
// 输入捕获:返回 true = 该类输入被 UI 占用(如 ImGui 窗口交互)。
|
||||||
|
// 生效范围:类型化事件回调(Key/Mouse*)+ 轮询接口
|
||||||
|
//(IsKeyPressed/GetMouseDelta)。窗口类事件(Resize/Close/Focus)不受影响。
|
||||||
|
// 未设置时全部放行(行为与无 UI 时一致)。
|
||||||
|
using CaptureFn = std::function<bool()>;
|
||||||
|
void SetInputCapture(CaptureFn keyboard, CaptureFn mouse)
|
||||||
|
{
|
||||||
|
m_KeyboardCapture = std::move(keyboard);
|
||||||
|
m_MouseCapture = std::move(mouse);
|
||||||
|
}
|
||||||
|
|
||||||
vk::SurfaceKHR CreateSurface(vk::Instance instance);
|
vk::SurfaceKHR CreateSurface(vk::Instance instance);
|
||||||
void DestroySurface(vk::Instance instance, vk::SurfaceKHR surface);
|
void DestroySurface(vk::Instance instance, vk::SurfaceKHR surface);
|
||||||
|
|
||||||
@@ -65,6 +76,10 @@ namespace Engine {
|
|||||||
bool m_FramebufferResized = false;
|
bool m_FramebufferResized = false;
|
||||||
EventCallbackFn m_EventCallback;
|
EventCallbackFn m_EventCallback;
|
||||||
|
|
||||||
|
// UI 输入捕获(ImGui 接管时置位)
|
||||||
|
CaptureFn m_KeyboardCapture;
|
||||||
|
CaptureFn m_MouseCapture;
|
||||||
|
|
||||||
// GLFW 静态回调(通过 window user pointer 找回 this)
|
// GLFW 静态回调(通过 window user pointer 找回 this)
|
||||||
static void FramebufferResizeCallback(GLFWwindow* window, int width, int height);
|
static void FramebufferResizeCallback(GLFWwindow* window, int width, int height);
|
||||||
static void KeyCallback(GLFWwindow* window, int key, int scancode, int action, int mods);
|
static void KeyCallback(GLFWwindow* window, int key, int scancode, int action, int mods);
|
||||||
|
|||||||
@@ -70,8 +70,9 @@ namespace Engine::RG {
|
|||||||
|
|
||||||
struct TextureDesc {
|
struct TextureDesc {
|
||||||
vk::Format format = vk::Format::eUndefined;
|
vk::Format format = vk::Format::eUndefined;
|
||||||
u32 width = 0; // 0 = 跟随目标(swapchain)尺寸
|
u32 width = 0; // 0 = 跟随目标(swapchain)尺寸 × sizeScale
|
||||||
u32 height = 0;
|
u32 height = 0;
|
||||||
|
f32 sizeScale = 1.0f; // 相对尺寸(后处理降采样用,如 bloom 0.5)
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---------- 物理镜像 ----------
|
// ---------- 物理镜像 ----------
|
||||||
@@ -154,6 +155,7 @@ namespace Engine::RG {
|
|||||||
ExecuteFn exec;
|
ExecuteFn exec;
|
||||||
bool sideEffect = false; // 无读写但必须执行(compute 写 buffer 等)
|
bool sideEffect = false; // 无读写但必须执行(compute 写 buffer 等)
|
||||||
bool culled = false;
|
bool culled = false;
|
||||||
|
vk::Extent2D extent{}; // 本 pass 的渲染尺寸(写入附件的最大分辨率,Compile 期解析)
|
||||||
|
|
||||||
// Compile 期预算好的 barrier:每个资源两个变体
|
// Compile 期预算好的 barrier:每个资源两个变体
|
||||||
// steady = 该槽位已被使用过(从 resting 状态出发)
|
// steady = 该槽位已被使用过(从 resting 状态出发)
|
||||||
@@ -225,6 +227,13 @@ namespace Engine::RG {
|
|||||||
// 每帧执行:按编译好的顺序录命令。要求 cmd 处于 recording 状态。
|
// 每帧执行:按编译好的顺序录命令。要求 cmd 处于 recording 状态。
|
||||||
void Execute(const FrameContext& frame);
|
void Execute(const FrameContext& frame);
|
||||||
|
|
||||||
|
// GPU timestamp(可选):注入 pool 后,每个 pass 前后各写一枚
|
||||||
|
// 时间戳,索引 = frameSlot * queriesPerFrame + 执行序位置 * 2。
|
||||||
|
// pool 由调用方创建(容量 ≥ framesInFlight × queriesPerFrame),
|
||||||
|
// 读取由调用方在本槽位 fence 等待之后进行。
|
||||||
|
void SetQueryPool(vk::QueryPool pool, u32 queriesPerFrame);
|
||||||
|
std::vector<std::string> GetPassNames() const;
|
||||||
|
|
||||||
// ---- 统计 ----
|
// ---- 统计 ----
|
||||||
|
|
||||||
u32 GetPassCount() const;
|
u32 GetPassCount() const;
|
||||||
@@ -274,6 +283,10 @@ namespace Engine::RG {
|
|||||||
// 拓扑排序后的执行序(存 pass 下标)
|
// 拓扑排序后的执行序(存 pass 下标)
|
||||||
std::vector<u32> m_ExecutionOrder;
|
std::vector<u32> m_ExecutionOrder;
|
||||||
|
|
||||||
|
// GPU timestamp(可选注入)
|
||||||
|
vk::QueryPool m_QueryPool{};
|
||||||
|
u32 m_QueriesPerFrame = 0;
|
||||||
|
|
||||||
// 物理资源池:key = (format, extent, usage),value = 空闲镜像列表
|
// 物理资源池:key = (format, extent, usage),value = 空闲镜像列表
|
||||||
struct PoolKey {
|
struct PoolKey {
|
||||||
u32 format;
|
u32 format;
|
||||||
|
|||||||
@@ -64,6 +64,8 @@ namespace Engine {
|
|||||||
void BuildGraph(); // 声明 pass 结构 + 首次 Compile
|
void BuildGraph(); // 声明 pass 结构 + 首次 Compile
|
||||||
void HandleResize();
|
void HandleResize();
|
||||||
void UpdateShadowDescriptors();
|
void UpdateShadowDescriptors();
|
||||||
|
void UpdateFullscreenDescriptors();
|
||||||
|
void ReadGpuTimes(u32 frameSlot);
|
||||||
|
|
||||||
Window& m_Window;
|
Window& m_Window;
|
||||||
|
|
||||||
@@ -96,10 +98,30 @@ namespace Engine {
|
|||||||
vk::ShaderEXT m_ShadowVertShader{};
|
vk::ShaderEXT m_ShadowVertShader{};
|
||||||
vk::ShaderEXT m_ShadowFragShader{};
|
vk::ShaderEXT m_ShadowFragShader{};
|
||||||
|
|
||||||
|
// Tonemap pass 装配:Set0 = {HDR 颜色图, Bloom 图},push = 16B
|
||||||
|
//(全屏 pass 通用:exposure/aces/bloomStrength/pad)
|
||||||
|
vk::PipelineLayout m_TonemapPipelineLayout;
|
||||||
|
vk::DescriptorSetLayout m_TonemapSetLayout;
|
||||||
|
std::vector<vk::DescriptorSet> m_TonemapDescriptorSets; // 每帧槽位一个
|
||||||
|
vk::Sampler m_TonemapSampler;
|
||||||
|
vk::ShaderEXT m_TonemapVertShader{};
|
||||||
|
vk::ShaderEXT m_TonemapFragShader{};
|
||||||
|
|
||||||
|
// Bloom:Extract(HDR→A) → BlurH(A→B) → BlurV(B→A),半分辨率
|
||||||
|
// 与 Tonemap 共用 set layout/pipeline layout/vertex shader
|
||||||
|
std::vector<vk::DescriptorSet> m_BloomExtractSets; // 每帧槽位
|
||||||
|
std::vector<vk::DescriptorSet> m_BloomBlurHSets;
|
||||||
|
std::vector<vk::DescriptorSet> m_BloomBlurVSets;
|
||||||
|
vk::ShaderEXT m_BloomExtractFrag{};
|
||||||
|
vk::ShaderEXT m_BloomBlurFrag{};
|
||||||
|
|
||||||
// ---- RenderGraph 资源句柄 ----
|
// ---- RenderGraph 资源句柄 ----
|
||||||
RG::TextureHandle m_Backbuffer{};
|
RG::TextureHandle m_Backbuffer{};
|
||||||
RG::TextureHandle m_Depth{};
|
RG::TextureHandle m_Depth{};
|
||||||
RG::TextureHandle m_ShadowMap{};
|
RG::TextureHandle m_ShadowMap{};
|
||||||
|
RG::TextureHandle m_HDRColor{}; // Forward 输出(线性 HDR),Tonemap 归一
|
||||||
|
RG::TextureHandle m_BloomA{}; // 半分辨率:提取 + 垂直模糊结果
|
||||||
|
RG::TextureHandle m_BloomB{}; // 半分辨率:水平模糊中间结果
|
||||||
|
|
||||||
// ---- 当帧绑定(Render() 期间有效,供 pass 回调读取)----
|
// ---- 当帧绑定(Render() 期间有效,供 pass 回调读取)----
|
||||||
ECS::Scene* m_BoundScene = nullptr;
|
ECS::Scene* m_BoundScene = nullptr;
|
||||||
@@ -109,6 +131,36 @@ namespace Engine {
|
|||||||
// 可选叠加层(不拥有;Application 持有)
|
// 可选叠加层(不拥有;Application 持有)
|
||||||
ImGuiLayer* m_ImGuiLayer = nullptr;
|
ImGuiLayer* m_ImGuiLayer = nullptr;
|
||||||
|
|
||||||
|
// ---- Tonemap/Bloom 参数(ImGui 可调)----
|
||||||
|
f32 m_Exposure = 1.0f;
|
||||||
|
bool m_UseACES = true;
|
||||||
|
f32 m_BloomThreshold = 1.0f; // HDR 域亮度阈值
|
||||||
|
f32 m_BloomStrength = 0.8f; // 叠加强度
|
||||||
|
|
||||||
|
// 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}; // 每槽位首帧跳过读取
|
||||||
|
|
||||||
|
public:
|
||||||
|
f32 GetExposure() const { return m_Exposure; }
|
||||||
|
void SetExposure(f32 e) { m_Exposure = e; }
|
||||||
|
bool GetUseACES() const { return m_UseACES; }
|
||||||
|
void SetUseACES(bool b) { m_UseACES = b; }
|
||||||
|
f32 GetBloomThreshold() const { return m_BloomThreshold; }
|
||||||
|
void SetBloomThreshold(f32 v) { m_BloomThreshold = v; }
|
||||||
|
f32 GetBloomStrength() const { return m_BloomStrength; }
|
||||||
|
void SetBloomStrength(f32 v) { m_BloomStrength = v; }
|
||||||
|
|
||||||
|
// pass 级 GPU 耗时(最近完成帧,毫秒;与 GetPassGpuMs 下标对应)
|
||||||
|
const std::vector<std::string>& GetPassNames() const { return m_PassNames; }
|
||||||
|
const std::vector<f32>& GetPassGpuMs() const { return m_PassGpuMs; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
|
||||||
vk::Extent2D m_Extent{};
|
vk::Extent2D m_Extent{};
|
||||||
u32 m_CurrentFrame = 0;
|
u32 m_CurrentFrame = 0;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -42,7 +42,10 @@ namespace Engine {
|
|||||||
vk::PipelineLayout CreatePipelineLayout(vk::DescriptorSetLayout layout,
|
vk::PipelineLayout CreatePipelineLayout(vk::DescriptorSetLayout layout,
|
||||||
u32 pushConstantSize);
|
u32 pushConstantSize);
|
||||||
|
|
||||||
// 多 set layout 的新接口
|
// 多 set layout 的新接口。
|
||||||
|
// push constant range 按程序级 vertex|fragment 声明(与
|
||||||
|
// ShaderManager 的 shader EXT 声明一致,VUID-08878);
|
||||||
|
// push 调用点必须带同样的双 stage(VUID-01796)
|
||||||
vk::PipelineLayout CreatePipelineLayout(
|
vk::PipelineLayout CreatePipelineLayout(
|
||||||
const std::vector<vk::DescriptorSetLayout>& layouts,
|
const std::vector<vk::DescriptorSetLayout>& layouts,
|
||||||
u32 pushConstantSize);
|
u32 pushConstantSize);
|
||||||
|
|||||||
@@ -11,6 +11,8 @@
|
|||||||
#include <Engine/Vulkan/Device.h>
|
#include <Engine/Vulkan/Device.h>
|
||||||
#include <Engine/ImGui/ImGuiLayer.h>
|
#include <Engine/ImGui/ImGuiLayer.h>
|
||||||
|
|
||||||
|
#include <imgui.h>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
namespace Engine {
|
namespace Engine {
|
||||||
@@ -57,6 +59,12 @@ namespace Engine {
|
|||||||
|
|
||||||
m_ImGuiLayer = CreateScope<ImGuiLayer>(GetWindow(), *m_Renderer);
|
m_ImGuiLayer = CreateScope<ImGuiLayer>(GetWindow(), *m_Renderer);
|
||||||
m_Renderer->SetImGuiLayer(m_ImGuiLayer.get());
|
m_Renderer->SetImGuiLayer(m_ImGuiLayer.get());
|
||||||
|
|
||||||
|
// 输入隔离:ImGui 想要的输入不再透给游戏侧
|
||||||
|
//(事件回调与轮询接口在 Window 层统一门控)
|
||||||
|
GetWindow().SetInputCapture(
|
||||||
|
[] { return ImGui::GetIO().WantCaptureKeyboard; },
|
||||||
|
[] { return ImGui::GetIO().WantCaptureMouse; });
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::Run()
|
void Application::Run()
|
||||||
|
|||||||
@@ -29,6 +29,9 @@ namespace Engine
|
|||||||
auto* self = static_cast<Window*>(glfwGetWindowUserPointer(window));
|
auto* self = static_cast<Window*>(glfwGetWindowUserPointer(window));
|
||||||
if (!self->m_EventCallback || key == GLFW_KEY_UNKNOWN) return;
|
if (!self->m_EventCallback || key == GLFW_KEY_UNKNOWN) return;
|
||||||
|
|
||||||
|
// UI 捕获键盘时,游戏侧不收键盘事件(ImGui 后端链式回调自行处理)
|
||||||
|
if (self->m_KeyboardCapture && self->m_KeyboardCapture()) return;
|
||||||
|
|
||||||
Event e;
|
Event e;
|
||||||
e.key = key;
|
e.key = key;
|
||||||
e.type = (action == GLFW_PRESS) ? EventType::KeyPress
|
e.type = (action == GLFW_PRESS) ? EventType::KeyPress
|
||||||
@@ -41,6 +44,7 @@ namespace Engine
|
|||||||
{
|
{
|
||||||
auto* self = static_cast<Window*>(glfwGetWindowUserPointer(window));
|
auto* self = static_cast<Window*>(glfwGetWindowUserPointer(window));
|
||||||
if (!self->m_EventCallback) return;
|
if (!self->m_EventCallback) return;
|
||||||
|
if (self->m_MouseCapture && self->m_MouseCapture()) return;
|
||||||
|
|
||||||
Event e;
|
Event e;
|
||||||
e.button = button;
|
e.button = button;
|
||||||
@@ -53,6 +57,7 @@ namespace Engine
|
|||||||
{
|
{
|
||||||
auto* self = static_cast<Window*>(glfwGetWindowUserPointer(window));
|
auto* self = static_cast<Window*>(glfwGetWindowUserPointer(window));
|
||||||
if (!self->m_EventCallback) return;
|
if (!self->m_EventCallback) return;
|
||||||
|
if (self->m_MouseCapture && self->m_MouseCapture()) return;
|
||||||
|
|
||||||
Event e;
|
Event e;
|
||||||
e.type = EventType::MouseMove;
|
e.type = EventType::MouseMove;
|
||||||
@@ -65,6 +70,7 @@ namespace Engine
|
|||||||
{
|
{
|
||||||
auto* self = static_cast<Window*>(glfwGetWindowUserPointer(window));
|
auto* self = static_cast<Window*>(glfwGetWindowUserPointer(window));
|
||||||
if (!self->m_EventCallback) return;
|
if (!self->m_EventCallback) return;
|
||||||
|
if (self->m_MouseCapture && self->m_MouseCapture()) return;
|
||||||
|
|
||||||
Event e;
|
Event e;
|
||||||
e.type = EventType::MouseScroll;
|
e.type = EventType::MouseScroll;
|
||||||
@@ -165,6 +171,8 @@ namespace Engine
|
|||||||
|
|
||||||
bool Window::IsKeyPressed(int key) const
|
bool Window::IsKeyPressed(int key) const
|
||||||
{
|
{
|
||||||
|
// UI 捕获键盘时,轮询接口同样静默(相机等轮询型消费方)
|
||||||
|
if (m_KeyboardCapture && m_KeyboardCapture()) return false;
|
||||||
return glfwGetKey(m_Window, key) == GLFW_PRESS;
|
return glfwGetKey(m_Window, key) == GLFW_PRESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,5 +199,11 @@ namespace Engine
|
|||||||
|
|
||||||
lastX = mx;
|
lastX = mx;
|
||||||
lastY = my;
|
lastY = my;
|
||||||
|
|
||||||
|
// UI 捕获鼠标时 delta 归零;lastX/Y 照常更新,避免释放后跳变
|
||||||
|
if (m_MouseCapture && m_MouseCapture())
|
||||||
|
{
|
||||||
|
dx = dy = 0.0f;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} // namespace Engine
|
} // namespace Engine
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <set>
|
#include <set>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
namespace Engine::RG {
|
namespace Engine::RG {
|
||||||
|
|
||||||
@@ -164,6 +165,22 @@ namespace Engine::RG {
|
|||||||
|
|
||||||
for (auto& entry : m_Textures) ResolveUsage(entry);
|
for (auto& entry : m_Textures) ResolveUsage(entry);
|
||||||
|
|
||||||
|
// 每 pass 渲染尺寸 = 其写入附件的最大分辨率(半分辨率后处理的
|
||||||
|
// viewport/渲染区域由此决定);无写入(如 Present)回退目标尺寸
|
||||||
|
for (auto& pass : m_Passes) {
|
||||||
|
u32 w = 0, h = 0;
|
||||||
|
for (const auto& use : pass.uses) {
|
||||||
|
if (!IsWriteUse(use.use)) continue;
|
||||||
|
const auto& d = m_Textures[use.resIndex].desc;
|
||||||
|
w = std::max(w, d.width ? d.width
|
||||||
|
: std::max(1u, u32(m_TargetExtent.width * d.sizeScale)));
|
||||||
|
h = std::max(h, d.height ? d.height
|
||||||
|
: std::max(1u, u32(m_TargetExtent.height * d.sizeScale)));
|
||||||
|
}
|
||||||
|
pass.extent = vk::Extent2D(w ? w : m_TargetExtent.width,
|
||||||
|
h ? h : m_TargetExtent.height);
|
||||||
|
}
|
||||||
|
|
||||||
CullPasses();
|
CullPasses();
|
||||||
Toposort();
|
Toposort();
|
||||||
ComputeLifetimes();
|
ComputeLifetimes();
|
||||||
@@ -272,34 +289,43 @@ namespace Engine::RG {
|
|||||||
|
|
||||||
void RenderGraph::Toposort()
|
void RenderGraph::Toposort()
|
||||||
{
|
{
|
||||||
// writer → reader 建边(去重),Kahn 拓扑排序。
|
// 按资源在声明序上的使用序列建边,Kahn 拓扑排序。
|
||||||
// 声明顺序通常已是拓扑序,但引擎级实现必须显式校验,
|
// RAW:reader → 依赖声明序上它的上一个 writer
|
||||||
// 避免上层重构 pass 顺序时引入环。
|
// WAR:writer → 依赖上一次 write 之后出现过的所有 reader
|
||||||
|
// WAW:writer → 上一个 writer
|
||||||
|
// 多写者(ping-pong 资源)合法:若对 reader 连接"所有"
|
||||||
|
// writer(含声明序在其后的),BlurH 会同时收到 Extract→BlurH 与
|
||||||
|
// BlurV→BlurH,而 BlurH→BlurV 又存在,直接成环。
|
||||||
|
// 声明顺序通常已是拓扑序,但引擎级实现必须显式校验。
|
||||||
|
|
||||||
std::vector<std::vector<u32>> writers(m_Textures.size());
|
const u32 N = static_cast<u32>(m_Passes.size());
|
||||||
for (u32 i = 0; i < m_Passes.size(); ++i) {
|
std::vector<std::set<u32>> adj(N);
|
||||||
|
std::vector<u32> indegree(N, 0);
|
||||||
|
|
||||||
|
auto addEdge = [&](u32 from, u32 to) {
|
||||||
|
if (from == to) return;
|
||||||
|
if (adj[from].insert(to).second) ++indegree[to];
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr u32 kNoWriter = UINT32_MAX;
|
||||||
|
std::vector<u32> lastWriter(m_Textures.size(), kNoWriter);
|
||||||
|
std::vector<std::set<u32>> pendingReaders(m_Textures.size());
|
||||||
|
|
||||||
|
for (u32 i = 0; i < N; ++i) {
|
||||||
if (m_Passes[i].culled) continue;
|
if (m_Passes[i].culled) continue;
|
||||||
|
|
||||||
for (const auto& use : m_Passes[i].uses) {
|
for (const auto& use : m_Passes[i].uses) {
|
||||||
|
auto& lw = lastWriter[use.resIndex];
|
||||||
|
auto& readers = pendingReaders[use.resIndex];
|
||||||
|
|
||||||
if (IsWriteUse(use.use)) {
|
if (IsWriteUse(use.use)) {
|
||||||
writers[use.resIndex].push_back(i);
|
for (u32 r : readers) addEdge(r, i); // WAR
|
||||||
}
|
readers.clear();
|
||||||
}
|
if (lw != kNoWriter) addEdge(lw, i); // WAW
|
||||||
}
|
lw = i;
|
||||||
|
} else {
|
||||||
std::vector<std::set<u32>> adj(m_Passes.size());
|
if (lw != kNoWriter) addEdge(lw, i); // RAW
|
||||||
std::vector<u32> indegree(m_Passes.size(), 0);
|
readers.insert(i);
|
||||||
|
|
||||||
for (u32 q = 0; q < m_Passes.size(); ++q) {
|
|
||||||
if (m_Passes[q].culled) continue;
|
|
||||||
std::set<u32> seenWriters;
|
|
||||||
for (const auto& use : m_Passes[q].uses) {
|
|
||||||
if (IsWriteUse(use.use)) continue;
|
|
||||||
for (u32 p : writers[use.resIndex]) {
|
|
||||||
if (p == q || m_Passes[p].culled) continue;
|
|
||||||
if (seenWriters.insert(p).second) {
|
|
||||||
adj[p].insert(q);
|
|
||||||
++indegree[q];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -309,7 +335,7 @@ namespace Engine::RG {
|
|||||||
|
|
||||||
// 同层按声明序出队,保证确定性
|
// 同层按声明序出队,保证确定性
|
||||||
std::set<u32> ready;
|
std::set<u32> ready;
|
||||||
for (u32 i = 0; i < m_Passes.size(); ++i) {
|
for (u32 i = 0; i < N; ++i) {
|
||||||
if (!m_Passes[i].culled && indegree[i] == 0) ready.insert(i);
|
if (!m_Passes[i].culled && indegree[i] == 0) ready.insert(i);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -458,9 +484,14 @@ namespace Engine::RG {
|
|||||||
for (auto& entry : m_Textures) {
|
for (auto& entry : m_Textures) {
|
||||||
if (entry.imported || entry.firstPass < 0) continue;
|
if (entry.imported || entry.firstPass < 0) continue;
|
||||||
|
|
||||||
|
// 显式尺寸优先;否则 = 目标尺寸 × sizeScale(后处理降采样)
|
||||||
vk::Extent2D extent{
|
vk::Extent2D extent{
|
||||||
entry.desc.width ? entry.desc.width : m_TargetExtent.width,
|
entry.desc.width
|
||||||
entry.desc.height ? entry.desc.height : m_TargetExtent.height
|
? entry.desc.width
|
||||||
|
: std::max(1u, u32(m_TargetExtent.width * entry.desc.sizeScale)),
|
||||||
|
entry.desc.height
|
||||||
|
? entry.desc.height
|
||||||
|
: std::max(1u, u32(m_TargetExtent.height * entry.desc.sizeScale))
|
||||||
};
|
};
|
||||||
|
|
||||||
for (u32 slot = 0; slot < m_FramesInFlight; ++slot) {
|
for (u32 slot = 0; slot < m_FramesInFlight; ++slot) {
|
||||||
@@ -567,20 +598,55 @@ namespace Engine::RG {
|
|||||||
const auto& entry = GetEntry(h);
|
const auto& entry = GetEntry(h);
|
||||||
if (entry.imported) return entry.importedView;
|
if (entry.imported) return entry.importedView;
|
||||||
if (frameSlot >= entry.physical.size() || !entry.physical[frameSlot]) {
|
if (frameSlot >= entry.physical.size() || !entry.physical[frameSlot]) {
|
||||||
ENGINE_FATAL("RenderGraph: no physical image for '{}' slot {} "
|
// ENGINE_FATAL 只记日志不中止,这里必须 throw 才能拦住空指针
|
||||||
"(compile first?)", entry.name, frameSlot);
|
//(Application::Run 的 catch 会接住并干净退出)
|
||||||
|
throw std::runtime_error(
|
||||||
|
"RenderGraph: no physical image for '" + entry.name +
|
||||||
|
"' slot " + std::to_string(frameSlot));
|
||||||
}
|
}
|
||||||
return entry.physical[frameSlot]->view;
|
return entry.physical[frameSlot]->view;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void RenderGraph::SetQueryPool(vk::QueryPool pool, u32 queriesPerFrame)
|
||||||
|
{
|
||||||
|
m_QueryPool = pool;
|
||||||
|
m_QueriesPerFrame = queriesPerFrame;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::string> RenderGraph::GetPassNames() const
|
||||||
|
{
|
||||||
|
std::vector<std::string> names;
|
||||||
|
names.reserve(m_ExecutionOrder.size());
|
||||||
|
for (const u32 passIdx : m_ExecutionOrder) {
|
||||||
|
names.push_back(m_Passes[passIdx].name);
|
||||||
|
}
|
||||||
|
return names;
|
||||||
|
}
|
||||||
|
|
||||||
// ---------- 执行 ----------
|
// ---------- 执行 ----------
|
||||||
|
|
||||||
void RenderGraph::Execute(const FrameContext& frame)
|
void RenderGraph::Execute(const FrameContext& frame)
|
||||||
{
|
{
|
||||||
|
// GPU timestamp:可选。池由 Renderer 创建注入;每帧开头重置本槽位
|
||||||
|
// 区间(上一轮结果已在 fence 等待后被读取),每个 pass 前后各一枚
|
||||||
|
// 时间戳(BOTTOM_OF_PIPE),索引按执行序排布
|
||||||
|
u32 tsPos = 0;
|
||||||
|
if (m_QueryPool) {
|
||||||
|
frame.cmd.resetQueryPool(m_QueryPool,
|
||||||
|
frame.frameSlot * m_QueriesPerFrame,
|
||||||
|
m_QueriesPerFrame);
|
||||||
|
}
|
||||||
|
|
||||||
for (const u32 passIdx : m_ExecutionOrder) {
|
for (const u32 passIdx : m_ExecutionOrder) {
|
||||||
auto& pass = m_Passes[passIdx];
|
auto& pass = m_Passes[passIdx];
|
||||||
if (pass.culled) continue;
|
if (pass.culled) continue;
|
||||||
|
|
||||||
|
if (m_QueryPool) {
|
||||||
|
frame.cmd.writeTimestamp2(
|
||||||
|
vk::PipelineStageFlagBits2::eBottomOfPipe,
|
||||||
|
m_QueryPool, frame.frameSlot * m_QueriesPerFrame + tsPos * 2);
|
||||||
|
}
|
||||||
|
|
||||||
// barrier:按槽位状态挑选变体,批量提交
|
// barrier:按槽位状态挑选变体,批量提交
|
||||||
m_ScratchBarriers.clear();
|
m_ScratchBarriers.clear();
|
||||||
for (const auto& bp : pass.barriers) {
|
for (const auto& bp : pass.barriers) {
|
||||||
@@ -614,10 +680,17 @@ namespace Engine::RG {
|
|||||||
RenderGraphContext ctx;
|
RenderGraphContext ctx;
|
||||||
ctx.cmd = frame.cmd;
|
ctx.cmd = frame.cmd;
|
||||||
ctx.frame = frame;
|
ctx.frame = frame;
|
||||||
ctx.extent = m_TargetExtent;
|
ctx.extent = pass.extent;
|
||||||
ctx.graph = this;
|
ctx.graph = this;
|
||||||
pass.exec(ctx);
|
pass.exec(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (m_QueryPool) {
|
||||||
|
frame.cmd.writeTimestamp2(
|
||||||
|
vk::PipelineStageFlagBits2::eBottomOfPipe,
|
||||||
|
m_QueryPool, frame.frameSlot * m_QueriesPerFrame + tsPos * 2 + 1);
|
||||||
|
}
|
||||||
|
++tsPos;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -56,6 +56,16 @@ namespace Engine
|
|||||||
constexpr u32 kShadowMapSize = 2048;
|
constexpr u32 kShadowMapSize = 2048;
|
||||||
constexpr u32 kShadowPushSize = static_cast<u32>(2 * sizeof(glm::mat4));
|
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 自适应)
|
// 光源正交包围盒(覆盖当前场景;后续按场景 AABB 自适应)
|
||||||
constexpr f32 kLightOrthoExtent = 15.0f;
|
constexpr f32 kLightOrthoExtent = 15.0f;
|
||||||
constexpr f32 kLightDistance = 30.0f;
|
constexpr f32 kLightDistance = 30.0f;
|
||||||
@@ -147,6 +157,60 @@ namespace Engine
|
|||||||
.setCompareEnable(VK_FALSE);
|
.setCompareEnable(VK_FALSE);
|
||||||
m_ShadowSampler = m_Device->GetDevice().createSampler(shadowSamplerInfo);
|
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 创建)
|
// 8. Swapchain(只管呈现;深度图由 RenderGraph 创建)
|
||||||
m_Swapchain = CreateScope<Swapchain>(*m_Device, *m_Allocator, m_Window);
|
m_Swapchain = CreateScope<Swapchain>(*m_Device, *m_Allocator, m_Window);
|
||||||
m_Extent = m_Swapchain->GetExtent();
|
m_Extent = m_Swapchain->GetExtent();
|
||||||
@@ -162,6 +226,10 @@ namespace Engine
|
|||||||
|
|
||||||
m_UBODescriptorSets.resize(kMaxFramesInFlight);
|
m_UBODescriptorSets.resize(kMaxFramesInFlight);
|
||||||
m_ShadowDescriptorSets.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) {
|
for (u32 slot = 0; slot < kMaxFramesInFlight; ++slot) {
|
||||||
m_UBODescriptorSets[slot] =
|
m_UBODescriptorSets[slot] =
|
||||||
m_DescriptorManager->AllocateDescriptorSet(m_UBOSetLayout);
|
m_DescriptorManager->AllocateDescriptorSet(m_UBOSetLayout);
|
||||||
@@ -171,13 +239,37 @@ namespace Engine
|
|||||||
|
|
||||||
m_ShadowDescriptorSets[slot] =
|
m_ShadowDescriptorSets[slot] =
|
||||||
m_DescriptorManager->AllocateDescriptorSet(m_ShadowSetLayout);
|
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&) 时按帧绑定)
|
// 11. RenderGraph:声明 + 编译(场景在 Render(Scene&) 时按帧绑定)
|
||||||
BuildGraph();
|
BuildGraph();
|
||||||
|
|
||||||
// Compile 已分配物理镜像:把各槽位的阴影图 view 绑进描述符
|
// Compile 已分配物理镜像:把各槽位的阴影图/HDR view 绑进描述符
|
||||||
UpdateShadowDescriptors();
|
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");
|
ENGINE_INFO("Renderer initialized");
|
||||||
}
|
}
|
||||||
@@ -200,13 +292,31 @@ namespace Engine
|
|||||||
if (m_ShadowFragShader) {
|
if (m_ShadowFragShader) {
|
||||||
vkDevice.destroyShaderEXT(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_ShadowSampler);
|
||||||
|
vkDevice.destroySampler(m_TonemapSampler);
|
||||||
|
if (m_QueryPool) {
|
||||||
|
vkDevice.destroyQueryPool(m_QueryPool);
|
||||||
|
}
|
||||||
vkDevice.destroyPipelineLayout(m_PipelineLayout);
|
vkDevice.destroyPipelineLayout(m_PipelineLayout);
|
||||||
vkDevice.destroyPipelineLayout(m_ShadowPipelineLayout);
|
vkDevice.destroyPipelineLayout(m_ShadowPipelineLayout);
|
||||||
|
vkDevice.destroyPipelineLayout(m_TonemapPipelineLayout);
|
||||||
vkDevice.destroyDescriptorSetLayout(m_UBOSetLayout);
|
vkDevice.destroyDescriptorSetLayout(m_UBOSetLayout);
|
||||||
vkDevice.destroyDescriptorSetLayout(m_TextureSetLayout);
|
vkDevice.destroyDescriptorSetLayout(m_TextureSetLayout);
|
||||||
vkDevice.destroyDescriptorSetLayout(m_ShadowSetLayout);
|
vkDevice.destroyDescriptorSetLayout(m_ShadowSetLayout);
|
||||||
|
vkDevice.destroyDescriptorSetLayout(m_TonemapSetLayout);
|
||||||
}
|
}
|
||||||
|
|
||||||
vk::Format Renderer::GetSwapchainFormat() const
|
vk::Format Renderer::GetSwapchainFormat() const
|
||||||
@@ -243,6 +353,21 @@ namespace Engine
|
|||||||
"ShadowMap",
|
"ShadowMap",
|
||||||
RG::TextureDesc{vk::Format::eD32Sfloat, kShadowMapSize, kShadowMapSize});
|
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:光源视角写深度(所有渲染体都写入,不做实体级开关——
|
// ---- Shadow pass:光源视角写深度(所有渲染体都写入,不做实体级开关——
|
||||||
// castShadow 应由材质/实体数据驱动剔除,而非 pass 逻辑特判)----
|
// castShadow 应由材质/实体数据驱动剔除,而非 pass 逻辑特判)----
|
||||||
m_RenderGraph->AddPass("Shadow",
|
m_RenderGraph->AddPass("Shadow",
|
||||||
@@ -321,8 +446,10 @@ namespace Engine
|
|||||||
if (!model) return;
|
if (!model) return;
|
||||||
|
|
||||||
const ShadowPush push{m_LightSpace, transform.world};
|
const ShadowPush push{m_LightSpace, transform.world};
|
||||||
|
// stage 必须覆盖 layout range 声明的全部 stage(VUID-01796)
|
||||||
cmd.pushConstants(m_ShadowPipelineLayout,
|
cmd.pushConstants(m_ShadowPipelineLayout,
|
||||||
vk::ShaderStageFlagBits::eVertex,
|
vk::ShaderStageFlagBits::eVertex |
|
||||||
|
vk::ShaderStageFlagBits::eFragment,
|
||||||
0, kShadowPushSize, &push);
|
0, kShadowPushSize, &push);
|
||||||
|
|
||||||
for (auto& mesh : model->GetMeshes()) {
|
for (auto& mesh : model->GetMeshes()) {
|
||||||
@@ -345,7 +472,7 @@ namespace Engine
|
|||||||
std::array<float, 4>{0.1f, 0.1f, 0.1f, 1.0f});
|
std::array<float, 4>{0.1f, 0.1f, 0.1f, 1.0f});
|
||||||
|
|
||||||
vk::RenderingAttachmentInfo colorAttachment;
|
vk::RenderingAttachmentInfo colorAttachment;
|
||||||
colorAttachment.setImageView(ctx.View(m_Backbuffer))
|
colorAttachment.setImageView(ctx.View(m_HDRColor))
|
||||||
.setImageLayout(vk::ImageLayout::eColorAttachmentOptimal)
|
.setImageLayout(vk::ImageLayout::eColorAttachmentOptimal)
|
||||||
.setLoadOp(vk::AttachmentLoadOp::eClear)
|
.setLoadOp(vk::AttachmentLoadOp::eClear)
|
||||||
.setStoreOp(vk::AttachmentStoreOp::eStore)
|
.setStoreOp(vk::AttachmentStoreOp::eStore)
|
||||||
@@ -447,8 +574,10 @@ namespace Engine
|
|||||||
Model* model = m_BoundAssets->GetModel(meshRenderer.model);
|
Model* model = m_BoundAssets->GetModel(meshRenderer.model);
|
||||||
if (!model) return;
|
if (!model) return;
|
||||||
|
|
||||||
|
// stage 必须覆盖 layout range 声明的全部 stage(VUID-01796)
|
||||||
cmd.pushConstants(m_PipelineLayout,
|
cmd.pushConstants(m_PipelineLayout,
|
||||||
vk::ShaderStageFlagBits::eVertex,
|
vk::ShaderStageFlagBits::eVertex |
|
||||||
|
vk::ShaderStageFlagBits::eFragment,
|
||||||
0, sizeof(glm::mat4), &transform.world);
|
0, sizeof(glm::mat4), &transform.world);
|
||||||
|
|
||||||
for (auto& mesh : model->GetMeshes()) {
|
for (auto& mesh : model->GetMeshes()) {
|
||||||
@@ -461,11 +590,137 @@ namespace Engine
|
|||||||
|
|
||||||
cmd.endRendering();
|
cmd.endRendering();
|
||||||
})
|
})
|
||||||
.Write(m_Backbuffer, RG::ResourceUse::ColorAttachment)
|
.Write(m_HDRColor, RG::ResourceUse::ColorAttachment)
|
||||||
.Write(m_Depth, RG::ResourceUse::DepthAttachment)
|
.Write(m_Depth, RG::ResourceUse::DepthAttachment)
|
||||||
.Read(m_ShadowMap, RG::ResourceUse::ShaderRead,
|
.Read(m_ShadowMap, RG::ResourceUse::ShaderRead,
|
||||||
vk::ShaderStageFlagBits::eFragment);
|
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,不清屏)----
|
// ---- ImGui pass:叠加层(loadOp=Load,不清屏)----
|
||||||
// 框架只负责把 draw data 录进 cmd;UI 内容全部由派生类
|
// 框架只负责把 draw data 录进 cmd;UI 内容全部由派生类
|
||||||
// 在 OnRenderOverlay() 里构建,框架自身不画任何 ImGui 控件。
|
// 在 OnRenderOverlay() 里构建,框架自身不画任何 ImGui 控件。
|
||||||
@@ -520,8 +775,9 @@ namespace Engine
|
|||||||
// RG 重编译:物理镜像按新尺寸轮换
|
// RG 重编译:物理镜像按新尺寸轮换
|
||||||
m_RenderGraph->SetTargetExtent(m_Extent);
|
m_RenderGraph->SetTargetExtent(m_Extent);
|
||||||
|
|
||||||
// 重编译后物理镜像可能变化,阴影描述符重新绑定
|
// 重编译后物理镜像可能变化,描述符重新绑定
|
||||||
UpdateShadowDescriptors();
|
UpdateShadowDescriptors();
|
||||||
|
UpdateFullscreenDescriptors();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Renderer::UpdateShadowDescriptors()
|
void Renderer::UpdateShadowDescriptors()
|
||||||
@@ -535,6 +791,66 @@ namespace Engine
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
void Renderer::Render(ECS::Scene& scene, Assets::AssetManager& assets)
|
||||||
{
|
{
|
||||||
m_BoundScene = &scene;
|
m_BoundScene = &scene;
|
||||||
@@ -611,6 +927,7 @@ namespace Engine
|
|||||||
// 此时本槽位的 fence 尚未 reset——先等它(保证 GPU 已完成本槽位上一轮),
|
// 此时本槽位的 fence 尚未 reset——先等它(保证 GPU 已完成本槽位上一轮),
|
||||||
// 再写 UBO,写-读竞争关闭。
|
// 再写 UBO,写-读竞争关闭。
|
||||||
m_SyncManager->WaitFence(frameSlot);
|
m_SyncManager->WaitFence(frameSlot);
|
||||||
|
ReadGpuTimes(frameSlot); // fence 已等:上一轮该槽位的查询已就绪
|
||||||
m_UniformBuffer->Update(frameSlot, &ubo, sizeof(ubo));
|
m_UniformBuffer->Update(frameSlot, &ubo, sizeof(ubo));
|
||||||
m_SyncManager->ResetFence(frameSlot);
|
m_SyncManager->ResetFence(frameSlot);
|
||||||
|
|
||||||
|
|||||||
@@ -115,14 +115,19 @@ namespace Engine
|
|||||||
return m_Device.GetDevice().createPipelineLayout(info);
|
return m_Device.GetDevice().createPipelineLayout(info);
|
||||||
}
|
}
|
||||||
|
|
||||||
vk::PipelineLayout DescriptorManager::CreatePipelineLayout(const std::vector<vk::DescriptorSetLayout>& layouts,u32 pushConstantSize)
|
vk::PipelineLayout DescriptorManager::CreatePipelineLayout(const std::vector<vk::DescriptorSetLayout>& layouts,
|
||||||
|
u32 pushConstantSize)
|
||||||
{
|
{
|
||||||
vk::PipelineLayoutCreateInfo info;
|
vk::PipelineLayoutCreateInfo info;
|
||||||
info.setSetLayouts(layouts);
|
info.setSetLayouts(layouts);
|
||||||
|
|
||||||
std::array<vk::PushConstantRange, 1> pushRanges;
|
std::array<vk::PushConstantRange, 1> pushRanges;
|
||||||
if (pushConstantSize > 0) {
|
if (pushConstantSize > 0) {
|
||||||
pushRanges[0].setStageFlags(vk::ShaderStageFlagBits::eVertex)
|
// 与 ShaderManager 一致:程序级 vertex|fragment 声明。
|
||||||
|
// 注意 vkCmdPushConstants 的 stageFlags 必须覆盖 range 的
|
||||||
|
// 全部 stage(VUID-01796),因此 push 调用点也要带双 stage
|
||||||
|
pushRanges[0].setStageFlags(vk::ShaderStageFlagBits::eVertex |
|
||||||
|
vk::ShaderStageFlagBits::eFragment)
|
||||||
.setOffset(0)
|
.setOffset(0)
|
||||||
.setSize(pushConstantSize);
|
.setSize(pushConstantSize);
|
||||||
info.setPushConstantRanges(pushRanges);
|
info.setPushConstantRanges(pushRanges);
|
||||||
|
|||||||
@@ -57,7 +57,12 @@ ShaderObject ShaderManager::CreateFromSPIRV(const std::vector<u32>& spirv,
|
|||||||
|
|
||||||
std::array<vk::PushConstantRange, 1> pushRanges;
|
std::array<vk::PushConstantRange, 1> pushRanges;
|
||||||
if (pushConstantSize > 0) {
|
if (pushConstantSize > 0) {
|
||||||
pushRanges[0].setStageFlags(vk::ShaderStageFlagBits::eVertex)
|
// push range 是"程序级"声明:shader object 要求同一程序绑定的
|
||||||
|
// 全部 shader(vert+frag)push range 定义完全一致(VUID-08878),
|
||||||
|
// 因此统一按 vertex|fragment 声明;shader 实际读取哪些字节由
|
||||||
|
// GLSL 的 push_constant block 决定,声明范围只是能力上限
|
||||||
|
pushRanges[0].setStageFlags(vk::ShaderStageFlagBits::eVertex |
|
||||||
|
vk::ShaderStageFlagBits::eFragment)
|
||||||
.setOffset(0)
|
.setOffset(0)
|
||||||
.setSize(pushConstantSize);
|
.setSize(pushConstantSize);
|
||||||
createInfo.setPushConstantRanges(pushRanges);
|
createInfo.setPushConstantRanges(pushRanges);
|
||||||
|
|||||||
@@ -134,9 +134,13 @@ namespace Engine
|
|||||||
|
|
||||||
vk::SurfaceFormatKHR Swapchain::ChooseSurfaceFormat(const std::vector<vk::SurfaceFormatKHR>& formats)
|
vk::SurfaceFormatKHR Swapchain::ChooseSurfaceFormat(const std::vector<vk::SurfaceFormatKHR>& formats)
|
||||||
{
|
{
|
||||||
|
// 用 Unorm 而非 Srgb:当前管线(场景光照 + ImGui)都是把
|
||||||
|
// "显示空间"颜色直接写进 framebuffer,若用 Srgb 附件硬件会再
|
||||||
|
// 做一次 linear→sRGB 提亮 → 整体泛白(ImGui 原生蓝 #4296FA
|
||||||
|
// 会被洗成 #8CCBFC)。等上 HDR/tonemap 管线后再切回 Srgb。
|
||||||
for (const auto& f : formats)
|
for (const auto& f : formats)
|
||||||
{
|
{
|
||||||
if (f.format == vk::Format::eB8G8R8A8Srgb &&
|
if (f.format == vk::Format::eB8G8R8A8Unorm &&
|
||||||
f.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear)
|
f.colorSpace == vk::ColorSpaceKHR::eSrgbNonlinear)
|
||||||
{
|
{
|
||||||
return f;
|
return f;
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
#version 450
|
||||||
|
|
||||||
|
// Bloom 高斯模糊:可分离 9-tap,方向由 push 给出(H/V 各跑一次)。
|
||||||
|
// 源图为半分辨率,线性采样 + 权重对应的偏移天然抗锯齿。
|
||||||
|
|
||||||
|
layout(location = 0) in vec2 fragUV;
|
||||||
|
layout(location = 0) out vec4 outColor;
|
||||||
|
|
||||||
|
layout(set = 0, binding = 0) uniform sampler2D srcImage;
|
||||||
|
|
||||||
|
layout(push_constant) uniform PushConstants {
|
||||||
|
float exposure; // 未用(程序级统一布局)
|
||||||
|
float stepX; // param0: UV 步长 x(水平 pass = 1/width,垂直 = 0)
|
||||||
|
float stepY; // param1: UV 步长 y
|
||||||
|
float _pad0;
|
||||||
|
} pc;
|
||||||
|
|
||||||
|
const float weights[5] = float[](0.227027, 0.1945946, 0.1216216, 0.054054, 0.016216);
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec2 off = vec2(pc.stepX, pc.stepY);
|
||||||
|
|
||||||
|
vec3 c = texture(srcImage, fragUV).rgb * weights[0];
|
||||||
|
for (int i = 1; i < 5; ++i) {
|
||||||
|
c += texture(srcImage, fragUV + off * float(i)).rgb * weights[i];
|
||||||
|
c += texture(srcImage, fragUV - off * float(i)).rgb * weights[i];
|
||||||
|
}
|
||||||
|
outColor = vec4(c, 1.0);
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
#version 450
|
||||||
|
|
||||||
|
// Bloom 提取:从 HDR 颜色图中滤出超过阈值的部分(软阈值,保留超出比例)。
|
||||||
|
// 全屏 pass 通用 push(16B):param0 = 亮度阈值(HDR 域,1.0 = 仅高光溢出)。
|
||||||
|
|
||||||
|
layout(location = 0) in vec2 fragUV;
|
||||||
|
layout(location = 0) out vec4 outColor;
|
||||||
|
|
||||||
|
layout(set = 0, binding = 0) uniform sampler2D hdrImage;
|
||||||
|
|
||||||
|
layout(push_constant) uniform PushConstants {
|
||||||
|
float exposure; // 未用(程序级统一布局)
|
||||||
|
float threshold; // param0
|
||||||
|
float _pad0;
|
||||||
|
float _pad1;
|
||||||
|
} pc;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec3 c = texture(hdrImage, fragUV).rgb;
|
||||||
|
float l = max(c.r, max(c.g, c.b));
|
||||||
|
|
||||||
|
// 软阈值:只保留超出 threshold 的能量比例,避免硬切产生的块状边缘
|
||||||
|
float k = max(l - pc.threshold, 0.0) / max(l, 1e-4);
|
||||||
|
outColor = vec4(c * k, 1.0);
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
#version 450
|
||||||
|
|
||||||
|
// Tonemap pass:HDR 线性颜色 → 显示空间。
|
||||||
|
// 这是显示变换的显式建模(对齐 UE/Unity:HDR SceneColor → tonemapper → UI)。
|
||||||
|
// ACES = Narkowicz 拟合(线性域映射到 [0,1]),可经 push constant 关闭
|
||||||
|
// 退回纯 gamma(旧观感);bloom 等后处理在 HDR 域(Tonemap 之前)插 pass。
|
||||||
|
|
||||||
|
layout(location = 0) in vec2 fragUV;
|
||||||
|
layout(location = 0) out vec4 outColor;
|
||||||
|
|
||||||
|
layout(set = 0, binding = 0) uniform sampler2D hdrImage;
|
||||||
|
layout(set = 0, binding = 1) uniform sampler2D bloomImage;
|
||||||
|
|
||||||
|
// 全屏 pass 通用 push(16B,程序级统一声明,VUID-08878)
|
||||||
|
layout(push_constant) uniform PushConstants {
|
||||||
|
float exposure; // 曝光系数
|
||||||
|
float aces; // >0.5 启用 ACES 曲线,否则纯 gamma
|
||||||
|
float bloomStrength; // bloom 叠加强度(0 = 关闭)
|
||||||
|
float _pad0;
|
||||||
|
} pc;
|
||||||
|
|
||||||
|
vec3 acesFilm(vec3 x) {
|
||||||
|
return clamp((x * (2.51 * x + 0.03)) / (x * (2.43 * x + 0.59) + 0.14), 0.0, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
// bloom 在 HDR 域叠加(半分辨率模糊图线性采样上采样)
|
||||||
|
vec3 hdr = texture(hdrImage, fragUV).rgb
|
||||||
|
+ texture(bloomImage, fragUV).rgb * pc.bloomStrength;
|
||||||
|
|
||||||
|
hdr *= pc.exposure;
|
||||||
|
|
||||||
|
vec3 mapped = (pc.aces > 0.5)
|
||||||
|
? acesFilm(hdr)
|
||||||
|
: clamp(hdr, 0.0, 1.0);
|
||||||
|
|
||||||
|
outColor = vec4(pow(mapped, vec3(1.0 / 2.2)), 1.0);
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
#version 450
|
||||||
|
|
||||||
|
// 全屏三角形:无顶点缓冲,从 gl_VertexIndex 派生 UV。
|
||||||
|
// 0 → (-1,3)? 经典三角覆盖整个屏幕:
|
||||||
|
// idx0:(-1,-1) idx1:(3,-1) idx2:(-1,3),UV 相应 [0..2],
|
||||||
|
// 光栅化插值后屏幕内 UV 恰好落在 [0,1]。
|
||||||
|
layout(location = 0) out vec2 fragUV;
|
||||||
|
|
||||||
|
// 全屏 pass 通用 push(16B,与 tonemap.frag 及所有全屏 pass 一致,VUID-08878)
|
||||||
|
layout(push_constant) uniform PushConstants {
|
||||||
|
float exposure;
|
||||||
|
float aces;
|
||||||
|
float _pad0;
|
||||||
|
float _pad1;
|
||||||
|
} pc;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec2 p = vec2(
|
||||||
|
float((gl_VertexIndex << 1) & 2),
|
||||||
|
float(gl_VertexIndex & 2));
|
||||||
|
fragUV = p;
|
||||||
|
gl_Position = vec4(p * 2.0 - 1.0, 0.0, 1.0);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user