Compare commits
25
Commits
41e4cc85d5
...
d8c481ce48
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d8c481ce48 | ||
|
|
b79df3c60e | ||
|
|
b6982d426f | ||
|
|
7d137fa9b1 | ||
|
|
57ab8901a9 | ||
|
|
1bc44e261b | ||
|
|
019c17c7e7 | ||
|
|
0ed7b4c5b8 | ||
|
|
c54f60c5ea | ||
|
|
8253c31ad5 | ||
|
|
a0c92af5db | ||
|
|
0328d4aaeb | ||
|
|
27b7f8785c | ||
|
|
a29aed2803 | ||
|
|
c75832e73e | ||
|
|
0de8498350 | ||
|
|
6f10fd624d | ||
|
|
718a658f5d | ||
|
|
91880d88fe | ||
|
|
fa9082706b | ||
|
|
df4871a49b | ||
|
|
d8274e8bdc | ||
|
|
cef32ea1ba | ||
|
|
6a3fcac072 | ||
|
|
7fca721a24 |
+3
-1
@@ -5,4 +5,6 @@ out/
|
|||||||
build/
|
build/
|
||||||
|
|
||||||
# 目前只上传代码,暂不上传assets
|
# 目前只上传代码,暂不上传assets
|
||||||
assets/
|
assets/
|
||||||
|
# 本地文档暂不上传
|
||||||
|
Docs/
|
||||||
|
|||||||
@@ -17,3 +17,9 @@
|
|||||||
path = Lib/imgui
|
path = Lib/imgui
|
||||||
url = https://github.com/ocornut/imgui.git
|
url = https://github.com/ocornut/imgui.git
|
||||||
branch = docking
|
branch = docking
|
||||||
|
[submodule "Lib/VulkanMemoryAllocator"]
|
||||||
|
path = Lib/VulkanMemoryAllocator
|
||||||
|
url = https://github.com/GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git
|
||||||
|
[submodule "Lib/tracy"]
|
||||||
|
path = Lib/tracy
|
||||||
|
url = https://github.com/wolfpld/tracy.git
|
||||||
|
|||||||
+146
-52
@@ -24,7 +24,6 @@
|
|||||||
|
|
||||||
#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;
|
||||||
@@ -48,64 +47,73 @@ protected:
|
|||||||
EnableImGui();
|
EnableImGui();
|
||||||
|
|
||||||
// 模型资产(AssetManager 负责加载/缓存/生命周期,返回句柄)
|
// 模型资产(AssetManager 负责加载/缓存/生命周期,返回句柄)
|
||||||
auto backpack = GetAssets().LoadModel("assets/models/backpack/backpack.obj");
|
// SunTemple:FBX + DDS 压缩贴图(DdsLoader 验证场景)
|
||||||
|
const auto model = GetAssets().LoadModel("assets/models/viking_room/viking_room.obj");
|
||||||
|
const auto temple = GetAssets().LoadModel("assets/models/SunTemple/SunTemple.fbx");
|
||||||
|
|
||||||
|
m_Spinners.push_back(scene.CreateEntity("backpack"));
|
||||||
|
m_Spinners[0].AddComponent<MeshRendererComponent>(MeshRendererComponent{model});
|
||||||
|
|
||||||
// 地面:程序化纯色平面(半边长 15,中心 y=-2)
|
// 地面:程序化纯色平面(半边长 15,中心 y=-2)
|
||||||
auto ground = GetAssets().CreatePlane(15.0f, 140, 140, 145);
|
const auto ground = GetAssets().CreatePlane(15.0f, 140, 140, 145);
|
||||||
|
|
||||||
m_Ground = scene.CreateEntity("Ground");
|
m_Ground = scene.CreateEntity("Ground");
|
||||||
m_Ground.AddComponent<TransformComponent>()
|
m_Ground.GetComponent<TransformComponent>()
|
||||||
.SetPosition(glm::vec3(0.0f, -0.5f, 0.0f))
|
.SetPosition(glm::vec3(0.0f, -0.5f, 0.0f)); // dirty → 帧循环统一 Compose
|
||||||
.Compose();
|
|
||||||
m_Ground.AddComponent<MeshRendererComponent>(MeshRendererComponent{ground});
|
m_Ground.AddComponent<MeshRendererComponent>(MeshRendererComponent{ground});
|
||||||
|
|
||||||
// 相机实体(primary = 当前渲染视角)
|
// 相机实体(primary = 当前渲染视角;位姿存实体 Transform)
|
||||||
m_Camera = scene.CreateEntity("MainCamera");
|
m_Camera = scene.CreateEntity("MainCamera");
|
||||||
auto& camera = m_Camera.AddComponent<CameraComponent>();
|
auto& camera = m_Camera.AddComponent<CameraComponent>();
|
||||||
camera.primary = true;
|
camera.primary = true;
|
||||||
camera.camera.SetPosition(glm::vec3(0.0f, 0.0f, 7.0f));
|
m_Camera.GetComponent<TransformComponent>()
|
||||||
|
.SetPosition(glm::vec3(0.0f, 0.0f, 7.0f));
|
||||||
|
|
||||||
// 背包实体 × 3(悬浮在地面上方)
|
// 神庙实体 × 1( FBX 单位较大,缩放到场景尺度)
|
||||||
for (int i = 0; i < 3; ++i) {
|
{
|
||||||
auto e = scene.CreateEntity("Backpack" + std::to_string(i));
|
auto e = scene.CreateEntity("SunTemple");
|
||||||
|
e.GetComponent<TransformComponent>()
|
||||||
e.AddComponent<TransformComponent>()
|
.SetScale(glm::vec3(0.05f));
|
||||||
.SetPosition(glm::vec3((i - 1) * 1.5f, 0.0f, 0.0f))
|
e.AddComponent<MeshRendererComponent>(MeshRendererComponent{temple});
|
||||||
.SetScale(glm::vec3(0.5f))
|
|
||||||
.Compose();
|
|
||||||
|
|
||||||
e.AddComponent<MeshRendererComponent>(MeshRendererComponent{backpack});
|
|
||||||
|
|
||||||
m_Spinners.push_back(e);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 灯光实体(第一个灯 = 阴影光源)
|
// 灯光实体(第一个灯 = 阴影光源)
|
||||||
m_KeyLight = scene.CreateEntity("KeyLight");
|
m_KeyLight = scene.CreateEntity("KeyLight");
|
||||||
m_KeyLight.AddComponent<LightComponent>(
|
m_KeyLight.AddComponent<LightComponent>(
|
||||||
LightComponent{glm::vec3(-0.5f, -0.6f, -0.35f),
|
LightComponent{
|
||||||
glm::vec4(1.0f, 0.95f, 0.9f, 1.0f)});
|
glm::vec3(-0.5f, -0.6f, -0.35f),
|
||||||
|
glm::vec4(1.0f, 0.95f, 0.9f, 1.0f)
|
||||||
|
});
|
||||||
|
|
||||||
scene.CreateEntity("FillLight")
|
scene.CreateEntity("FillLight")
|
||||||
.AddComponent<LightComponent>(
|
.AddComponent<LightComponent>(
|
||||||
LightComponent{glm::vec3(0.5f, -0.5f, 0.3f),
|
LightComponent{
|
||||||
glm::vec4(0.3f, 0.5f, 1.0f, 0.8f)});
|
glm::vec3(0.5f, -0.5f, 0.3f),
|
||||||
|
glm::vec4(0.3f, 0.5f, 1.0f, 0.8f)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void OnUpdate(f32 deltaTime) override
|
void OnUpdate(f32 deltaTime) override
|
||||||
{
|
{
|
||||||
m_Time += deltaTime;
|
// 暂停时冻结动画时钟(帧历史验证:暂停后 Temporal Diff
|
||||||
|
// 视图应在 2 帧内整屏变黑;相机移动同样会被 diff 捕捉到)
|
||||||
|
if (!m_PauseAnim)
|
||||||
|
{
|
||||||
|
m_Time += deltaTime;
|
||||||
|
}
|
||||||
|
|
||||||
// 相机(WASD/Space/Shift 移动)
|
// 相机(WASD/Space/Shift 移动;位姿读写实体 Transform)
|
||||||
m_Camera.GetComponent<CameraComponent>()
|
m_Camera.GetComponent<CameraComponent>()
|
||||||
.camera.Update(GetWindow(), deltaTime);
|
.camera.Update(GetWindow(),
|
||||||
|
m_Camera.GetComponent<TransformComponent>(),
|
||||||
|
deltaTime);
|
||||||
|
|
||||||
// 旋转动画:编辑 Transform 分量后 Compose 重算 world
|
// 旋转动画:编辑 Transform 分量后 Compose 重算 world
|
||||||
for (size_t i = 0; i < m_Spinners.size(); ++i) {
|
for (size_t i = 0; i < m_Spinners.size(); ++i)
|
||||||
|
{
|
||||||
auto& t = m_Spinners[i].GetComponent<TransformComponent>();
|
auto& t = m_Spinners[i].GetComponent<TransformComponent>();
|
||||||
t.position = glm::vec3((static_cast<f32>(i) - 1.0f) * 1.5f, 0.0f, 0.0f);
|
t.SetRotation(glm::vec3(0.0f, m_Time * 60.0f + static_cast<f32>(i) * 40.0f, 0.0f));
|
||||||
t.rotation.y = m_Time * 60.0f + static_cast<f32>(i) * 40.0f;
|
t.SetScale(glm::vec3(0.5f));
|
||||||
t.scale = glm::vec3(0.5f);
|
|
||||||
t.Compose();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,18 +123,18 @@ protected:
|
|||||||
|
|
||||||
void OnRenderOverlay() override
|
void OnRenderOverlay() override
|
||||||
{
|
{
|
||||||
if (!m_OverlayVisible) return;
|
|
||||||
|
|
||||||
ImGui::ShowDemoWindow();
|
ImGui::ShowDemoWindow();
|
||||||
|
|
||||||
ImGui::Begin("Scene Debug");
|
ImGui::Begin("Scene Debug");
|
||||||
|
|
||||||
// 阴影光源方向:拖动滑条实时看影子变化
|
// 阴影光源方向:拖动滑条实时看影子变化
|
||||||
if (auto* light = m_KeyLight.TryGetComponent<LightComponent>()) {
|
if (auto* light = m_KeyLight.TryGetComponent<LightComponent>())
|
||||||
|
{
|
||||||
ImGui::Text("Key Light (shadow caster)");
|
ImGui::Text("Key Light (shadow caster)");
|
||||||
ImGui::SliderFloat3("direction",
|
ImGui::SliderFloat3("direction",
|
||||||
&light->direction.x, -1.0f, 1.0f);
|
&light->direction.x, -1.0f, 1.0f);
|
||||||
if (ImGui::Button("reset")) {
|
if (ImGui::Button("reset"))
|
||||||
|
{
|
||||||
light->direction = glm::vec3(-0.5f, -0.6f, -0.35f);
|
light->direction = glm::vec3(-0.5f, -0.6f, -0.35f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -136,76 +144,162 @@ protected:
|
|||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
ImGui::Text("Tonemap");
|
ImGui::Text("Tonemap");
|
||||||
f32 exposure = renderer.GetExposure();
|
f32 exposure = renderer.GetExposure();
|
||||||
if (ImGui::SliderFloat("exposure", &exposure, 0.1f, 4.0f)) {
|
if (ImGui::SliderFloat("exposure", &exposure, 0.1f, 4.0f))
|
||||||
|
{
|
||||||
renderer.SetExposure(exposure);
|
renderer.SetExposure(exposure);
|
||||||
}
|
}
|
||||||
bool aces = renderer.GetUseACES();
|
bool aces = renderer.GetUseACES();
|
||||||
if (ImGui::Checkbox("ACES", &aces)) {
|
if (ImGui::Checkbox("ACES", &aces))
|
||||||
|
{
|
||||||
renderer.SetUseACES(aces);
|
renderer.SetUseACES(aces);
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::Text("Bloom");
|
ImGui::Text("Bloom");
|
||||||
f32 threshold = renderer.GetBloomThreshold();
|
f32 threshold = renderer.GetBloomThreshold();
|
||||||
if (ImGui::SliderFloat("threshold", &threshold, 0.0f, 2.0f)) {
|
if (ImGui::SliderFloat("threshold", &threshold, 0.0f, 2.0f))
|
||||||
|
{
|
||||||
renderer.SetBloomThreshold(threshold);
|
renderer.SetBloomThreshold(threshold);
|
||||||
}
|
}
|
||||||
f32 strength = renderer.GetBloomStrength();
|
f32 strength = renderer.GetBloomStrength();
|
||||||
if (ImGui::SliderFloat("strength", &strength, 0.0f, 2.0f)) {
|
if (ImGui::SliderFloat("strength", &strength, 0.0f, 2.0f))
|
||||||
|
{
|
||||||
renderer.SetBloomStrength(strength);
|
renderer.SetBloomStrength(strength);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 调试视图:帧历史差异(静止=黑,运动边缘发光=链路正确)
|
||||||
|
bool temporalDiff = renderer.GetShowTemporalDiff();
|
||||||
|
if (ImGui::Checkbox("Temporal Diff View", &temporalDiff))
|
||||||
|
{
|
||||||
|
renderer.SetShowTemporalDiff(temporalDiff);
|
||||||
|
}
|
||||||
|
ImGui::Checkbox("Pause Animation", &m_PauseAnim);
|
||||||
|
|
||||||
// Pass 级 GPU 耗时(RG timestamp)
|
// Pass 级 GPU 耗时(RG timestamp)
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
ImGui::Text("GPU Timings");
|
ImGui::Text("GPU Timings");
|
||||||
|
ImGui::Text("instances: %u main / %u shadow",
|
||||||
|
renderer.GetMainInstanceCount(),
|
||||||
|
renderer.GetShadowInstanceCount());
|
||||||
const auto& names = renderer.GetPassNames();
|
const auto& names = renderer.GetPassNames();
|
||||||
const auto& times = renderer.GetPassGpuMs();
|
const auto& times = renderer.GetPassGpuMs();
|
||||||
for (size_t i = 0; i < names.size() && i < times.size(); ++i) {
|
for (size_t i = 0; i < names.size() && i < times.size(); ++i)
|
||||||
|
{
|
||||||
ImGui::Text(" %-12s %.3f ms", names[i].c_str(), times[i]);
|
ImGui::Text(" %-12s %.3f ms", names[i].c_str(), times[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 帧率控制
|
||||||
|
auto& app = *this;
|
||||||
|
ImGui::Separator();
|
||||||
|
ImGui::Text("Frame");
|
||||||
|
bool vsync = app.GetVSync();
|
||||||
|
if (ImGui::Checkbox("VSync", &vsync))
|
||||||
|
{
|
||||||
|
app.SetVSync(vsync);
|
||||||
|
if (!vsync)
|
||||||
|
{
|
||||||
|
const f32 target = app.GetTargetFps();
|
||||||
|
app.SetTargetFps(target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!vsync)
|
||||||
|
{
|
||||||
|
f32 target = app.GetTargetFps();
|
||||||
|
if (ImGui::SliderFloat("target fps", &target, 24.0f, 360.0f, "%.0f"))
|
||||||
|
{
|
||||||
|
app.SetTargetFps(target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ImGui::Separator();
|
ImGui::Separator();
|
||||||
ImGui::Text("fps: %.1f frame: %llu",
|
ImGui::Text("fps: %.1f frame: %llu",
|
||||||
1.0f / GetDeltaTime(),
|
1.0f / GetDeltaTime(),
|
||||||
static_cast<unsigned long long>(GetFrameCount()));
|
static_cast<unsigned long long>(GetFrameCount()));
|
||||||
|
|
||||||
ImGui::End();
|
ImGui::End();
|
||||||
|
|
||||||
|
|
||||||
|
// 自定义重载事件: 重载关闭事件模态框
|
||||||
|
{
|
||||||
|
if (m_ShowQuitConfirm)
|
||||||
|
{
|
||||||
|
ImGui::OpenPopup("Should quit?");
|
||||||
|
m_ShowQuitConfirm = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::SetNextWindowPos(
|
||||||
|
ImGui::GetMainViewport()->GetCenter(),
|
||||||
|
ImGuiCond_Appearing, ImVec2(0.5f, 0.5f));
|
||||||
|
|
||||||
|
if (ImGui::BeginPopupModal("Should quit?", nullptr,
|
||||||
|
ImGuiWindowFlags_AlwaysAutoResize))
|
||||||
|
{
|
||||||
|
ImGui::TextUnformatted("Quit the application?");
|
||||||
|
ImGui::Separator();
|
||||||
|
if (ImGui::Button("Yes", ImVec2(120, 0)))
|
||||||
|
{
|
||||||
|
m_QuitConfirmed = true;
|
||||||
|
ImGui::CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
ImGui::SameLine();
|
||||||
|
if (ImGui::Button("No", ImVec2(120, 0)))
|
||||||
|
{
|
||||||
|
ImGui::CloseCurrentPopup();
|
||||||
|
}
|
||||||
|
ImGui::EndPopup();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_QuitConfirmed)
|
||||||
|
{
|
||||||
|
m_QuitConfirmed = false;
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnWindowClose(const Event& event) override
|
||||||
|
{
|
||||||
|
m_ShowQuitConfirm = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void OnKeyPress(const Event& event) override
|
void OnKeyPress(const Event& event) override
|
||||||
{
|
{
|
||||||
if (event.key == GLFW_KEY_GRAVE_ACCENT) {
|
if (event.key == GLFW_KEY_GRAVE_ACCENT)
|
||||||
m_OverlayVisible = !m_OverlayVisible; // ` 反折叠加层
|
{
|
||||||
|
SetImGuiEnabled(!GetImGuiEnabled());
|
||||||
}
|
}
|
||||||
if (event.key == GLFW_KEY_F1) {
|
if (event.key == GLFW_KEY_F1)
|
||||||
|
{
|
||||||
STATS.PrintComparison();
|
STATS.PrintComparison();
|
||||||
}
|
}
|
||||||
if (event.key == GLFW_KEY_ESCAPE) {
|
|
||||||
Close();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void OnWindowResize(const Event& event) override
|
void OnWindowResize(const Event& event) override
|
||||||
{
|
{
|
||||||
ENGINE_INFO("App: window resized to {}x{}", event.width, event.height);
|
ENGINE_INFO("App: window resized to {}x{}", event.width, event.height);
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
EntityHandle m_Camera{}; // Scene::CreateEntity 之前为空
|
bool m_ShowQuitConfirm = false;
|
||||||
|
bool m_QuitConfirmed = false;
|
||||||
|
EntityHandle m_Camera{}; // Scene::CreateEntity 之前为空
|
||||||
EntityHandle m_Ground{};
|
EntityHandle m_Ground{};
|
||||||
EntityHandle m_KeyLight{};
|
EntityHandle m_KeyLight{};
|
||||||
std::vector<EntityHandle> m_Spinners;
|
std::vector<EntityHandle> m_Spinners;
|
||||||
bool m_OverlayVisible = true;
|
|
||||||
f32 m_Time = 0.0f;
|
f32 m_Time = 0.0f;
|
||||||
|
bool m_PauseAnim = false; // 冻结动画时钟(帧历史 diff 验证用)
|
||||||
};
|
};
|
||||||
|
|
||||||
int main()
|
int main()
|
||||||
{
|
{
|
||||||
SandboxApp app;
|
SandboxApp app;
|
||||||
|
|
||||||
try {
|
try
|
||||||
|
{
|
||||||
app.Run();
|
app.Run();
|
||||||
}
|
}
|
||||||
catch (const std::exception& e) {
|
catch (const std::exception& e)
|
||||||
|
{
|
||||||
ENGINE_FATAL("Exception: {}", e.what());
|
ENGINE_FATAL("Exception: {}", e.what());
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ add_library(Engine STATIC ${ENGINE_SOURCES} ${IMGUI_SOURCES})
|
|||||||
target_include_directories(Engine PUBLIC
|
target_include_directories(Engine PUBLIC
|
||||||
${CMAKE_CURRENT_SOURCE_DIR}/include
|
${CMAKE_CURRENT_SOURCE_DIR}/include
|
||||||
${CMAKE_SOURCE_DIR}/Lib/entt/src
|
${CMAKE_SOURCE_DIR}/Lib/entt/src
|
||||||
|
${CMAKE_SOURCE_DIR}/Lib/VulkanMemoryAllocator/include
|
||||||
${IMGUI_DIR}
|
${IMGUI_DIR}
|
||||||
${IMGUI_DIR}/backends
|
${IMGUI_DIR}/backends
|
||||||
)
|
)
|
||||||
@@ -34,4 +35,13 @@ target_link_libraries(Engine PUBLIC
|
|||||||
assimp::assimp
|
assimp::assimp
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Tracy(CPU/GPU 帧分析):宏关闭时全部退化空操作,零开销
|
||||||
|
set(TRACY_DIR ${CMAKE_SOURCE_DIR}/Lib/tracy)
|
||||||
|
target_include_directories(Engine PUBLIC ${TRACY_DIR}/public)
|
||||||
|
target_sources(Engine PRIVATE ${TRACY_DIR}/public/TracyClient.cpp)
|
||||||
|
target_compile_definitions(Engine PUBLIC TRACY_ENABLE)
|
||||||
|
|
||||||
|
find_package(Threads REQUIRED)
|
||||||
|
target_link_libraries(Engine PUBLIC Threads::Threads)
|
||||||
|
|
||||||
target_compile_definitions(Engine PRIVATE VULKAN_HPP_DISPATCH_LOADER_DYNAMIC=1)
|
target_compile_definitions(Engine PRIVATE VULKAN_HPP_DISPATCH_LOADER_DYNAMIC=1)
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
//
|
|
||||||
// Created by atdunbg on 2026/9/22.
|
|
||||||
//
|
|
||||||
// AssetHandle:资产句柄 = (池内索引, 生成号),与 Entity/RG 句柄同一哲学。
|
|
||||||
//
|
|
||||||
// 组件、场景数据只存句柄;所有权归 AssetManager。
|
|
||||||
// 悬空句柄(generation 不匹配)会被 Get 显式拒绝,杜绝 use-after-free。
|
|
||||||
//
|
|
||||||
|
|
||||||
#ifndef HELLOVULKAN_ASSETHANDLE_H
|
|
||||||
#define HELLOVULKAN_ASSETHANDLE_H
|
|
||||||
|
|
||||||
#include <Engine/Core/Types.h>
|
|
||||||
|
|
||||||
// Model 前向声明(句柄不依赖完整类型)
|
|
||||||
namespace Engine { class Model; }
|
|
||||||
|
|
||||||
namespace Engine::Assets {
|
|
||||||
|
|
||||||
inline constexpr u32 kInvalidAssetIndex = 0xFFFFFFFF;
|
|
||||||
|
|
||||||
template<typename T>
|
|
||||||
struct AssetHandle {
|
|
||||||
u32 index = kInvalidAssetIndex;
|
|
||||||
u32 generation = 0;
|
|
||||||
|
|
||||||
bool Valid() const { return index != kInvalidAssetIndex; }
|
|
||||||
explicit operator bool() const { return Valid(); }
|
|
||||||
bool operator==(const AssetHandle& o) const = default;
|
|
||||||
};
|
|
||||||
|
|
||||||
// 常用别名
|
|
||||||
using ModelHandle = AssetHandle<Engine::Model>;
|
|
||||||
|
|
||||||
} // namespace Engine::Assets
|
|
||||||
|
|
||||||
#endif //HELLOVULKAN_ASSETHANDLE_H
|
|
||||||
@@ -3,14 +3,9 @@
|
|||||||
//
|
//
|
||||||
// AssetManager:引擎资产层。
|
// AssetManager:引擎资产层。
|
||||||
//
|
//
|
||||||
// 职责划分(对照 Renderer):
|
// 职责:路径 → 解析/加载 → 缓存去重 → 句柄分发 + 生命周期管理。
|
||||||
// - 资产层:路径 → 解析/加载 → 缓存去重 → 句柄分发 + 生命周期管理
|
// GPU 创建不在这里:经 IAssetUploader 接口(渲染层提供实现)完成,
|
||||||
// - 渲染层:拿已解析资产创建/绑定 GPU 资源
|
// 本类不感知 Vulkan——依赖方向 Assets → 接口,实现由组合根注入。
|
||||||
//
|
|
||||||
// Model 的构造仍需要 GPU 上下文(buffer/image upload),这是现阶段的
|
|
||||||
// 折中:资产层"使用"GPU 层的接口,但 Renderer 不再知道磁盘和缓存。
|
|
||||||
// 后续演进:把 Model 拆成 CPU 解析数据 + GPU 上传两段,实现真正的
|
|
||||||
// 异步加载(IO 线程解析 → 主线程/上传队列提交),本文件结构已为此留好位置。
|
|
||||||
//
|
//
|
||||||
// 组件存句柄不存指针的理由:
|
// 组件存句柄不存指针的理由:
|
||||||
// shared_ptr 会引入共享所有权 → 原子引用计数(swap-remove 拷贝时)
|
// shared_ptr 会引入共享所有权 → 原子引用计数(swap-remove 拷贝时)
|
||||||
@@ -23,7 +18,8 @@
|
|||||||
|
|
||||||
#include <Engine/Core/Types.h>
|
#include <Engine/Core/Types.h>
|
||||||
#include <Engine/Core/Log.h>
|
#include <Engine/Core/Log.h>
|
||||||
#include <Engine/Assets/AssetHandle.h>
|
#include <Engine/Core/AssetRef.h>
|
||||||
|
#include <Engine/Assets/IAssetUploader.h>
|
||||||
#include <Engine/Renderer/Model.h>
|
#include <Engine/Renderer/Model.h>
|
||||||
|
|
||||||
#include <functional>
|
#include <functional>
|
||||||
@@ -32,11 +28,6 @@
|
|||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace Engine {
|
namespace Engine {
|
||||||
|
|
||||||
class Device;
|
|
||||||
class MemoryAllocator;
|
|
||||||
class DescriptorManager;
|
|
||||||
|
|
||||||
namespace Assets {
|
namespace Assets {
|
||||||
|
|
||||||
// ---------- 通用资产缓存(不感知 Vulkan / 磁盘细节由 loader 决定) ----------
|
// ---------- 通用资产缓存(不感知 Vulkan / 磁盘细节由 loader 决定) ----------
|
||||||
@@ -48,7 +39,7 @@ namespace Engine {
|
|||||||
using LoaderFn = std::function<Scope<T>(const std::string&)>;
|
using LoaderFn = std::function<Scope<T>(const std::string&)>;
|
||||||
|
|
||||||
// 加载(带路径去重)。失败返回无效句柄。
|
// 加载(带路径去重)。失败返回无效句柄。
|
||||||
AssetHandle<T> Load(const std::string& path, const LoaderFn& loader)
|
AssetRef<T> Load(const std::string& path, const LoaderFn& loader)
|
||||||
{
|
{
|
||||||
if (auto it = m_ByPath.find(path); it != m_ByPath.end()) {
|
if (auto it = m_ByPath.find(path); it != m_ByPath.end()) {
|
||||||
return it->second;
|
return it->second;
|
||||||
@@ -60,7 +51,7 @@ namespace Engine {
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
AssetHandle<T> h;
|
AssetRef<T> h;
|
||||||
if (!m_Free.empty()) {
|
if (!m_Free.empty()) {
|
||||||
h.index = m_Free.back();
|
h.index = m_Free.back();
|
||||||
m_Free.pop_back();
|
m_Free.pop_back();
|
||||||
@@ -77,7 +68,7 @@ namespace Engine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 句柄 → 资产指针。悬空句柄 FATAL(bug 信号);已卸载返回 nullptr。
|
// 句柄 → 资产指针。悬空句柄 FATAL(bug 信号);已卸载返回 nullptr。
|
||||||
T* Get(AssetHandle<T> h) const
|
T* Get(AssetRef<T> h) const
|
||||||
{
|
{
|
||||||
if (!h.Valid() || h.index >= m_Entries.size()) {
|
if (!h.Valid() || h.index >= m_Entries.size()) {
|
||||||
ENGINE_FATAL("AssetCache::Get: invalid handle (index={}, gen={})",
|
ENGINE_FATAL("AssetCache::Get: invalid handle (index={}, gen={})",
|
||||||
@@ -94,7 +85,7 @@ namespace Engine {
|
|||||||
// 卸载:句柄即刻失效(generation 递增),路径缓存清除。
|
// 卸载:句柄即刻失效(generation 递增),路径缓存清除。
|
||||||
// 注意:GPU 侧何时安全释放由上层(Renderer fence)保证——
|
// 注意:GPU 侧何时安全释放由上层(Renderer fence)保证——
|
||||||
// 本类只管所有权,不管 GPU 生命周期。
|
// 本类只管所有权,不管 GPU 生命周期。
|
||||||
void Unload(AssetHandle<T> h)
|
void Unload(AssetRef<T> h)
|
||||||
{
|
{
|
||||||
if (!h.Valid() || h.index >= m_Entries.size()) return;
|
if (!h.Valid() || h.index >= m_Entries.size()) return;
|
||||||
|
|
||||||
@@ -109,7 +100,7 @@ namespace Engine {
|
|||||||
m_Free.push_back(h.index);
|
m_Free.push_back(h.index);
|
||||||
}
|
}
|
||||||
|
|
||||||
const std::string& Path(AssetHandle<T> h) const
|
const std::string& Path(AssetRef<T> h) const
|
||||||
{
|
{
|
||||||
static const std::string kEmpty;
|
static const std::string kEmpty;
|
||||||
if (!h.Valid() || h.index >= m_Entries.size()) return kEmpty;
|
if (!h.Valid() || h.index >= m_Entries.size()) return kEmpty;
|
||||||
@@ -132,29 +123,27 @@ namespace Engine {
|
|||||||
|
|
||||||
std::vector<Entry> m_Entries;
|
std::vector<Entry> m_Entries;
|
||||||
std::vector<u32> m_Free;
|
std::vector<u32> m_Free;
|
||||||
std::unordered_map<std::string, AssetHandle<T>> m_ByPath;
|
std::unordered_map<std::string, AssetRef<T>> m_ByPath;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---------- AssetManager:全引擎资产入口 ----------
|
// ---------- AssetManager:全引擎资产入口 ----------
|
||||||
// (ModelHandle 别名来自 AssetHandle.h)
|
|
||||||
|
|
||||||
class AssetManager {
|
class AssetManager {
|
||||||
public:
|
public:
|
||||||
// GPU 上下文由组合根(Application)在 Renderer 装配完成后注入
|
// 上传服务由组合根(Application)注入:渲染层实现接口,
|
||||||
AssetManager(Device& device, MemoryAllocator& allocator,
|
// 资产层不感知 Vulkan 具体类
|
||||||
DescriptorManager& descriptors,
|
explicit AssetManager(IAssetUploader& uploader);
|
||||||
vk::DescriptorSetLayout textureLayout);
|
|
||||||
~AssetManager() = default;
|
~AssetManager() = default;
|
||||||
|
|
||||||
AssetManager(const AssetManager&) = delete;
|
AssetManager(const AssetManager&) = delete;
|
||||||
AssetManager& operator=(const AssetManager&) = delete;
|
AssetManager& operator=(const AssetManager&) = delete;
|
||||||
|
|
||||||
ModelHandle LoadModel(const std::string& path);
|
ModelRef LoadModel(const std::string& path);
|
||||||
Model* GetModel(ModelHandle h) const { return m_Models.Get(h); }
|
Model* GetModel(ModelRef h) const { return m_Models.Get(h); }
|
||||||
void UnloadModel(ModelHandle h) { m_Models.Unload(h); }
|
void UnloadModel(ModelRef h) { m_Models.Unload(h); }
|
||||||
|
|
||||||
// 程序化平面(纯色贴图):size 为半边长。带缓存,同参数共享一份。
|
// 程序化平面(纯色贴图):size 为半边长。带缓存,同参数共享一份。
|
||||||
ModelHandle CreatePlane(f32 halfSize, u8 r, u8 g, u8 b);
|
ModelRef CreatePlane(f32 halfSize, u8 r, u8 g, u8 b);
|
||||||
|
|
||||||
AssetCache<Model>& Models() { return m_Models; }
|
AssetCache<Model>& Models() { return m_Models; }
|
||||||
|
|
||||||
@@ -162,10 +151,7 @@ namespace Engine {
|
|||||||
// 各自一个 AssetCache<T> + 便捷方法,模式与 Model 完全一致。
|
// 各自一个 AssetCache<T> + 便捷方法,模式与 Model 完全一致。
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Device& m_Device;
|
IAssetUploader& m_Uploader;
|
||||||
MemoryAllocator& m_Allocator;
|
|
||||||
DescriptorManager& m_Descriptors;
|
|
||||||
vk::DescriptorSetLayout m_TextureLayout;
|
|
||||||
|
|
||||||
AssetCache<Model> m_Models;
|
AssetCache<Model> m_Models;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
//
|
||||||
|
// Created by atdunbg on 2026/9/23.
|
||||||
|
//
|
||||||
|
// DDS 文件解析(最小实现,不解码压缩块)。
|
||||||
|
//
|
||||||
|
// 设计:GPU 原生支持 BC 压缩格式——DDS 的压缩数据**直接上传**为
|
||||||
|
// BC1/BC2/BC3/BC4/BC5/BC6H/BC7 纹理由硬件采样解码,不做 CPU 解码。
|
||||||
|
// 输出中性 DdsFormat(本层禁 Vulkan 头),vk::Format 映射与 sRGB
|
||||||
|
// 语义由调用方(Texture,渲染侧)决定。
|
||||||
|
//
|
||||||
|
// 支持:
|
||||||
|
// - legacy fourcc:DXT1/DXT3/DXT5/ATI1/BC4U/ATI2/BC5U
|
||||||
|
// - DX10 扩展头:BC1..BC7、R8G8B8A8、B8G8R8A8
|
||||||
|
// - 未压缩 32bpp(DDPF_RGB | DDPF_ALPHAPIXELS)
|
||||||
|
// - 2D + mip 链(cubemap 面标志暂不支持,报错)
|
||||||
|
//
|
||||||
|
// 输出:Mips[r] = 第 r 级完整数据(BC 按块行紧凑排布:
|
||||||
|
// bytes = ceil(w/4)*ceil(h/4)*blockBytes)。
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef HELLOVULKAN_DDSLOADER_H
|
||||||
|
#define HELLOVULKAN_DDSLOADER_H
|
||||||
|
|
||||||
|
#include <Engine/Core/Types.h>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace Engine::Assets::Dds {
|
||||||
|
|
||||||
|
// 中性格式(Assets 层不感知 Vulkan;映射在渲染侧 Texture)
|
||||||
|
enum class DdsFormat {
|
||||||
|
BC1, BC2, BC3, BC4, BC5, BC6H, BC7,
|
||||||
|
RGBA8,
|
||||||
|
BGRA8, // 按字节序上传后视作 RGBA(近似,触发警告)
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DdsData {
|
||||||
|
u32 Width = 0;
|
||||||
|
u32 Height = 0;
|
||||||
|
u32 MipLevels = 1;
|
||||||
|
DdsFormat Format = DdsFormat::RGBA8;
|
||||||
|
std::vector<std::vector<u8>> Mips; // 每级完整数据(紧凑排布)
|
||||||
|
bool BlockCompressed = false; // BC 格式(rowPitch 按块计算)
|
||||||
|
};
|
||||||
|
|
||||||
|
// 解析 DDS 文件;失败返回 false 并写 errMsg
|
||||||
|
bool Load(const std::string& path, DdsData& out, std::string& errMsg);
|
||||||
|
|
||||||
|
// 指定 mip 级的单行字节跨度(BC = 块行;RGBA8 = w*4)
|
||||||
|
u32 RowPitch(const DdsData& data, u32 mipLevel);
|
||||||
|
|
||||||
|
} // namespace Engine::Assets::Dds
|
||||||
|
|
||||||
|
#endif //HELLOVULKAN_DDSLOADER_H
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
//
|
||||||
|
// Created by atdunbg on 2026/9/23.
|
||||||
|
//
|
||||||
|
// IAssetUploader:资产层 → GPU 的上传接口。
|
||||||
|
//
|
||||||
|
// 资产层(AssetManager/AssetCache)只认这个接口,不感知 Vulkan 具体类
|
||||||
|
// (Device/Allocator/DescriptorManager)。实现由渲染层提供
|
||||||
|
// (VulkanAssetUploader),组合根(Renderer)装配后注入。
|
||||||
|
//
|
||||||
|
// 对齐 UE:资产系统(AssetManager/StreamableManager)与渲染资源创建
|
||||||
|
// (rendering thread / RHI)分离,资产层只拿到"可用的渲染资源"。
|
||||||
|
//
|
||||||
|
// 后续演进点:接口加返回 future / 回调即为异步加载
|
||||||
|
// (IO 线程解析 → 上传队列提交),本接口已是正确的接缝。
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef HELLOVULKAN_IASSETUPLOADER_H
|
||||||
|
#define HELLOVULKAN_IASSETUPLOADER_H
|
||||||
|
|
||||||
|
#include <Engine/Core/Types.h>
|
||||||
|
#include <Engine/Renderer/Model.h>
|
||||||
|
|
||||||
|
namespace Engine::Assets {
|
||||||
|
|
||||||
|
class IAssetUploader {
|
||||||
|
public:
|
||||||
|
virtual ~IAssetUploader() = default;
|
||||||
|
|
||||||
|
// 磁盘模型 → 解析 + GPU 上传(失败返回 nullptr)
|
||||||
|
virtual Scope<Model> LoadModel(const std::string& path) = 0;
|
||||||
|
|
||||||
|
// 程序化纯色平面(size 为半边长)
|
||||||
|
virtual Scope<Model> CreatePlane(f32 halfSize, u8 r, u8 g, u8 b) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Engine::Assets
|
||||||
|
|
||||||
|
#endif //HELLOVULKAN_IASSETUPLOADER_H
|
||||||
@@ -26,6 +26,8 @@
|
|||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
#include "Engine/ImGui/ImGuiLayer.h"
|
||||||
|
|
||||||
namespace Engine {
|
namespace Engine {
|
||||||
|
|
||||||
class Window;
|
class Window;
|
||||||
@@ -54,6 +56,14 @@ namespace Engine {
|
|||||||
// 主渲染(ImGui pass 把 draw data 叠加到场景上)。
|
// 主渲染(ImGui pass 把 draw data 叠加到场景上)。
|
||||||
void EnableImGui();
|
void EnableImGui();
|
||||||
|
|
||||||
|
// ---- 帧率控制 ----
|
||||||
|
// vsync 开启时 present 自带节奏,手动限帧不生效;
|
||||||
|
// 限帧目标最低 24fps(防止过低刷新导致交互卡死),无上限
|
||||||
|
void SetVSync(bool vsync);
|
||||||
|
bool GetVSync() const { return m_VSync; }
|
||||||
|
void SetTargetFps(f32 fps) { m_TargetFps = std::max(24.0f, fps); }
|
||||||
|
f32 GetTargetFps() const { return m_TargetFps; }
|
||||||
|
|
||||||
// ---- 供派生类访问的模块 ----
|
// ---- 供派生类访问的模块 ----
|
||||||
Window& GetWindow() const { return *m_Window; }
|
Window& GetWindow() const { return *m_Window; }
|
||||||
Renderer& GetRenderer() const { return *m_Renderer; }
|
Renderer& GetRenderer() const { return *m_Renderer; }
|
||||||
@@ -62,6 +72,11 @@ namespace Engine {
|
|||||||
f32 GetDeltaTime() const { return m_DeltaTime; }
|
f32 GetDeltaTime() const { return m_DeltaTime; }
|
||||||
u64 GetFrameCount() const { return m_FrameCount; }
|
u64 GetFrameCount() const { return m_FrameCount; }
|
||||||
|
|
||||||
|
// ImGui相关
|
||||||
|
void SetImGuiEnabled(const bool enable) { if (m_ImGuiLayer) m_ImGuiLayer->SetEnabled(enable); }
|
||||||
|
// 理论上ImGuiLayer不应该会空,暂时静默处理
|
||||||
|
bool GetImGuiEnabled() const { return m_ImGuiLayer ? m_ImGuiLayer->IsEnabled() : false; }
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
// ---- 生命周期钩子 ----
|
// ---- 生命周期钩子 ----
|
||||||
|
|
||||||
@@ -108,6 +123,10 @@ namespace Engine {
|
|||||||
f32 m_DeltaTime = 0.0f;
|
f32 m_DeltaTime = 0.0f;
|
||||||
u64 m_FrameCount = 0;
|
u64 m_FrameCount = 0;
|
||||||
|
|
||||||
|
// 帧率控制
|
||||||
|
bool m_VSync = true; // 垂直同步(present 自带节奏)
|
||||||
|
f32 m_TargetFps = 60.0f; // 限帧目标(≥24)
|
||||||
|
|
||||||
std::chrono::steady_clock::time_point m_LastFrameTime;
|
std::chrono::steady_clock::time_point m_LastFrameTime;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,45 @@
|
|||||||
|
//
|
||||||
|
// Created by atdunbg on 2026/9/23.
|
||||||
|
//
|
||||||
|
// AssetRef:中性资产引用 = (池内索引, 生成号),与 Entity/RG 句柄同一哲学。
|
||||||
|
//
|
||||||
|
// 放在 Core 层(不依赖 Assets/Vulkan):ECS 组件、序列化数据只存这个
|
||||||
|
// 纯值类型。对齐 UE/Unity 的做法——组件持有"资产引用",但不感知资产
|
||||||
|
// 系统内部:Unity 的 InstanceID、UE 的 FSoftObjectPath 本质都是中性引用
|
||||||
|
// 值,解析归资产系统(AssetManager)。
|
||||||
|
// 悬空引用(generation 不匹配)由 AssetCache::Get 显式拒绝。
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef HELLOVULKAN_ASSETREF_H
|
||||||
|
#define HELLOVULKAN_ASSETREF_H
|
||||||
|
|
||||||
|
#include <Engine/Core/Types.h>
|
||||||
|
|
||||||
|
// 资产类型前向声明(必须放全局层:namespace 内前向声明会创建
|
||||||
|
// Engine::Assets::Engine::Model 这类错误嵌套命名空间)
|
||||||
|
namespace Engine {
|
||||||
|
class Model;
|
||||||
|
class Texture;
|
||||||
|
class Material;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Engine {
|
||||||
|
|
||||||
|
inline constexpr u32 kInvalidAssetIndex = 0xFFFFFFFF;
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
struct AssetRef {
|
||||||
|
u32 index = kInvalidAssetIndex;
|
||||||
|
u32 generation = 0;
|
||||||
|
|
||||||
|
bool Valid() const { return index != kInvalidAssetIndex; }
|
||||||
|
explicit operator bool() const { return Valid(); }
|
||||||
|
bool operator==(const AssetRef& o) const = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 常用别名(新增资产类型时在这里加一行)
|
||||||
|
using ModelRef = AssetRef<Model>;
|
||||||
|
|
||||||
|
} // namespace Engine
|
||||||
|
|
||||||
|
#endif //HELLOVULKAN_ASSETREF_H
|
||||||
@@ -87,6 +87,8 @@ namespace Engine {
|
|||||||
static void CursorPosCallback(GLFWwindow* window, double xpos, double ypos);
|
static void CursorPosCallback(GLFWwindow* window, double xpos, double ypos);
|
||||||
static void ScrollCallback(GLFWwindow* window, double xoffset, double yoffset);
|
static void ScrollCallback(GLFWwindow* window, double xoffset, double yoffset);
|
||||||
static void WindowFocusCallback(GLFWwindow* window, int focused);
|
static void WindowFocusCallback(GLFWwindow* window, int focused);
|
||||||
|
|
||||||
|
static void WindowCloseCallback(GLFWwindow* window);
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Engine
|
} // namespace Engine
|
||||||
|
|||||||
@@ -10,8 +10,8 @@
|
|||||||
|
|
||||||
#include <Engine/Core/Types.h>
|
#include <Engine/Core/Types.h>
|
||||||
#include <Engine/Core/UUID.h>
|
#include <Engine/Core/UUID.h>
|
||||||
|
#include <Engine/Core/AssetRef.h>
|
||||||
#include <Engine/Renderer/Camera.h>
|
#include <Engine/Renderer/Camera.h>
|
||||||
#include <Engine/Assets/AssetHandle.h>
|
|
||||||
|
|
||||||
#include <glm/glm.hpp>
|
#include <glm/glm.hpp>
|
||||||
#include <glm/gtc/matrix_transform.hpp>
|
#include <glm/gtc/matrix_transform.hpp>
|
||||||
@@ -34,37 +34,53 @@ namespace Engine::ECS {
|
|||||||
std::string name;
|
std::string name;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 世界变换:可编辑的 SRT 分量 + 派生的世界矩阵。
|
// 世界变换:UE USceneComponent 的 encapsulation 模式——
|
||||||
// 编辑分量后调用 Compose() 重算 world;渲染热路径只读 world。
|
// SRT 分量私有,所有写入口标脏;world 矩阵由帧循环的
|
||||||
|
// Scene::UpdateTransforms() 只对脏的实体重算(transform-update
|
||||||
|
// 阶段),并自动向未来的子层级传播。渲染热路径只读 World()。
|
||||||
// 层级(父子变换)留给后续的层级同步系统。
|
// 层级(父子变换)留给后续的层级同步系统。
|
||||||
struct TransformComponent {
|
struct TransformComponent {
|
||||||
glm::vec3 position{0.0f};
|
// ---- 读取 ----
|
||||||
glm::vec3 rotation{0.0f}; // 欧拉角(度),应用顺序 Y→X→Z
|
const glm::vec3& GetPosition() const { return m_Position; }
|
||||||
glm::vec3 scale{1.0f};
|
const glm::vec3& GetRotation() const { return m_Rotation; } // 欧拉角(度)
|
||||||
glm::mat4 world{1.0f};
|
const glm::vec3& GetScale() const { return m_Scale; }
|
||||||
|
const glm::mat4& World() const { return m_World; } // 要求已由 UpdateTransforms 计算
|
||||||
|
|
||||||
TransformComponent& SetPosition(const glm::vec3& p) { position = p; return *this; }
|
// ---- 写入(标脏,脏实体由 UpdateTransforms 重算)----
|
||||||
TransformComponent& SetRotation(const glm::vec3& r) { rotation = r; return *this; }
|
TransformComponent& SetPosition(const glm::vec3& p) { if (m_Position != p) { m_Position = p; m_Dirty = true; } return *this; }
|
||||||
TransformComponent& SetScale(const glm::vec3& s) { scale = s; return *this; }
|
TransformComponent& SetRotation(const glm::vec3& r) { if (m_Rotation != r) { m_Rotation = r; m_Dirty = true; } return *this; }
|
||||||
|
TransformComponent& SetScale(const glm::vec3& s) { if (m_Scale != s) { m_Scale = s; m_Dirty = true; } return *this; }
|
||||||
|
|
||||||
// 分量 → world 矩阵(T * R * S)
|
bool IsDirty() const { return m_Dirty; }
|
||||||
|
|
||||||
|
// SRT 分量 → world 矩阵(T * R * S),清除脏标记。
|
||||||
|
// 由 Scene::UpdateTransforms 调用(或层级系统),勿手动
|
||||||
TransformComponent& Compose()
|
TransformComponent& Compose()
|
||||||
{
|
{
|
||||||
glm::mat4 t = glm::translate(glm::mat4(1.0f), position);
|
const glm::mat4 t = glm::translate(glm::mat4(1.0f), m_Position);
|
||||||
glm::mat4 r =
|
const glm::mat4 r =
|
||||||
glm::rotate(glm::mat4(1.0f), glm::radians(rotation.y), glm::vec3(0, 1, 0)) *
|
glm::rotate(glm::mat4(1.0f), glm::radians(m_Rotation.y), glm::vec3(0, 1, 0)) *
|
||||||
glm::rotate(glm::mat4(1.0f), glm::radians(rotation.x), glm::vec3(1, 0, 0)) *
|
glm::rotate(glm::mat4(1.0f), glm::radians(m_Rotation.x), glm::vec3(1, 0, 0)) *
|
||||||
glm::rotate(glm::mat4(1.0f), glm::radians(rotation.z), glm::vec3(0, 0, 1));
|
glm::rotate(glm::mat4(1.0f), glm::radians(m_Rotation.z), glm::vec3(0, 0, 1));
|
||||||
glm::mat4 s = glm::scale(glm::mat4(1.0f), scale);
|
const glm::mat4 s = glm::scale(glm::mat4(1.0f), m_Scale);
|
||||||
world = t * r * s;
|
m_World = t * r * s;
|
||||||
|
m_Dirty = false;
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
glm::vec3 m_Position{0.0f};
|
||||||
|
glm::vec3 m_Rotation{0.0f};
|
||||||
|
glm::vec3 m_Scale{1.0f};
|
||||||
|
glm::mat4 m_World{1.0f};
|
||||||
|
bool m_Dirty = true; // 出生即脏:首次 UpdateTransforms 计算 world
|
||||||
};
|
};
|
||||||
|
|
||||||
// 网格渲染体:引用共享的 Model 资产。
|
// 网格渲染体:引用共享的 Model 资产。
|
||||||
// 存句柄不存指针/Ref:所有权归 AssetManager,悬空由句柄 generation 拦截。
|
// 存中性 AssetRef(Core 层纯值类型,Unity InstanceID 思路)——
|
||||||
|
// ECS 不感知资产系统内部;解析归 AssetManager,悬空由 generation 拦截。
|
||||||
struct MeshRendererComponent {
|
struct MeshRendererComponent {
|
||||||
Assets::ModelHandle model;
|
ModelRef model;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 相机。一个场景中 primary=true 的实体是当前渲染视角。
|
// 相机。一个场景中 primary=true 的实体是当前渲染视角。
|
||||||
|
|||||||
@@ -51,6 +51,11 @@ namespace Engine::ECS {
|
|||||||
EntityHandle CreateEntity(std::string name = {});
|
EntityHandle CreateEntity(std::string name = {});
|
||||||
void DestroyEntity(Entity e); // 延迟:帧尾统一销毁
|
void DestroyEntity(Entity e); // 延迟:帧尾统一销毁
|
||||||
void FlushDestroyQueue(); // 每帧末尾由 Application 调用
|
void FlushDestroyQueue(); // 每帧末尾由 Application 调用
|
||||||
|
|
||||||
|
// 帧循环统一重算脏 transform 的 world 矩阵(UE transform-update
|
||||||
|
// 阶段模式)——由 Application 在 OnUpdate 之后、渲染之前调用,
|
||||||
|
// 用户代码永远不需要手动 Compose
|
||||||
|
void UpdateTransforms();
|
||||||
bool IsAlive(Entity e) const { return m_Registry.valid(e); }
|
bool IsAlive(Entity e) const { return m_Registry.valid(e); }
|
||||||
|
|
||||||
// 按稳定 UUID 查实体(序列化/跨引用场景);找不到返回 entt::null
|
// 按稳定 UUID 查实体(序列化/跨引用场景);找不到返回 entt::null
|
||||||
@@ -86,8 +91,10 @@ namespace Engine::ECS {
|
|||||||
|
|
||||||
// ---- 常用查询 ----
|
// ---- 常用查询 ----
|
||||||
|
|
||||||
// primary 相机(渲染视角来源);无则返回 nullptr
|
// 主相机(primary=true,渲染视角来源);无则返回 nullptr。
|
||||||
CameraComponent* GetPrimaryCamera();
|
// outTransform 可选:带出该实体的 TransformComponent
|
||||||
|
//(相机位姿的单一事实源)
|
||||||
|
CameraComponent* GetPrimaryCamera(TransformComponent** outTransform = nullptr);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Registry m_Registry;
|
Registry m_Registry;
|
||||||
|
|||||||
@@ -16,7 +16,6 @@
|
|||||||
//
|
//
|
||||||
// 已知边界(后续演进点):
|
// 已知边界(后续演进点):
|
||||||
// - 暂不支持异步 compute queue / 跨队列信号量
|
// - 暂不支持异步 compute queue / 跨队列信号量
|
||||||
// - 暂不支持内存别名(需 VMA)与帧历史资源(TAA 等)
|
|
||||||
// - 同一 pass 内对同一资源"读+写"(input attachment / RWM)未建模
|
// - 同一 pass 内对同一资源"读+写"(input attachment / RWM)未建模
|
||||||
//
|
//
|
||||||
|
|
||||||
@@ -98,6 +97,10 @@ namespace Engine::RG {
|
|||||||
vk::ImageView View(TextureHandle h) const;
|
vk::ImageView View(TextureHandle h) const;
|
||||||
vk::Image Image(TextureHandle h) const;
|
vk::Image Image(TextureHandle h) const;
|
||||||
|
|
||||||
|
// 帧历史资源:上一帧槽位的物理镜像((slot + framesInFlight - 1) % F)。
|
||||||
|
// 采样描述符经 GetPhysicalView(h, 历史槽位) 预绑(同阴影图模式)。
|
||||||
|
u32 HistorySlot(u32 frameSlot) const;
|
||||||
|
|
||||||
class RenderGraph* graph = nullptr;
|
class RenderGraph* graph = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -131,6 +134,10 @@ namespace Engine::RG {
|
|||||||
};
|
};
|
||||||
std::vector<SlotState> slots;
|
std::vector<SlotState> slots;
|
||||||
|
|
||||||
|
// 是否存在帧历史读(Compile 期校验:历史资源必须有同帧正常使用,
|
||||||
|
// 否则没有物理镜像可读)
|
||||||
|
bool hasHistoryUse = false;
|
||||||
|
|
||||||
// Compile 期状态链解算用(执行序上的"当前"状态)
|
// Compile 期状态链解算用(执行序上的"当前"状态)
|
||||||
vk::ImageLayout chainLayout = vk::ImageLayout::eUndefined;
|
vk::ImageLayout chainLayout = vk::ImageLayout::eUndefined;
|
||||||
vk::PipelineStageFlags2 chainStage{};
|
vk::PipelineStageFlags2 chainStage{};
|
||||||
@@ -150,6 +157,7 @@ namespace Engine::RG {
|
|||||||
u32 resIndex;
|
u32 resIndex;
|
||||||
ResourceUse use;
|
ResourceUse use;
|
||||||
vk::ShaderStageFlags readStage; // ShaderRead 时的消费 stage
|
vk::ShaderStageFlags readStage; // ShaderRead 时的消费 stage
|
||||||
|
bool history = false; // 帧历史读:读上一帧槽位的物理镜像
|
||||||
};
|
};
|
||||||
std::vector<Use> uses;
|
std::vector<Use> uses;
|
||||||
ExecuteFn exec;
|
ExecuteFn exec;
|
||||||
@@ -161,12 +169,19 @@ namespace Engine::RG {
|
|||||||
// steady = 该槽位已被使用过(从 resting 状态出发)
|
// steady = 该槽位已被使用过(从 resting 状态出发)
|
||||||
// first = 该槽位首次使用(从 Undefined 出发)
|
// first = 该槽位首次使用(从 Undefined 出发)
|
||||||
// imported 资源恒用 first(acquire 后 layout 语义上为 Undefined)
|
// imported 资源恒用 first(acquire 后 layout 语义上为 Undefined)
|
||||||
|
// history = 帧历史读(操作上一帧槽位的镜像,steady 变体带跨帧可见性同步)
|
||||||
struct BarrierPair {
|
struct BarrierPair {
|
||||||
u32 resIndex;
|
u32 resIndex;
|
||||||
vk::ImageMemoryBarrier2 steady;
|
vk::ImageMemoryBarrier2 steady;
|
||||||
vk::ImageMemoryBarrier2 first;
|
vk::ImageMemoryBarrier2 first;
|
||||||
|
bool history = false;
|
||||||
};
|
};
|
||||||
std::vector<BarrierPair> barriers;
|
std::vector<BarrierPair> barriers;
|
||||||
|
|
||||||
|
// 历史读的出口 barrier(exec 之后执行):历史镜像被临时转进读
|
||||||
|
// layout 采样,读完必须恢复 resting layout,否则下一帧该槽位
|
||||||
|
// entry barrier 的 oldLayout 与实际 layout 脱节
|
||||||
|
std::vector<BarrierPair> postBarriers;
|
||||||
};
|
};
|
||||||
|
|
||||||
// ---------- RenderGraph ----------
|
// ---------- RenderGraph ----------
|
||||||
@@ -195,6 +210,11 @@ namespace Engine::RG {
|
|||||||
// 注意:物理镜像按槽位各一份,槽位变化/重编译后必须重新获取。
|
// 注意:物理镜像按槽位各一份,槽位变化/重编译后必须重新获取。
|
||||||
vk::ImageView GetPhysicalView(TextureHandle h, u32 frameSlot) const;
|
vk::ImageView GetPhysicalView(TextureHandle h, u32 frameSlot) const;
|
||||||
|
|
||||||
|
// 帧历史槽位 = 上一帧使用的槽位:(slot - 1 + F) mod F
|
||||||
|
u32 HistorySlot(u32 frameSlot) const {
|
||||||
|
return (frameSlot + m_FramesInFlight - 1) % m_FramesInFlight;
|
||||||
|
}
|
||||||
|
|
||||||
// ---- Pass 声明 ----
|
// ---- Pass 声明 ----
|
||||||
|
|
||||||
class PassBuilder {
|
class PassBuilder {
|
||||||
@@ -202,6 +222,12 @@ namespace Engine::RG {
|
|||||||
PassBuilder& Write(TextureHandle h, ResourceUse use);
|
PassBuilder& Write(TextureHandle h, ResourceUse use);
|
||||||
PassBuilder& Read(TextureHandle h, ResourceUse use,
|
PassBuilder& Read(TextureHandle h, ResourceUse use,
|
||||||
vk::ShaderStageFlags stage = vk::ShaderStageFlagBits::eFragment);
|
vk::ShaderStageFlags stage = vk::ShaderStageFlagBits::eFragment);
|
||||||
|
// 帧历史读:读上一帧槽位的物理镜像(TAA/运动模糊/时域滤波用)。
|
||||||
|
// - 不产生图内边:跨帧依赖由同队列提交序 + 历史 barrier 保证
|
||||||
|
// - 资源必须有同帧正常使用(否则无物理镜像),Compile 期校验
|
||||||
|
// - 采样描述符在 pass 内用 GetPhysicalView(h, HistorySlot(slot)) 预绑
|
||||||
|
PassBuilder& ReadHistory(TextureHandle h, ResourceUse use,
|
||||||
|
vk::ShaderStageFlags stage = vk::ShaderStageFlagBits::eFragment);
|
||||||
PassBuilder& SideEffect();
|
PassBuilder& SideEffect();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
@@ -2,8 +2,11 @@
|
|||||||
// Created by atdunbg on 2026/9/22.
|
// Created by atdunbg on 2026/9/22.
|
||||||
//
|
//
|
||||||
// Camera:独立的相机模块。
|
// Camera:独立的相机模块。
|
||||||
// 职责:输入驱动的运动更新 + 视图/投影矩阵计算。
|
//
|
||||||
// 不再依赖具体渲染器——Renderer/RenderGraph 只消费它的矩阵输出。
|
// 设计(Transform 单一事实源):相机的位置与朝向完全由实体上的
|
||||||
|
// TransformComponent 承载——Update 把输入直接写回 transform(末尾
|
||||||
|
// Compose),视图矩阵 = 实体世界矩阵的逆(本地 -Z 为视线方向)。
|
||||||
|
// 相机类自身不保存任何位姿状态,杜绝"位置两套来源"的脱节。
|
||||||
//
|
//
|
||||||
|
|
||||||
#ifndef HELLOVULKAN_CAMERA_H
|
#ifndef HELLOVULKAN_CAMERA_H
|
||||||
@@ -15,25 +18,20 @@
|
|||||||
namespace Engine {
|
namespace Engine {
|
||||||
|
|
||||||
class Window;
|
class Window;
|
||||||
|
namespace ECS { struct TransformComponent; }
|
||||||
|
|
||||||
class Camera {
|
class Camera {
|
||||||
public:
|
public:
|
||||||
// 每帧调用:读取窗口输入,更新位置与朝向
|
// 输入驱动:WASD/Space/Shift 移动 + 鼠标视角(需 SetMouseLookEnabled)。
|
||||||
void Update(Window& window, f32 deltaTime);
|
// 全部写回传入的 transform,有变更才 Compose。
|
||||||
|
void Update(Window& window, ECS::TransformComponent& transform, f32 deltaTime);
|
||||||
|
|
||||||
glm::mat4 GetView() const;
|
// 相机视图 = 实体世界矩阵的逆(要求 transform 已 Compose)
|
||||||
|
glm::mat4 GetView(const ECS::TransformComponent& transform) const;
|
||||||
|
|
||||||
// Vulkan 投影:内部完成 Y 翻转(clip space Y 向下)和 [0,1] 深度
|
// Vulkan 投影:内部完成 Y 翻转(clip space Y 向下)和 [0,1] 深度
|
||||||
glm::mat4 GetProjection(f32 aspect, f32 nearZ, f32 farZ) const;
|
glm::mat4 GetProjection(f32 aspect, f32 nearZ, f32 farZ) const;
|
||||||
|
|
||||||
glm::vec3 GetPosition() const { return m_Position; }
|
|
||||||
void SetPosition(const glm::vec3& pos) { m_Position = pos; }
|
|
||||||
|
|
||||||
void SetYaw(f32 yaw) { m_Yaw = yaw; }
|
|
||||||
void SetPitch(f32 pitch) { m_Pitch = pitch; }
|
|
||||||
f32 GetYaw() const { return m_Yaw; }
|
|
||||||
f32 GetPitch() const { return m_Pitch; }
|
|
||||||
|
|
||||||
void SetMoveSpeed(f32 speed) { m_MoveSpeed = speed; }
|
void SetMoveSpeed(f32 speed) { m_MoveSpeed = speed; }
|
||||||
void SetMouseSensitivity(f32 sens) { m_MouseSensitivity = sens; }
|
void SetMouseSensitivity(f32 sens) { m_MouseSensitivity = sens; }
|
||||||
|
|
||||||
@@ -41,24 +39,10 @@ namespace Engine {
|
|||||||
void SetMouseLookEnabled(bool enabled) { m_MouseLookEnabled = enabled; }
|
void SetMouseLookEnabled(bool enabled) { m_MouseLookEnabled = enabled; }
|
||||||
bool IsMouseLookEnabled() const { return m_MouseLookEnabled; }
|
bool IsMouseLookEnabled() const { return m_MouseLookEnabled; }
|
||||||
|
|
||||||
glm::vec3 GetForward() const { return m_Forward; }
|
|
||||||
glm::vec3 GetRight() const { return m_Right; }
|
|
||||||
glm::vec3 GetUp() const { return m_Up; }
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void UpdateBasis();
|
|
||||||
|
|
||||||
glm::vec3 m_Position{0.0f, 0.0f, 7.0f};
|
|
||||||
f32 m_Yaw = -90.0f; // 朝向 -Z
|
|
||||||
f32 m_Pitch = 0.0f;
|
|
||||||
f32 m_MoveSpeed = 2.5f;
|
f32 m_MoveSpeed = 2.5f;
|
||||||
f32 m_MouseSensitivity = 0.1f;
|
f32 m_MouseSensitivity = 0.1f;
|
||||||
bool m_MouseLookEnabled = false;
|
bool m_MouseLookEnabled = false;
|
||||||
|
|
||||||
// 缓存的方向基向量,Update 时刷新,避免每帧三角函数重复计算
|
|
||||||
glm::vec3 m_Forward{0.0f, 0.0f, -1.0f};
|
|
||||||
glm::vec3 m_Right{1.0f, 0.0f, 0.0f};
|
|
||||||
glm::vec3 m_Up{0.0f, 1.0f, 0.0f};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Engine
|
} // namespace Engine
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
//
|
||||||
|
// Created by atdunbg on 2026/9/23.
|
||||||
|
//
|
||||||
|
// Frustum:Gribb-Hartmann 视锥平面提取 + 包围球相交测试。
|
||||||
|
//
|
||||||
|
// 平面从 view-proj 矩阵的行组合中直接抽出(无需分解参数),
|
||||||
|
// 用于按视图剔除(相机视图 / 光源视图各自独立一份)。
|
||||||
|
// 注意:near/far 平面按 Vulkan [0,1] 深度约定提取
|
||||||
|
//(z_clip ∈ [0,1]:near = row2,far = row3 - row2)——
|
||||||
|
// 因此必须传已做 [0,1] 重映射的矩阵(相机 proj / lightSpace 均是)。
|
||||||
|
// 平面在提取后归一化:包围球测试的 radius 是欧氏长度,
|
||||||
|
// 未归一化的平面距离会被透视因数放大,导致视锥边缘误剔。
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef HELLOVULKAN_FRUSTUM_H
|
||||||
|
#define HELLOVULKAN_FRUSTUM_H
|
||||||
|
|
||||||
|
#include <Engine/Core/Types.h>
|
||||||
|
#include <glm/glm.hpp>
|
||||||
|
#include <array>
|
||||||
|
|
||||||
|
namespace Engine {
|
||||||
|
|
||||||
|
struct Frustum {
|
||||||
|
// 依次:left, right, bottom, top, near, far
|
||||||
|
// xyz = 单位法线,w = 原点到平面的有符号距离
|
||||||
|
std::array<glm::vec4, 6> planes{};
|
||||||
|
|
||||||
|
// 从 view-proj 矩阵提取 6 平面(列主序 glm 矩阵的"行"组合)。
|
||||||
|
// 必须传 [0,1] 深度重映射后的矩阵。
|
||||||
|
static Frustum FromMatrix(const glm::mat4& m)
|
||||||
|
{
|
||||||
|
Frustum f;
|
||||||
|
// 列主序下的"行 i": (m[0][i], m[1][i], m[2][i], m[3][i])
|
||||||
|
const auto row = [&m](u32 i) {
|
||||||
|
return glm::vec4(m[0][i], m[1][i], m[2][i], m[3][i]);
|
||||||
|
};
|
||||||
|
const glm::vec4 r0 = row(0);
|
||||||
|
const glm::vec4 r1 = row(1);
|
||||||
|
const glm::vec4 r2 = row(2);
|
||||||
|
const glm::vec4 r3 = row(3);
|
||||||
|
|
||||||
|
f.planes[0] = r3 + r0; // left: x' >= -w
|
||||||
|
f.planes[1] = r3 - r0; // right: x' <= w
|
||||||
|
f.planes[2] = r3 + r1; // bottom: y' >= -w
|
||||||
|
f.planes[3] = r3 - r1; // top: y' <= w
|
||||||
|
f.planes[4] = r2; // near: z' >= 0(Vulkan [0,1])
|
||||||
|
f.planes[5] = r3 - r2; // far: z' <= w
|
||||||
|
|
||||||
|
// 归一化:法线长度把透视/缩放因数从距离里除掉
|
||||||
|
for (auto& p : f.planes) {
|
||||||
|
const f32 len = glm::length(glm::vec3(p));
|
||||||
|
if (len > 1e-8f) {
|
||||||
|
p /= len;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 包围球与视锥相交测试(外接球近似,略保守——宁多画不漏画)。
|
||||||
|
// 对每个平面:球心在平面负侧且距离超过 radius 才算外。
|
||||||
|
bool IntersectsSphere(const glm::vec3& center, f32 radius) const
|
||||||
|
{
|
||||||
|
for (const auto& p : planes) {
|
||||||
|
if (glm::dot(glm::vec3(p), center) + p.w < -radius) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Engine
|
||||||
|
|
||||||
|
#endif //HELLOVULKAN_FRUSTUM_H
|
||||||
@@ -1,12 +1,22 @@
|
|||||||
//
|
//
|
||||||
// Created by atdunbg on 2026/9/21.
|
// Created by atdunbg on 2026/9/21.
|
||||||
//
|
//
|
||||||
|
// Material:着色材质 = 一组纹理槽 + 一个描述符集。
|
||||||
|
//
|
||||||
|
// 槽位(binding = 槽位序号,与 simple_shader.frag 契约一致):
|
||||||
|
// 0 albedo(BaseColor,sRGB) 1 normal(切线空间法线,线性)
|
||||||
|
// 2 specular(高光强度/颜色,线性) 3 emissive(自发光,sRGB)
|
||||||
|
//
|
||||||
|
// 纹理非拥有(Model 的纹理缓存持有);空槽由调用方传默认纹理
|
||||||
|
//(白/平法线/黑/黑,Model 持有),shader 永远可以安全采样。
|
||||||
|
//
|
||||||
|
|
||||||
#ifndef HELLOVULKAN_MATERIAL_H
|
#ifndef HELLOVULKAN_MATERIAL_H
|
||||||
#define HELLOVULKAN_MATERIAL_H
|
#define HELLOVULKAN_MATERIAL_H
|
||||||
|
|
||||||
#include <Engine/Core/Types.h>
|
#include <Engine/Core/Types.h>
|
||||||
#include <vulkan/vulkan.hpp>
|
#include <vulkan/vulkan.hpp>
|
||||||
|
#include <array>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
namespace Engine
|
namespace Engine
|
||||||
@@ -18,20 +28,32 @@ namespace Engine
|
|||||||
class Material
|
class Material
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
// 槽位序号(与 binding 对齐)
|
||||||
|
enum Slot : u32 {
|
||||||
|
Albedo = 0,
|
||||||
|
Normal = 1,
|
||||||
|
Specular = 2,
|
||||||
|
Emissive = 3,
|
||||||
|
kSlotCount = 4,
|
||||||
|
};
|
||||||
|
|
||||||
Material(Device& device,
|
Material(Device& device,
|
||||||
DescriptorManager& descriptorManager,
|
DescriptorManager& descriptorManager,
|
||||||
vk::DescriptorSetLayout layout,
|
vk::DescriptorSetLayout layout,
|
||||||
Texture* texture);
|
const std::array<Texture*, kSlotCount>& textures);
|
||||||
~Material();
|
~Material();
|
||||||
|
|
||||||
Material(const Material&) = delete;
|
Material(const Material&) = delete;
|
||||||
Material& operator=(const Material&) = delete;
|
Material& operator=(const Material&) = delete;
|
||||||
|
|
||||||
vk::DescriptorSet GetDescriptorSet() const { return m_DescriptorSet; }
|
vk::DescriptorSet GetDescriptorSet() const { return m_DescriptorSet; }
|
||||||
Texture* GetTexture() const { return m_Texture; }
|
Texture* GetTexture(Slot slot) const { return m_Textures[slot]; }
|
||||||
|
|
||||||
|
// 材质缓存键:同贴图组合共享一份(Model 层使用)
|
||||||
|
static std::string MakeCacheKey(const std::string paths[kSlotCount]);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Texture* m_Texture = nullptr;
|
std::array<Texture*, kSlotCount> m_Textures{};
|
||||||
vk::DescriptorSet m_DescriptorSet;
|
vk::DescriptorSet m_DescriptorSet;
|
||||||
};
|
};
|
||||||
} // namespace Engine
|
} // namespace Engine
|
||||||
|
|||||||
@@ -21,25 +21,36 @@ namespace Engine
|
|||||||
class Mesh
|
class Mesh
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
// material 非拥有指针:Model 的材质缓存拥有(同贴图共享一份)
|
||||||
Mesh(Device& device, MemoryAllocator& allocator,
|
Mesh(Device& device, MemoryAllocator& allocator,
|
||||||
const std::vector<Vertex>& vertices,
|
const std::vector<Vertex>& vertices,
|
||||||
const std::vector<u32>& indices,
|
const std::vector<u32>& indices,
|
||||||
Scope<Material> material);
|
Material* material);
|
||||||
~Mesh();
|
~Mesh();
|
||||||
|
|
||||||
Mesh(const Mesh&) = delete;
|
Mesh(const Mesh&) = delete;
|
||||||
Mesh& operator=(const Mesh&) = delete;
|
Mesh& operator=(const Mesh&) = delete;
|
||||||
|
|
||||||
void Draw(vk::CommandBuffer cmd) const;
|
// instanceCount/firstInstance:GPUDriven 按 mesh 分 batch 后,
|
||||||
|
// 同 mesh 的 N 个实体一次 draw 完成,模型矩阵经实例索引取
|
||||||
|
void Draw(vk::CommandBuffer cmd, u32 instanceCount = 1, u32 firstInstance = 0) const;
|
||||||
|
|
||||||
Material* GetMaterial() const { return m_Material.get(); }
|
Material* GetMaterial() const { return m_Material; }
|
||||||
u32 GetIndexCount() const { return m_IndexCount; }
|
u32 GetIndexCount() const { return m_IndexCount; }
|
||||||
|
|
||||||
|
// 局部空间包围球(构造时从顶点算出),视锥剔除用
|
||||||
|
struct Bounds {
|
||||||
|
glm::vec3 center{0.0f};
|
||||||
|
f32 radius = 0.0f;
|
||||||
|
};
|
||||||
|
const Bounds& GetBounds() const { return m_Bounds; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Scope<Buffer> m_VertexBuffer;
|
Scope<Buffer> m_VertexBuffer;
|
||||||
Scope<Buffer> m_IndexBuffer;
|
Scope<Buffer> m_IndexBuffer;
|
||||||
Scope<Material> m_Material;
|
Material* m_Material;
|
||||||
u32 m_IndexCount = 0;
|
u32 m_IndexCount = 0;
|
||||||
|
Bounds m_Bounds;
|
||||||
|
|
||||||
u64 m_VertexBytes = 0;
|
u64 m_VertexBytes = 0;
|
||||||
u64 m_IndexBytes = 0;
|
u64 m_IndexBytes = 0;
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <Engine/Core/Types.h>
|
#include <Engine/Core/Types.h>
|
||||||
|
#include <Engine/Renderer/Material.h>
|
||||||
#include <Engine/Renderer/Mesh.h>
|
#include <Engine/Renderer/Mesh.h>
|
||||||
#include <vulkan/vulkan.hpp>
|
#include <vulkan/vulkan.hpp>
|
||||||
#include <array>
|
#include <array>
|
||||||
@@ -25,6 +26,7 @@ namespace Engine
|
|||||||
class MemoryAllocator;
|
class MemoryAllocator;
|
||||||
class DescriptorManager;
|
class DescriptorManager;
|
||||||
class Texture;
|
class Texture;
|
||||||
|
class Material;
|
||||||
|
|
||||||
class Model
|
class Model
|
||||||
{
|
{
|
||||||
@@ -58,20 +60,29 @@ namespace Engine
|
|||||||
void LoadModel(const std::string& path);
|
void LoadModel(const std::string& path);
|
||||||
void ProcessNode(aiNode* node, const aiScene* scene);
|
void ProcessNode(aiNode* node, const aiScene* scene);
|
||||||
Scope<Mesh> ProcessMesh(aiMesh* mesh, const aiScene* scene);
|
Scope<Mesh> ProcessMesh(aiMesh* mesh, const aiScene* scene);
|
||||||
|
void InitDefaultTextures();
|
||||||
|
|
||||||
Texture* GetOrLoadTexture(const std::string& path);
|
Texture* GetOrLoadTexture(const std::string& path, bool srgb);
|
||||||
|
|
||||||
|
// 材质缓存入口:同贴图组合共享同一 Material(一个描述符集),
|
||||||
|
// 槽位路径为空/文件缺失折到对应默认纹理。路径已做反斜杠归一化。
|
||||||
|
// paths 下标 = Material::Slot(albedo/normal/specular/emissive)
|
||||||
|
Material* GetOrCreateMaterial(const std::string paths[Material::kSlotCount]);
|
||||||
|
|
||||||
Device& m_Device;
|
Device& m_Device;
|
||||||
MemoryAllocator& m_Allocator;
|
MemoryAllocator& m_Allocator;
|
||||||
DescriptorManager& m_DescriptorManager;
|
DescriptorManager& m_DescriptorManager;
|
||||||
vk::DescriptorSetLayout m_TextureLayout;
|
vk::DescriptorSetLayout m_TextureLayout;
|
||||||
|
|
||||||
|
// 缓存声明在 m_Meshes 之前:mesh 持 Material* 裸指针,
|
||||||
|
// 析构序 = meshes 先于材质/纹理缓存
|
||||||
|
std::unordered_map<std::string, Scope<Material>> m_MaterialCache;
|
||||||
|
std::unordered_map<std::string, Scope<Texture>> m_TextureCache;
|
||||||
|
// 各槽位默认纹理(材质缺图时兜底):白 / 平法线 / 黑高光 / 黑自发光
|
||||||
|
std::array<Scope<Texture>, Material::kSlotCount> m_DefaultTextures;
|
||||||
|
|
||||||
std::vector<Scope<Mesh>> m_Meshes;
|
std::vector<Scope<Mesh>> m_Meshes;
|
||||||
std::string m_ModelPath;
|
std::string m_ModelPath;
|
||||||
|
|
||||||
// 纹理缓存:同一路径只加载一次
|
|
||||||
std::unordered_map<std::string, Scope<Texture>> m_TextureCache;
|
|
||||||
Scope<Texture> m_DefaultTexture;
|
|
||||||
};
|
};
|
||||||
} // namespace Engine
|
} // namespace Engine
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
//
|
||||||
|
// Created by atdunbg on 2026/9/23.
|
||||||
|
//
|
||||||
|
// BloomPass:Extract(HDR 软阈值)→ BlurH → BlurV,全部半分辨率。
|
||||||
|
// 一个"效果"对应多个 RG pass 的例子(Unity 的一个 ScriptableRenderPass
|
||||||
|
// 常含多个 PassEvent 阶段,同构)。
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef HELLOVULKAN_BLOOMPASS_H
|
||||||
|
#define HELLOVULKAN_BLOOMPASS_H
|
||||||
|
|
||||||
|
#include <Engine/Renderer/Passes/RenderPass.h>
|
||||||
|
#include <Engine/Renderer/Passes/FullscreenGpu.h>
|
||||||
|
#include <vulkan/vulkan.hpp>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace Engine::Passes {
|
||||||
|
|
||||||
|
class BloomPass final : public RenderPass {
|
||||||
|
public:
|
||||||
|
BloomPass(FullscreenGpu& gpu, PostProcessParams& params,
|
||||||
|
RG::TextureHandle hdrColor,
|
||||||
|
RG::TextureHandle bloomA, RG::TextureHandle bloomB);
|
||||||
|
~BloomPass() override = default;
|
||||||
|
|
||||||
|
void Register(RG::RenderGraph& rg) override;
|
||||||
|
void OnTargetsRebuilt(RG::RenderGraph& rg) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
FullscreenGpu& m_Gpu;
|
||||||
|
PostProcessParams& m_Params;
|
||||||
|
RG::TextureHandle m_HDR;
|
||||||
|
RG::TextureHandle m_A; // 提取 + 垂直模糊结果
|
||||||
|
RG::TextureHandle m_B; // 水平模糊中间结果
|
||||||
|
|
||||||
|
// per-slot 输入描述符
|
||||||
|
std::vector<vk::DescriptorSet> m_ExtractSets;
|
||||||
|
std::vector<vk::DescriptorSet> m_BlurHSets;
|
||||||
|
std::vector<vk::DescriptorSet> m_BlurVSets;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Engine::Passes
|
||||||
|
|
||||||
|
#endif //HELLOVULKAN_BLOOMPASS_H
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
//
|
||||||
|
// Created by atdunbg on 2026/9/23.
|
||||||
|
//
|
||||||
|
// ForwardPass:相机视角渲染到 HDR 颜色图(线性光照值)。
|
||||||
|
// 显示变换不在本 pass——HDRColor 经 Tonemap 归一后才写 Backbuffer
|
||||||
|
// (对齐 UE/Unity:SceneColor(HDR) → tonemapper → UI)。
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef HELLOVULKAN_FORWARDPASS_H
|
||||||
|
#define HELLOVULKAN_FORWARDPASS_H
|
||||||
|
|
||||||
|
#include <Engine/Renderer/Passes/RenderPass.h>
|
||||||
|
#include <Engine/Renderer/Passes/SceneGpu.h>
|
||||||
|
#include <Engine/Renderer/SceneTypes.h>
|
||||||
|
|
||||||
|
namespace Engine::Passes {
|
||||||
|
|
||||||
|
class ForwardPass final : public RenderPass {
|
||||||
|
public:
|
||||||
|
ForwardPass(SceneGpu& gpu, FrameSceneData& frame,
|
||||||
|
RG::TextureHandle hdrColor, RG::TextureHandle velocity,
|
||||||
|
RG::TextureHandle depth,
|
||||||
|
const std::array<RG::TextureHandle, kMaxCascades>& shadowMaps)
|
||||||
|
: m_Gpu(gpu), m_Frame(frame),
|
||||||
|
m_HDRColor(hdrColor), m_Velocity(velocity),
|
||||||
|
m_Depth(depth), m_ShadowMaps(shadowMaps) {}
|
||||||
|
|
||||||
|
void Register(RG::RenderGraph& rg) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
SceneGpu& m_Gpu;
|
||||||
|
FrameSceneData& m_Frame;
|
||||||
|
RG::TextureHandle m_HDRColor;
|
||||||
|
RG::TextureHandle m_Velocity; // TAA 运动向量(第二颜色附件)
|
||||||
|
RG::TextureHandle m_Depth;
|
||||||
|
std::array<RG::TextureHandle, kMaxCascades> m_ShadowMaps;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Engine::Passes
|
||||||
|
|
||||||
|
#endif //HELLOVULKAN_FORWARDPASS_H
|
||||||
@@ -0,0 +1,81 @@
|
|||||||
|
//
|
||||||
|
// Created by atdunbg on 2026/9/23.
|
||||||
|
//
|
||||||
|
// FullscreenGpu:全屏后处理共享装配(Tonemap/Bloom 系 pass 共用)。
|
||||||
|
//
|
||||||
|
// 全屏 pass 的共性收口在这里:Set0 = {输入0, 输入1}、push 16B 通用结构
|
||||||
|
// (规避 VUID-08878 的"range 定义必须一致")、无顶点输入全屏三角、
|
||||||
|
// 公共录制函数。新增全屏效果 = 复用本类 + 新增一个 frag shader。
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef HELLOVULKAN_FULLSCREENGPU_H
|
||||||
|
#define HELLOVULKAN_FULLSCREENGPU_H
|
||||||
|
|
||||||
|
#include <Engine/Core/Types.h>
|
||||||
|
#include <Engine/RenderGraph/RenderGraph.h>
|
||||||
|
#include <vulkan/vulkan.hpp>
|
||||||
|
|
||||||
|
namespace Engine {
|
||||||
|
|
||||||
|
class Device;
|
||||||
|
class DescriptorManager;
|
||||||
|
class ShaderManager;
|
||||||
|
|
||||||
|
namespace Passes {
|
||||||
|
|
||||||
|
// 全屏 pass 通用 push(16B):所有全屏 pass 统一声明与推送
|
||||||
|
struct FullscreenPush {
|
||||||
|
f32 exposure;
|
||||||
|
f32 param0; // 各 pass 自定语义(tonemap: aces 开关)
|
||||||
|
f32 param1;
|
||||||
|
f32 param2;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 后处理链参数(ImGui 可调;Renderer 持有,pass 按引用读取)
|
||||||
|
struct PostProcessParams {
|
||||||
|
f32 exposure = 1.0f;
|
||||||
|
bool useACES = true;
|
||||||
|
f32 bloomThreshold = 1.0f; // HDR 域亮度阈值
|
||||||
|
f32 bloomStrength = 0.8f; // 叠加强度
|
||||||
|
bool showTemporalDiff = false; // 调试视图:显示帧历史差异
|
||||||
|
};
|
||||||
|
|
||||||
|
class FullscreenGpu {
|
||||||
|
public:
|
||||||
|
FullscreenGpu(Device& device, DescriptorManager& descriptors,
|
||||||
|
ShaderManager& shaders);
|
||||||
|
~FullscreenGpu();
|
||||||
|
|
||||||
|
FullscreenGpu(const FullscreenGpu&) = delete;
|
||||||
|
FullscreenGpu& operator=(const FullscreenGpu&) = delete;
|
||||||
|
|
||||||
|
vk::DescriptorSetLayout setLayout; // Set0 = {输入0, 输入1, 调试输入}
|
||||||
|
vk::PipelineLayout layout; // push 16B(FullscreenPush)
|
||||||
|
vk::Sampler sampler;
|
||||||
|
vk::ShaderEXT vert{}, extractFrag{}, blurFrag{}, tonemapFrag{};
|
||||||
|
vk::ShaderEXT diffFrag{}; // temporal_diff(帧历史调试)
|
||||||
|
vk::ShaderEXT taaFrag{}; // taa(时域抗锯齿 resolve)
|
||||||
|
|
||||||
|
// 各 pass 自己持有 per-slot set;分配统一走这里
|
||||||
|
vk::DescriptorSet AllocateSet();
|
||||||
|
|
||||||
|
// 输入图片绑定(RG 重编译后重绑;物理镜像按槽位各一份)
|
||||||
|
void UpdateSetInput(vk::DescriptorSet set, u32 binding,
|
||||||
|
RG::RenderGraph& rg, RG::TextureHandle input,
|
||||||
|
u32 slot);
|
||||||
|
|
||||||
|
// 全屏 pass 公共录制:动态状态/draw 全一致,仅附件 view、
|
||||||
|
// 输入 set、push 值不同
|
||||||
|
void RecordFullscreen(RG::RenderGraphContext& ctx, vk::ImageView view,
|
||||||
|
vk::ShaderEXT frag, const vk::DescriptorSet& set,
|
||||||
|
const FullscreenPush& push);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Device& m_Device;
|
||||||
|
DescriptorManager& m_Descriptors;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Passes
|
||||||
|
} // namespace Engine
|
||||||
|
|
||||||
|
#endif //HELLOVULKAN_FULLSCREENGPU_H
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
//
|
||||||
|
// Created by atdunbg on 2026/9/23.
|
||||||
|
//
|
||||||
|
// ImGuiPass:叠加层(loadOp=Load,不清屏)。
|
||||||
|
// 框架只负责把 draw data 录进 cmd;UI 内容全部由派生类在
|
||||||
|
// OnRenderOverlay() 里构建,框架自身不画任何 ImGui 控件。
|
||||||
|
// 未启用叠加层时空转(begin/end 一次空 rendering region)。
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef HELLOVULKAN_IMGUIPASS_H
|
||||||
|
#define HELLOVULKAN_IMGUIPASS_H
|
||||||
|
|
||||||
|
#include <Engine/Renderer/Passes/RenderPass.h>
|
||||||
|
|
||||||
|
namespace Engine {
|
||||||
|
|
||||||
|
class ImGuiLayer;
|
||||||
|
|
||||||
|
namespace Passes {
|
||||||
|
|
||||||
|
class ImGuiPass final : public RenderPass {
|
||||||
|
public:
|
||||||
|
// 二级指针:Renderer::SetImGuiLayer 在 Register 之后才可能被调
|
||||||
|
//(EnableImGui 时机由派生类决定),执行期解引用取最新值
|
||||||
|
ImGuiPass(ImGuiLayer** layer, RG::TextureHandle backbuffer)
|
||||||
|
: m_Layer(layer), m_Backbuffer(backbuffer) {}
|
||||||
|
|
||||||
|
void Register(RG::RenderGraph& rg) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
ImGuiLayer** m_Layer;
|
||||||
|
RG::TextureHandle m_Backbuffer;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Passes
|
||||||
|
} // namespace Engine
|
||||||
|
|
||||||
|
#endif //HELLOVULKAN_IMGUIPASS_H
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
//
|
||||||
|
// Created by atdunbg on 2026/9/23.
|
||||||
|
//
|
||||||
|
// PresentPass:transition 到 ePresentSrcKHR。
|
||||||
|
// layout 转换由 RG 的 barrier 完成,实际 vkQueuePresentKHR 由 Renderer 驱动。
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef HELLOVULKAN_PRESENTPASS_H
|
||||||
|
#define HELLOVULKAN_PRESENTPASS_H
|
||||||
|
|
||||||
|
#include <Engine/Renderer/Passes/RenderPass.h>
|
||||||
|
|
||||||
|
namespace Engine::Passes {
|
||||||
|
|
||||||
|
class PresentPass final : public RenderPass {
|
||||||
|
public:
|
||||||
|
explicit PresentPass(RG::TextureHandle backbuffer)
|
||||||
|
: m_Backbuffer(backbuffer) {}
|
||||||
|
|
||||||
|
void Register(RG::RenderGraph& rg) override
|
||||||
|
{
|
||||||
|
rg.AddPass("Present", [](RG::RenderGraphContext&) {})
|
||||||
|
.Read(m_Backbuffer, RG::ResourceUse::Present);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
RG::TextureHandle m_Backbuffer;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Engine::Passes
|
||||||
|
|
||||||
|
#endif //HELLOVULKAN_PRESENTPASS_H
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
//
|
||||||
|
// Created by atdunbg on 2026/9/23.
|
||||||
|
//
|
||||||
|
// RenderPass:渲染 pass 对象基类(Unity ScriptableRenderPass 思路)。
|
||||||
|
//
|
||||||
|
// pass 自己负责两件事,框架(Renderer)不再手写"超级函数":
|
||||||
|
// Register —— 向 RenderGraph 声明资源使用 + 录制 lambda;
|
||||||
|
// 执行序由 RG 拓扑排序决定(writer 必先于 reader),
|
||||||
|
// 与注册顺序无关
|
||||||
|
// OnTargetsRebuilt —— RG 重编译(resize / vsync 切换)后重绑描述符
|
||||||
|
// (物理镜像按槽位可能全部换新)
|
||||||
|
//
|
||||||
|
// pass 之间不互相依赖:数据流只经 RG 资源句柄。新增效果 = 新增一个
|
||||||
|
// RenderPass 子类 + Renderer::BuildGraph 里注册一行,其它代码不动。
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef HELLOVULKAN_RENDERPASS_H
|
||||||
|
#define HELLOVULKAN_RENDERPASS_H
|
||||||
|
|
||||||
|
#include <Engine/RenderGraph/RenderGraph.h>
|
||||||
|
|
||||||
|
namespace Engine::Passes {
|
||||||
|
|
||||||
|
class RenderPass {
|
||||||
|
public:
|
||||||
|
virtual ~RenderPass() = default;
|
||||||
|
|
||||||
|
virtual void Register(RG::RenderGraph& rg) = 0;
|
||||||
|
virtual void OnTargetsRebuilt(RG::RenderGraph& rg) { (void)rg; }
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Engine::Passes
|
||||||
|
|
||||||
|
#endif //HELLOVULKAN_RENDERPASS_H
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
//
|
||||||
|
// Created by atdunbg on 2026/9/23.
|
||||||
|
//
|
||||||
|
// SceneGpu:场景级 GPU 共享装配(Shadow/Forward 及未来几何类 pass 共用)。
|
||||||
|
//
|
||||||
|
// 从 Renderer 拆出的装配体:set 布局 / pipeline 布局 / shader object /
|
||||||
|
// 采样器 / per-slot 描述符集合。新增几何 pass(延迟、调试线框等)直接
|
||||||
|
// 复用本装配,不必碰 Renderer。
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef HELLOVULKAN_SCENEGPU_H
|
||||||
|
#define HELLOVULKAN_SCENEGPU_H
|
||||||
|
|
||||||
|
#include <Engine/Core/FrameContext.h>
|
||||||
|
#include <Engine/Renderer/SceneTypes.h>
|
||||||
|
#include <Engine/RenderGraph/RenderGraph.h>
|
||||||
|
#include <vulkan/vulkan.hpp>
|
||||||
|
#include <array>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace Engine {
|
||||||
|
|
||||||
|
class Device;
|
||||||
|
class DescriptorManager;
|
||||||
|
class ShaderManager;
|
||||||
|
class UniformBuffer;
|
||||||
|
|
||||||
|
namespace Passes {
|
||||||
|
|
||||||
|
class SceneGpu {
|
||||||
|
public:
|
||||||
|
SceneGpu(Device& device, DescriptorManager& descriptors,
|
||||||
|
ShaderManager& shaders,
|
||||||
|
UniformBuffer& ubo, UniformBuffer& objectBuffer,
|
||||||
|
UniformBuffer& prevObjectBuffer);
|
||||||
|
~SceneGpu();
|
||||||
|
|
||||||
|
SceneGpu(const SceneGpu&) = delete;
|
||||||
|
SceneGpu& operator=(const SceneGpu&) = delete;
|
||||||
|
|
||||||
|
// ---- set 布局(shader 契约,改绑定必须同步 shaders/)----
|
||||||
|
vk::DescriptorSetLayout uboSetLayout; // Set0 = {UBO, ObjectBuffer, PrevObjectBuffer}
|
||||||
|
vk::DescriptorSetLayout textureSetLayout; // Set1 = {albedo}
|
||||||
|
vk::DescriptorSetLayout shadowSetLayout; // Set2 = {级联 0/1/2 阴影图}
|
||||||
|
vk::PipelineLayout forwardLayout; // 3 sets,无 push(GPUDriven 经 SSBO)
|
||||||
|
vk::PipelineLayout shadowLayout; // Set0,push = lightVP 64B
|
||||||
|
|
||||||
|
// per-slot 描述符:Set0(UBO+实例 SSBO)与 Set2(阴影图采样)
|
||||||
|
std::vector<vk::DescriptorSet> uboSets;
|
||||||
|
std::vector<vk::DescriptorSet> shadowSets;
|
||||||
|
|
||||||
|
// 阴影采样器:clamp-to-border 白色 = 光视锥外自动无阴影
|
||||||
|
//(省 shader 出界分支)
|
||||||
|
vk::Sampler shadowSampler;
|
||||||
|
|
||||||
|
vk::ShaderEXT forwardVert{}, forwardFrag{};
|
||||||
|
vk::ShaderEXT shadowVert{}, shadowFrag{};
|
||||||
|
|
||||||
|
// RG 重编译后重绑阴影图(物理镜像按槽位各一份)
|
||||||
|
void BindShadowMaps(RG::RenderGraph& rg,
|
||||||
|
const std::array<RG::TextureHandle, kMaxCascades>& maps);
|
||||||
|
|
||||||
|
private:
|
||||||
|
Device& m_Device;
|
||||||
|
DescriptorManager& m_Descriptors;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Passes
|
||||||
|
} // namespace Engine
|
||||||
|
|
||||||
|
#endif //HELLOVULKAN_SCENEGPU_H
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
//
|
||||||
|
// Created by atdunbg on 2026/9/23.
|
||||||
|
//
|
||||||
|
// ShadowPass:CSM 级联阴影。单 pass 渲 kMaxCascades 个 region,
|
||||||
|
// 每级联独立光源 VP(FrameSceneData::lightSpace[c],SceneRenderer 拟合),
|
||||||
|
// 消费光源视锥剔除结果(FrameSceneData::shadowView——相机外实体仍投影子)。
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef HELLOVULKAN_SHADOWPASS_H
|
||||||
|
#define HELLOVULKAN_SHADOWPASS_H
|
||||||
|
|
||||||
|
#include <Engine/Renderer/Passes/RenderPass.h>
|
||||||
|
#include <Engine/Renderer/Passes/SceneGpu.h>
|
||||||
|
#include <Engine/Renderer/SceneTypes.h>
|
||||||
|
#include <array>
|
||||||
|
|
||||||
|
namespace Engine::Passes {
|
||||||
|
|
||||||
|
class ShadowPass final : public RenderPass {
|
||||||
|
public:
|
||||||
|
ShadowPass(SceneGpu& gpu, FrameSceneData& frame,
|
||||||
|
const std::array<RG::TextureHandle, kMaxCascades>& shadowMaps)
|
||||||
|
: m_Gpu(gpu), m_Frame(frame), m_ShadowMaps(shadowMaps) {}
|
||||||
|
|
||||||
|
void Register(RG::RenderGraph& rg) override;
|
||||||
|
void OnTargetsRebuilt(RG::RenderGraph& rg) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
SceneGpu& m_Gpu;
|
||||||
|
FrameSceneData& m_Frame;
|
||||||
|
std::array<RG::TextureHandle, kMaxCascades> m_ShadowMaps;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Engine::Passes
|
||||||
|
|
||||||
|
#endif //HELLOVULKAN_SHADOWPASS_H
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
//
|
||||||
|
// Created by atdunbg on 2026/9/23.
|
||||||
|
//
|
||||||
|
// TAAPass:时域抗锯齿 resolve。
|
||||||
|
// 读当前帧 HDR + velocity + 历史累积(ReadHistory 自引用 TAAColor),
|
||||||
|
// 邻域 clamp 去鬼影后时域混合。TAAColor 既是输出也是下一帧的历史——
|
||||||
|
// 这是帧历史资源最典型的"自引用"形态(UE 的 history pool 同构)。
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef HELLOVULKAN_TAAPASS_H
|
||||||
|
#define HELLOVULKAN_TAAPASS_H
|
||||||
|
|
||||||
|
#include <Engine/Renderer/Passes/RenderPass.h>
|
||||||
|
#include <Engine/Renderer/Passes/FullscreenGpu.h>
|
||||||
|
#include <vulkan/vulkan.hpp>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace Engine::Passes {
|
||||||
|
|
||||||
|
class TAAPass final : public RenderPass {
|
||||||
|
public:
|
||||||
|
TAAPass(FullscreenGpu& gpu, f32 historyAlpha,
|
||||||
|
RG::TextureHandle hdrColor, RG::TextureHandle velocity,
|
||||||
|
RG::TextureHandle taaColor);
|
||||||
|
~TAAPass() override = default;
|
||||||
|
|
||||||
|
void Register(RG::RenderGraph& rg) override;
|
||||||
|
void OnTargetsRebuilt(RG::RenderGraph& rg) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
FullscreenGpu& m_Gpu;
|
||||||
|
f32 m_HistoryAlpha;
|
||||||
|
RG::TextureHandle m_HDR;
|
||||||
|
RG::TextureHandle m_Velocity;
|
||||||
|
RG::TextureHandle m_TAAColor; // 输出 = 下一帧的历史
|
||||||
|
|
||||||
|
std::vector<vk::DescriptorSet> m_Sets; // per-slot
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Engine::Passes
|
||||||
|
|
||||||
|
#endif //HELLOVULKAN_TAAPASS_H
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
//
|
||||||
|
// Created by atdunbg on 2026/9/23.
|
||||||
|
//
|
||||||
|
// TemporalDiffPass:帧历史链路调试视图。
|
||||||
|
// 读 HDRColor 的帧历史镜像(ReadHistory)与当前帧,输出 |prev − cur| × 增益。
|
||||||
|
// 静止画面纯黑、运动边缘发光 = 帧历史机制正确;噪声/残影错位 = 有 bug。
|
||||||
|
// 也是 RenderGraph::ReadHistory 的参考实现(TAA 照此模式接入)。
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef HELLOVULKAN_TEMPORALDIFFPASS_H
|
||||||
|
#define HELLOVULKAN_TEMPORALDIFFPASS_H
|
||||||
|
|
||||||
|
#include <Engine/Renderer/Passes/RenderPass.h>
|
||||||
|
#include <Engine/Renderer/Passes/FullscreenGpu.h>
|
||||||
|
#include <vulkan/vulkan.hpp>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace Engine::Passes {
|
||||||
|
|
||||||
|
class TemporalDiffPass final : public RenderPass {
|
||||||
|
public:
|
||||||
|
TemporalDiffPass(FullscreenGpu& gpu, f32 gain,
|
||||||
|
RG::TextureHandle hdrColor, RG::TextureHandle output);
|
||||||
|
~TemporalDiffPass() override = default;
|
||||||
|
|
||||||
|
void Register(RG::RenderGraph& rg) override;
|
||||||
|
void OnTargetsRebuilt(RG::RenderGraph& rg) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
FullscreenGpu& m_Gpu;
|
||||||
|
f32 m_Gain;
|
||||||
|
RG::TextureHandle m_HDR;
|
||||||
|
RG::TextureHandle m_Output;
|
||||||
|
|
||||||
|
std::vector<vk::DescriptorSet> m_Sets; // per-slot
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Engine::Passes
|
||||||
|
|
||||||
|
#endif //HELLOVULKAN_TEMPORALDIFFPASS_H
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
//
|
||||||
|
// Created by atdunbg on 2026/9/23.
|
||||||
|
//
|
||||||
|
// TonemapPass:HDR(+bloom)→ 显示空间,全屏三角写 Backbuffer。
|
||||||
|
// 显示变换在此显式建模(对齐 UE/Unity:HDR SceneColor → tonemapper → UI)。
|
||||||
|
// Backbuffer 从此只接收"显示空间"颜色:tonemap 直接写、ImGui 叠加。
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef HELLOVULKAN_TONEMAPPASS_H
|
||||||
|
#define HELLOVULKAN_TONEMAPPASS_H
|
||||||
|
|
||||||
|
#include <Engine/Renderer/Passes/RenderPass.h>
|
||||||
|
#include <Engine/Renderer/Passes/FullscreenGpu.h>
|
||||||
|
#include <vulkan/vulkan.hpp>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace Engine::Passes {
|
||||||
|
|
||||||
|
class TonemapPass final : public RenderPass {
|
||||||
|
public:
|
||||||
|
TonemapPass(FullscreenGpu& gpu, PostProcessParams& params,
|
||||||
|
RG::TextureHandle hdrColor, RG::TextureHandle bloomA,
|
||||||
|
RG::TextureHandle temporalDiff, RG::TextureHandle backbuffer);
|
||||||
|
~TonemapPass() override = default;
|
||||||
|
|
||||||
|
void Register(RG::RenderGraph& rg) override;
|
||||||
|
void OnTargetsRebuilt(RG::RenderGraph& rg) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
FullscreenGpu& m_Gpu;
|
||||||
|
PostProcessParams& m_Params;
|
||||||
|
RG::TextureHandle m_HDR;
|
||||||
|
RG::TextureHandle m_A;
|
||||||
|
RG::TextureHandle m_TemporalDiff; // 调试视图输入(当前帧槽位)
|
||||||
|
RG::TextureHandle m_Backbuffer;
|
||||||
|
|
||||||
|
std::vector<vk::DescriptorSet> m_Sets; // per-slot
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Engine::Passes
|
||||||
|
|
||||||
|
#endif //HELLOVULKAN_TONEMAPPASS_H
|
||||||
@@ -1,6 +1,19 @@
|
|||||||
//
|
//
|
||||||
// Created by atdunbg on 2026/9/20.
|
// 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
|
#ifndef HELLOVULKAN_RENDERER_H
|
||||||
#define HELLOVULKAN_RENDERER_H
|
#define HELLOVULKAN_RENDERER_H
|
||||||
@@ -9,12 +22,12 @@
|
|||||||
|
|
||||||
#include <Engine/Core/Types.h>
|
#include <Engine/Core/Types.h>
|
||||||
#include <Engine/Core/FrameContext.h>
|
#include <Engine/Core/FrameContext.h>
|
||||||
|
#include <Engine/Renderer/SceneTypes.h>
|
||||||
#include <Engine/RenderGraph/RenderGraph.h>
|
#include <Engine/RenderGraph/RenderGraph.h>
|
||||||
#include <vulkan/vulkan.hpp>
|
#include <vulkan/vulkan.hpp>
|
||||||
|
#include <array>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#include <glm/glm.hpp>
|
|
||||||
|
|
||||||
namespace Engine {
|
namespace Engine {
|
||||||
|
|
||||||
class Window;
|
class Window;
|
||||||
@@ -26,18 +39,14 @@ namespace Engine {
|
|||||||
class SyncManager;
|
class SyncManager;
|
||||||
class CommandManager;
|
class CommandManager;
|
||||||
class UniformBuffer;
|
class UniformBuffer;
|
||||||
class Model;
|
class SceneRenderer;
|
||||||
|
class VulkanAssetUploader;
|
||||||
namespace ECS { class Scene; }
|
|
||||||
namespace Assets { class AssetManager; }
|
|
||||||
class ImGuiLayer;
|
class ImGuiLayer;
|
||||||
|
|
||||||
// Renderer 职责(收敛后):
|
namespace ECS { class Scene; }
|
||||||
// 1. 装配底层模块(Device/Swapchain/Sync/Command/Descriptor/Shader)
|
namespace Assets { class AssetManager; class IAssetUploader; }
|
||||||
// 2. 声明并驱动 RenderGraph(pass 结构在这里组装)
|
namespace Passes { class RenderPass; class SceneGpu; class FullscreenGpu; class PostProcessParams; }
|
||||||
// 3. Render(Scene&, AssetManager&):从 ECS 场景读取相机/灯光/渲染体,
|
|
||||||
// 经资产层解析句柄后绘制
|
|
||||||
// 不拥有场景,不加载资产(资产归 AssetManager,磁盘 IO 与这里无关)。
|
|
||||||
class Renderer {
|
class Renderer {
|
||||||
public:
|
public:
|
||||||
Renderer(Window& window);
|
Renderer(Window& window);
|
||||||
@@ -52,91 +61,85 @@ namespace Engine {
|
|||||||
Device& GetDevice() const { return *m_Device; }
|
Device& GetDevice() const { return *m_Device; }
|
||||||
MemoryAllocator& GetAllocator() const { return *m_Allocator; }
|
MemoryAllocator& GetAllocator() const { return *m_Allocator; }
|
||||||
DescriptorManager& GetDescriptorManager() const { return *m_DescriptorManager; }
|
DescriptorManager& GetDescriptorManager() const { return *m_DescriptorManager; }
|
||||||
vk::DescriptorSetLayout GetTextureSetLayout() const { return m_TextureSetLayout; }
|
vk::DescriptorSetLayout GetTextureSetLayout() const; // 实现在 cpp(SceneGpu 成员)
|
||||||
vk::Format GetSwapchainFormat() const; // Swapchain 不完整,实现在 cpp
|
vk::Format GetSwapchainFormat() const;
|
||||||
u32 GetSwapchainImageCount() const;
|
u32 GetSwapchainImageCount() const;
|
||||||
|
// 资产上传服务(渲染层对资产层提供的 IAssetUploader 实现)
|
||||||
|
Assets::IAssetUploader& GetAssetUploader() const;
|
||||||
|
|
||||||
// 可选叠加层(ImGui):不设置则 RG 里的 ImGui pass 为空转。
|
// 可选叠加层(ImGui):不设置则 RG 里的 ImGui pass 为空转。
|
||||||
// 生命周期:层必须先于 Renderer 析构(Application 成员序保证)。
|
// 生命周期:层必须先于 Renderer 析构(Application 成员序保证)。
|
||||||
void SetImGuiLayer(ImGuiLayer* layer) { m_ImGuiLayer = layer; }
|
void SetImGuiLayer(ImGuiLayer* layer) { m_ImGuiLayer = layer; }
|
||||||
|
|
||||||
|
// ---- 后处理参数(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);
|
||||||
|
// 调试视图:显示帧历史差异(TemporalDiff pass 产出)
|
||||||
|
bool GetShowTemporalDiff() const;
|
||||||
|
void SetShowTemporalDiff(bool b);
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
|
||||||
|
// 垂直同步开关(present mode 切换会重建 swapchain),
|
||||||
|
// 只应在帧 UI 构建段调用(Render 之前)
|
||||||
|
void SetVSync(bool vsync);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void BuildGraph(); // 声明 pass 结构 + 首次 Compile
|
void BuildGraph(); // 声明 RG 资源 + 注册 pass 对象
|
||||||
void HandleResize();
|
void HandleResize();
|
||||||
void UpdateShadowDescriptors();
|
|
||||||
void UpdateFullscreenDescriptors();
|
|
||||||
void ReadGpuTimes(u32 frameSlot);
|
void ReadGpuTimes(u32 frameSlot);
|
||||||
|
|
||||||
Window& m_Window;
|
Window& m_Window;
|
||||||
|
|
||||||
// ---- 底层模块(析构序:后声明者先析构,RG 在 Device/Allocator 之前)----
|
// ---- 底层模块(析构序:后声明者先析构;引用他人者先析构)----
|
||||||
Scope<Device> m_Device;
|
Scope<Device> m_Device;
|
||||||
Scope<MemoryAllocator> m_Allocator;
|
Scope<MemoryAllocator> m_Allocator;
|
||||||
Scope<DescriptorManager> m_DescriptorManager;
|
Scope<DescriptorManager> m_DescriptorManager;
|
||||||
Scope<ShaderManager> m_ShaderManager;
|
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<Passes::SceneGpu> m_SceneGpu; // 场景类 pass 共享装配
|
||||||
|
Scope<Passes::FullscreenGpu> m_FullscreenGpu; // 全屏 pass 共享装配
|
||||||
Scope<Swapchain> m_Swapchain;
|
Scope<Swapchain> m_Swapchain;
|
||||||
Scope<SyncManager> m_SyncManager;
|
Scope<SyncManager> m_SyncManager;
|
||||||
Scope<CommandManager> m_CommandManager;
|
Scope<CommandManager> m_CommandManager;
|
||||||
Scope<UniformBuffer> m_UniformBuffer;
|
Scope<SceneRenderer> m_SceneRenderer; // 场景翻译层(先于 pass 构造)
|
||||||
Scope<RG::RenderGraph> m_RenderGraph;
|
Scope<RG::RenderGraph> m_RenderGraph;
|
||||||
|
Scope<VulkanAssetUploader> m_AssetUploader;
|
||||||
|
|
||||||
// ---- 管线装配(shader object 方案,无 vk::Pipeline)----
|
// pass 对象(注册序;执行序由 RG 拓扑排序决定)
|
||||||
// Forward layout:3 个 set(UBO/贴图/阴影图)+ 128B push(与 Shadow pass
|
std::vector<Scope<Passes::RenderPass>> m_Passes;
|
||||||
// 共用上限;forward 的 shader block 只有 64B,block ⊂ range 合法)
|
|
||||||
vk::PipelineLayout m_PipelineLayout;
|
|
||||||
vk::DescriptorSetLayout m_UBOSetLayout;
|
|
||||||
vk::DescriptorSetLayout m_TextureSetLayout;
|
|
||||||
vk::DescriptorSetLayout m_ShadowSetLayout;
|
|
||||||
std::vector<vk::DescriptorSet> m_UBODescriptorSets; // 每帧槽位一个
|
|
||||||
std::vector<vk::DescriptorSet> m_ShadowDescriptorSets; // 每帧槽位一个
|
|
||||||
vk::Sampler m_ShadowSampler;
|
|
||||||
vk::ShaderEXT m_VertShader{};
|
|
||||||
vk::ShaderEXT m_FragShader{};
|
|
||||||
|
|
||||||
// Shadow pass 独立装配:无 set,push constant = {lightVP, model} 128B
|
|
||||||
vk::PipelineLayout m_ShadowPipelineLayout;
|
|
||||||
vk::ShaderEXT m_ShadowVertShader{};
|
|
||||||
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{};
|
std::array<RG::TextureHandle, kMaxCascades> m_ShadowMaps{}; // CSM 级联
|
||||||
RG::TextureHandle m_HDRColor{}; // Forward 输出(线性 HDR),Tonemap 归一
|
RG::TextureHandle m_HDRColor{}; // Forward 输出(线性 HDR),TAA 归一
|
||||||
|
RG::TextureHandle m_Velocity{}; // Forward 输出(clip 空间运动向量)
|
||||||
|
RG::TextureHandle m_TAAColor{}; // TAA 累积输出(自引用历史),后处理源
|
||||||
RG::TextureHandle m_BloomA{}; // 半分辨率:提取 + 垂直模糊结果
|
RG::TextureHandle m_BloomA{}; // 半分辨率:提取 + 垂直模糊结果
|
||||||
RG::TextureHandle m_BloomB{}; // 半分辨率:水平模糊中间结果
|
RG::TextureHandle m_BloomB{}; // 半分辨率:水平模糊中间结果
|
||||||
|
RG::TextureHandle m_TemporalDiff{}; // 半分辨率:帧历史差异(调试)
|
||||||
|
|
||||||
// ---- 当帧绑定(Render() 期间有效,供 pass 回调读取)----
|
// pass 共享的可调参数(Renderer 持有,App 层访问器转发)
|
||||||
ECS::Scene* m_BoundScene = nullptr;
|
Scope<Passes::PostProcessParams> m_PostParams;
|
||||||
Assets::AssetManager* m_BoundAssets = nullptr;
|
|
||||||
glm::mat4 m_LightSpace{1.0f}; // 阴影光源 VP(每帧由 Render 算好)
|
|
||||||
|
|
||||||
// 可选叠加层(不拥有;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 可读)
|
// GPU timestamp(pass 级耗时,ImGui 可读)
|
||||||
vk::QueryPool m_QueryPool;
|
vk::QueryPool m_QueryPool;
|
||||||
u32 m_QueriesPerFrame = 0; // 每帧槽位的查询数 = pass 数 × 2
|
u32 m_QueriesPerFrame = 0; // 每帧槽位的查询数 = pass 数 × 2
|
||||||
@@ -145,22 +148,6 @@ namespace Engine {
|
|||||||
std::vector<f32> m_PassGpuMs; // 最近一帧各 pass 耗时
|
std::vector<f32> m_PassGpuMs; // 最近一帧各 pass 耗时
|
||||||
bool m_TimesPrimed[2] = {false, false}; // 每槽位首帧跳过读取
|
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;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,93 @@
|
|||||||
|
//
|
||||||
|
// Created by atdunbg on 2026/9/23.
|
||||||
|
//
|
||||||
|
// SceneRenderer:场景 → 渲染数据的每帧翻译层(UE FSceneRenderer /
|
||||||
|
// Unity CullingResults 的对应物)。
|
||||||
|
//
|
||||||
|
// 职责(全部是"读场景、产数据",不碰 GPU、不碰 Vulkan):
|
||||||
|
// 1. 相机/灯光/环境色 → UniformBufferObject 语义组装(含阴影光空间矩阵)
|
||||||
|
// 2. 单趟实例收集 + 双视图剔除(相机视锥/光源视锥各自独立可见列表——
|
||||||
|
// 相机外实体仍可能投影子进视野,shadow popping 的教训)
|
||||||
|
// Renderer 拿 FrameSceneData 写 buffer、驱动 pass;场景理解全部收口在此。
|
||||||
|
//
|
||||||
|
// 扩展性:
|
||||||
|
// - 多光源:lights 数组已就位(kMaxLights),每灯各自 lightSpace 即 CSM 前置
|
||||||
|
// - 级联阴影:BuildFrame 内按级联多出几份 lightSpace + shadowView,
|
||||||
|
// pass 侧按索引消费,本类结构不变
|
||||||
|
// - 遮挡剔除/GPU 剔除:只改本类收集逻辑,下游无感知
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef HELLOVULKAN_SCENERENDERER_H
|
||||||
|
#define HELLOVULKAN_SCENERENDERER_H
|
||||||
|
|
||||||
|
#include <Engine/Core/Types.h>
|
||||||
|
#include <Engine/Renderer/SceneTypes.h>
|
||||||
|
#include <vulkan/vulkan.hpp>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
namespace Engine {
|
||||||
|
|
||||||
|
namespace ECS { class Scene; }
|
||||||
|
namespace Assets { class AssetManager; }
|
||||||
|
|
||||||
|
class SceneRenderer {
|
||||||
|
public:
|
||||||
|
// 从场景构建当帧渲染数据。无 primary 相机返回 false(渲染跳帧)。
|
||||||
|
// targetExtent 用于 TAA 子像素抖动的 NDC 偏移折算。
|
||||||
|
bool BuildFrame(ECS::Scene& scene, Assets::AssetManager& assets,
|
||||||
|
vk::Extent2D targetExtent);
|
||||||
|
|
||||||
|
FrameSceneData& Frame() { return m_Frame; }
|
||||||
|
|
||||||
|
// ---- 遥测 ----
|
||||||
|
u32 GetMainInstanceCount() const { return m_Frame.mainView.instanceCount; }
|
||||||
|
u32 GetShadowInstanceCount() const
|
||||||
|
{
|
||||||
|
u32 sum = 0;
|
||||||
|
for (const auto& c : m_Frame.shadowCascades) sum += c.instanceCount;
|
||||||
|
return sum;
|
||||||
|
}
|
||||||
|
f32 GetCollectMs() const { return m_Frame.collectMs; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
// CSM:把相机视锥按实用分割切 N 段,每段独立拟合光源正交投影
|
||||||
|
//(含纹素对齐防阴影边缘闪烁)。写入(Shadow pass push)与采样
|
||||||
|
//(Forward UBO)共用同一组矩阵。
|
||||||
|
void ComputeCascades(const glm::vec3& shadowDir);
|
||||||
|
|
||||||
|
FrameSceneData m_Frame;
|
||||||
|
|
||||||
|
// ---- 剔除中间量(每帧 clear 复用,拼接进 m_Frame 后保留容量)----
|
||||||
|
struct ViewScratch {
|
||||||
|
CullResult result;
|
||||||
|
std::vector<glm::mat4> worlds;
|
||||||
|
std::vector<glm::mat4> prevWorlds;
|
||||||
|
std::unordered_map<Mesh*, u32> lookup;
|
||||||
|
|
||||||
|
void Clear()
|
||||||
|
{
|
||||||
|
result.batches.clear();
|
||||||
|
result.instanceCount = 0;
|
||||||
|
worlds.clear();
|
||||||
|
prevWorlds.clear();
|
||||||
|
lookup.clear();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
ViewScratch m_MainScratch;
|
||||||
|
ViewScratch m_CascScratch[kMaxCascades];
|
||||||
|
|
||||||
|
// ---- TAA 时域状态 ----
|
||||||
|
u64 m_FrameIndex = 0; // 抖动序列游标(Halton 2,3 ×8 采样)
|
||||||
|
glm::mat4 m_LastView{1.0f}; // 上一帧视图(velocity)
|
||||||
|
glm::mat4 m_LastProjNoJitter{1.0f}; // 上一帧未抖动投影(velocity)
|
||||||
|
// 实体的上一帧世界矩阵(key = entt 实体 integral)。
|
||||||
|
// 每帧重建 m_CurWorlds 后与 m_PrevWorlds swap——自动清理销毁实体
|
||||||
|
std::unordered_map<u32, glm::mat4> m_PrevWorlds;
|
||||||
|
std::unordered_map<u32, glm::mat4> m_CurWorlds;
|
||||||
|
|
||||||
|
bool m_TruncationWarned = false; // 实例缓冲打满只告警一次
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Engine
|
||||||
|
|
||||||
|
#endif //HELLOVULKAN_SCENERENDERER_H
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
//
|
||||||
|
// Created by atdunbg on 2026/9/23.
|
||||||
|
//
|
||||||
|
// SceneTypes:场景 → GPU 的数据契约(CPU 结构镜像 shader 布局)。
|
||||||
|
//
|
||||||
|
// SceneRenderer 生产、Renderer/各 pass 消费;改字段必须同步 shaders/。
|
||||||
|
// 常量也集中在这里:SSBO 容量、灯数、阴影图尺寸都是"渲染预算",
|
||||||
|
// 改任何一个都要同步评估 buffer/池容量。
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef HELLOVULKAN_SCENETYPES_H
|
||||||
|
#define HELLOVULKAN_SCENETYPES_H
|
||||||
|
|
||||||
|
#include <Engine/Core/Types.h>
|
||||||
|
#include <glm/glm.hpp>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace Engine {
|
||||||
|
|
||||||
|
class Mesh;
|
||||||
|
|
||||||
|
// GPUDriven:按 mesh 聚合的绘制批
|
||||||
|
struct InstanceBatch {
|
||||||
|
Mesh* mesh;
|
||||||
|
u32 first; // 在实例矩阵数组中的起始下标
|
||||||
|
u32 count; // 实例数
|
||||||
|
};
|
||||||
|
|
||||||
|
// 一个渲染视图的剔除结果(相机视图 / 光源视图各一份)
|
||||||
|
struct CullResult {
|
||||||
|
std::vector<InstanceBatch> batches;
|
||||||
|
u32 instanceCount = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
constexpr u32 kMaxLights = 4;
|
||||||
|
constexpr u32 kMaxInstances = 16384; // 实例 SSBO 容量(矩阵数)
|
||||||
|
constexpr u32 kMaxCascades = 3; // CSM 级联数
|
||||||
|
constexpr u32 kCascadeRes = 1024; // 每级联阴影图边长
|
||||||
|
|
||||||
|
// 镜像 shader 侧 UBO 布局的灯光项
|
||||||
|
struct LightData {
|
||||||
|
glm::vec4 direction; // xyz = 方向(已归一),w 未用
|
||||||
|
glm::vec4 color; // rgb = 颜色,a = 强度
|
||||||
|
};
|
||||||
|
|
||||||
|
// 镜像 simple_shader 的 UBO 布局(std140 语义由 shader 侧对齐)。
|
||||||
|
// 字段顺序改动必须同步 simple_shader.vert/frag(shadow.vert 只用 SSBO)。
|
||||||
|
struct UniformBufferObject {
|
||||||
|
glm::mat4 view;
|
||||||
|
glm::mat4 proj; // 抖动后的投影(光栅化用)
|
||||||
|
glm::mat4 lightSpace[kMaxCascades]; // CSM 各级联光空间 VP([0,1] 重映射)
|
||||||
|
glm::vec4 cascadeSplits; // xyz = 级联 0..2 远边界(视图深度,正值),w 未用
|
||||||
|
glm::vec4 ambientColor;
|
||||||
|
LightData lights[kMaxLights];
|
||||||
|
|
||||||
|
// ---- TAA(velocity 在 vertex 里算)----
|
||||||
|
glm::mat4 projNoJitter; // 未抖动投影(velocity 当前帧 clip 用)
|
||||||
|
glm::mat4 prevView; // 上一帧视图(velocity)
|
||||||
|
glm::mat4 prevProj; // 上一帧未抖动投影(velocity)
|
||||||
|
glm::vec4 cameraPos; // 相机世界位置(高光 V 向量)
|
||||||
|
};
|
||||||
|
|
||||||
|
// 每帧从场景收集的渲染数据(SceneRenderer 生产,pass 消费)。
|
||||||
|
// 复用成员容量:clear() 不缩容,稳态零每帧堆分配。
|
||||||
|
struct FrameSceneData {
|
||||||
|
UniformBufferObject ubo{};
|
||||||
|
|
||||||
|
// 实例矩阵数组 = [主视图段 | 阴影视图段](共享同一 per-slot SSBO)
|
||||||
|
std::vector<glm::mat4> instanceModels;
|
||||||
|
// 上一帧世界矩阵,与 instanceModels 平行对齐(同一下标 = 同一
|
||||||
|
// 实例的上一帧姿态,TAA velocity 用);首帧/新实体 = 当前矩阵
|
||||||
|
std::vector<glm::mat4> prevInstanceModels;
|
||||||
|
CullResult mainView; // 相机视锥 → Forward pass
|
||||||
|
// 每级联独立剔除结果(Shadow pass 按 region 各画各的——
|
||||||
|
// 级联盒沿光方向加了 caster pad,影子不会被剔除裁掉)
|
||||||
|
CullResult shadowCascades[kMaxCascades];
|
||||||
|
|
||||||
|
f32 collectMs = 0.0f; // 收集+剔除 CPU 耗时(遥测)
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Engine
|
||||||
|
|
||||||
|
#endif //HELLOVULKAN_SCENETYPES_H
|
||||||
@@ -14,6 +14,7 @@ namespace Engine {
|
|||||||
glm::vec3 position;
|
glm::vec3 position;
|
||||||
glm::vec3 normal;
|
glm::vec3 normal;
|
||||||
glm::vec2 uv;
|
glm::vec2 uv;
|
||||||
|
glm::vec3 tangent; // 切线(法线贴图 TBN 用;assimp CalcTangentSpace)
|
||||||
|
|
||||||
static vk::VertexInputBindingDescription2EXT GetBindingDescription() {
|
static vk::VertexInputBindingDescription2EXT GetBindingDescription() {
|
||||||
vk::VertexInputBindingDescription2EXT binding;
|
vk::VertexInputBindingDescription2EXT binding;
|
||||||
@@ -24,8 +25,8 @@ namespace Engine {
|
|||||||
return binding;
|
return binding;
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::array<vk::VertexInputAttributeDescription2EXT, 3> GetAttributeDescriptions() {
|
static std::array<vk::VertexInputAttributeDescription2EXT, 4> GetAttributeDescriptions() {
|
||||||
std::array<vk::VertexInputAttributeDescription2EXT, 3> attrs;
|
std::array<vk::VertexInputAttributeDescription2EXT, 4> attrs;
|
||||||
|
|
||||||
attrs[0].setLocation(0)
|
attrs[0].setLocation(0)
|
||||||
.setBinding(0)
|
.setBinding(0)
|
||||||
@@ -42,6 +43,11 @@ namespace Engine {
|
|||||||
.setFormat(vk::Format::eR32G32Sfloat)
|
.setFormat(vk::Format::eR32G32Sfloat)
|
||||||
.setOffset(offsetof(Vertex, uv));
|
.setOffset(offsetof(Vertex, uv));
|
||||||
|
|
||||||
|
attrs[3].setLocation(3)
|
||||||
|
.setBinding(0)
|
||||||
|
.setFormat(vk::Format::eR32G32B32Sfloat)
|
||||||
|
.setOffset(offsetof(Vertex, tangent));
|
||||||
|
|
||||||
return attrs;
|
return attrs;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
//
|
||||||
|
// Created by atdunbg on 2026/9/23.
|
||||||
|
//
|
||||||
|
// VulkanAssetUploader:IAssetUploader 的渲染层实现。
|
||||||
|
//
|
||||||
|
// 属于渲染层对资产层提供的服务:持有 GPU 上下文,负责把解析后的
|
||||||
|
// 资产数据创建为 Vulkan 资源。资产层(AssetManager)只看到接口,
|
||||||
|
// 不知道 Device/Allocator 的存在——依赖方向:Assets → IAssetUploader
|
||||||
|
// ← VulkanAssetUploader(渲染层),永远不出现 Assets → Vulkan 具体。
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef HELLOVULKAN_VULKANASSETUPLOADER_H
|
||||||
|
#define HELLOVULKAN_VULKANASSETUPLOADER_H
|
||||||
|
|
||||||
|
#include <Engine/Assets/IAssetUploader.h>
|
||||||
|
|
||||||
|
namespace Engine {
|
||||||
|
|
||||||
|
class Device;
|
||||||
|
class MemoryAllocator;
|
||||||
|
class DescriptorManager;
|
||||||
|
|
||||||
|
class VulkanAssetUploader final : public Assets::IAssetUploader {
|
||||||
|
public:
|
||||||
|
VulkanAssetUploader(Device& device, MemoryAllocator& allocator,
|
||||||
|
DescriptorManager& descriptors,
|
||||||
|
vk::DescriptorSetLayout textureLayout)
|
||||||
|
: m_Device(device), m_Allocator(allocator),
|
||||||
|
m_Descriptors(descriptors), m_TextureLayout(textureLayout) {}
|
||||||
|
|
||||||
|
Scope<Model> LoadModel(const std::string& path) override;
|
||||||
|
Scope<Model> CreatePlane(f32 halfSize, u8 r, u8 g, u8 b) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Device& m_Device;
|
||||||
|
MemoryAllocator& m_Allocator;
|
||||||
|
DescriptorManager& m_Descriptors;
|
||||||
|
vk::DescriptorSetLayout m_TextureLayout;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Engine
|
||||||
|
|
||||||
|
#endif //HELLOVULKAN_VULKANASSETUPLOADER_H
|
||||||
@@ -36,11 +36,6 @@ namespace Engine {
|
|||||||
vk::Buffer m_Buffer;
|
vk::Buffer m_Buffer;
|
||||||
vk::DeviceSize m_Size;
|
vk::DeviceSize m_Size;
|
||||||
|
|
||||||
vk::DeviceMemory m_Memory;
|
|
||||||
vk::DeviceSize m_Offset;
|
|
||||||
|
|
||||||
vk::DeviceSize m_BlockSize; // 所属内存块的总大小
|
|
||||||
|
|
||||||
MemoryAllocator::Allocation m_Allocation;
|
MemoryAllocator::Allocation m_Allocation;
|
||||||
MemoryAllocator& m_Allocator;
|
MemoryAllocator& m_Allocator;
|
||||||
|
|
||||||
|
|||||||
@@ -34,6 +34,8 @@ namespace Engine {
|
|||||||
|
|
||||||
void UpdateUniformBuffer(vk::DescriptorSet set, u32 binding,
|
void UpdateUniformBuffer(vk::DescriptorSet set, u32 binding,
|
||||||
vk::Buffer buffer, u64 size);
|
vk::Buffer buffer, u64 size);
|
||||||
|
void UpdateStorageBuffer(vk::DescriptorSet set, u32 binding,
|
||||||
|
vk::Buffer buffer, u64 size);
|
||||||
|
|
||||||
void UpdateTexture(vk::DescriptorSet set, u32 binding,
|
void UpdateTexture(vk::DescriptorSet set, u32 binding,
|
||||||
vk::Sampler sampler, vk::ImageView view);
|
vk::Sampler sampler, vk::ImageView view);
|
||||||
@@ -52,9 +54,17 @@ namespace Engine {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
Device& m_Device;
|
Device& m_Device;
|
||||||
vk::DescriptorPool m_Pool;
|
|
||||||
|
|
||||||
static constexpr u32 kMaxSets = 1000;
|
// 可增长描述符池(分块):场景材质集数量不可预估(1G 资产场景
|
||||||
|
// 可能上万),固定上限迟早打爆。耗尽时追加一块继续分配——
|
||||||
|
// 对齐 UE/Unity 的"growable pool/bindless 堆"思路的 Vulkan 版。
|
||||||
|
// 材质去重(同贴图共享 Material)仍是大场景的第一道闸门。
|
||||||
|
std::vector<vk::DescriptorPool> m_Pools;
|
||||||
|
|
||||||
|
// 每块的容量(set 数;各类型描述符数同值)
|
||||||
|
static constexpr u32 kChunkSets = 4096;
|
||||||
|
|
||||||
|
vk::DescriptorPool CreateChunk();
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Engine
|
} // namespace Engine
|
||||||
|
|||||||
@@ -1,68 +1,74 @@
|
|||||||
//
|
//
|
||||||
// Created by atdunbg on 2026/9/20.
|
// Created by atdunbg on 2026/9/20.
|
||||||
//
|
//
|
||||||
|
// MemoryAllocator:显存分配器,VMA 后端。
|
||||||
|
//
|
||||||
|
// 2026-09-23 从手写 bump+free-list 块分配迁移到 VMA(VulkanMemoryAllocator):
|
||||||
|
// - TLSF 式通用分配 + DedicatedAllocation 扩展自动启用
|
||||||
|
// - 线性分配器(staging 类短命资源自动走线性算法)
|
||||||
|
// - VK_EXT_memory_budget 感知(分配决策参考真实显存余量)
|
||||||
|
// - 为后续内存别名(同一 VkDeviceMemory 多资源重绑)铺路
|
||||||
|
//
|
||||||
|
// 接口哲学:不再是"Allocate(reqs) + 手动 bind"——VMA 明确不推荐把分配与
|
||||||
|
// 绑定拆开。创建即分配即绑定,销毁必须走 Destroy(资源与内存成对释放),
|
||||||
|
// 这是本类存在的意义:把 VMA 的正确用法约束成一个出口。
|
||||||
|
//
|
||||||
|
|
||||||
#ifndef HELLOVULKAN_MEMORYALLOCATOR_H
|
#ifndef HELLOVULKAN_MEMORYALLOCATOR_H
|
||||||
#define HELLOVULKAN_MEMORYALLOCATOR_H
|
#define HELLOVULKAN_MEMORYALLOCATOR_H
|
||||||
|
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <vulkan/vulkan.hpp>
|
#include <vulkan/vulkan.hpp>
|
||||||
#include <array>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "Engine/Core/Types.h"
|
#include "Engine/Core/Types.h"
|
||||||
|
|
||||||
|
// VMA 内部类型前向声明(实现在 VmaImpl.cpp,头文件不引入 vk_mem_alloc.h)
|
||||||
|
struct VmaAllocator_T;
|
||||||
|
struct VmaAllocation_T;
|
||||||
|
|
||||||
namespace Engine {
|
namespace Engine {
|
||||||
|
|
||||||
class Device;
|
class Device;
|
||||||
|
|
||||||
class MemoryAllocator {
|
class MemoryAllocator {
|
||||||
public:
|
public:
|
||||||
MemoryAllocator(Device& device);
|
explicit MemoryAllocator(Device& device);
|
||||||
~MemoryAllocator();
|
~MemoryAllocator();
|
||||||
|
|
||||||
MemoryAllocator(const MemoryAllocator&) = delete;
|
MemoryAllocator(const MemoryAllocator&) = delete;
|
||||||
MemoryAllocator& operator=(const MemoryAllocator&) = delete;
|
MemoryAllocator& operator=(const MemoryAllocator&) = delete;
|
||||||
|
|
||||||
|
// 分配结果:资源句柄 + 分配句柄合一。
|
||||||
|
// Buffer/Image 二选一有值;MappedPtr 仅 HOST_VISIBLE 非空(持久映射)。
|
||||||
struct Allocation {
|
struct Allocation {
|
||||||
vk::DeviceMemory Memory;
|
vk::Buffer Buffer{};
|
||||||
vk::DeviceSize Offset;
|
vk::Image Image{};
|
||||||
vk::DeviceSize Size;
|
|
||||||
void* MappedPtr = nullptr;
|
void* MappedPtr = nullptr;
|
||||||
|
vk::DeviceSize Size = 0;
|
||||||
// 内部用于 Free
|
|
||||||
u32 BlockIndex = 0;
|
|
||||||
u32 TypeIndex = 0;
|
|
||||||
bool Valid = false;
|
bool Valid = false;
|
||||||
|
|
||||||
|
// VMA 内部句柄(上层不要碰)
|
||||||
|
VmaAllocation_T* Handle = nullptr;
|
||||||
};
|
};
|
||||||
|
|
||||||
Allocation Allocate(vk::MemoryRequirements reqs, vk::MemoryPropertyFlags props);
|
// 创建 + 分配 + 绑定一步完成(VMA 惯用法)
|
||||||
void Free(const Allocation& alloc);
|
Allocation CreateBuffer(vk::DeviceSize size, vk::BufferUsageFlags usage,
|
||||||
|
vk::MemoryPropertyFlags props);
|
||||||
|
Allocation CreateImage(const vk::ImageCreateInfo& imageInfo,
|
||||||
|
vk::MemoryPropertyFlags props);
|
||||||
|
|
||||||
|
// 销毁资源并释放分配(按 alloc.Buffer/Image 判断类型),随后 alloc 归零
|
||||||
|
void Destroy(Allocation& alloc);
|
||||||
|
|
||||||
|
// HOST_VISIBLE 非 coherent 写入后冲刷(VMA 内部对 coherent 自动跳过)
|
||||||
|
void Flush(const Allocation& alloc);
|
||||||
|
|
||||||
void PrintStats() const;
|
void PrintStats() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct FreeRange {
|
|
||||||
vk::DeviceSize Offset;
|
|
||||||
vk::DeviceSize Size;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Block {
|
|
||||||
vk::DeviceMemory Memory;
|
|
||||||
vk::DeviceSize Size;
|
|
||||||
vk::DeviceSize Used = 0; // bump 分配用
|
|
||||||
void* MappedPtr = nullptr;
|
|
||||||
bool IsMapped = false;
|
|
||||||
std::vector<FreeRange> FreeList; // 已释放的区域
|
|
||||||
};
|
|
||||||
|
|
||||||
Device& m_Device;
|
Device& m_Device;
|
||||||
std::array<std::vector<Block>, VK_MAX_MEMORY_TYPES> m_Blocks;
|
VmaAllocator_T* m_Vma = nullptr;
|
||||||
std::array<vk::DeviceSize, VK_MAX_MEMORY_TYPES> m_TotalAllocated{};
|
|
||||||
|
|
||||||
vk::DeviceSize GetBlockSize(vk::MemoryPropertyFlags props) const;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Engine
|
} // namespace Engine
|
||||||
|
|||||||
@@ -28,6 +28,11 @@ namespace Engine
|
|||||||
vk::SwapchainKHR Get() const { return m_Swapchain; }
|
vk::SwapchainKHR Get() const { return m_Swapchain; }
|
||||||
vk::Format GetFormat() const { return m_Format; }
|
vk::Format GetFormat() const { return m_Format; }
|
||||||
vk::Extent2D GetExtent() const { return m_Extent; }
|
vk::Extent2D GetExtent() const { return m_Extent; }
|
||||||
|
|
||||||
|
// 垂直同步开关:影响 present mode(Mailbox ↔ Immediate),
|
||||||
|
// 切换后需 Recreate 才生效
|
||||||
|
void SetVSync(bool vsync) { m_VSync = vsync; }
|
||||||
|
bool GetVSync() const { return m_VSync; }
|
||||||
uint32_t GetImageCount() const { return static_cast<u32>(m_Images.size()); }
|
uint32_t GetImageCount() const { return static_cast<u32>(m_Images.size()); }
|
||||||
|
|
||||||
const std::vector<vk::Image>& GetImages() const { return m_Images; }
|
const std::vector<vk::Image>& GetImages() const { return m_Images; }
|
||||||
@@ -56,6 +61,7 @@ namespace Engine
|
|||||||
|
|
||||||
vk::Format m_Format;
|
vk::Format m_Format;
|
||||||
vk::Extent2D m_Extent;
|
vk::Extent2D m_Extent;
|
||||||
|
bool m_VSync = true;
|
||||||
};
|
};
|
||||||
} // namespace Engine
|
} // namespace Engine
|
||||||
|
|
||||||
|
|||||||
@@ -21,13 +21,16 @@ namespace Engine {
|
|||||||
|
|
||||||
class Texture {
|
class Texture {
|
||||||
public:
|
public:
|
||||||
// 从磁盘图片加载
|
// 从磁盘图片加载(按扩展名分发:.dds → DdsLoader,其它 → stbi)。
|
||||||
Texture(Device& device, MemoryAllocator& allocator, const std::string& path);
|
// srgb:颜色贴图语义(BaseColor/Emissive)为 true;数据图
|
||||||
|
//(Normal/Specular 等)为 false —— 决定 vk 格式的 _SRGB 变体
|
||||||
|
Texture(Device& device, MemoryAllocator& allocator, const std::string& path,
|
||||||
|
bool srgb = true);
|
||||||
|
|
||||||
// 程序化纹理:纯色(1x1),用于地面/调试等不依赖磁盘资产的场景
|
// 程序化纹理:纯色(1x1),用于地面/调试等不依赖磁盘资产的场景
|
||||||
struct ProceduralTag {};
|
struct ProceduralTag {};
|
||||||
static Scope<Texture> CreateSolidColor(Device& device, MemoryAllocator& allocator,
|
static Scope<Texture> CreateSolidColor(Device& device, MemoryAllocator& allocator,
|
||||||
u8 r, u8 g, u8 b);
|
u8 r, u8 g, u8 b, bool srgb = true);
|
||||||
|
|
||||||
~Texture();
|
~Texture();
|
||||||
|
|
||||||
@@ -38,21 +41,28 @@ namespace Engine {
|
|||||||
vk::ImageView GetView() const { return m_View; }
|
vk::ImageView GetView() const { return m_View; }
|
||||||
u32 GetWidth() const { return m_Width; }
|
u32 GetWidth() const { return m_Width; }
|
||||||
u32 GetHeight() const { return m_Height; }
|
u32 GetHeight() const { return m_Height; }
|
||||||
|
vk::Format GetFormat() const { return m_Format; }
|
||||||
|
u32 GetMipLevels() const { return m_MipLevels; }
|
||||||
|
|
||||||
vk::Sampler GetSampler() const { return m_Sampler; }
|
vk::Sampler GetSampler() const { return m_Sampler; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// 两段式构造:先确定像素来源(磁盘 / 程序化),再走公共上传路径
|
// 两段式构造:先确定像素来源(磁盘 / 程序化),再走公共上传路径
|
||||||
Texture(Device& device, MemoryAllocator& allocator, ProceduralTag,
|
Texture(Device& device, MemoryAllocator& allocator, ProceduralTag,
|
||||||
const std::array<u8, 4>& rgba);
|
const std::array<u8, 4>& rgba, bool srgb);
|
||||||
|
|
||||||
// 公共路径:staging → device local image → view/sampler
|
// 公共路径:staging → device local image → view/sampler(RGBA8 单 mip)
|
||||||
void InitFromPixels(const void* pixels, u32 width, u32 height);
|
void InitFromPixels(const void* pixels, u32 width, u32 height, bool srgb);
|
||||||
|
|
||||||
|
// DDS 路径:压缩数据直传(BC 硬件采样解码)+ 文件自带 mip 链
|
||||||
|
void InitFromDds(const std::string& path, bool srgb);
|
||||||
|
|
||||||
Device& m_Device;
|
Device& m_Device;
|
||||||
vk::Image m_Image;
|
vk::Image m_Image;
|
||||||
vk::ImageView m_View;
|
vk::ImageView m_View;
|
||||||
vk::DeviceMemory m_Memory;
|
|
||||||
|
vk::Format m_Format = vk::Format::eUndefined;
|
||||||
|
u32 m_MipLevels = 1;
|
||||||
|
|
||||||
vk::Sampler m_Sampler;
|
vk::Sampler m_Sampler;
|
||||||
|
|
||||||
|
|||||||
@@ -25,8 +25,11 @@ namespace Engine {
|
|||||||
// 接口已按"写入哪个槽位"设计,届时只是内部实现替换。
|
// 接口已按"写入哪个槽位"设计,届时只是内部实现替换。
|
||||||
class UniformBuffer {
|
class UniformBuffer {
|
||||||
public:
|
public:
|
||||||
|
// usage:默认 eUniformBuffer;GPUDriven 实例数据传 eStorageBuffer
|
||||||
|
//(ring 化的正确性保证对所有 per-frame 数据一致)
|
||||||
UniformBuffer(Device& device, MemoryAllocator& allocator,
|
UniformBuffer(Device& device, MemoryAllocator& allocator,
|
||||||
u64 size, u32 frameCount);
|
u64 size, u32 frameCount,
|
||||||
|
vk::BufferUsageFlags usage = vk::BufferUsageFlagBits::eUniformBuffer);
|
||||||
~UniformBuffer() = default;
|
~UniformBuffer() = default;
|
||||||
|
|
||||||
UniformBuffer(const UniformBuffer&) = delete;
|
UniformBuffer(const UniformBuffer&) = delete;
|
||||||
|
|||||||
@@ -3,41 +3,31 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
#include <Engine/Assets/AssetManager.h>
|
#include <Engine/Assets/AssetManager.h>
|
||||||
#include <Engine/Vulkan/Device.h>
|
|
||||||
#include <Engine/Vulkan/MemoryAllocator.h>
|
|
||||||
#include <Engine/Vulkan/DescriptorManager.h>
|
|
||||||
#include <Engine/Core/Log.h>
|
#include <Engine/Core/Log.h>
|
||||||
|
|
||||||
namespace Engine::Assets {
|
namespace Engine::Assets {
|
||||||
|
|
||||||
AssetManager::AssetManager(Device& device, MemoryAllocator& allocator,
|
AssetManager::AssetManager(IAssetUploader& uploader)
|
||||||
DescriptorManager& descriptors,
|
: m_Uploader(uploader)
|
||||||
vk::DescriptorSetLayout textureLayout)
|
|
||||||
: m_Device(device), m_Allocator(allocator),
|
|
||||||
m_Descriptors(descriptors), m_TextureLayout(textureLayout)
|
|
||||||
{
|
{
|
||||||
ENGINE_INFO("AssetManager created");
|
ENGINE_INFO("AssetManager created");
|
||||||
}
|
}
|
||||||
|
|
||||||
ModelHandle AssetManager::LoadModel(const std::string& path)
|
ModelRef AssetManager::LoadModel(const std::string& path)
|
||||||
{
|
{
|
||||||
return m_Models.Load(path, [this](const std::string& p) -> Scope<Model> {
|
// GPU 上传经接口发生在渲染层实现里,本层只管缓存与句柄
|
||||||
// GPU 上传在此发生:资产层调用渲染层接口完成 buffer/image 创建
|
return m_Models.Load(path,
|
||||||
return CreateScope<Model>(
|
[this](const std::string& p) { return m_Uploader.LoadModel(p); });
|
||||||
m_Device, m_Allocator, m_Descriptors, m_TextureLayout, p);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ModelHandle AssetManager::CreatePlane(f32 halfSize, u8 r, u8 g, u8 b)
|
ModelRef AssetManager::CreatePlane(f32 halfSize, u8 r, u8 g, u8 b)
|
||||||
{
|
{
|
||||||
// 程序化资产复用同一缓存:键 = 伪路径 + 参数
|
// 程序化资产复用同一缓存:键 = 伪路径 + 参数
|
||||||
std::string key = "procedural://plane/" + std::to_string(halfSize) +
|
std::string key = "procedural://plane/" + std::to_string(halfSize) +
|
||||||
"/" + std::to_string(r) + "," + std::to_string(g) +
|
"/" + std::to_string(r) + "," + std::to_string(g) +
|
||||||
"," + std::to_string(b);
|
"," + std::to_string(b);
|
||||||
return m_Models.Load(key, [&](const std::string&) -> Scope<Model> {
|
return m_Models.Load(key,
|
||||||
return Model::CreatePlane(m_Device, m_Allocator, m_Descriptors,
|
[&](const std::string&) { return m_Uploader.CreatePlane(halfSize, r, g, b); });
|
||||||
m_TextureLayout, halfSize, r, g, b);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Engine::Assets
|
} // namespace Engine::Assets
|
||||||
|
|||||||
@@ -0,0 +1,170 @@
|
|||||||
|
//
|
||||||
|
// Created by atdunbg on 2026/9/23.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <Engine/Assets/DdsLoader.h>
|
||||||
|
#include <Engine/Core/Log.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstring>
|
||||||
|
#include <fstream>
|
||||||
|
|
||||||
|
namespace Engine::Assets::Dds
|
||||||
|
{
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
constexpr u32 kMagic = 0x20534444; // "DDS "
|
||||||
|
|
||||||
|
// 头部偏移(dwSpfFourCC @ 84)
|
||||||
|
constexpr size_t kOffsetHeight = 12;
|
||||||
|
constexpr size_t kOffsetWidth = 16;
|
||||||
|
constexpr size_t kOffsetMips = 28;
|
||||||
|
constexpr size_t kOffsetFourCC = 84;
|
||||||
|
constexpr size_t kHeaderSize = 128;
|
||||||
|
constexpr size_t kDx10Size = 20;
|
||||||
|
|
||||||
|
u32 ReadU32(const u8* p) { return *reinterpret_cast<const u32*>(p); }
|
||||||
|
|
||||||
|
// legacy fourcc → 中性格式
|
||||||
|
bool MapFourCC(u32 fourcc, DdsFormat& out)
|
||||||
|
{
|
||||||
|
switch (fourcc) {
|
||||||
|
case 0x31545844: out = DdsFormat::BC1; return true; // "DXT1"
|
||||||
|
case 0x33545844: out = DdsFormat::BC2; return true; // "DXT3"
|
||||||
|
case 0x35545844: out = DdsFormat::BC3; return true; // "DXT5"
|
||||||
|
case 0x31495441: out = DdsFormat::BC4; return true; // "ATI1"
|
||||||
|
case 0x55344342: out = DdsFormat::BC4; return true; // "BC4U"
|
||||||
|
case 0x32495441: out = DdsFormat::BC5; return true; // "ATI2"
|
||||||
|
case 0x55354342: out = DdsFormat::BC5; return true; // "BC5U"
|
||||||
|
default: return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// DX10 DXGI 值 → 中性格式
|
||||||
|
bool MapDxgi(u32 dxgi, DdsFormat& out)
|
||||||
|
{
|
||||||
|
switch (dxgi) {
|
||||||
|
case 70: case 71: out = DdsFormat::BC1; return true; // BC1
|
||||||
|
case 73: case 74: out = DdsFormat::BC2; return true; // BC2
|
||||||
|
case 77: case 78: out = DdsFormat::BC3; return true; // BC3
|
||||||
|
case 79: case 80: out = DdsFormat::BC4; return true; // BC4
|
||||||
|
case 82: case 83: out = DdsFormat::BC5; return true; // BC5
|
||||||
|
case 94: case 95: out = DdsFormat::BC6H; return true; // BC6H
|
||||||
|
case 97: case 98: out = DdsFormat::BC7; return true; // BC7
|
||||||
|
case 28: case 29: out = DdsFormat::RGBA8; return true; // R8G8B8A8
|
||||||
|
case 87: case 91: out = DdsFormat::BGRA8; return true; // B8G8R8A8(近似)
|
||||||
|
default: return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 BytesPerBlock(DdsFormat format)
|
||||||
|
{
|
||||||
|
switch (format) {
|
||||||
|
case DdsFormat::BC1:
|
||||||
|
case DdsFormat::BC4:
|
||||||
|
return 8;
|
||||||
|
case DdsFormat::BC2:
|
||||||
|
case DdsFormat::BC3:
|
||||||
|
case DdsFormat::BC5:
|
||||||
|
case DdsFormat::BC6H:
|
||||||
|
case DdsFormat::BC7:
|
||||||
|
return 16;
|
||||||
|
default:
|
||||||
|
return 4; // RGBA8/BGRA8 系
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsBlockCompressed(DdsFormat format)
|
||||||
|
{
|
||||||
|
return format != DdsFormat::RGBA8 && format != DdsFormat::BGRA8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Load(const std::string& path, DdsData& out, std::string& errMsg)
|
||||||
|
{
|
||||||
|
std::ifstream f(path, std::ios::binary);
|
||||||
|
if (!f) {
|
||||||
|
errMsg = "无法打开文件";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
u8 header[kHeaderSize];
|
||||||
|
f.read(reinterpret_cast<char*>(header), kHeaderSize);
|
||||||
|
if (!f || ReadU32(header) != kMagic) {
|
||||||
|
errMsg = "DDS 魔数不符";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
out.Width = ReadU32(header + kOffsetWidth);
|
||||||
|
out.Height = ReadU32(header + kOffsetHeight);
|
||||||
|
const u32 mips = ReadU32(header + kOffsetMips);
|
||||||
|
out.MipLevels = mips ? mips : 1;
|
||||||
|
|
||||||
|
const u32 fourcc = ReadU32(header + kOffsetFourCC);
|
||||||
|
size_t dataOffset = kHeaderSize;
|
||||||
|
|
||||||
|
if (fourcc == 0x30313044) { // "DX10"
|
||||||
|
u8 dx10[kDx10Size];
|
||||||
|
f.read(reinterpret_cast<char*>(dx10), kDx10Size);
|
||||||
|
if (!f) {
|
||||||
|
errMsg = "DX10 头不完整";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const u32 dxgi = ReadU32(dx10);
|
||||||
|
if (!MapDxgi(dxgi, out.Format)) {
|
||||||
|
errMsg = "DX10 DXGI 格式不支持";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
dataOffset += kDx10Size;
|
||||||
|
} else if (!MapFourCC(fourcc, out.Format)) {
|
||||||
|
// 未压缩路径:仅支持 32bpp RGBA(DDPF_ALPHAPIXELS(0x1) | DDPF_RGB(0x40))
|
||||||
|
const u32 ddspfFlags = ReadU32(header + 80);
|
||||||
|
if (ddspfFlags & 0x41) {
|
||||||
|
out.Format = DdsFormat::BGRA8; // 常见 32bpp DDS 是 B8G8R8A8 字节序
|
||||||
|
ENGINE_WARN("DDS: 未压缩 32bpp 按 BGRA8 上传 '{}'", path);
|
||||||
|
} else {
|
||||||
|
errMsg = "fourcc 不支持";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
out.BlockCompressed = IsBlockCompressed(out.Format);
|
||||||
|
f.seekg(dataOffset);
|
||||||
|
|
||||||
|
// 数据量校验 + 逐级读取(紧凑排布)
|
||||||
|
const u32 bpb = BytesPerBlock(out.Format);
|
||||||
|
const u32 cols = out.BlockCompressed ? 4 : 1;
|
||||||
|
out.Mips.resize(out.MipLevels);
|
||||||
|
|
||||||
|
u32 w = out.Width, h = out.Height;
|
||||||
|
for (u32 mip = 0; mip < out.MipLevels; ++mip) {
|
||||||
|
const u32 blocksW = std::max(1u, (w + cols - 1) / cols);
|
||||||
|
const u32 blocksH = std::max(1u, (h + cols - 1) / cols);
|
||||||
|
const size_t bytes = size_t(blocksW) * blocksH * bpb;
|
||||||
|
out.Mips[mip].resize(bytes);
|
||||||
|
f.read(reinterpret_cast<char*>(out.Mips[mip].data()),
|
||||||
|
std::streamsize(bytes));
|
||||||
|
if (!f) {
|
||||||
|
errMsg = "mip 数据不完整";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
w = std::max(1u, w / 2);
|
||||||
|
h = std::max(1u, h / 2);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 RowPitch(const DdsData& data, u32 mipLevel)
|
||||||
|
{
|
||||||
|
u32 w = data.Width;
|
||||||
|
for (u32 m = 0; m < mipLevel; ++m) {
|
||||||
|
w = std::max(1u, w / 2);
|
||||||
|
}
|
||||||
|
const u32 bpb = BytesPerBlock(data.Format);
|
||||||
|
if (data.BlockCompressed) {
|
||||||
|
return std::max(1u, (w + 3) / 4) * bpb;
|
||||||
|
}
|
||||||
|
return w * bpb;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Engine::Assets::Dds
|
||||||
@@ -13,7 +13,10 @@
|
|||||||
|
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
|
|
||||||
|
#include <tracy/Tracy.hpp>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <thread>
|
||||||
|
|
||||||
namespace Engine {
|
namespace Engine {
|
||||||
|
|
||||||
@@ -29,13 +32,10 @@ namespace Engine {
|
|||||||
m_Scene = CreateScope<ECS::Scene>();
|
m_Scene = CreateScope<ECS::Scene>();
|
||||||
m_Renderer = CreateScope<Renderer>(*m_Window);
|
m_Renderer = CreateScope<Renderer>(*m_Window);
|
||||||
|
|
||||||
// 资产层:复用渲染器的 GPU 上下文;
|
// 资产层:上传服务由渲染层提供(IAssetUploader 实现),
|
||||||
|
// 资产层不感知 Vulkan 具体类。
|
||||||
// 声明在 Renderer 之后 → 析构先于 Renderer(它引用其子模块)
|
// 声明在 Renderer 之后 → 析构先于 Renderer(它引用其子模块)
|
||||||
m_Assets = CreateScope<Assets::AssetManager>(
|
m_Assets = CreateScope<Assets::AssetManager>(m_Renderer->GetAssetUploader());
|
||||||
m_Renderer->GetDevice(),
|
|
||||||
m_Renderer->GetAllocator(),
|
|
||||||
m_Renderer->GetDescriptorManager(),
|
|
||||||
m_Renderer->GetTextureSetLayout());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Application::~Application()
|
Application::~Application()
|
||||||
@@ -67,6 +67,13 @@ namespace Engine {
|
|||||||
[] { return ImGui::GetIO().WantCaptureMouse; });
|
[] { return ImGui::GetIO().WantCaptureMouse; });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Application::SetVSync(bool vsync)
|
||||||
|
{
|
||||||
|
if (m_VSync == vsync) return;
|
||||||
|
m_VSync = vsync;
|
||||||
|
m_Renderer->SetVSync(vsync);
|
||||||
|
}
|
||||||
|
|
||||||
void Application::Run()
|
void Application::Run()
|
||||||
{
|
{
|
||||||
ENGINE_INFO("Application starting");
|
ENGINE_INFO("Application starting");
|
||||||
@@ -74,9 +81,13 @@ namespace Engine {
|
|||||||
m_Running = true;
|
m_Running = true;
|
||||||
m_LastFrameTime = std::chrono::steady_clock::now();
|
m_LastFrameTime = std::chrono::steady_clock::now();
|
||||||
|
|
||||||
|
tracy::SetThreadName("Main"); // 线程命名:否则 GUI 显示 Unknown
|
||||||
|
|
||||||
OnInit();
|
OnInit();
|
||||||
|
|
||||||
while (m_Running && !m_Window->ShouldClose()) {
|
while (m_Running) {
|
||||||
|
const auto frameStart = std::chrono::steady_clock::now();
|
||||||
|
|
||||||
m_Window->PollEvents();
|
m_Window->PollEvents();
|
||||||
|
|
||||||
auto now = std::chrono::steady_clock::now();
|
auto now = std::chrono::steady_clock::now();
|
||||||
@@ -89,23 +100,44 @@ namespace Engine {
|
|||||||
|
|
||||||
// 延迟销毁在渲染前统一 flush,保证渲染遍历时池结构不变
|
// 延迟销毁在渲染前统一 flush,保证渲染遍历时池结构不变
|
||||||
m_Scene->FlushDestroyQueue();
|
m_Scene->FlushDestroyQueue();
|
||||||
|
m_Scene->UpdateTransforms(); // UE transform-update 阶段:渲染前保证 world 新鲜
|
||||||
|
|
||||||
|
const auto uiStart = std::chrono::steady_clock::now();
|
||||||
|
|
||||||
// ---- UI 构建段:OnRenderOverlay 只"声明"控件,不直接碰 GPU ----
|
// ---- UI 构建段:OnRenderOverlay 只"声明"控件,不直接碰 GPU ----
|
||||||
if (m_ImGuiLayer) {
|
if (m_ImGuiLayer && m_ImGuiLayer->IsEnabled())
|
||||||
|
{
|
||||||
m_ImGuiLayer->NewFrame();
|
m_ImGuiLayer->NewFrame();
|
||||||
}
|
OnRenderOverlay();
|
||||||
|
|
||||||
OnRenderOverlay();
|
|
||||||
|
|
||||||
if (m_ImGuiLayer) {
|
|
||||||
m_ImGuiLayer->BuildDrawData();
|
m_ImGuiLayer->BuildDrawData();
|
||||||
}
|
}
|
||||||
|
const auto uiEnd = std::chrono::steady_clock::now();
|
||||||
|
|
||||||
// ---- 主渲染(框架行为):RG 顺序 Forward → ImGui(loadOp=Load) →
|
// ---- 主渲染(框架行为):RG 顺序 Forward → ImGui(loadOp=Load) →
|
||||||
// Present,draw data 在 ImGui pass 里才真正提交 GPU ----
|
// Present,draw data 在 ImGui pass 里才真正提交 GPU ----
|
||||||
m_Renderer->Render(*m_Scene, *m_Assets);
|
m_Renderer->Render(*m_Scene, *m_Assets);
|
||||||
|
|
||||||
|
// ---- 帧率控制:vsync 开启时 present 自带节奏;否则按目标限帧 ----
|
||||||
|
// 策略:先 sleep 到剩 ~2ms,再自旋——精度高且不烧 CPU
|
||||||
|
if (!m_VSync) {
|
||||||
|
const auto deadline = frameStart +
|
||||||
|
std::chrono::microseconds(static_cast<long long>(
|
||||||
|
1e6 / m_TargetFps));
|
||||||
|
auto now2 = std::chrono::steady_clock::now();
|
||||||
|
if (now2 < deadline) {
|
||||||
|
const auto remaining = std::chrono::duration_cast<
|
||||||
|
std::chrono::milliseconds>(deadline - now2);
|
||||||
|
if (remaining.count() > 2) {
|
||||||
|
std::this_thread::sleep_for(
|
||||||
|
remaining - std::chrono::milliseconds(2));
|
||||||
|
}
|
||||||
|
while (std::chrono::steady_clock::now() < deadline) {
|
||||||
|
// 自旋等待最后 ~2ms
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
++m_FrameCount;
|
++m_FrameCount;
|
||||||
|
FrameMark; // Tracy:帧边界(一帧的时间线切分)
|
||||||
}
|
}
|
||||||
|
|
||||||
OnShutdown();
|
OnShutdown();
|
||||||
@@ -133,7 +165,6 @@ namespace Engine {
|
|||||||
|
|
||||||
void Application::OnWindowClose(const Event& event)
|
void Application::OnWindowClose(const Event& event)
|
||||||
{
|
{
|
||||||
// 默认行为:直接退出。需要"退出确认"的程序覆写本钩子。
|
|
||||||
Close();
|
Close();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ namespace Engine
|
|||||||
|
|
||||||
void Window::KeyCallback(GLFWwindow* window, int key, int scancode, int action, int mods)
|
void Window::KeyCallback(GLFWwindow* window, int key, int scancode, int action, int mods)
|
||||||
{
|
{
|
||||||
auto* self = static_cast<Window*>(glfwGetWindowUserPointer(window));
|
const 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 后端链式回调自行处理)
|
// UI 捕获键盘时,游戏侧不收键盘事件(ImGui 后端链式回调自行处理)
|
||||||
@@ -42,7 +42,7 @@ namespace Engine
|
|||||||
|
|
||||||
void Window::MouseButtonCallback(GLFWwindow* window, int button, int action, int mods)
|
void Window::MouseButtonCallback(GLFWwindow* window, int button, int action, int mods)
|
||||||
{
|
{
|
||||||
auto* self = static_cast<Window*>(glfwGetWindowUserPointer(window));
|
const 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;
|
if (self->m_MouseCapture && self->m_MouseCapture()) return;
|
||||||
|
|
||||||
@@ -55,7 +55,7 @@ namespace Engine
|
|||||||
|
|
||||||
void Window::CursorPosCallback(GLFWwindow* window, double xpos, double ypos)
|
void Window::CursorPosCallback(GLFWwindow* window, double xpos, double ypos)
|
||||||
{
|
{
|
||||||
auto* self = static_cast<Window*>(glfwGetWindowUserPointer(window));
|
const 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;
|
if (self->m_MouseCapture && self->m_MouseCapture()) return;
|
||||||
|
|
||||||
@@ -68,7 +68,7 @@ namespace Engine
|
|||||||
|
|
||||||
void Window::ScrollCallback(GLFWwindow* window, double xoffset, double yoffset)
|
void Window::ScrollCallback(GLFWwindow* window, double xoffset, double yoffset)
|
||||||
{
|
{
|
||||||
auto* self = static_cast<Window*>(glfwGetWindowUserPointer(window));
|
const 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;
|
if (self->m_MouseCapture && self->m_MouseCapture()) return;
|
||||||
|
|
||||||
@@ -81,7 +81,7 @@ namespace Engine
|
|||||||
|
|
||||||
void Window::WindowFocusCallback(GLFWwindow* window, int focused)
|
void Window::WindowFocusCallback(GLFWwindow* window, int focused)
|
||||||
{
|
{
|
||||||
auto* self = static_cast<Window*>(glfwGetWindowUserPointer(window));
|
const auto* self = static_cast<Window*>(glfwGetWindowUserPointer(window));
|
||||||
if (!self->m_EventCallback) return;
|
if (!self->m_EventCallback) return;
|
||||||
|
|
||||||
Event e;
|
Event e;
|
||||||
@@ -89,6 +89,16 @@ namespace Engine
|
|||||||
self->m_EventCallback(e);
|
self->m_EventCallback(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Window::WindowCloseCallback(GLFWwindow* window)
|
||||||
|
{
|
||||||
|
const auto* self = static_cast<Window*>(glfwGetWindowUserPointer(window));
|
||||||
|
if (!self->m_EventCallback) return;
|
||||||
|
|
||||||
|
Event e;
|
||||||
|
e.type = EventType::WindowClose;
|
||||||
|
self->m_EventCallback(e);
|
||||||
|
}
|
||||||
|
|
||||||
Window::Window(const std::string& title, u32 width, u32 height)
|
Window::Window(const std::string& title, u32 width, u32 height)
|
||||||
: m_Width(width), m_Height(height), m_Title(title)
|
: m_Width(width), m_Height(height), m_Title(title)
|
||||||
{
|
{
|
||||||
@@ -100,6 +110,8 @@ namespace Engine
|
|||||||
|
|
||||||
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
|
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
|
||||||
// glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
|
// glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
|
||||||
|
glfwWindowHint(GLFW_TRANSPARENT_FRAMEBUFFER, GLFW_TRUE);
|
||||||
|
// 设置窗口为半透明
|
||||||
|
|
||||||
m_Window = glfwCreateWindow(width, height, title.c_str(), nullptr, nullptr);
|
m_Window = glfwCreateWindow(width, height, title.c_str(), nullptr, nullptr);
|
||||||
if (!m_Window)
|
if (!m_Window)
|
||||||
@@ -108,6 +120,7 @@ namespace Engine
|
|||||||
glfwTerminate();
|
glfwTerminate();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
glfwSetWindowOpacity(m_Window, 0.5f);
|
||||||
|
|
||||||
ENGINE_INFO("Window created: {} ({}x{})", title, width, height);
|
ENGINE_INFO("Window created: {} ({}x{})", title, width, height);
|
||||||
|
|
||||||
@@ -120,6 +133,8 @@ namespace Engine
|
|||||||
glfwSetCursorPosCallback(m_Window, CursorPosCallback);
|
glfwSetCursorPosCallback(m_Window, CursorPosCallback);
|
||||||
glfwSetScrollCallback(m_Window, ScrollCallback);
|
glfwSetScrollCallback(m_Window, ScrollCallback);
|
||||||
glfwSetWindowFocusCallback(m_Window, WindowFocusCallback);
|
glfwSetWindowFocusCallback(m_Window, WindowFocusCallback);
|
||||||
|
|
||||||
|
glfwSetWindowCloseCallback(m_Window, WindowCloseCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
Window::~Window()
|
Window::~Window()
|
||||||
|
|||||||
@@ -6,13 +6,16 @@
|
|||||||
#include <Engine/ECS/EntityHandle.h>
|
#include <Engine/ECS/EntityHandle.h>
|
||||||
#include <Engine/Core/Log.h>
|
#include <Engine/Core/Log.h>
|
||||||
|
|
||||||
|
#include <tracy/Tracy.hpp>
|
||||||
|
|
||||||
namespace Engine::ECS {
|
namespace Engine::ECS {
|
||||||
|
|
||||||
EntityHandle Scene::CreateEntity(std::string name)
|
EntityHandle Scene::CreateEntity(std::string name)
|
||||||
{
|
{
|
||||||
Entity e = m_Registry.create();
|
Entity e = m_Registry.create();
|
||||||
// IdComponent 自动挂载:每个实体出生即有稳定 UUID
|
// 出生即有:UUID(稳定身份)+ Transform(空间存在性,Unity 语义)
|
||||||
m_Registry.emplace<IdComponent>(e, IdComponent{UUID::New()});
|
m_Registry.emplace<IdComponent>(e, IdComponent{UUID::New()});
|
||||||
|
m_Registry.emplace<TransformComponent>(e);
|
||||||
if (!name.empty()) {
|
if (!name.empty()) {
|
||||||
m_Registry.emplace<NameComponent>(e, NameComponent{std::move(name)});
|
m_Registry.emplace<NameComponent>(e, NameComponent{std::move(name)});
|
||||||
}
|
}
|
||||||
@@ -52,12 +55,30 @@ namespace Engine::ECS {
|
|||||||
m_PendingDestroy.clear();
|
m_PendingDestroy.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
CameraComponent* Scene::GetPrimaryCamera()
|
void Scene::UpdateTransforms()
|
||||||
|
{
|
||||||
|
ZoneScoped;
|
||||||
|
// 只重算脏的:UE transform-update 阶段模式。
|
||||||
|
// 静止实体(绝大多数)零成本;层级传播在此扩展
|
||||||
|
m_Registry.view<TransformComponent>().each([](TransformComponent& t) {
|
||||||
|
if (t.IsDirty()) t.Compose();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
CameraComponent* Scene::GetPrimaryCamera(TransformComponent** outTransform)
|
||||||
{
|
{
|
||||||
CameraComponent* found = nullptr;
|
CameraComponent* found = nullptr;
|
||||||
m_Registry.view<CameraComponent>().each([&](Entity, CameraComponent& cc) {
|
Entity foundEntity = entt::null;
|
||||||
if (!found && cc.primary) found = &cc;
|
m_Registry.view<CameraComponent>().each([&](Entity e, CameraComponent& cc) {
|
||||||
|
if (!found && cc.primary) {
|
||||||
|
found = &cc;
|
||||||
|
foundEntity = e;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
if (outTransform) {
|
||||||
|
*outTransform = (found && foundEntity != entt::null)
|
||||||
|
? &m_Registry.get<TransformComponent>(foundEntity) : nullptr;
|
||||||
|
}
|
||||||
return found;
|
return found;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -45,8 +45,7 @@ namespace Engine::RG {
|
|||||||
for (auto& img : images) {
|
for (auto& img : images) {
|
||||||
if (!img->valid) continue;
|
if (!img->valid) continue;
|
||||||
vkDevice.destroyImageView(img->view);
|
vkDevice.destroyImageView(img->view);
|
||||||
vkDevice.destroyImage(img->image);
|
m_Allocator.Destroy(img->allocation); // image 资源与显存成对释放
|
||||||
m_Allocator.Free(img->allocation);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
m_Pool.clear();
|
m_Pool.clear();
|
||||||
@@ -131,6 +130,23 @@ namespace Engine::RG {
|
|||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RenderGraph::PassBuilder& RenderGraph::PassBuilder::ReadHistory(
|
||||||
|
TextureHandle h, ResourceUse use, vk::ShaderStageFlags stage)
|
||||||
|
{
|
||||||
|
m_Graph.CheckMutable();
|
||||||
|
m_Graph.CheckHandle(h);
|
||||||
|
auto& entry = m_Graph.m_Textures[h.index];
|
||||||
|
if (entry.imported) {
|
||||||
|
// swapchain image 不按槽位持久存在,历史读无意义
|
||||||
|
throw std::runtime_error(
|
||||||
|
"RenderGraph: history read of imported resource '" + entry.name +
|
||||||
|
"' is not supported");
|
||||||
|
}
|
||||||
|
entry.hasHistoryUse = true;
|
||||||
|
m_Graph.m_Passes[m_PassIndex].uses.push_back({h.index, use, stage, true});
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
RenderGraph::PassBuilder& RenderGraph::PassBuilder::SideEffect()
|
RenderGraph::PassBuilder& RenderGraph::PassBuilder::SideEffect()
|
||||||
{
|
{
|
||||||
m_Graph.CheckMutable();
|
m_Graph.CheckMutable();
|
||||||
@@ -161,6 +177,7 @@ namespace Engine::RG {
|
|||||||
for (auto& pass : m_Passes) {
|
for (auto& pass : m_Passes) {
|
||||||
pass.culled = false;
|
pass.culled = false;
|
||||||
pass.barriers.clear();
|
pass.barriers.clear();
|
||||||
|
pass.postBarriers.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto& entry : m_Textures) ResolveUsage(entry);
|
for (auto& entry : m_Textures) ResolveUsage(entry);
|
||||||
@@ -266,6 +283,7 @@ namespace Engine::RG {
|
|||||||
|
|
||||||
bool keep = pass.sideEffect;
|
bool keep = pass.sideEffect;
|
||||||
for (const auto& use : pass.uses) {
|
for (const auto& use : pass.uses) {
|
||||||
|
// 帧历史读照常参与:它消费了上一帧的产出,生产者必须存活
|
||||||
if (IsWriteUse(use.use) && readByKept[use.resIndex]) {
|
if (IsWriteUse(use.use) && readByKept[use.resIndex]) {
|
||||||
keep = true;
|
keep = true;
|
||||||
} else if (m_Textures[use.resIndex].imported) {
|
} else if (m_Textures[use.resIndex].imported) {
|
||||||
@@ -318,6 +336,11 @@ namespace Engine::RG {
|
|||||||
auto& lw = lastWriter[use.resIndex];
|
auto& lw = lastWriter[use.resIndex];
|
||||||
auto& readers = pendingReaders[use.resIndex];
|
auto& readers = pendingReaders[use.resIndex];
|
||||||
|
|
||||||
|
// 帧历史读不建边:它消费的是上一帧槽位的物理镜像(与本帧
|
||||||
|
// 任何写操作无资源竞争),跨帧依赖由同队列提交序 + 历史
|
||||||
|
// barrier(ALL_COMMANDS 可见性同步)保证
|
||||||
|
if (use.history) continue;
|
||||||
|
|
||||||
if (IsWriteUse(use.use)) {
|
if (IsWriteUse(use.use)) {
|
||||||
for (u32 r : readers) addEdge(r, i); // WAR
|
for (u32 r : readers) addEdge(r, i); // WAR
|
||||||
readers.clear();
|
readers.clear();
|
||||||
@@ -364,6 +387,7 @@ namespace Engine::RG {
|
|||||||
for (u32 pos = 0; pos < m_ExecutionOrder.size(); ++pos) {
|
for (u32 pos = 0; pos < m_ExecutionOrder.size(); ++pos) {
|
||||||
const u32 passIdx = m_ExecutionOrder[pos];
|
const u32 passIdx = m_ExecutionOrder[pos];
|
||||||
for (const auto& use : m_Passes[passIdx].uses) {
|
for (const auto& use : m_Passes[passIdx].uses) {
|
||||||
|
if (use.history) continue; // 帧历史读不改变本帧状态链与生命周期
|
||||||
auto& entry = m_Textures[use.resIndex];
|
auto& entry = m_Textures[use.resIndex];
|
||||||
if (entry.firstPass < 0) entry.firstPass = static_cast<i32>(pos);
|
if (entry.firstPass < 0) entry.firstPass = static_cast<i32>(pos);
|
||||||
entry.lastPass = static_cast<i32>(pos);
|
entry.lastPass = static_cast<i32>(pos);
|
||||||
@@ -388,6 +412,7 @@ namespace Engine::RG {
|
|||||||
// ---- Pass A:状态链推演 ----
|
// ---- Pass A:状态链推演 ----
|
||||||
for (const u32 passIdx : m_ExecutionOrder) {
|
for (const u32 passIdx : m_ExecutionOrder) {
|
||||||
for (const auto& use : m_Passes[passIdx].uses) {
|
for (const auto& use : m_Passes[passIdx].uses) {
|
||||||
|
if (use.history) continue; // 历史读操作另一槽位的镜像,不入本帧链
|
||||||
auto& entry = m_Textures[use.resIndex];
|
auto& entry = m_Textures[use.resIndex];
|
||||||
entry.chainLayout = UseLayout(use.use);
|
entry.chainLayout = UseLayout(use.use);
|
||||||
entry.chainStage = UseDstStage(use);
|
entry.chainStage = UseDstStage(use);
|
||||||
@@ -417,6 +442,50 @@ namespace Engine::RG {
|
|||||||
for (const auto& use : pass.uses) {
|
for (const auto& use : pass.uses) {
|
||||||
auto& entry = m_Textures[use.resIndex];
|
auto& entry = m_Textures[use.resIndex];
|
||||||
|
|
||||||
|
// ---- 帧历史读:操作上一帧槽位的物理镜像,不进入本帧状态链。
|
||||||
|
// first = 该槽位镜像从未用过(图刚编译/重编译),old=Undefined
|
||||||
|
// steady = 跨帧可见性同步:src=ALL_COMMANDS|MEMORY_WRITE
|
||||||
|
// 覆盖上一帧提交对该镜像的全部写入(同队列提交序
|
||||||
|
// 保证 barrier 执行时上一帧命令已全部完成,
|
||||||
|
// ALL_COMMANDS 把可用性+可见性显式闭合),
|
||||||
|
// old/new = resting layout(历史镜像静止在读布局)
|
||||||
|
if (use.history) {
|
||||||
|
const vk::ImageLayout desiredLayout = UseLayout(use.use);
|
||||||
|
const vk::PipelineStageFlags2 dstStage = UseDstStage(use);
|
||||||
|
const vk::AccessFlags2 dstAccess = UseDstAccess(use);
|
||||||
|
|
||||||
|
vk::ImageMemoryBarrier2 first;
|
||||||
|
first.setSrcStageMask(vk::PipelineStageFlagBits2::eNone)
|
||||||
|
.setSrcAccessMask(vk::AccessFlagBits2::eNone)
|
||||||
|
.setDstStageMask(dstStage)
|
||||||
|
.setDstAccessMask(dstAccess)
|
||||||
|
.setOldLayout(vk::ImageLayout::eUndefined)
|
||||||
|
.setNewLayout(desiredLayout)
|
||||||
|
.setSubresourceRange({entry.aspect, 0, 1, 0, 1});
|
||||||
|
|
||||||
|
vk::ImageMemoryBarrier2 steady = first;
|
||||||
|
steady.setSrcStageMask(vk::PipelineStageFlagBits2::eAllCommands)
|
||||||
|
.setSrcAccessMask(vk::AccessFlagBits2::eMemoryWrite)
|
||||||
|
.setOldLayout(entry.restingLayout);
|
||||||
|
|
||||||
|
pass.barriers.push_back({use.resIndex, steady, first, true});
|
||||||
|
|
||||||
|
// 出口 barrier:采样完成后把历史镜像转回 resting layout。
|
||||||
|
// (若 desired == resting 则是无害的空转换,但保留它以
|
||||||
|
// 统一语义——src scope 同时给出读取完成的内存序)
|
||||||
|
vk::ImageMemoryBarrier2 exit;
|
||||||
|
exit.setSrcStageMask(dstStage)
|
||||||
|
.setSrcAccessMask(dstAccess)
|
||||||
|
.setDstStageMask(vk::PipelineStageFlagBits2::eNone)
|
||||||
|
.setDstAccessMask(vk::AccessFlagBits2::eNone)
|
||||||
|
.setOldLayout(desiredLayout)
|
||||||
|
.setNewLayout(entry.restingLayout)
|
||||||
|
.setSubresourceRange({entry.aspect, 0, 1, 0, 1});
|
||||||
|
pass.postBarriers.push_back({use.resIndex, exit, exit, true});
|
||||||
|
|
||||||
|
continue; // 不推进 chain:本帧链只描述当前槽位镜像
|
||||||
|
}
|
||||||
|
|
||||||
const vk::ImageLayout desiredLayout = UseLayout(use.use);
|
const vk::ImageLayout desiredLayout = UseLayout(use.use);
|
||||||
const vk::PipelineStageFlags2 dstStage = UseDstStage(use);
|
const vk::PipelineStageFlags2 dstStage = UseDstStage(use);
|
||||||
const vk::AccessFlags2 dstAccess = UseDstAccess(use);
|
const vk::AccessFlags2 dstAccess = UseDstAccess(use);
|
||||||
@@ -452,6 +521,17 @@ namespace Engine::RG {
|
|||||||
steady = makeBarrier(entry.restingLayout,
|
steady = makeBarrier(entry.restingLayout,
|
||||||
entry.restingStage,
|
entry.restingStage,
|
||||||
entry.restingAccess);
|
entry.restingAccess);
|
||||||
|
if (entry.hasHistoryUse) {
|
||||||
|
// 历史读打破了"同槽位两次进入之间有 fence 全等保"
|
||||||
|
// 的前提(上一帧对该槽位镜像的历史读不在本槽位
|
||||||
|
// fence 的保护范围内),src 升级为 ALL_COMMANDS
|
||||||
|
// 覆盖跨帧的一切触碰
|
||||||
|
steady.setSrcStageMask(
|
||||||
|
vk::PipelineStageFlagBits2::eAllCommands)
|
||||||
|
.setSrcAccessMask(
|
||||||
|
vk::AccessFlagBits2::eMemoryWrite |
|
||||||
|
vk::AccessFlagBits2::eMemoryRead);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pass.barriers.push_back({use.resIndex, steady, first});
|
pass.barriers.push_back({use.resIndex, steady, first});
|
||||||
@@ -482,7 +562,16 @@ namespace Engine::RG {
|
|||||||
void RenderGraph::AllocatePhysical()
|
void RenderGraph::AllocatePhysical()
|
||||||
{
|
{
|
||||||
for (auto& entry : m_Textures) {
|
for (auto& entry : m_Textures) {
|
||||||
if (entry.imported || entry.firstPass < 0) continue;
|
if (entry.imported) continue;
|
||||||
|
|
||||||
|
// 帧历史校验:历史资源必须有同帧正常使用(否则既无产出也无镜像)
|
||||||
|
if (entry.hasHistoryUse && entry.firstPass < 0) {
|
||||||
|
throw std::runtime_error(
|
||||||
|
"RenderGraph: history-read resource '" + entry.name +
|
||||||
|
"' has no same-frame use (nothing to read history of)");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry.firstPass < 0) continue;
|
||||||
|
|
||||||
// 显式尺寸优先;否则 = 目标尺寸 × sizeScale(后处理降采样)
|
// 显式尺寸优先;否则 = 目标尺寸 × sizeScale(后处理降采样)
|
||||||
vk::Extent2D extent{
|
vk::Extent2D extent{
|
||||||
@@ -530,8 +619,7 @@ namespace Engine::RG {
|
|||||||
for (auto& img : it->second) {
|
for (auto& img : it->second) {
|
||||||
if (!img->valid) continue;
|
if (!img->valid) continue;
|
||||||
vkDevice.destroyImageView(img->view);
|
vkDevice.destroyImageView(img->view);
|
||||||
vkDevice.destroyImage(img->image);
|
m_Allocator.Destroy(img->allocation);
|
||||||
m_Allocator.Free(img->allocation);
|
|
||||||
}
|
}
|
||||||
it = m_Pool.erase(it);
|
it = m_Pool.erase(it);
|
||||||
} else {
|
} else {
|
||||||
@@ -576,11 +664,10 @@ namespace Engine::RG {
|
|||||||
.setSharingMode(vk::SharingMode::eExclusive)
|
.setSharingMode(vk::SharingMode::eExclusive)
|
||||||
.setInitialLayout(vk::ImageLayout::eUndefined);
|
.setInitialLayout(vk::ImageLayout::eUndefined);
|
||||||
|
|
||||||
img->image = vkDevice.createImage(imageInfo);
|
// VMA:创建 + 分配 + 绑定一步完成
|
||||||
|
img->allocation = m_Allocator.CreateImage(imageInfo,
|
||||||
auto reqs = vkDevice.getImageMemoryRequirements(img->image);
|
vk::MemoryPropertyFlagBits::eDeviceLocal);
|
||||||
img->allocation = m_Allocator.Allocate(reqs, vk::MemoryPropertyFlagBits::eDeviceLocal);
|
img->image = img->allocation.Image;
|
||||||
vkDevice.bindImageMemory(img->image, img->allocation.Memory, img->allocation.Offset);
|
|
||||||
|
|
||||||
vk::ImageViewCreateInfo viewInfo;
|
vk::ImageViewCreateInfo viewInfo;
|
||||||
viewInfo.setImage(img->image)
|
viewInfo.setImage(img->image)
|
||||||
@@ -652,21 +739,27 @@ namespace Engine::RG {
|
|||||||
for (const auto& bp : pass.barriers) {
|
for (const auto& bp : pass.barriers) {
|
||||||
auto& entry = m_Textures[bp.resIndex];
|
auto& entry = m_Textures[bp.resIndex];
|
||||||
|
|
||||||
|
u32 imageSlot = frame.frameSlot;
|
||||||
bool useFirst;
|
bool useFirst;
|
||||||
if (entry.imported) {
|
if (entry.imported) {
|
||||||
useFirst = true; // 恒为 Undefined 起点
|
useFirst = true; // 恒为 Undefined 起点
|
||||||
|
} else if (bp.history) {
|
||||||
|
// 帧历史:操作上一帧槽位的物理镜像。
|
||||||
|
// used 标记持久(跨帧),首帧走 first,之后走 steady
|
||||||
|
imageSlot = HistorySlot(frame.frameSlot);
|
||||||
|
useFirst = !entry.slots[imageSlot].used;
|
||||||
|
entry.slots[imageSlot].used = true;
|
||||||
} else {
|
} else {
|
||||||
auto& slot = entry.slots[frame.frameSlot];
|
useFirst = !entry.slots[imageSlot].used;
|
||||||
useFirst = !slot.used;
|
entry.slots[imageSlot].used = true;
|
||||||
slot.used = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
vk::ImageMemoryBarrier2 b = useFirst ? bp.first : bp.steady;
|
vk::ImageMemoryBarrier2 b = useFirst ? bp.first : bp.steady;
|
||||||
// image 句柄在执行期绑定:imported 用当帧 acquire 的 image,
|
// image 句柄在执行期绑定:imported 用当帧 acquire 的 image,
|
||||||
// created 用当前帧槽位的物理镜像(每个槽位各有一份)
|
// created 用当前帧槽位(历史 barrier 用上一帧槽位)的物理镜像
|
||||||
b.setImage(entry.imported
|
b.setImage(entry.imported
|
||||||
? entry.importedImage
|
? entry.importedImage
|
||||||
: entry.physical[frame.frameSlot]->image);
|
: entry.physical[imageSlot]->image);
|
||||||
m_ScratchBarriers.push_back(b);
|
m_ScratchBarriers.push_back(b);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -685,6 +778,21 @@ namespace Engine::RG {
|
|||||||
pass.exec(ctx);
|
pass.exec(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 历史读出口:恢复上一帧槽位镜像的 resting layout
|
||||||
|
if (!pass.postBarriers.empty()) {
|
||||||
|
m_ScratchBarriers.clear();
|
||||||
|
for (const auto& bp : pass.postBarriers) {
|
||||||
|
auto& entry = m_Textures[bp.resIndex];
|
||||||
|
const u32 histSlot = HistorySlot(frame.frameSlot);
|
||||||
|
vk::ImageMemoryBarrier2 b = bp.steady;
|
||||||
|
b.setImage(entry.physical[histSlot]->image);
|
||||||
|
m_ScratchBarriers.push_back(b);
|
||||||
|
}
|
||||||
|
vk::DependencyInfo dep;
|
||||||
|
dep.setImageMemoryBarriers(m_ScratchBarriers);
|
||||||
|
frame.cmd.pipelineBarrier2(dep);
|
||||||
|
}
|
||||||
|
|
||||||
if (m_QueryPool) {
|
if (m_QueryPool) {
|
||||||
frame.cmd.writeTimestamp2(
|
frame.cmd.writeTimestamp2(
|
||||||
vk::PipelineStageFlagBits2::eBottomOfPipe,
|
vk::PipelineStageFlagBits2::eBottomOfPipe,
|
||||||
@@ -710,6 +818,11 @@ namespace Engine::RG {
|
|||||||
return entry.physical[frame.frameSlot]->image;
|
return entry.physical[frame.frameSlot]->image;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
u32 RenderGraphContext::HistorySlot(u32 frameSlot) const
|
||||||
|
{
|
||||||
|
return graph->HistorySlot(frameSlot);
|
||||||
|
}
|
||||||
|
|
||||||
// ---------- 工具 / 统计 ----------
|
// ---------- 工具 / 统计 ----------
|
||||||
|
|
||||||
TextureEntry& RenderGraph::GetEntry(TextureHandle h)
|
TextureEntry& RenderGraph::GetEntry(TextureHandle h)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
#include <Engine/Renderer/Camera.h>
|
#include <Engine/Renderer/Camera.h>
|
||||||
#include <Engine/Core/Window.h>
|
#include <Engine/Core/Window.h>
|
||||||
|
#include <Engine/ECS/Components.h>
|
||||||
|
|
||||||
#include <glm/gtc/matrix_transform.hpp>
|
#include <glm/gtc/matrix_transform.hpp>
|
||||||
|
|
||||||
@@ -11,9 +12,9 @@
|
|||||||
|
|
||||||
namespace Engine {
|
namespace Engine {
|
||||||
|
|
||||||
void Camera::Update(Window& window, f32 deltaTime)
|
void Camera::Update(Window& window, ECS::TransformComponent& t, f32 deltaTime)
|
||||||
{
|
{
|
||||||
// 鼠标旋转(需显式启用,配合 cursor capture 使用)
|
// 鼠标旋转:直接写实体旋转(yaw → rotation.y,pitch → rotation.x)
|
||||||
if (m_MouseLookEnabled)
|
if (m_MouseLookEnabled)
|
||||||
{
|
{
|
||||||
f32 mouseDX = 0, mouseDY = 0;
|
f32 mouseDX = 0, mouseDY = 0;
|
||||||
@@ -21,37 +22,38 @@ namespace Engine {
|
|||||||
|
|
||||||
if (mouseDX != 0 || mouseDY != 0)
|
if (mouseDX != 0 || mouseDY != 0)
|
||||||
{
|
{
|
||||||
m_Yaw += mouseDX * m_MouseSensitivity;
|
glm::vec3 rot = t.GetRotation();
|
||||||
m_Pitch -= mouseDY * m_MouseSensitivity;
|
rot.y += mouseDX * m_MouseSensitivity;
|
||||||
m_Pitch = glm::clamp(m_Pitch, -89.0f, 89.0f);
|
rot.x = glm::clamp(
|
||||||
|
rot.x - mouseDY * m_MouseSensitivity, -89.0f, 89.0f);
|
||||||
|
t.SetRotation(rot);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
UpdateBasis();
|
// 基向量由实体旋转决定(本地 -Z 为视线方向;应用顺序 Y→X,
|
||||||
|
// 与 TransformComponent::Compose 一致;roll 不参与 FPS 相机)
|
||||||
|
const glm::vec3& rot = t.GetRotation();
|
||||||
|
const glm::mat3 r = static_cast<glm::mat3>(
|
||||||
|
glm::rotate(glm::mat4(1.0f), glm::radians(rot.y),
|
||||||
|
glm::vec3(0, 1, 0)) *
|
||||||
|
glm::rotate(glm::mat4(1.0f), glm::radians(rot.x),
|
||||||
|
glm::vec3(1, 0, 0)));
|
||||||
|
const glm::vec3 forward = r * glm::vec3(0, 0, -1);
|
||||||
|
const glm::vec3 right = r * glm::vec3(1, 0, 0);
|
||||||
|
|
||||||
f32 speed = m_MoveSpeed * deltaTime;
|
f32 speed = m_MoveSpeed * deltaTime;
|
||||||
if (window.IsKeyPressed(GLFW_KEY_W)) m_Position += m_Forward * speed;
|
if (window.IsKeyPressed(GLFW_KEY_W)) { t.SetPosition(t.GetPosition() + forward * speed); }
|
||||||
if (window.IsKeyPressed(GLFW_KEY_S)) m_Position -= m_Forward * speed;
|
if (window.IsKeyPressed(GLFW_KEY_S)) { t.SetPosition(t.GetPosition() - forward * speed); }
|
||||||
if (window.IsKeyPressed(GLFW_KEY_A)) m_Position -= m_Right * speed;
|
if (window.IsKeyPressed(GLFW_KEY_A)) { t.SetPosition(t.GetPosition() - right * speed); }
|
||||||
if (window.IsKeyPressed(GLFW_KEY_D)) m_Position += m_Right * speed;
|
if (window.IsKeyPressed(GLFW_KEY_D)) { t.SetPosition(t.GetPosition() + right * speed); }
|
||||||
if (window.IsKeyPressed(GLFW_KEY_SPACE)) m_Position.y += speed;
|
if (window.IsKeyPressed(GLFW_KEY_SPACE)) { t.SetPosition(t.GetPosition() + glm::vec3(0, speed, 0)); }
|
||||||
if (window.IsKeyPressed(GLFW_KEY_LEFT_SHIFT)) m_Position.y -= speed;
|
if (window.IsKeyPressed(GLFW_KEY_LEFT_SHIFT)) { t.SetPosition(t.GetPosition() - glm::vec3(0, speed, 0)); }
|
||||||
}
|
}
|
||||||
|
|
||||||
void Camera::UpdateBasis()
|
glm::mat4 Camera::GetView(const ECS::TransformComponent& t) const
|
||||||
{
|
{
|
||||||
m_Forward.x = cosf(glm::radians(m_Yaw)) * cosf(glm::radians(m_Pitch));
|
// 相机作为实体:视图 = 世界矩阵的逆(无重复位姿状态)
|
||||||
m_Forward.y = sinf(glm::radians(m_Pitch));
|
return glm::inverse(t.World());
|
||||||
m_Forward.z = sinf(glm::radians(m_Yaw)) * cosf(glm::radians(m_Pitch));
|
|
||||||
m_Forward = glm::normalize(m_Forward);
|
|
||||||
|
|
||||||
m_Right = glm::normalize(glm::cross(m_Forward, glm::vec3(0, 1, 0)));
|
|
||||||
m_Up = glm::normalize(glm::cross(m_Right, m_Forward));
|
|
||||||
}
|
|
||||||
|
|
||||||
glm::mat4 Camera::GetView() const
|
|
||||||
{
|
|
||||||
return glm::lookAt(m_Position, m_Position + m_Forward, m_Up);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
glm::mat4 Camera::GetProjection(f32 aspect, f32 nearZ, f32 farZ) const
|
glm::mat4 Camera::GetProjection(f32 aspect, f32 nearZ, f32 farZ) const
|
||||||
|
|||||||
@@ -12,19 +12,31 @@
|
|||||||
namespace Engine
|
namespace Engine
|
||||||
{
|
{
|
||||||
Material::Material(Device& device,
|
Material::Material(Device& device,
|
||||||
DescriptorManager& descriptorManager,
|
DescriptorManager& descriptorManager,
|
||||||
vk::DescriptorSetLayout layout,
|
vk::DescriptorSetLayout layout,
|
||||||
Texture* texture)
|
const std::array<Texture*, kSlotCount>& textures)
|
||||||
: m_Texture(texture)
|
: m_Textures(textures)
|
||||||
{
|
{
|
||||||
m_DescriptorSet = descriptorManager.AllocateDescriptorSet(layout);
|
m_DescriptorSet = descriptorManager.AllocateDescriptorSet(layout);
|
||||||
|
|
||||||
descriptorManager.UpdateTexture(
|
for (u32 slot = 0; slot < kSlotCount; ++slot) {
|
||||||
m_DescriptorSet, 0,
|
descriptorManager.UpdateTexture(
|
||||||
m_Texture->GetSampler(),
|
m_DescriptorSet, slot,
|
||||||
m_Texture->GetView());
|
m_Textures[slot]->GetSampler(),
|
||||||
|
m_Textures[slot]->GetView());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
Material::~Material() = default;
|
Material::~Material() = default;
|
||||||
|
|
||||||
|
std::string Material::MakeCacheKey(const std::string paths[kSlotCount])
|
||||||
|
{
|
||||||
|
std::string key;
|
||||||
|
for (u32 slot = 0; slot < kSlotCount; ++slot) {
|
||||||
|
if (slot) key += '\n';
|
||||||
|
key += paths[slot];
|
||||||
|
}
|
||||||
|
return key;
|
||||||
|
}
|
||||||
} // namespace Engine
|
} // namespace Engine
|
||||||
|
|||||||
@@ -9,6 +9,8 @@
|
|||||||
#include <Engine/Vulkan/Buffer.h>
|
#include <Engine/Vulkan/Buffer.h>
|
||||||
#include <Engine/Core/Log.h>
|
#include <Engine/Core/Log.h>
|
||||||
|
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
#include "Engine/Core/Stats.h"
|
#include "Engine/Core/Stats.h"
|
||||||
|
|
||||||
namespace Engine
|
namespace Engine
|
||||||
@@ -16,8 +18,8 @@ namespace Engine
|
|||||||
Mesh::Mesh(Device& device, MemoryAllocator& allocator,
|
Mesh::Mesh(Device& device, MemoryAllocator& allocator,
|
||||||
const std::vector<Vertex>& vertices,
|
const std::vector<Vertex>& vertices,
|
||||||
const std::vector<u32>& indices,
|
const std::vector<u32>& indices,
|
||||||
Scope<Material> material)
|
Material* material)
|
||||||
: m_Material(std::move(material))
|
: m_Material(material)
|
||||||
{
|
{
|
||||||
if (vertices.empty() || indices.empty())
|
if (vertices.empty() || indices.empty())
|
||||||
{
|
{
|
||||||
@@ -43,6 +45,22 @@ namespace Engine
|
|||||||
|
|
||||||
m_IndexCount = static_cast<u32>(indices.size());
|
m_IndexCount = static_cast<u32>(indices.size());
|
||||||
|
|
||||||
|
// 局部空间包围球:顶点包围盒 → 中心 + 最大半径(视锥剔除用)
|
||||||
|
{
|
||||||
|
glm::vec3 mn(std::numeric_limits<f32>::max());
|
||||||
|
glm::vec3 mx(std::numeric_limits<f32>::lowest());
|
||||||
|
for (const auto& v : vertices) {
|
||||||
|
mn = glm::min(mn, v.position);
|
||||||
|
mx = glm::max(mx, v.position);
|
||||||
|
}
|
||||||
|
m_Bounds.center = (mn + mx) * 0.5f;
|
||||||
|
m_Bounds.radius = 0.0f;
|
||||||
|
for (const auto& v : vertices) {
|
||||||
|
m_Bounds.radius = glm::max(m_Bounds.radius,
|
||||||
|
glm::length(v.position - m_Bounds.center));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ENGINE_INFO("Mesh created: {} vertices, {} indices", vertices.size(), indices.size());
|
ENGINE_INFO("Mesh created: {} vertices, {} indices", vertices.size(), indices.size());
|
||||||
|
|
||||||
m_VertexBytes = sizeof(Vertex) * vertices.size();
|
m_VertexBytes = sizeof(Vertex) * vertices.size();
|
||||||
@@ -55,10 +73,10 @@ namespace Engine
|
|||||||
STATS.RemoveMesh(m_VertexBytes, m_IndexBytes);
|
STATS.RemoveMesh(m_VertexBytes, m_IndexBytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Mesh::Draw(vk::CommandBuffer cmd) const
|
void Mesh::Draw(vk::CommandBuffer cmd, u32 instanceCount, u32 firstInstance) const
|
||||||
{
|
{
|
||||||
cmd.bindVertexBuffers(0, m_VertexBuffer->Get(), {0});
|
cmd.bindVertexBuffers(0, m_VertexBuffer->Get(), {0});
|
||||||
cmd.bindIndexBuffer(m_IndexBuffer->Get(), 0, vk::IndexType::eUint32);
|
cmd.bindIndexBuffer(m_IndexBuffer->Get(), 0, vk::IndexType::eUint32);
|
||||||
cmd.drawIndexed(m_IndexCount, 1, 0, 0, 0);
|
cmd.drawIndexed(m_IndexCount, instanceCount, 0, 0, firstInstance);
|
||||||
}
|
}
|
||||||
} // namespace Engine
|
} // namespace Engine
|
||||||
|
|||||||
@@ -15,6 +15,7 @@
|
|||||||
#include <assimp/postprocess.h>
|
#include <assimp/postprocess.h>
|
||||||
|
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
namespace Engine {
|
namespace Engine {
|
||||||
|
|
||||||
@@ -27,15 +28,27 @@ Model::Model(Device& device, MemoryAllocator& allocator,
|
|||||||
, m_DescriptorManager(descriptorManager)
|
, m_DescriptorManager(descriptorManager)
|
||||||
, m_TextureLayout(textureLayout)
|
, m_TextureLayout(textureLayout)
|
||||||
{
|
{
|
||||||
// 兜底纹理
|
InitDefaultTextures();
|
||||||
m_DefaultTexture = CreateScope<Texture>(
|
|
||||||
m_Device, m_Allocator, "assets/textures/default.tga");
|
|
||||||
|
|
||||||
LoadModel(path);
|
LoadModel(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
Model::~Model() = default;
|
Model::~Model() = default;
|
||||||
|
|
||||||
|
void Model::InitDefaultTextures()
|
||||||
|
{
|
||||||
|
// 各材质槽位的兜底纹理(1x1,开销可忽略):
|
||||||
|
// albedo 白(受光) / normal 平法线 (0.5,0.5,1) / specular 黑 / emissive 黑
|
||||||
|
m_DefaultTextures[Material::Albedo] =
|
||||||
|
Texture::CreateSolidColor(m_Device, m_Allocator, 255, 255, 255);
|
||||||
|
m_DefaultTextures[Material::Normal] =
|
||||||
|
Texture::CreateSolidColor(m_Device, m_Allocator, 128, 128, 255, false);
|
||||||
|
m_DefaultTextures[Material::Specular] =
|
||||||
|
Texture::CreateSolidColor(m_Device, m_Allocator, 0, 0, 0, false);
|
||||||
|
m_DefaultTextures[Material::Emissive] =
|
||||||
|
Texture::CreateSolidColor(m_Device, m_Allocator, 0, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
Model::Model(Device& device, MemoryAllocator& allocator,
|
Model::Model(Device& device, MemoryAllocator& allocator,
|
||||||
DescriptorManager& descriptorManager,
|
DescriptorManager& descriptorManager,
|
||||||
vk::DescriptorSetLayout textureLayout,
|
vk::DescriptorSetLayout textureLayout,
|
||||||
@@ -45,7 +58,8 @@ Model::Model(Device& device, MemoryAllocator& allocator,
|
|||||||
, m_DescriptorManager(descriptorManager)
|
, m_DescriptorManager(descriptorManager)
|
||||||
, m_TextureLayout(textureLayout)
|
, m_TextureLayout(textureLayout)
|
||||||
{
|
{
|
||||||
// 纯色 1x1 贴图
|
// 纯色 1x1 贴图(albedo 兜底)
|
||||||
|
InitDefaultTextures();
|
||||||
auto tex = Texture::CreateSolidColor(device, allocator, color[0], color[1], color[2]);
|
auto tex = Texture::CreateSolidColor(device, allocator, color[0], color[1], color[2]);
|
||||||
|
|
||||||
// XZ 平面,法线 +Y,从上往下看 CCW 绕序
|
// XZ 平面,法线 +Y,从上往下看 CCW 绕序
|
||||||
@@ -58,14 +72,12 @@ Model::Model(Device& device, MemoryAllocator& allocator,
|
|||||||
};
|
};
|
||||||
const std::vector<u32> indices = {0, 1, 2, 0, 2, 3};
|
const std::vector<u32> indices = {0, 1, 2, 0, 2, 3};
|
||||||
|
|
||||||
auto mat = CreateScope<Material>(
|
// 材质/纹理生命周期随模型(缓存持有)
|
||||||
m_Device, m_DescriptorManager, m_TextureLayout, tex.get());
|
std::string paths[Material::kSlotCount] = {"", "", "", ""};
|
||||||
|
Material* mat = GetOrCreateMaterial(paths); // 全默认材质
|
||||||
|
|
||||||
m_Meshes.push_back(CreateScope<Mesh>(
|
m_Meshes.push_back(CreateScope<Mesh>(
|
||||||
m_Device, m_Allocator, vertices, indices, std::move(mat)));
|
m_Device, m_Allocator, vertices, indices, mat));
|
||||||
|
|
||||||
// 贴图生命周期随模型
|
|
||||||
m_DefaultTexture = std::move(tex);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Scope<Model> Model::CreatePlane(Device& device, MemoryAllocator& allocator,
|
Scope<Model> Model::CreatePlane(Device& device, MemoryAllocator& allocator,
|
||||||
@@ -87,6 +99,7 @@ void Model::LoadModel(const std::string& path)
|
|||||||
aiProcess_Triangulate |
|
aiProcess_Triangulate |
|
||||||
aiProcess_FlipUVs |
|
aiProcess_FlipUVs |
|
||||||
aiProcess_GenSmoothNormals |
|
aiProcess_GenSmoothNormals |
|
||||||
|
aiProcess_CalcTangentSpace | // 法线贴图 TBN
|
||||||
aiProcess_JoinIdenticalVertices);
|
aiProcess_JoinIdenticalVertices);
|
||||||
|
|
||||||
if (!scene || (scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE) || !scene->mRootNode) {
|
if (!scene || (scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE) || !scene->mRootNode) {
|
||||||
@@ -110,9 +123,15 @@ void Model::ProcessNode(aiNode* node, const aiScene* scene)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Texture* Model::GetOrLoadTexture(const std::string& path)
|
Texture* Model::GetOrLoadTexture(const std::string& rawPath, bool srgb)
|
||||||
{
|
{
|
||||||
auto it = m_TextureCache.find(path);
|
// FBX/DCC 导出的路径常带反斜杠(Windows 语义),统一成正斜杠
|
||||||
|
std::string path = rawPath;
|
||||||
|
std::replace(path.begin(), path.end(), '\\', '/');
|
||||||
|
|
||||||
|
// 缓存键带 sRGB 语义:同一路径可能同时被颜色槽与数据槽引用
|
||||||
|
std::string key = (srgb ? "s:" : "l:") + path;
|
||||||
|
auto it = m_TextureCache.find(key);
|
||||||
if (it != m_TextureCache.end()) {
|
if (it != m_TextureCache.end()) {
|
||||||
return it->second.get();
|
return it->second.get();
|
||||||
}
|
}
|
||||||
@@ -120,12 +139,39 @@ Texture* Model::GetOrLoadTexture(const std::string& path)
|
|||||||
// 文件不存在则用兜底纹理
|
// 文件不存在则用兜底纹理
|
||||||
if (!std::filesystem::exists(path)) {
|
if (!std::filesystem::exists(path)) {
|
||||||
ENGINE_WARN("Texture not found, using default: {}", path);
|
ENGINE_WARN("Texture not found, using default: {}", path);
|
||||||
return m_DefaultTexture.get();
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto tex = CreateScope<Texture>(m_Device, m_Allocator, path);
|
auto tex = CreateScope<Texture>(m_Device, m_Allocator, path, srgb);
|
||||||
Texture* ptr = tex.get();
|
Texture* ptr = tex.get();
|
||||||
m_TextureCache.emplace(path, std::move(tex));
|
m_TextureCache.emplace(key, std::move(tex));
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
Material* Model::GetOrCreateMaterial(const std::string paths[Material::kSlotCount])
|
||||||
|
{
|
||||||
|
const std::string key = Material::MakeCacheKey(paths);
|
||||||
|
if (auto it = m_MaterialCache.find(key); it != m_MaterialCache.end()) {
|
||||||
|
return it->second.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 每槽位解析纹理:空/缺失折到该槽位默认纹理
|
||||||
|
std::array<Texture*, Material::kSlotCount> textures{};
|
||||||
|
// 槽位 sRGB 语义:颜色图 true,数据图 false
|
||||||
|
const bool slotSrgb[Material::kSlotCount] = {true, false, false, true};
|
||||||
|
|
||||||
|
for (u32 slot = 0; slot < Material::kSlotCount; ++slot) {
|
||||||
|
Texture* tex = nullptr;
|
||||||
|
if (!paths[slot].empty()) {
|
||||||
|
tex = GetOrLoadTexture(paths[slot], slotSrgb[slot]);
|
||||||
|
}
|
||||||
|
textures[slot] = tex ? tex : m_DefaultTextures[slot].get();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto mat = CreateScope<Material>(
|
||||||
|
m_Device, m_DescriptorManager, m_TextureLayout, textures);
|
||||||
|
Material* ptr = mat.get();
|
||||||
|
m_MaterialCache.emplace(key, std::move(mat));
|
||||||
return ptr;
|
return ptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -160,6 +206,15 @@ Scope<Mesh> Model::ProcessMesh(aiMesh* mesh, const aiScene* scene)
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 切线(法线贴图 TBN;无切线数据置零,shader 侧退回几何法线)
|
||||||
|
if (mesh->mTangents) {
|
||||||
|
vertex.tangent = {
|
||||||
|
mesh->mTangents[i].x,
|
||||||
|
mesh->mTangents[i].y,
|
||||||
|
mesh->mTangents[i].z
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
vertices.push_back(vertex);
|
vertices.push_back(vertex);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,30 +225,35 @@ Scope<Mesh> Model::ProcessMesh(aiMesh* mesh, const aiScene* scene)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 读取材质纹理路径
|
// 读取材质纹理路径(4 槽:albedo/normal/specular/emissive)
|
||||||
std::string texturePath;
|
std::string texPaths[Material::kSlotCount];
|
||||||
|
|
||||||
if (mesh->mMaterialIndex >= 0) {
|
if (mesh->mMaterialIndex >= 0) {
|
||||||
aiMaterial* material = scene->mMaterials[mesh->mMaterialIndex];
|
aiMaterial* material = scene->mMaterials[mesh->mMaterialIndex];
|
||||||
|
const std::filesystem::path modelDir =
|
||||||
|
std::filesystem::path(m_ModelPath).parent_path();
|
||||||
|
|
||||||
aiString path;
|
const std::pair<aiTextureType, u32> slots[] = {
|
||||||
if (material->GetTexture(aiTextureType_DIFFUSE, 0, &path) == AI_SUCCESS) {
|
{aiTextureType_DIFFUSE, Material::Albedo},
|
||||||
std::filesystem::path modelDir =
|
{aiTextureType_NORMALS, Material::Normal},
|
||||||
std::filesystem::path(m_ModelPath).parent_path();
|
{aiTextureType_SPECULAR, Material::Specular},
|
||||||
texturePath = (modelDir / path.C_Str()).string();
|
{aiTextureType_EMISSIVE, Material::Emissive},
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const auto& [type, slot] : slots) {
|
||||||
|
aiString path;
|
||||||
|
if (material->GetTexture(type, 0, &path) == AI_SUCCESS &&
|
||||||
|
path.length > 0) {
|
||||||
|
texPaths[slot] = (modelDir / path.C_Str()).string();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 从缓存拿,没有才加载
|
// 材质缓存:同贴图组合 → 同一 Material(一个描述符集),mesh 数 ≠ 集数
|
||||||
Texture* tex = texturePath.empty()
|
Material* mat = GetOrCreateMaterial(texPaths);
|
||||||
? m_DefaultTexture.get()
|
|
||||||
: GetOrLoadTexture(texturePath);
|
|
||||||
|
|
||||||
auto mat = CreateScope<Material>(
|
|
||||||
m_Device, m_DescriptorManager, m_TextureLayout, tex);
|
|
||||||
|
|
||||||
return CreateScope<Mesh>(
|
return CreateScope<Mesh>(
|
||||||
m_Device, m_Allocator, vertices, indices, std::move(mat));
|
m_Device, m_Allocator, vertices, indices, mat);
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Engine
|
} // namespace Engine
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
//
|
||||||
|
// Created by atdunbg on 2026/9/23.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <Engine/Renderer/Passes/BloomPass.h>
|
||||||
|
#include <Engine/Core/FrameContext.h>
|
||||||
|
|
||||||
|
namespace Engine::Passes
|
||||||
|
{
|
||||||
|
BloomPass::BloomPass(FullscreenGpu& gpu, PostProcessParams& params,
|
||||||
|
RG::TextureHandle hdrColor,
|
||||||
|
RG::TextureHandle bloomA, RG::TextureHandle bloomB)
|
||||||
|
: m_Gpu(gpu), m_Params(params),
|
||||||
|
m_HDR(hdrColor), m_A(bloomA), m_B(bloomB)
|
||||||
|
{
|
||||||
|
m_ExtractSets.resize(kMaxFramesInFlight);
|
||||||
|
m_BlurHSets.resize(kMaxFramesInFlight);
|
||||||
|
m_BlurVSets.resize(kMaxFramesInFlight);
|
||||||
|
for (u32 slot = 0; slot < kMaxFramesInFlight; ++slot) {
|
||||||
|
m_ExtractSets[slot] = m_Gpu.AllocateSet();
|
||||||
|
m_BlurHSets[slot] = m_Gpu.AllocateSet();
|
||||||
|
m_BlurVSets[slot] = m_Gpu.AllocateSet();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BloomPass::Register(RG::RenderGraph& rg)
|
||||||
|
{
|
||||||
|
// Extract:HDR → 半分辨率 A(软阈值由 frag 的 param0 传入)
|
||||||
|
rg.AddPass("BloomExtract",
|
||||||
|
[this](RG::RenderGraphContext& ctx) {
|
||||||
|
m_Gpu.RecordFullscreen(ctx, ctx.View(m_A), m_Gpu.extractFrag,
|
||||||
|
m_ExtractSets[ctx.frame.frameSlot],
|
||||||
|
FullscreenPush{0.0f, m_Params.bloomThreshold, 0.0f, 0.0f});
|
||||||
|
})
|
||||||
|
.Read(m_HDR, RG::ResourceUse::ShaderRead,
|
||||||
|
vk::ShaderStageFlagBits::eFragment)
|
||||||
|
.Write(m_A, RG::ResourceUse::ColorAttachment);
|
||||||
|
|
||||||
|
// BlurH:A → B(水平步长经 push 传入)
|
||||||
|
rg.AddPass("BloomBlurH",
|
||||||
|
[this](RG::RenderGraphContext& ctx) {
|
||||||
|
m_Gpu.RecordFullscreen(ctx, ctx.View(m_B), m_Gpu.blurFrag,
|
||||||
|
m_BlurHSets[ctx.frame.frameSlot],
|
||||||
|
FullscreenPush{0.0f,
|
||||||
|
1.0f / ctx.extent.width, 0.0f,
|
||||||
|
0.0f});
|
||||||
|
})
|
||||||
|
.Read(m_A, RG::ResourceUse::ShaderRead,
|
||||||
|
vk::ShaderStageFlagBits::eFragment)
|
||||||
|
.Write(m_B, RG::ResourceUse::ColorAttachment);
|
||||||
|
|
||||||
|
// BlurV:B → A(垂直步长)
|
||||||
|
rg.AddPass("BloomBlurV",
|
||||||
|
[this](RG::RenderGraphContext& ctx) {
|
||||||
|
m_Gpu.RecordFullscreen(ctx, ctx.View(m_A), m_Gpu.blurFrag,
|
||||||
|
m_BlurVSets[ctx.frame.frameSlot],
|
||||||
|
FullscreenPush{0.0f, 0.0f,
|
||||||
|
1.0f / ctx.extent.height,
|
||||||
|
0.0f});
|
||||||
|
})
|
||||||
|
.Read(m_B, RG::ResourceUse::ShaderRead,
|
||||||
|
vk::ShaderStageFlagBits::eFragment)
|
||||||
|
.Write(m_A, RG::ResourceUse::ColorAttachment);
|
||||||
|
}
|
||||||
|
|
||||||
|
void BloomPass::OnTargetsRebuilt(RG::RenderGraph& rg)
|
||||||
|
{
|
||||||
|
for (u32 slot = 0; slot < kMaxFramesInFlight; ++slot) {
|
||||||
|
m_Gpu.UpdateSetInput(m_ExtractSets[slot], 0, rg, m_HDR, slot);
|
||||||
|
m_Gpu.UpdateSetInput(m_BlurHSets[slot], 0, rg, m_A, slot);
|
||||||
|
m_Gpu.UpdateSetInput(m_BlurVSets[slot], 0, rg, m_B, slot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Engine::Passes
|
||||||
@@ -0,0 +1,157 @@
|
|||||||
|
//
|
||||||
|
// Created by atdunbg on 2026/9/23.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <Engine/Renderer/Passes/ForwardPass.h>
|
||||||
|
#include <Engine/Renderer/Vertex.h>
|
||||||
|
#include <Engine/Renderer/Mesh.h>
|
||||||
|
#include <Engine/Renderer/Material.h>
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <span>
|
||||||
|
|
||||||
|
namespace Engine::Passes
|
||||||
|
{
|
||||||
|
void ForwardPass::Register(RG::RenderGraph& rg)
|
||||||
|
{
|
||||||
|
rg.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, 0.0f});
|
||||||
|
|
||||||
|
vk::RenderingAttachmentInfo colorAttachments[2];
|
||||||
|
colorAttachments[0].setImageView(ctx.View(m_HDRColor))
|
||||||
|
.setImageLayout(vk::ImageLayout::eColorAttachmentOptimal)
|
||||||
|
.setLoadOp(vk::AttachmentLoadOp::eClear)
|
||||||
|
.setStoreOp(vk::AttachmentStoreOp::eStore)
|
||||||
|
.setClearValue(clearValue);
|
||||||
|
|
||||||
|
// velocity 附件:清 0 = 静止(背景无几何 → 历史原位采样)
|
||||||
|
vk::ClearValue velocityClear;
|
||||||
|
velocityClear.color = vk::ClearColorValue(std::array<float, 4>{0.f, 0.f, 0.f, 0.f});
|
||||||
|
colorAttachments[1].setImageView(ctx.View(m_Velocity))
|
||||||
|
.setImageLayout(vk::ImageLayout::eColorAttachmentOptimal)
|
||||||
|
.setLoadOp(vk::AttachmentLoadOp::eClear)
|
||||||
|
.setStoreOp(vk::AttachmentStoreOp::eStore)
|
||||||
|
.setClearValue(velocityClear);
|
||||||
|
|
||||||
|
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)));
|
||||||
|
|
||||||
|
std::array<vk::RenderingAttachmentInfo, 2> colorArray = {
|
||||||
|
colorAttachments[0], colorAttachments[1]};
|
||||||
|
|
||||||
|
vk::RenderingInfo renderingInfo;
|
||||||
|
renderingInfo.setRenderArea({{0, 0}, ctx.extent})
|
||||||
|
.setLayerCount(1)
|
||||||
|
.setColorAttachments(colorArray)
|
||||||
|
.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_Gpu.forwardVert, m_Gpu.forwardFrag};
|
||||||
|
cmd.bindShadersEXT(stages, shaders);
|
||||||
|
|
||||||
|
// Set 0:本帧槽位的 UBO;Set 2:本帧槽位的阴影图
|
||||||
|
cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics,
|
||||||
|
m_Gpu.forwardLayout, 0,
|
||||||
|
m_Gpu.uboSets[ctx.frame.frameSlot], {});
|
||||||
|
cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics,
|
||||||
|
m_Gpu.forwardLayout, 2,
|
||||||
|
m_Gpu.shadowSets[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;
|
||||||
|
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);
|
||||||
|
|
||||||
|
vk::ColorComponentFlags colorWriteMask =
|
||||||
|
vk::ColorComponentFlagBits::eR |
|
||||||
|
vk::ColorComponentFlagBits::eG |
|
||||||
|
vk::ColorComponentFlagBits::eB |
|
||||||
|
vk::ColorComponentFlagBits::eA;
|
||||||
|
|
||||||
|
// 两个颜色附件(HDR + velocity)都是无混合直写
|
||||||
|
cmd.setColorBlendEnableEXT(0,
|
||||||
|
std::array<vk::Bool32, 2>{blendEnable, blendEnable});
|
||||||
|
cmd.setColorBlendEquationEXT(0,
|
||||||
|
std::array<vk::ColorBlendEquationEXT, 2>{blendEquation, blendEquation});
|
||||||
|
cmd.setColorWriteMaskEXT(0,
|
||||||
|
std::array<vk::ColorComponentFlags, 2>{colorWriteMask, colorWriteMask});
|
||||||
|
|
||||||
|
// ---- 绘制:GPUDriven 按 mesh 分 batch(相机视锥剔除后)----
|
||||||
|
// 实例矩阵已由 Renderer 写入 ObjectBuffer(per-slot SSBO);
|
||||||
|
// 同 mesh 的 N 个可见实体合并为一次 drawIndexed(instanceCount=N),
|
||||||
|
// draw 数 = 可见唯一 mesh 数,与实体数量解耦
|
||||||
|
for (const auto& batch : m_Frame.mainView.batches) {
|
||||||
|
cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics,
|
||||||
|
m_Gpu.forwardLayout, 1,
|
||||||
|
batch.mesh->GetMaterial()->GetDescriptorSet(), {});
|
||||||
|
batch.mesh->Draw(cmd, batch.count, batch.first);
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.endRendering();
|
||||||
|
})
|
||||||
|
.Write(m_HDRColor, RG::ResourceUse::ColorAttachment)
|
||||||
|
.Write(m_Velocity, RG::ResourceUse::ColorAttachment)
|
||||||
|
.Write(m_Depth, RG::ResourceUse::DepthAttachment)
|
||||||
|
.Read(m_ShadowMaps[0], RG::ResourceUse::ShaderRead,
|
||||||
|
vk::ShaderStageFlagBits::eFragment)
|
||||||
|
.Read(m_ShadowMaps[1], RG::ResourceUse::ShaderRead,
|
||||||
|
vk::ShaderStageFlagBits::eFragment)
|
||||||
|
.Read(m_ShadowMaps[2], RG::ResourceUse::ShaderRead,
|
||||||
|
vk::ShaderStageFlagBits::eFragment);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Engine::Passes
|
||||||
@@ -0,0 +1,193 @@
|
|||||||
|
//
|
||||||
|
// Created by atdunbg on 2026/9/23.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <Engine/Renderer/Passes/FullscreenGpu.h>
|
||||||
|
#include <Engine/Vulkan/Device.h>
|
||||||
|
#include <Engine/Vulkan/DescriptorManager.h>
|
||||||
|
#include <Engine/Vulkan/ShaderManager.h>
|
||||||
|
#include <Engine/Core/Log.h>
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
|
||||||
|
namespace Engine::Passes
|
||||||
|
{
|
||||||
|
FullscreenGpu::FullscreenGpu(Device& device, DescriptorManager& descriptors,
|
||||||
|
ShaderManager& shaders)
|
||||||
|
: m_Device(device), m_Descriptors(descriptors)
|
||||||
|
{
|
||||||
|
// Set0 = {HDR 颜色图, Bloom 图, 调试输入},push = 16B 通用结构
|
||||||
|
//(binding2 只被 tonemap 的调试视图使用;bloom 系 shader 静态
|
||||||
|
// 使用的 binding 是布局的子集,合法)
|
||||||
|
std::vector<DescriptorBinding> bindings = {
|
||||||
|
{0, vk::DescriptorType::eCombinedImageSampler,
|
||||||
|
vk::ShaderStageFlagBits::eFragment},
|
||||||
|
{1, vk::DescriptorType::eCombinedImageSampler,
|
||||||
|
vk::ShaderStageFlagBits::eFragment},
|
||||||
|
{2, vk::DescriptorType::eCombinedImageSampler,
|
||||||
|
vk::ShaderStageFlagBits::eFragment},
|
||||||
|
};
|
||||||
|
setLayout = descriptors.CreateLayout(bindings);
|
||||||
|
|
||||||
|
layout = descriptors.CreatePipelineLayout(
|
||||||
|
std::vector<vk::DescriptorSetLayout>{setLayout},
|
||||||
|
static_cast<u32>(sizeof(FullscreenPush)));
|
||||||
|
|
||||||
|
sampler = 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));
|
||||||
|
|
||||||
|
const std::string shaderDir = "shaders/";
|
||||||
|
auto vertShader = shaders.CreateFromFile(
|
||||||
|
shaderDir + "tonemap.vert.spv",
|
||||||
|
vk::ShaderStageFlagBits::eVertex,
|
||||||
|
std::vector<vk::DescriptorSetLayout>{setLayout},
|
||||||
|
static_cast<u32>(sizeof(FullscreenPush)));
|
||||||
|
auto tonemapF = shaders.CreateFromFile(
|
||||||
|
shaderDir + "tonemap.frag.spv",
|
||||||
|
vk::ShaderStageFlagBits::eFragment,
|
||||||
|
std::vector<vk::DescriptorSetLayout>{setLayout},
|
||||||
|
static_cast<u32>(sizeof(FullscreenPush)));
|
||||||
|
auto extractF = shaders.CreateFromFile(
|
||||||
|
shaderDir + "bloom_extract.frag.spv",
|
||||||
|
vk::ShaderStageFlagBits::eFragment,
|
||||||
|
std::vector<vk::DescriptorSetLayout>{setLayout},
|
||||||
|
static_cast<u32>(sizeof(FullscreenPush)));
|
||||||
|
auto blurF = shaders.CreateFromFile(
|
||||||
|
shaderDir + "bloom_blur.frag.spv",
|
||||||
|
vk::ShaderStageFlagBits::eFragment,
|
||||||
|
std::vector<vk::DescriptorSetLayout>{setLayout},
|
||||||
|
static_cast<u32>(sizeof(FullscreenPush)));
|
||||||
|
auto diffF = shaders.CreateFromFile(
|
||||||
|
shaderDir + "temporal_diff.frag.spv",
|
||||||
|
vk::ShaderStageFlagBits::eFragment,
|
||||||
|
std::vector<vk::DescriptorSetLayout>{setLayout},
|
||||||
|
static_cast<u32>(sizeof(FullscreenPush)));
|
||||||
|
auto taaF = shaders.CreateFromFile(
|
||||||
|
shaderDir + "taa.frag.spv",
|
||||||
|
vk::ShaderStageFlagBits::eFragment,
|
||||||
|
std::vector<vk::DescriptorSetLayout>{setLayout},
|
||||||
|
static_cast<u32>(sizeof(FullscreenPush)));
|
||||||
|
|
||||||
|
vert = vertShader.Handle;
|
||||||
|
tonemapFrag = tonemapF.Handle;
|
||||||
|
extractFrag = extractF.Handle;
|
||||||
|
blurFrag = blurF.Handle;
|
||||||
|
diffFrag = diffF.Handle;
|
||||||
|
taaFrag = taaF.Handle;
|
||||||
|
|
||||||
|
if (!vert || !tonemapFrag || !extractFrag || !blurFrag ||
|
||||||
|
!diffFrag || !taaFrag) {
|
||||||
|
ENGINE_FATAL("FullscreenGpu: failed to create shader objects");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FullscreenGpu::~FullscreenGpu()
|
||||||
|
{
|
||||||
|
const auto& vkDevice = m_Device.GetDevice();
|
||||||
|
|
||||||
|
if (vert) vkDevice.destroyShaderEXT(vert);
|
||||||
|
if (tonemapFrag) vkDevice.destroyShaderEXT(tonemapFrag);
|
||||||
|
if (extractFrag) vkDevice.destroyShaderEXT(extractFrag);
|
||||||
|
if (blurFrag) vkDevice.destroyShaderEXT(blurFrag);
|
||||||
|
if (diffFrag) vkDevice.destroyShaderEXT(diffFrag);
|
||||||
|
if (taaFrag) vkDevice.destroyShaderEXT(taaFrag);
|
||||||
|
|
||||||
|
vkDevice.destroySampler(sampler);
|
||||||
|
vkDevice.destroyPipelineLayout(layout);
|
||||||
|
vkDevice.destroyDescriptorSetLayout(setLayout);
|
||||||
|
}
|
||||||
|
|
||||||
|
vk::DescriptorSet FullscreenGpu::AllocateSet()
|
||||||
|
{
|
||||||
|
return m_Descriptors.AllocateDescriptorSet(setLayout);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FullscreenGpu::UpdateSetInput(vk::DescriptorSet set, u32 binding,
|
||||||
|
RG::RenderGraph& rg, RG::TextureHandle input,
|
||||||
|
u32 slot)
|
||||||
|
{
|
||||||
|
// 物理镜像按帧槽位各一份:每个槽位的描述符绑各自的 view
|
||||||
|
m_Descriptors.UpdateTexture(set, binding, sampler,
|
||||||
|
rg.GetPhysicalView(input, slot));
|
||||||
|
}
|
||||||
|
|
||||||
|
void FullscreenGpu::RecordFullscreen(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 = {vert, frag};
|
||||||
|
cmd.bindShadersEXT(stages, shaders);
|
||||||
|
|
||||||
|
cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics,
|
||||||
|
layout, 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(layout,
|
||||||
|
vk::ShaderStageFlagBits::eVertex |
|
||||||
|
vk::ShaderStageFlagBits::eFragment,
|
||||||
|
0, sizeof(FullscreenPush), &push);
|
||||||
|
|
||||||
|
cmd.draw(3, 1, 0, 0);
|
||||||
|
cmd.endRendering();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Engine::Passes
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
//
|
||||||
|
// Created by atdunbg on 2026/9/23.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <Engine/Renderer/Passes/ImGuiPass.h>
|
||||||
|
#include <Engine/ImGui/ImGuiLayer.h>
|
||||||
|
|
||||||
|
namespace Engine::Passes
|
||||||
|
{
|
||||||
|
void ImGuiPass::Register(RG::RenderGraph& rg)
|
||||||
|
{
|
||||||
|
rg.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_Layer) {
|
||||||
|
(*m_Layer)->RenderDrawData(cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.endRendering();
|
||||||
|
}).Write(m_Backbuffer, RG::ResourceUse::ColorAttachment);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Engine::Passes
|
||||||
@@ -0,0 +1,157 @@
|
|||||||
|
//
|
||||||
|
// Created by atdunbg on 2026/9/23.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <Engine/Renderer/Passes/SceneGpu.h>
|
||||||
|
#include <Engine/Renderer/Material.h>
|
||||||
|
#include <Engine/Vulkan/Device.h>
|
||||||
|
#include <Engine/Vulkan/DescriptorManager.h>
|
||||||
|
#include <Engine/Vulkan/ShaderManager.h>
|
||||||
|
#include <Engine/Vulkan/UniformBuffer.h>
|
||||||
|
#include <Engine/Core/Log.h>
|
||||||
|
|
||||||
|
#include <glm/glm.hpp>
|
||||||
|
|
||||||
|
namespace Engine::Passes
|
||||||
|
{
|
||||||
|
SceneGpu::SceneGpu(Device& device, DescriptorManager& descriptors,
|
||||||
|
ShaderManager& shaders,
|
||||||
|
UniformBuffer& ubo, UniformBuffer& objectBuffer,
|
||||||
|
UniformBuffer& prevObjectBuffer)
|
||||||
|
: m_Device(device), m_Descriptors(descriptors)
|
||||||
|
{
|
||||||
|
// Set 0: UBO + GPUDriven 实例数据(SSBO,vertex 读取)
|
||||||
|
// + 上一帧实例矩阵(TAA velocity,vertex 读取)
|
||||||
|
std::vector<DescriptorBinding> uboBindings = {
|
||||||
|
{0, vk::DescriptorType::eUniformBuffer,
|
||||||
|
vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment},
|
||||||
|
{1, vk::DescriptorType::eStorageBuffer,
|
||||||
|
vk::ShaderStageFlagBits::eVertex},
|
||||||
|
{2, vk::DescriptorType::eStorageBuffer,
|
||||||
|
vk::ShaderStageFlagBits::eVertex},
|
||||||
|
};
|
||||||
|
uboSetLayout = descriptors.CreateLayout(uboBindings);
|
||||||
|
|
||||||
|
// Set 1: 材质纹理槽(binding = Material::Slot:0 albedo / 1 normal /
|
||||||
|
// 2 specular / 3 emissive)
|
||||||
|
std::vector<DescriptorBinding> texBindings;
|
||||||
|
for (u32 slot = 0; slot < Material::kSlotCount; ++slot) {
|
||||||
|
texBindings.push_back(
|
||||||
|
{slot, vk::DescriptorType::eCombinedImageSampler,
|
||||||
|
vk::ShaderStageFlagBits::eFragment});
|
||||||
|
}
|
||||||
|
textureSetLayout = descriptors.CreateLayout(texBindings);
|
||||||
|
|
||||||
|
// Set 2: CSM 级联阴影图(fragment 采样,binding = 级联序号)
|
||||||
|
std::vector<DescriptorBinding> shadowBindings;
|
||||||
|
for (u32 c = 0; c < kMaxCascades; ++c) {
|
||||||
|
shadowBindings.push_back(
|
||||||
|
{c, vk::DescriptorType::eCombinedImageSampler,
|
||||||
|
vk::ShaderStageFlagBits::eFragment});
|
||||||
|
}
|
||||||
|
shadowSetLayout = descriptors.CreateLayout(shadowBindings);
|
||||||
|
|
||||||
|
// Forward pipeline layout:三个 set;无 push(GPUDriven 经 SSBO)
|
||||||
|
forwardLayout = descriptors.CreatePipelineLayout(
|
||||||
|
{uboSetLayout, textureSetLayout, shadowSetLayout}, 0);
|
||||||
|
|
||||||
|
// Shadow pipeline layout:Set0 = UBO/ObjectBuffer(实例矩阵),
|
||||||
|
// push = lightVP 64B
|
||||||
|
shadowLayout = descriptors.CreatePipelineLayout(
|
||||||
|
std::vector<vk::DescriptorSetLayout>{uboSetLayout},
|
||||||
|
sizeof(glm::mat4));
|
||||||
|
|
||||||
|
const std::string shaderDir = "shaders/";
|
||||||
|
// forward vert/frag 均无 push(range 定义一致:都没有,VUID-08878)
|
||||||
|
auto vert = shaders.CreateFromFile(
|
||||||
|
shaderDir + "simple_shader.vert.spv",
|
||||||
|
vk::ShaderStageFlagBits::eVertex,
|
||||||
|
{uboSetLayout, textureSetLayout, shadowSetLayout}, 0);
|
||||||
|
auto frag = shaders.CreateFromFile(
|
||||||
|
shaderDir + "simple_shader.frag.spv",
|
||||||
|
vk::ShaderStageFlagBits::eFragment,
|
||||||
|
{uboSetLayout, textureSetLayout, shadowSetLayout}, 0);
|
||||||
|
|
||||||
|
// shadow shader:depth-only,push = lightVP
|
||||||
|
auto shadowVertShader = shaders.CreateFromFile(
|
||||||
|
shaderDir + "shadow.vert.spv",
|
||||||
|
vk::ShaderStageFlagBits::eVertex,
|
||||||
|
std::vector<vk::DescriptorSetLayout>{uboSetLayout},
|
||||||
|
sizeof(glm::mat4));
|
||||||
|
auto shadowFragShader = shaders.CreateFromFile(
|
||||||
|
shaderDir + "shadow.frag.spv",
|
||||||
|
vk::ShaderStageFlagBits::eFragment,
|
||||||
|
std::vector<vk::DescriptorSetLayout>{uboSetLayout},
|
||||||
|
sizeof(glm::mat4));
|
||||||
|
|
||||||
|
forwardVert = vert.Handle;
|
||||||
|
forwardFrag = frag.Handle;
|
||||||
|
shadowVert = shadowVertShader.Handle;
|
||||||
|
shadowFrag = shadowFragShader.Handle;
|
||||||
|
|
||||||
|
if (!forwardVert || !forwardFrag || !shadowVert || !shadowFrag) {
|
||||||
|
ENGINE_FATAL("SceneGpu: failed to create shader objects");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 阴影采样器:clamp-to-border 白色 = 光视锥之外无阴影
|
||||||
|
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);
|
||||||
|
shadowSampler = device.GetDevice().createSampler(shadowSamplerInfo);
|
||||||
|
|
||||||
|
// per-slot 描述符分配 + Set0 绑定(buffer 句柄全程稳定,不用重绑)
|
||||||
|
uboSets.resize(kMaxFramesInFlight);
|
||||||
|
shadowSets.resize(kMaxFramesInFlight);
|
||||||
|
for (u32 slot = 0; slot < kMaxFramesInFlight; ++slot) {
|
||||||
|
uboSets[slot] = descriptors.AllocateDescriptorSet(uboSetLayout);
|
||||||
|
descriptors.UpdateUniformBuffer(
|
||||||
|
uboSets[slot], 0, ubo.Get(slot), ubo.GetSize());
|
||||||
|
descriptors.UpdateStorageBuffer(
|
||||||
|
uboSets[slot], 1, objectBuffer.Get(slot), objectBuffer.GetSize());
|
||||||
|
descriptors.UpdateStorageBuffer(
|
||||||
|
uboSets[slot], 2, prevObjectBuffer.Get(slot),
|
||||||
|
prevObjectBuffer.GetSize());
|
||||||
|
|
||||||
|
shadowSets[slot] = descriptors.AllocateDescriptorSet(shadowSetLayout);
|
||||||
|
}
|
||||||
|
// shadowSets 的图片绑定在 Compile 后由 BindShadowMap 完成
|
||||||
|
}
|
||||||
|
|
||||||
|
SceneGpu::~SceneGpu()
|
||||||
|
{
|
||||||
|
const auto& vkDevice = m_Device.GetDevice();
|
||||||
|
|
||||||
|
if (forwardVert) vkDevice.destroyShaderEXT(forwardVert);
|
||||||
|
if (forwardFrag) vkDevice.destroyShaderEXT(forwardFrag);
|
||||||
|
if (shadowVert) vkDevice.destroyShaderEXT(shadowVert);
|
||||||
|
if (shadowFrag) vkDevice.destroyShaderEXT(shadowFrag);
|
||||||
|
|
||||||
|
vkDevice.destroySampler(shadowSampler);
|
||||||
|
vkDevice.destroyPipelineLayout(forwardLayout);
|
||||||
|
vkDevice.destroyPipelineLayout(shadowLayout);
|
||||||
|
vkDevice.destroyDescriptorSetLayout(uboSetLayout);
|
||||||
|
vkDevice.destroyDescriptorSetLayout(textureSetLayout);
|
||||||
|
vkDevice.destroyDescriptorSetLayout(shadowSetLayout);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SceneGpu::BindShadowMaps(RG::RenderGraph& rg,
|
||||||
|
const std::array<RG::TextureHandle, kMaxCascades>& maps)
|
||||||
|
{
|
||||||
|
// 物理镜像按帧槽位各一份:每个槽位的描述符绑各自的 view
|
||||||
|
for (u32 slot = 0; slot < kMaxFramesInFlight; ++slot) {
|
||||||
|
for (u32 c = 0; c < kMaxCascades; ++c) {
|
||||||
|
m_Descriptors.UpdateTexture(
|
||||||
|
shadowSets[slot], c, shadowSampler,
|
||||||
|
rg.GetPhysicalView(maps[c], slot));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Engine::Passes
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
//
|
||||||
|
// Created by atdunbg on 2026/9/23.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <Engine/Renderer/Passes/ShadowPass.h>
|
||||||
|
#include <Engine/Renderer/Vertex.h>
|
||||||
|
#include <Engine/Renderer/Mesh.h>
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
|
||||||
|
namespace Engine::Passes
|
||||||
|
{
|
||||||
|
void ShadowPass::Register(RG::RenderGraph& rg)
|
||||||
|
{
|
||||||
|
// 所有渲染体都写入每个级联,不做实体级开关——castShadow 应由
|
||||||
|
// 材质/实体数据驱动剔除,而非 pass 逻辑特判。
|
||||||
|
// (每级联独立剔除是后续优化点:shadowView 细分为 per-cascade 列表)
|
||||||
|
rg.AddPass("Shadow", [this](RG::RenderGraphContext& ctx) {
|
||||||
|
const vk::CommandBuffer cmd = ctx.cmd;
|
||||||
|
|
||||||
|
for (u32 c = 0; c < kMaxCascades; ++c) {
|
||||||
|
vk::RenderingAttachmentInfo depthAttachment;
|
||||||
|
depthAttachment.setImageView(ctx.View(m_ShadowMaps[c]))
|
||||||
|
.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}, {kCascadeRes, kCascadeRes}})
|
||||||
|
.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_Gpu.shadowVert, m_Gpu.shadowFrag};
|
||||||
|
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>(kCascadeRes),
|
||||||
|
static_cast<float>(kCascadeRes),
|
||||||
|
0.0f, 1.0f);
|
||||||
|
cmd.setViewportWithCount(viewport);
|
||||||
|
|
||||||
|
vk::Rect2D scissor({0, 0}, {kCascadeRes, kCascadeRes});
|
||||||
|
cmd.setScissorWithCount(scissor);
|
||||||
|
|
||||||
|
// ---- 绘制:GPUDriven——与 Forward 共用实例数据,但用
|
||||||
|
// 光源视锥剔除后的 batch(相机外实体仍投影子)----
|
||||||
|
cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics,
|
||||||
|
m_Gpu.shadowLayout, 0,
|
||||||
|
m_Gpu.uboSets[ctx.frame.frameSlot], {});
|
||||||
|
|
||||||
|
// stage 必须覆盖 layout range 声明的全部 stage(VUID-01796)
|
||||||
|
// 每级联 push 各自的光空间 VP
|
||||||
|
cmd.pushConstants(m_Gpu.shadowLayout,
|
||||||
|
vk::ShaderStageFlagBits::eVertex |
|
||||||
|
vk::ShaderStageFlagBits::eFragment,
|
||||||
|
0, sizeof(glm::mat4),
|
||||||
|
&m_Frame.ubo.lightSpace[c]);
|
||||||
|
|
||||||
|
for (const auto& batch : m_Frame.shadowCascades[c].batches) {
|
||||||
|
batch.mesh->Draw(cmd, batch.count, batch.first);
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.endRendering();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.Write(m_ShadowMaps[0], RG::ResourceUse::DepthAttachment)
|
||||||
|
.Write(m_ShadowMaps[1], RG::ResourceUse::DepthAttachment)
|
||||||
|
.Write(m_ShadowMaps[2], RG::ResourceUse::DepthAttachment);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ShadowPass::OnTargetsRebuilt(RG::RenderGraph& rg)
|
||||||
|
{
|
||||||
|
m_Gpu.BindShadowMaps(rg, m_ShadowMaps);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Engine::Passes
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
//
|
||||||
|
// Created by atdunbg on 2026/9/23.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <Engine/Renderer/Passes/TAAPass.h>
|
||||||
|
#include <Engine/Core/FrameContext.h>
|
||||||
|
|
||||||
|
namespace Engine::Passes
|
||||||
|
{
|
||||||
|
TAAPass::TAAPass(FullscreenGpu& gpu, f32 historyAlpha,
|
||||||
|
RG::TextureHandle hdrColor, RG::TextureHandle velocity,
|
||||||
|
RG::TextureHandle taaColor)
|
||||||
|
: m_Gpu(gpu), m_HistoryAlpha(historyAlpha),
|
||||||
|
m_HDR(hdrColor), m_Velocity(velocity), m_TAAColor(taaColor)
|
||||||
|
{
|
||||||
|
m_Sets.resize(kMaxFramesInFlight);
|
||||||
|
for (u32 slot = 0; slot < kMaxFramesInFlight; ++slot) {
|
||||||
|
m_Sets[slot] = m_Gpu.AllocateSet();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TAAPass::Register(RG::RenderGraph& rg)
|
||||||
|
{
|
||||||
|
rg.AddPass("TAA",
|
||||||
|
[this](RG::RenderGraphContext& ctx) {
|
||||||
|
m_Gpu.RecordFullscreen(ctx, ctx.View(m_TAAColor), m_Gpu.taaFrag,
|
||||||
|
m_Sets[ctx.frame.frameSlot],
|
||||||
|
FullscreenPush{0.0f, m_HistoryAlpha, 0.0f, 0.0f});
|
||||||
|
})
|
||||||
|
.Read(m_HDR, RG::ResourceUse::ShaderRead,
|
||||||
|
vk::ShaderStageFlagBits::eFragment)
|
||||||
|
.Read(m_Velocity, RG::ResourceUse::ShaderRead,
|
||||||
|
vk::ShaderStageFlagBits::eFragment)
|
||||||
|
// 自引用历史:TAAColor 的上一帧槽位就是累积历史
|
||||||
|
.ReadHistory(m_TAAColor, RG::ResourceUse::ShaderRead,
|
||||||
|
vk::ShaderStageFlagBits::eFragment)
|
||||||
|
.Write(m_TAAColor, RG::ResourceUse::ColorAttachment);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TAAPass::OnTargetsRebuilt(RG::RenderGraph& rg)
|
||||||
|
{
|
||||||
|
for (u32 slot = 0; slot < kMaxFramesInFlight; ++slot) {
|
||||||
|
m_Gpu.UpdateSetInput(m_Sets[slot], 0, rg, m_HDR, slot);
|
||||||
|
m_Gpu.UpdateSetInput(m_Sets[slot], 1, rg, m_Velocity, slot);
|
||||||
|
// binding2 = 历史(上一帧槽位镜像)
|
||||||
|
m_Gpu.UpdateSetInput(m_Sets[slot], 2, rg, m_TAAColor,
|
||||||
|
rg.HistorySlot(slot));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Engine::Passes
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
//
|
||||||
|
// Created by atdunbg on 2026/9/23.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <Engine/Renderer/Passes/TemporalDiffPass.h>
|
||||||
|
#include <Engine/Core/FrameContext.h>
|
||||||
|
|
||||||
|
namespace Engine::Passes
|
||||||
|
{
|
||||||
|
TemporalDiffPass::TemporalDiffPass(FullscreenGpu& gpu, f32 gain,
|
||||||
|
RG::TextureHandle hdrColor,
|
||||||
|
RG::TextureHandle output)
|
||||||
|
: m_Gpu(gpu), m_Gain(gain), m_HDR(hdrColor), m_Output(output)
|
||||||
|
{
|
||||||
|
m_Sets.resize(kMaxFramesInFlight);
|
||||||
|
for (u32 slot = 0; slot < kMaxFramesInFlight; ++slot) {
|
||||||
|
m_Sets[slot] = m_Gpu.AllocateSet();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TemporalDiffPass::Register(RG::RenderGraph& rg)
|
||||||
|
{
|
||||||
|
// 帧历史读 + 当前帧读:同一虚拟资源的两个槽位镜像在同一个
|
||||||
|
// pass 里采样(TAA 的标准形态)。历史读不建图内边,跨帧同步
|
||||||
|
// 由 RG 的历史 barrier(ALL_COMMANDS 可见性)保证。
|
||||||
|
rg.AddPass("TemporalDiff",
|
||||||
|
[this](RG::RenderGraphContext& ctx) {
|
||||||
|
m_Gpu.RecordFullscreen(ctx, ctx.View(m_Output), m_Gpu.diffFrag,
|
||||||
|
m_Sets[ctx.frame.frameSlot],
|
||||||
|
FullscreenPush{0.0f, m_Gain, 0.0f, 0.0f});
|
||||||
|
})
|
||||||
|
.ReadHistory(m_HDR, RG::ResourceUse::ShaderRead,
|
||||||
|
vk::ShaderStageFlagBits::eFragment)
|
||||||
|
.Read(m_HDR, RG::ResourceUse::ShaderRead,
|
||||||
|
vk::ShaderStageFlagBits::eFragment)
|
||||||
|
.Write(m_Output, RG::ResourceUse::ColorAttachment);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TemporalDiffPass::OnTargetsRebuilt(RG::RenderGraph& rg)
|
||||||
|
{
|
||||||
|
for (u32 slot = 0; slot < kMaxFramesInFlight; ++slot) {
|
||||||
|
// binding0 = 帧历史(上一帧槽位镜像),binding1 = 当前帧
|
||||||
|
m_Gpu.UpdateSetInput(m_Sets[slot], 0, rg, m_HDR,
|
||||||
|
rg.HistorySlot(slot));
|
||||||
|
m_Gpu.UpdateSetInput(m_Sets[slot], 1, rg, m_HDR, slot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Engine::Passes
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
//
|
||||||
|
// Created by atdunbg on 2026/9/23.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <Engine/Renderer/Passes/TonemapPass.h>
|
||||||
|
#include <Engine/Core/FrameContext.h>
|
||||||
|
|
||||||
|
namespace Engine::Passes
|
||||||
|
{
|
||||||
|
TonemapPass::TonemapPass(FullscreenGpu& gpu, PostProcessParams& params,
|
||||||
|
RG::TextureHandle hdrColor, RG::TextureHandle bloomA,
|
||||||
|
RG::TextureHandle temporalDiff,
|
||||||
|
RG::TextureHandle backbuffer)
|
||||||
|
: m_Gpu(gpu), m_Params(params),
|
||||||
|
m_HDR(hdrColor), m_A(bloomA), m_TemporalDiff(temporalDiff),
|
||||||
|
m_Backbuffer(backbuffer)
|
||||||
|
{
|
||||||
|
m_Sets.resize(kMaxFramesInFlight);
|
||||||
|
for (u32 slot = 0; slot < kMaxFramesInFlight; ++slot) {
|
||||||
|
m_Sets[slot] = m_Gpu.AllocateSet();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void TonemapPass::Register(RG::RenderGraph& rg)
|
||||||
|
{
|
||||||
|
rg.AddPass("Tonemap",
|
||||||
|
[this](RG::RenderGraphContext& ctx) {
|
||||||
|
m_Gpu.RecordFullscreen(ctx, ctx.View(m_Backbuffer),
|
||||||
|
m_Gpu.tonemapFrag,
|
||||||
|
m_Sets[ctx.frame.frameSlot],
|
||||||
|
FullscreenPush{m_Params.exposure,
|
||||||
|
m_Params.useACES ? 1.0f : 0.0f,
|
||||||
|
m_Params.bloomStrength,
|
||||||
|
m_Params.showTemporalDiff ? 1.0f : 0.0f});
|
||||||
|
})
|
||||||
|
.Write(m_Backbuffer, RG::ResourceUse::ColorAttachment)
|
||||||
|
.Read(m_HDR, RG::ResourceUse::ShaderRead,
|
||||||
|
vk::ShaderStageFlagBits::eFragment)
|
||||||
|
.Read(m_A, RG::ResourceUse::ShaderRead,
|
||||||
|
vk::ShaderStageFlagBits::eFragment)
|
||||||
|
// shader 静态采样 binding2(调试视图开关在 push constant 里),
|
||||||
|
// 声明读否则本 pass 的产出无人消费会被剔除、镜像也不会分配
|
||||||
|
.Read(m_TemporalDiff, RG::ResourceUse::ShaderRead,
|
||||||
|
vk::ShaderStageFlagBits::eFragment);
|
||||||
|
}
|
||||||
|
|
||||||
|
void TonemapPass::OnTargetsRebuilt(RG::RenderGraph& rg)
|
||||||
|
{
|
||||||
|
for (u32 slot = 0; slot < kMaxFramesInFlight; ++slot) {
|
||||||
|
m_Gpu.UpdateSetInput(m_Sets[slot], 0, rg, m_HDR, slot);
|
||||||
|
m_Gpu.UpdateSetInput(m_Sets[slot], 1, rg, m_A, slot);
|
||||||
|
m_Gpu.UpdateSetInput(m_Sets[slot], 2, rg, m_TemporalDiff, slot);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Engine::Passes
|
||||||
+174
-766
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,312 @@
|
|||||||
|
//
|
||||||
|
// Created by atdunbg on 2026/9/23.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <Engine/Renderer/SceneRenderer.h>
|
||||||
|
#include <Engine/ECS/Scene.h>
|
||||||
|
#include <Engine/ECS/Components.h>
|
||||||
|
#include <Engine/Renderer/Camera.h>
|
||||||
|
#include <Engine/Renderer/Frustum.h>
|
||||||
|
#include <Engine/Renderer/Model.h>
|
||||||
|
#include <Engine/Renderer/Mesh.h>
|
||||||
|
#include <Engine/Assets/AssetManager.h>
|
||||||
|
#include <Engine/Core/Log.h>
|
||||||
|
|
||||||
|
#include <glm/glm.hpp>
|
||||||
|
#include <glm/gtc/matrix_transform.hpp>
|
||||||
|
|
||||||
|
#include <tracy/Tracy.hpp>
|
||||||
|
|
||||||
|
#include <chrono>
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
|
namespace Engine
|
||||||
|
{
|
||||||
|
using namespace ECS; // TransformComponent / Entity 等直接使用
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
// 相机裁剪范围(与 BuildFrame 中 GetProjection 的参数一致)
|
||||||
|
constexpr f32 kCameraNear = 0.1f;
|
||||||
|
constexpr f32 kCameraFar = 100.0f;
|
||||||
|
// 实用分割插值因子(0=均匀,1=对数;0.6 是业界常用值)
|
||||||
|
constexpr f32 kCascadeLambda = 0.6f;
|
||||||
|
|
||||||
|
// Halton 低差异序列(TAA 子像素抖动)
|
||||||
|
f32 Halton(u32 index, u32 base)
|
||||||
|
{
|
||||||
|
f32 f = 1.0f, r = 0.0f;
|
||||||
|
while (index > 0) {
|
||||||
|
f /= static_cast<f32>(base);
|
||||||
|
r += f * static_cast<f32>(index % base);
|
||||||
|
index /= base;
|
||||||
|
}
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SceneRenderer::ComputeCascades(const glm::vec3& shadowDir)
|
||||||
|
{
|
||||||
|
constexpr u32 N = kMaxCascades;
|
||||||
|
|
||||||
|
// ---- 1. 实用分割:均匀与对数分割按 λ 插值(视图深度,正值)----
|
||||||
|
f32 splits[N];
|
||||||
|
for (u32 i = 1; i <= N; ++i) {
|
||||||
|
const f32 t = static_cast<f32>(i) / N;
|
||||||
|
const f32 uniform = kCameraNear + (kCameraFar - kCameraNear) * t;
|
||||||
|
const f32 log = kCameraNear * glm::pow(kCameraFar / kCameraNear, t);
|
||||||
|
splits[i - 1] = glm::mix(uniform, log, kCascadeLambda);
|
||||||
|
}
|
||||||
|
for (u32 c = 0; c < N; ++c) {
|
||||||
|
m_Frame.ubo.cascadeSplits[c] = splits[c];
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- 2. 全视锥 8 角反投影到世界(NDC z: 0=近 1=远,Vulkan [0,1])----
|
||||||
|
const glm::mat4 invVP = glm::inverse(m_Frame.ubo.projNoJitter * m_Frame.ubo.view);
|
||||||
|
glm::vec3 nearC[4], farC[4];
|
||||||
|
const glm::vec2 ndcXY[4] = {{-1,-1},{1,-1},{-1,1},{1,1}};
|
||||||
|
for (u32 i = 0; i < 4; ++i) {
|
||||||
|
glm::vec4 p = invVP * glm::vec4(ndcXY[i], 0.0f, 1.0f);
|
||||||
|
nearC[i] = glm::vec3(p / p.w);
|
||||||
|
p = invVP * glm::vec4(ndcXY[i], 1.0f, 1.0f);
|
||||||
|
farC[i] = glm::vec3(p / p.w);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- 3. 每级联:切片角点 → 光空间 AABB 拟合 → 纹素对齐 ----
|
||||||
|
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);
|
||||||
|
|
||||||
|
for (u32 c = 0; c < N; ++c) {
|
||||||
|
const f32 d0 = (c == 0) ? kCameraNear : splits[c - 1];
|
||||||
|
const f32 d1 = splits[c];
|
||||||
|
const f32 t0 = (d0 - kCameraNear) / (kCameraFar - kCameraNear);
|
||||||
|
const f32 t1 = (d1 - kCameraNear) / (kCameraFar - kCameraNear);
|
||||||
|
|
||||||
|
glm::vec3 slice[8];
|
||||||
|
for (u32 i = 0; i < 4; ++i) {
|
||||||
|
slice[i] = glm::mix(nearC[i], farC[i], t0);
|
||||||
|
slice[4 + i] = glm::mix(nearC[i], farC[i], t1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 光视图:位于切片中心,沿光方向看
|
||||||
|
glm::vec3 center(0.0f);
|
||||||
|
for (const auto& p : slice) center += p;
|
||||||
|
center *= (1.0f / 8.0f);
|
||||||
|
const glm::mat4 lightView = glm::lookAt(center, center + shadowDir, up);
|
||||||
|
|
||||||
|
glm::vec3 mn( std::numeric_limits<f32>::max());
|
||||||
|
glm::vec3 mx( std::numeric_limits<f32>::lowest());
|
||||||
|
for (const auto& p : slice) {
|
||||||
|
const glm::vec3 lv = glm::vec3(lightView * glm::vec4(p, 1.0f));
|
||||||
|
mn = glm::min(mn, lv);
|
||||||
|
mx = glm::max(mx, lv);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 沿光方向扩展深度范围:切片盒外的 caster(如高树、城墙)
|
||||||
|
// 也能投影子进本级联——per-cascade 剔除的正确性前提。
|
||||||
|
// 只扩 z 不缩 XY,阴影分辨率不受影响
|
||||||
|
constexpr f32 kCasterPad = 30.0f;
|
||||||
|
mn.z -= kCasterPad;
|
||||||
|
mx.z += kCasterPad;
|
||||||
|
|
||||||
|
// 纹素对齐:X/Y 边界量化到纹素网格(消除相机移动时的阴影边缘闪烁)
|
||||||
|
const f32 texelX = (mx.x - mn.x) / static_cast<f32>(kCascadeRes);
|
||||||
|
const f32 texelY = (mx.y - mn.y) / static_cast<f32>(kCascadeRes);
|
||||||
|
mn.x = glm::floor(mn.x / texelX) * texelX;
|
||||||
|
mn.y = glm::floor(mn.y / texelY) * texelY;
|
||||||
|
mx.x = mn.x + glm::ceil((mx.x - mn.x) / texelX) * texelX;
|
||||||
|
mx.y = mn.y + glm::ceil((mx.y - mn.y) / texelY) * texelY;
|
||||||
|
|
||||||
|
// 光视图空间里 z 为负(看向 -Z);近远距离取正
|
||||||
|
glm::mat4 lightProj = glm::ortho(mn.x, mx.x, mn.y, mx.y,
|
||||||
|
-mx.z, -mn.z);
|
||||||
|
// Vulkan clip 深度 [0,1](ortho 的 w_clip=1,偏移折进 z 平移分量)
|
||||||
|
lightProj[2][2] *= 0.5f;
|
||||||
|
lightProj[3][2] = lightProj[3][2] * 0.5f + 0.5f;
|
||||||
|
|
||||||
|
m_Frame.ubo.lightSpace[c] = lightProj * lightView;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SceneRenderer::BuildFrame(ECS::Scene& scene, Assets::AssetManager& assets,
|
||||||
|
vk::Extent2D targetExtent)
|
||||||
|
{
|
||||||
|
ZoneScopedN("BuildFrame");
|
||||||
|
const auto collectStart = std::chrono::steady_clock::now();
|
||||||
|
|
||||||
|
// ---- 0. TAA 时域状态先行(velocity = 上一帧姿态与相机)----
|
||||||
|
UniformBufferObject& ubo = m_Frame.ubo;
|
||||||
|
ubo.prevView = m_LastView;
|
||||||
|
ubo.prevProj = m_LastProjNoJitter;
|
||||||
|
|
||||||
|
// ---- 1. 相机(位姿单一事实源 = 相机实体的 Transform)----
|
||||||
|
TransformComponent* camTransform = nullptr;
|
||||||
|
auto* cameraComp = scene.GetPrimaryCamera(&camTransform);
|
||||||
|
if (!cameraComp || !camTransform) {
|
||||||
|
// 无相机:本帧跳过(正常场景必有相机,安全网)
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const Camera& camera = cameraComp->camera;
|
||||||
|
|
||||||
|
const f32 aspect = static_cast<f32>(targetExtent.width) /
|
||||||
|
static_cast<f32>(targetExtent.height);
|
||||||
|
ubo.view = camera.GetView(*camTransform);
|
||||||
|
ubo.proj = camera.GetProjection(aspect, 0.1f, 100.0f);
|
||||||
|
|
||||||
|
// 未抖动投影:velocity 的当前帧 clip 位置用它(抖动不能进 velocity)
|
||||||
|
ubo.projNoJitter = ubo.proj;
|
||||||
|
|
||||||
|
// 子像素抖动(Halton×8):光栅化采样位置逐帧偏移,TAA 累积去锯齿。
|
||||||
|
// NDC 偏移 = 像素偏移 × 2 / 尺寸
|
||||||
|
{
|
||||||
|
const u32 idx = static_cast<u32>(m_FrameIndex % 8);
|
||||||
|
const f32 jx = Halton(idx + 1, 2) - 0.5f;
|
||||||
|
const f32 jy = Halton(idx + 1, 3) - 0.5f;
|
||||||
|
ubo.proj[2][0] += jx * 2.0f / static_cast<f32>(targetExtent.width);
|
||||||
|
ubo.proj[2][1] += jy * 2.0f / static_cast<f32>(targetExtent.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
ubo.ambientColor = scene.GetSettings().ambientColor;
|
||||||
|
|
||||||
|
// ---- 2. 灯光(第一个灯作为阴影光源)----
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
ComputeCascades(shadowDir);
|
||||||
|
ubo.cameraPos = glm::vec4(camTransform->GetPosition(), 1.0f);
|
||||||
|
|
||||||
|
// ---- 3. GPUDriven:单趟收集 + 多视图剔除 ----
|
||||||
|
// 同作用域第二个 zone:ZoneScopedN 变量名固定会重声明,用 ZoneNamedN
|
||||||
|
ZoneNamedN( ___tracy_collect, "GPUDriven Collect", true );
|
||||||
|
// 每个实例的包围球对 4 个视锥分别测试:
|
||||||
|
// 相机视锥 → 主视图(Forward 画)
|
||||||
|
// 各级联光视锥 → 各自的阴影 batch(Shadow 按 region 各画各的)
|
||||||
|
// 级联盒沿光方向加了 caster pad(见 ComputeCascades):
|
||||||
|
// 盒外 caster 投进级联的影子不会被剔除裁掉。
|
||||||
|
// 收集按视图分桶,最后拼接成 [主段|级联0|级联1|级联2],
|
||||||
|
// batch.first 拼接时加上段基址,保证段内区间连续。
|
||||||
|
m_Frame.instanceModels.clear();
|
||||||
|
m_Frame.prevInstanceModels.clear();
|
||||||
|
m_MainScratch.Clear();
|
||||||
|
for (auto& s : m_CascScratch) s.Clear();
|
||||||
|
u32 totalInstances = 0;
|
||||||
|
bool instanceTruncated = false;
|
||||||
|
|
||||||
|
// 剔除平面提取按 [0,1] 深度约定——相机 proj 与 lightSpace 都已
|
||||||
|
// 做 [0,1] 重映射;原始 [-1,1] 矩阵的 near 平面公式不同,勿用
|
||||||
|
const Frustum mainFrustum = Frustum::FromMatrix(ubo.proj * ubo.view);
|
||||||
|
Frustum cascFrustum[kMaxCascades];
|
||||||
|
for (u32 c = 0; c < kMaxCascades; ++c) {
|
||||||
|
cascFrustum[c] = Frustum::FromMatrix(ubo.lightSpace[c]);
|
||||||
|
}
|
||||||
|
|
||||||
|
scene.Each<TransformComponent, MeshRendererComponent>(
|
||||||
|
[&](Entity entity, TransformComponent& t, MeshRendererComponent& mr) {
|
||||||
|
Model* model = assets.GetModel(mr.model);
|
||||||
|
if (!model) return;
|
||||||
|
|
||||||
|
// TAA velocity:本实例的上一帧世界矩阵(首帧/新实体 = 当前
|
||||||
|
// 矩阵 → 速度为 0,不产生假运动)
|
||||||
|
const u32 entKey = static_cast<u32>(entt::to_integral(entity));
|
||||||
|
glm::mat4 prevWorld = t.World();
|
||||||
|
if (auto it = m_PrevWorlds.find(entKey); it != m_PrevWorlds.end()) {
|
||||||
|
prevWorld = it->second;
|
||||||
|
}
|
||||||
|
m_CurWorlds.emplace(entKey, t.World());
|
||||||
|
|
||||||
|
// 实例世界缩放(矩阵列向量最大长度,包围球半径随缩放)
|
||||||
|
const f32 worldScale = glm::max(
|
||||||
|
glm::length(glm::vec3(t.World()[0])),
|
||||||
|
glm::max(glm::length(glm::vec3(t.World()[1])),
|
||||||
|
glm::length(glm::vec3(t.World()[2]))));
|
||||||
|
|
||||||
|
for (auto& mesh : model->GetMeshes()) {
|
||||||
|
const auto& bounds = mesh->GetBounds();
|
||||||
|
const glm::vec3 worldCenter =
|
||||||
|
glm::vec3(t.World() * glm::vec4(bounds.center, 1.0f));
|
||||||
|
const f32 worldRadius = bounds.radius * worldScale;
|
||||||
|
|
||||||
|
auto tryAppend = [&](ViewScratch& s, const Frustum& frustum) {
|
||||||
|
if (!frustum.IntersectsSphere(worldCenter, worldRadius)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (totalInstances >= kMaxInstances) {
|
||||||
|
instanceTruncated = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const auto [it, inserted] = s.lookup.emplace(
|
||||||
|
mesh.get(), static_cast<u32>(s.worlds.size()));
|
||||||
|
if (inserted) {
|
||||||
|
s.result.batches.push_back({mesh.get(),
|
||||||
|
static_cast<u32>(s.worlds.size()), 0});
|
||||||
|
}
|
||||||
|
s.result.batches[it->second].count++;
|
||||||
|
s.result.instanceCount++;
|
||||||
|
s.worlds.push_back(t.World());
|
||||||
|
s.prevWorlds.push_back(prevWorld);
|
||||||
|
++totalInstances;
|
||||||
|
};
|
||||||
|
|
||||||
|
tryAppend(m_MainScratch, mainFrustum);
|
||||||
|
for (u32 c = 0; c < kMaxCascades; ++c) {
|
||||||
|
tryAppend(m_CascScratch[c], cascFrustum[c]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// ---- 4. 拼接:[主段|级联0|级联1|级联2],batch.first 加段基址 ----
|
||||||
|
auto appendView = [&](CullResult& dst, ViewScratch& s) {
|
||||||
|
const u32 base = static_cast<u32>(m_Frame.instanceModels.size());
|
||||||
|
dst.batches.clear();
|
||||||
|
dst.batches.reserve(s.result.batches.size());
|
||||||
|
for (auto& b : s.result.batches) {
|
||||||
|
b.first += base;
|
||||||
|
dst.batches.push_back(b);
|
||||||
|
}
|
||||||
|
dst.instanceCount = s.result.instanceCount;
|
||||||
|
m_Frame.instanceModels.insert(m_Frame.instanceModels.end(),
|
||||||
|
s.worlds.begin(), s.worlds.end());
|
||||||
|
m_Frame.prevInstanceModels.insert(m_Frame.prevInstanceModels.end(),
|
||||||
|
s.prevWorlds.begin(),
|
||||||
|
s.prevWorlds.end());
|
||||||
|
};
|
||||||
|
appendView(m_Frame.mainView, m_MainScratch);
|
||||||
|
for (u32 c = 0; c < kMaxCascades; ++c) {
|
||||||
|
appendView(m_Frame.shadowCascades[c], m_CascScratch[c]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 帧末滚动时域状态:cur → prev(每帧重建,销毁的实体自动清出)
|
||||||
|
m_PrevWorlds.swap(m_CurWorlds);
|
||||||
|
m_CurWorlds.clear();
|
||||||
|
m_LastView = ubo.view;
|
||||||
|
m_LastProjNoJitter = ubo.projNoJitter;
|
||||||
|
++m_FrameIndex;
|
||||||
|
|
||||||
|
if (instanceTruncated && !m_TruncationWarned) {
|
||||||
|
// 只告警一次(每帧告警会刷屏)
|
||||||
|
m_TruncationWarned = true;
|
||||||
|
ENGINE_WARN("GPUDriven: instance buffer full ({}), entities truncated",
|
||||||
|
kMaxInstances);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_Frame.collectMs = std::chrono::duration<f32, std::milli>(
|
||||||
|
std::chrono::steady_clock::now() - collectStart).count();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Engine
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
//
|
||||||
|
// Created by atdunbg on 2026/9/23.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <Engine/Renderer/VulkanAssetUploader.h>
|
||||||
|
#include <Engine/Renderer/Model.h>
|
||||||
|
|
||||||
|
namespace Engine {
|
||||||
|
|
||||||
|
Scope<Model> VulkanAssetUploader::LoadModel(const std::string& path)
|
||||||
|
{
|
||||||
|
// GPU 上传在此发生:解析(assimp)+ buffer/image 创建一步完成
|
||||||
|
return CreateScope<Model>(
|
||||||
|
m_Device, m_Allocator, m_Descriptors, m_TextureLayout, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
Scope<Model> VulkanAssetUploader::CreatePlane(f32 halfSize, u8 r, u8 g, u8 b)
|
||||||
|
{
|
||||||
|
return Model::CreatePlane(m_Device, m_Allocator, m_Descriptors,
|
||||||
|
m_TextureLayout, halfSize, r, g, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Engine
|
||||||
@@ -18,31 +18,18 @@ namespace Engine {
|
|||||||
vk::MemoryPropertyFlags memProps)
|
vk::MemoryPropertyFlags memProps)
|
||||||
: m_Device(device), m_Allocator(allocator) , m_Size(size)
|
: m_Device(device), m_Allocator(allocator) , m_Size(size)
|
||||||
{
|
{
|
||||||
vk::BufferCreateInfo bufferInfo;
|
// VMA 惯用法:创建 + 分配 + 绑定一步完成;
|
||||||
bufferInfo.setSize(size)
|
// HOST_VISIBLE 时持久映射,MappedPtr 直接可用
|
||||||
.setUsage(usage)
|
m_Allocation = allocator.CreateBuffer(size, usage, memProps);
|
||||||
.setSharingMode(vk::SharingMode::eExclusive);
|
m_Buffer = m_Allocation.Buffer;
|
||||||
|
m_MappedPtr = m_Allocation.MappedPtr;
|
||||||
m_Buffer = m_Device.GetDevice().createBuffer(bufferInfo);
|
|
||||||
|
|
||||||
auto reqs = m_Device.GetDevice().getBufferMemoryRequirements(m_Buffer);
|
|
||||||
auto alloc = allocator.Allocate(reqs, memProps);
|
|
||||||
|
|
||||||
m_Memory = alloc.Memory;
|
|
||||||
m_Offset = alloc.Offset;
|
|
||||||
m_MappedPtr = alloc.MappedPtr;
|
|
||||||
|
|
||||||
m_Device.GetDevice().bindBufferMemory(m_Buffer, m_Memory, m_Offset);
|
|
||||||
|
|
||||||
m_Allocation = alloc; // 成员变量,保存起来
|
|
||||||
|
|
||||||
STATS.AddBuffer(m_Size);
|
STATS.AddBuffer(m_Size);
|
||||||
}
|
}
|
||||||
|
|
||||||
Buffer::~Buffer()
|
Buffer::~Buffer()
|
||||||
{
|
{
|
||||||
m_Device.GetDevice().destroyBuffer(m_Buffer);
|
m_Allocator.Destroy(m_Allocation);
|
||||||
m_Allocator.Free(m_Allocation);
|
|
||||||
STATS.RemoveBuffer(m_Size);
|
STATS.RemoveBuffer(m_Size);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,12 +41,7 @@ namespace Engine {
|
|||||||
}
|
}
|
||||||
memcpy(static_cast<char*>(m_MappedPtr) + offset, data, size);
|
memcpy(static_cast<char*>(m_MappedPtr) + offset, data, size);
|
||||||
|
|
||||||
// flush 整个内存块,避免对齐问题
|
// 非 coherent 内存写后冲刷(coherent 时 VMA 内部自动跳过)
|
||||||
vk::MappedMemoryRange range;
|
m_Allocator.Flush(m_Allocation);
|
||||||
range.setMemory(m_Memory)
|
|
||||||
.setOffset(0)
|
|
||||||
.setSize(VK_WHOLE_SIZE);
|
|
||||||
|
|
||||||
m_Device.GetDevice().flushMappedMemoryRanges(range);
|
|
||||||
}
|
}
|
||||||
} // namespace Engine
|
} // namespace Engine
|
||||||
|
|||||||
@@ -11,23 +11,32 @@ namespace Engine
|
|||||||
DescriptorManager::DescriptorManager(Device& device)
|
DescriptorManager::DescriptorManager(Device& device)
|
||||||
: m_Device(device)
|
: m_Device(device)
|
||||||
{
|
{
|
||||||
std::array<vk::DescriptorPoolSize, 2> poolSizes = {
|
m_Pools.push_back(CreateChunk());
|
||||||
vk::DescriptorPoolSize(vk::DescriptorType::eUniformBuffer, kMaxSets),
|
ENGINE_INFO("DescriptorManager created (growable, chunk = {} sets)",
|
||||||
vk::DescriptorPoolSize(vk::DescriptorType::eCombinedImageSampler, kMaxSets),
|
kChunkSets);
|
||||||
};
|
|
||||||
|
|
||||||
vk::DescriptorPoolCreateInfo poolInfo;
|
|
||||||
poolInfo.setMaxSets(kMaxSets)
|
|
||||||
.setPoolSizes(poolSizes)
|
|
||||||
.setFlags(vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet);
|
|
||||||
|
|
||||||
m_Pool = m_Device.GetDevice().createDescriptorPool(poolInfo);
|
|
||||||
ENGINE_INFO("DescriptorManager created");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
DescriptorManager::~DescriptorManager()
|
DescriptorManager::~DescriptorManager()
|
||||||
{
|
{
|
||||||
m_Device.GetDevice().destroyDescriptorPool(m_Pool);
|
for (auto pool : m_Pools) {
|
||||||
|
m_Device.GetDevice().destroyDescriptorPool(pool);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vk::DescriptorPool DescriptorManager::CreateChunk()
|
||||||
|
{
|
||||||
|
std::array<vk::DescriptorPoolSize, 3> poolSizes = {
|
||||||
|
vk::DescriptorPoolSize(vk::DescriptorType::eUniformBuffer, kChunkSets),
|
||||||
|
vk::DescriptorPoolSize(vk::DescriptorType::eCombinedImageSampler, kChunkSets),
|
||||||
|
vk::DescriptorPoolSize(vk::DescriptorType::eStorageBuffer, kChunkSets),
|
||||||
|
};
|
||||||
|
|
||||||
|
vk::DescriptorPoolCreateInfo poolInfo;
|
||||||
|
poolInfo.setMaxSets(kChunkSets)
|
||||||
|
.setPoolSizes(poolSizes)
|
||||||
|
.setFlags(vk::DescriptorPoolCreateFlagBits::eFreeDescriptorSet);
|
||||||
|
|
||||||
|
return m_Device.GetDevice().createDescriptorPool(poolInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
vk::DescriptorSetLayout DescriptorManager::CreateLayout(const std::vector<DescriptorBinding>& bindings)
|
vk::DescriptorSetLayout DescriptorManager::CreateLayout(const std::vector<DescriptorBinding>& bindings)
|
||||||
@@ -51,12 +60,24 @@ namespace Engine
|
|||||||
|
|
||||||
vk::DescriptorSet DescriptorManager::AllocateDescriptorSet(vk::DescriptorSetLayout layout)
|
vk::DescriptorSet DescriptorManager::AllocateDescriptorSet(vk::DescriptorSetLayout layout)
|
||||||
{
|
{
|
||||||
vk::DescriptorSetAllocateInfo allocInfo;
|
// 从最新块分配;耗尽(OutOfPool/Fragmented)则追加一块重试
|
||||||
allocInfo.setDescriptorPool(m_Pool)
|
for (;;) {
|
||||||
.setSetLayouts(layout);
|
try {
|
||||||
|
vk::DescriptorSetAllocateInfo allocInfo;
|
||||||
auto sets = m_Device.GetDevice().allocateDescriptorSets(allocInfo);
|
allocInfo.setDescriptorPool(m_Pools.back())
|
||||||
return sets[0];
|
.setSetLayouts(layout);
|
||||||
|
auto sets = m_Device.GetDevice().allocateDescriptorSets(allocInfo);
|
||||||
|
return sets[0];
|
||||||
|
} catch (const vk::OutOfPoolMemoryError&) {
|
||||||
|
m_Pools.push_back(CreateChunk());
|
||||||
|
ENGINE_INFO("DescriptorManager: pool chunk grown -> {} chunks",
|
||||||
|
m_Pools.size());
|
||||||
|
} catch (const vk::FragmentedPoolError&) {
|
||||||
|
m_Pools.push_back(CreateChunk());
|
||||||
|
ENGINE_INFO("DescriptorManager: pool fragmented, new chunk -> {}",
|
||||||
|
m_Pools.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void DescriptorManager::UpdateUniformBuffer(vk::DescriptorSet set, u32 binding,
|
void DescriptorManager::UpdateUniformBuffer(vk::DescriptorSet set, u32 binding,
|
||||||
@@ -76,6 +97,23 @@ namespace Engine
|
|||||||
m_Device.GetDevice().updateDescriptorSets(write, {});
|
m_Device.GetDevice().updateDescriptorSets(write, {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void DescriptorManager::UpdateStorageBuffer(vk::DescriptorSet set, u32 binding,
|
||||||
|
vk::Buffer buffer, u64 size)
|
||||||
|
{
|
||||||
|
vk::DescriptorBufferInfo bufferInfo;
|
||||||
|
bufferInfo.setBuffer(buffer).setOffset(0).setRange(size);
|
||||||
|
|
||||||
|
vk::WriteDescriptorSet write;
|
||||||
|
write.setDstSet(set)
|
||||||
|
.setDstBinding(binding)
|
||||||
|
.setDstArrayElement(0)
|
||||||
|
.setDescriptorType(vk::DescriptorType::eStorageBuffer)
|
||||||
|
.setDescriptorCount(1)
|
||||||
|
.setBufferInfo(bufferInfo);
|
||||||
|
|
||||||
|
m_Device.GetDevice().updateDescriptorSets(write, {});
|
||||||
|
}
|
||||||
|
|
||||||
void DescriptorManager::UpdateTexture(vk::DescriptorSet set, u32 binding,
|
void DescriptorManager::UpdateTexture(vk::DescriptorSet set, u32 binding,
|
||||||
vk::Sampler sampler, vk::ImageView view)
|
vk::Sampler sampler, vk::ImageView view)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -6,220 +6,147 @@
|
|||||||
#include <Engine/Vulkan/Device.h>
|
#include <Engine/Vulkan/Device.h>
|
||||||
#include <Engine/Core/Log.h>
|
#include <Engine/Core/Log.h>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <vk_mem_alloc.h>
|
||||||
|
#include <stdexcept>
|
||||||
|
|
||||||
#include "Engine/Core/Stats.h"
|
#include "Engine/Core/Stats.h"
|
||||||
|
|
||||||
namespace Engine {
|
namespace Engine {
|
||||||
|
|
||||||
MemoryAllocator::MemoryAllocator(Device& device)
|
namespace
|
||||||
: m_Device(device)
|
{
|
||||||
{
|
// props → VMA 创建参数:
|
||||||
ENGINE_INFO("MemoryAllocator created");
|
// HOST_VISIBLE → AUTO + 持久映射 + 顺序写访问(VMA 自动挑 host 内存类型)
|
||||||
}
|
// 其余(DeviceLocal)→ AUTO_PREFER_DEVICE(优先显存,允许溢出到 BAR)
|
||||||
|
VmaAllocationCreateInfo MakeCreateInfo(vk::MemoryPropertyFlags props)
|
||||||
MemoryAllocator::~MemoryAllocator()
|
{
|
||||||
{
|
VmaAllocationCreateInfo ci{};
|
||||||
const auto& vkDevice = m_Device.GetDevice();
|
if (props & vk::MemoryPropertyFlagBits::eHostVisible) {
|
||||||
|
ci.usage = VMA_MEMORY_USAGE_AUTO;
|
||||||
for (auto& blocks : m_Blocks) {
|
ci.flags = VMA_ALLOCATION_CREATE_MAPPED_BIT
|
||||||
for (auto& block : blocks) {
|
| VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT;
|
||||||
if (block.IsMapped) {
|
} else {
|
||||||
vkDevice.unmapMemory(block.Memory);
|
ci.usage = VMA_MEMORY_USAGE_AUTO_PREFER_DEVICE;
|
||||||
}
|
|
||||||
vkDevice.freeMemory(block.Memory);
|
|
||||||
|
|
||||||
STATS.RemoveMemoryBlock(block.Size);
|
|
||||||
}
|
|
||||||
blocks.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
vk::DeviceSize MemoryAllocator::GetBlockSize(vk::MemoryPropertyFlags props) const
|
|
||||||
{
|
|
||||||
if (props & vk::MemoryPropertyFlagBits::eHostVisible) {
|
|
||||||
return 16ull * 1024 * 1024; // 16 MB
|
|
||||||
}
|
|
||||||
return 64ull * 1024 * 1024; // 64 MB
|
|
||||||
}
|
|
||||||
|
|
||||||
MemoryAllocator::Allocation MemoryAllocator::Allocate(
|
|
||||||
vk::MemoryRequirements reqs, vk::MemoryPropertyFlags props)
|
|
||||||
{
|
|
||||||
u32 typeIndex = m_Device.FindMemoryType(reqs.memoryTypeBits, props);
|
|
||||||
auto& blocks = m_Blocks[typeIndex];
|
|
||||||
|
|
||||||
// 1. 扫描已有块的 FreeList,找能放下的区域(first-fit)
|
|
||||||
for (u32 i = 0; i < blocks.size(); ++i) {
|
|
||||||
auto& block = blocks[i];
|
|
||||||
for (auto it = block.FreeList.begin(); it != block.FreeList.end(); ++it) {
|
|
||||||
vk::DeviceSize alignedOffset =
|
|
||||||
(it->Offset + reqs.alignment - 1) & ~(reqs.alignment - 1);
|
|
||||||
vk::DeviceSize end = alignedOffset + reqs.size;
|
|
||||||
|
|
||||||
if (end <= it->Offset + it->Size) {
|
|
||||||
// 找到了
|
|
||||||
Allocation alloc;
|
|
||||||
alloc.Memory = block.Memory;
|
|
||||||
alloc.Offset = alignedOffset;
|
|
||||||
alloc.Size = reqs.size;
|
|
||||||
alloc.MappedPtr = block.IsMapped
|
|
||||||
? static_cast<char*>(block.MappedPtr) + alignedOffset
|
|
||||||
: nullptr;
|
|
||||||
alloc.BlockIndex = i;
|
|
||||||
alloc.TypeIndex = typeIndex;
|
|
||||||
alloc.Valid = true;
|
|
||||||
|
|
||||||
// 更新 FreeList
|
|
||||||
if (alignedOffset == it->Offset && reqs.size == it->Size) {
|
|
||||||
// 整个区域被用了,移除
|
|
||||||
block.FreeList.erase(it);
|
|
||||||
} else if (alignedOffset == it->Offset) {
|
|
||||||
// 从头部切
|
|
||||||
it->Offset += reqs.size;
|
|
||||||
it->Size -= reqs.size;
|
|
||||||
} else if (end == it->Offset + it->Size) {
|
|
||||||
// 从尾部切
|
|
||||||
it->Size = alignedOffset - it->Offset;
|
|
||||||
} else {
|
|
||||||
// 从中间切,拆成两段
|
|
||||||
FreeRange tail;
|
|
||||||
tail.Offset = end;
|
|
||||||
tail.Size = (it->Offset + it->Size) - end;
|
|
||||||
it->Size = alignedOffset - it->Offset;
|
|
||||||
block.FreeList.insert(it + 1, tail);
|
|
||||||
}
|
|
||||||
|
|
||||||
return alloc;
|
|
||||||
}
|
}
|
||||||
|
// requiredFlags 兜底:调用方明确要求的属性 VMA 一定满足
|
||||||
|
ci.requiredFlags = static_cast<VkMemoryPropertyFlags>(props);
|
||||||
|
return ci;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. 在已有块的尾部 bump 分配
|
MemoryAllocator::MemoryAllocator(Device& device)
|
||||||
for (u32 i = 0; i < blocks.size(); ++i) {
|
: m_Device(device)
|
||||||
auto& block = blocks[i];
|
{
|
||||||
vk::DeviceSize alignedOffset =
|
VmaAllocatorCreateInfo ci{};
|
||||||
(block.Used + reqs.alignment - 1) & ~(reqs.alignment - 1);
|
ci.instance = static_cast<VkInstance>(device.GetInstance());
|
||||||
|
ci.physicalDevice = static_cast<VkPhysicalDevice>(device.GetPhysicalDevice());
|
||||||
|
ci.device = static_cast<VkDevice>(device.GetDevice());
|
||||||
|
// 与 Device 创建实例时的 API 版本一致(启用 1.1+ 核心化的
|
||||||
|
// dedicated allocation / memory requirements 控制等路径)
|
||||||
|
ci.vulkanApiVersion = VK_API_VERSION_1_4;
|
||||||
|
// VMA_STATIC_VULKAN_FUNCTIONS=1:直接用链接的 Vulkan loader 函数表
|
||||||
|
|
||||||
if (alignedOffset + reqs.size <= block.Size) {
|
if (vmaCreateAllocator(&ci, &m_Vma) != VK_SUCCESS) {
|
||||||
Allocation alloc;
|
throw std::runtime_error("MemoryAllocator: vmaCreateAllocator failed");
|
||||||
alloc.Memory = block.Memory;
|
}
|
||||||
alloc.Offset = alignedOffset;
|
|
||||||
alloc.Size = reqs.size;
|
|
||||||
alloc.MappedPtr = block.IsMapped
|
|
||||||
? static_cast<char*>(block.MappedPtr) + alignedOffset
|
|
||||||
: nullptr;
|
|
||||||
alloc.BlockIndex = i;
|
|
||||||
alloc.TypeIndex = typeIndex;
|
|
||||||
alloc.Valid = true;
|
|
||||||
|
|
||||||
block.Used = alignedOffset + reqs.size;
|
ENGINE_INFO("MemoryAllocator created (VMA backend)");
|
||||||
return alloc;
|
}
|
||||||
|
|
||||||
|
MemoryAllocator::~MemoryAllocator()
|
||||||
|
{
|
||||||
|
if (m_Vma) {
|
||||||
|
vmaDestroyAllocator(m_Vma);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. 创建新块
|
MemoryAllocator::Allocation MemoryAllocator::CreateBuffer(
|
||||||
vk::DeviceSize blockSize = std::max(GetBlockSize(props), reqs.size);
|
vk::DeviceSize size, vk::BufferUsageFlags usage, vk::MemoryPropertyFlags props)
|
||||||
|
{
|
||||||
|
VkBufferCreateInfo bi{VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO};
|
||||||
|
bi.size = size;
|
||||||
|
bi.usage = static_cast<VkBufferUsageFlags>(usage);
|
||||||
|
bi.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
|
||||||
|
|
||||||
vk::MemoryAllocateInfo allocInfo;
|
VmaAllocationCreateInfo aci = MakeCreateInfo(props);
|
||||||
allocInfo.setAllocationSize(blockSize)
|
|
||||||
.setMemoryTypeIndex(typeIndex);
|
|
||||||
|
|
||||||
Block block;
|
VkBuffer buffer = nullptr;
|
||||||
block.Memory = m_Device.GetDevice().allocateMemory(allocInfo);
|
VmaAllocation handle = nullptr;
|
||||||
block.Size = blockSize;
|
if (vmaCreateBuffer(m_Vma, &bi, &aci, &buffer, &handle, nullptr) != VK_SUCCESS) {
|
||||||
block.Used = 0;
|
ENGINE_ERROR("MemoryAllocator: CreateBuffer({} bytes) failed", size);
|
||||||
|
return {};
|
||||||
if (props & vk::MemoryPropertyFlagBits::eHostVisible) {
|
|
||||||
block.MappedPtr = m_Device.GetDevice().mapMemory(
|
|
||||||
block.Memory, 0, VK_WHOLE_SIZE);
|
|
||||||
block.IsMapped = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
blocks.push_back(std::move(block));
|
|
||||||
m_TotalAllocated[typeIndex] += blockSize;
|
|
||||||
|
|
||||||
u32 newIndex = static_cast<u32>(blocks.size() - 1);
|
|
||||||
auto& newBlock = blocks[newIndex];
|
|
||||||
|
|
||||||
vk::DeviceSize alignedOffset = (reqs.alignment - 1) & ~(reqs.alignment - 1);
|
|
||||||
|
|
||||||
Allocation alloc;
|
|
||||||
alloc.Memory = newBlock.Memory;
|
|
||||||
alloc.Offset = alignedOffset;
|
|
||||||
alloc.Size = reqs.size;
|
|
||||||
alloc.MappedPtr = newBlock.IsMapped
|
|
||||||
? static_cast<char*>(newBlock.MappedPtr) + alignedOffset
|
|
||||||
: nullptr;
|
|
||||||
alloc.BlockIndex = newIndex;
|
|
||||||
alloc.TypeIndex = typeIndex;
|
|
||||||
alloc.Valid = true;
|
|
||||||
|
|
||||||
newBlock.Used = alignedOffset + reqs.size;
|
|
||||||
|
|
||||||
ENGINE_INFO("MemoryAllocator: new block of {} bytes (type {})",
|
|
||||||
blockSize, typeIndex);
|
|
||||||
|
|
||||||
STATS.AddMemoryBlock(blockSize);
|
|
||||||
|
|
||||||
STATS.AddMemoryUsed(reqs.size);
|
|
||||||
return alloc;
|
|
||||||
}
|
|
||||||
|
|
||||||
void MemoryAllocator::Free(const Allocation& alloc)
|
|
||||||
{
|
|
||||||
if (!alloc.Valid) return;
|
|
||||||
STATS.RemoveMemoryUsed(alloc.Size);
|
|
||||||
|
|
||||||
auto& block = m_Blocks[alloc.TypeIndex][alloc.BlockIndex];
|
|
||||||
|
|
||||||
FreeRange range;
|
|
||||||
range.Offset = alloc.Offset;
|
|
||||||
range.Size = alloc.Size;
|
|
||||||
|
|
||||||
// 插入 FreeList,保持按 offset 排序,方便合并
|
|
||||||
auto it = std::lower_bound(
|
|
||||||
block.FreeList.begin(), block.FreeList.end(), range,
|
|
||||||
[](const FreeRange& a, const FreeRange& b) {
|
|
||||||
return a.Offset < b.Offset;
|
|
||||||
});
|
|
||||||
|
|
||||||
it = block.FreeList.insert(it, range);
|
|
||||||
|
|
||||||
// 向前合并
|
|
||||||
if (it != block.FreeList.begin()) {
|
|
||||||
auto prev = it - 1;
|
|
||||||
if (prev->Offset + prev->Size == it->Offset) {
|
|
||||||
prev->Size += it->Size;
|
|
||||||
block.FreeList.erase(it);
|
|
||||||
it = prev;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Allocation out;
|
||||||
|
out.Buffer = buffer;
|
||||||
|
out.Size = size;
|
||||||
|
out.Handle = handle;
|
||||||
|
out.Valid = true;
|
||||||
|
|
||||||
|
VmaAllocationInfo info{};
|
||||||
|
vmaGetAllocationInfo(m_Vma, handle, &info);
|
||||||
|
out.MappedPtr = info.pMappedData; // 非 MAPPED 标记时为 null
|
||||||
|
|
||||||
|
STATS.AddMemoryUsed(size);
|
||||||
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 向后合并
|
MemoryAllocator::Allocation MemoryAllocator::CreateImage(
|
||||||
auto next = it + 1;
|
const vk::ImageCreateInfo& imageInfo, vk::MemoryPropertyFlags props)
|
||||||
if (next != block.FreeList.end()) {
|
{
|
||||||
if (it->Offset + it->Size == next->Offset) {
|
VkImageCreateInfo ii = imageInfo; // 同布局,隐式转换
|
||||||
it->Size += next->Size;
|
|
||||||
block.FreeList.erase(next);
|
VmaAllocationCreateInfo aci = MakeCreateInfo(props);
|
||||||
|
|
||||||
|
VkImage image = nullptr;
|
||||||
|
VmaAllocation handle = nullptr;
|
||||||
|
if (vmaCreateImage(m_Vma, &ii, &aci, &image, &handle, nullptr) != VK_SUCCESS) {
|
||||||
|
ENGINE_ERROR("MemoryAllocator: CreateImage({}x{}) failed",
|
||||||
|
imageInfo.extent.width, imageInfo.extent.height);
|
||||||
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Allocation out;
|
||||||
|
out.Image = image;
|
||||||
|
out.Size = ii.extent.depth * ii.extent.height * ii.extent.width; // 近似值(统计用)
|
||||||
|
out.Handle = handle;
|
||||||
|
out.Valid = true;
|
||||||
|
|
||||||
|
STATS.AddMemoryUsed(out.Size);
|
||||||
|
return out;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
void MemoryAllocator::PrintStats() const
|
void MemoryAllocator::Destroy(Allocation& alloc)
|
||||||
{
|
{
|
||||||
for (u32 type = 0; type < VK_MAX_MEMORY_TYPES; ++type) {
|
if (!alloc.Valid) return;
|
||||||
if (m_Blocks[type].empty()) continue;
|
|
||||||
|
|
||||||
vk::DeviceSize totalUsed = 0;
|
// VMA 要求资源与分配成对销毁,顺序不限但句柄必须配对
|
||||||
for (auto& b : m_Blocks[type]) totalUsed += b.Used;
|
if (alloc.Buffer) {
|
||||||
|
vmaDestroyBuffer(m_Vma, alloc.Buffer, alloc.Handle);
|
||||||
|
} else if (alloc.Image) {
|
||||||
|
vmaDestroyImage(m_Vma, alloc.Image, alloc.Handle);
|
||||||
|
}
|
||||||
|
|
||||||
ENGINE_INFO(" Type {}: {} blocks, total allocated {} MB, used {} MB",
|
STATS.RemoveMemoryUsed(alloc.Size);
|
||||||
type,
|
alloc = {};
|
||||||
m_Blocks[type].size(),
|
|
||||||
m_TotalAllocated[type] / (1024 * 1024),
|
|
||||||
totalUsed / (1024 * 1024));
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Engine
|
void MemoryAllocator::Flush(const Allocation& alloc)
|
||||||
|
{
|
||||||
|
if (!alloc.Valid || !alloc.Handle) return;
|
||||||
|
// HOST_COHERENT 时 VMA 内部自动跳过
|
||||||
|
vmaFlushAllocation(m_Vma, alloc.Handle, 0, VK_WHOLE_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
void MemoryAllocator::PrintStats() const
|
||||||
|
{
|
||||||
|
VmaTotalStatistics stats;
|
||||||
|
vmaCalculateStatistics(m_Vma, &stats);
|
||||||
|
ENGINE_INFO(" VMA: {} allocations in {} blocks, total {} MB (used {} MB)",
|
||||||
|
stats.total.statistics.allocationCount,
|
||||||
|
stats.total.statistics.blockCount,
|
||||||
|
stats.total.statistics.blockBytes / (1024 * 1024),
|
||||||
|
stats.total.statistics.allocationBytes / (1024 * 1024));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Engine
|
||||||
|
|||||||
@@ -51,6 +51,21 @@ namespace Engine
|
|||||||
}
|
}
|
||||||
|
|
||||||
vk::SwapchainCreateInfoKHR createInfo;
|
vk::SwapchainCreateInfoKHR createInfo;
|
||||||
|
|
||||||
|
vk::CompositeAlphaFlagBitsKHR compositeAlpha = vk::CompositeAlphaFlagBitsKHR::eOpaque;
|
||||||
|
if (caps.supportedCompositeAlpha & vk::CompositeAlphaFlagBitsKHR::ePreMultiplied)
|
||||||
|
{
|
||||||
|
compositeAlpha = vk::CompositeAlphaFlagBitsKHR::ePreMultiplied;
|
||||||
|
}
|
||||||
|
else if (caps.supportedCompositeAlpha & vk::CompositeAlphaFlagBitsKHR::ePostMultiplied)
|
||||||
|
{
|
||||||
|
compositeAlpha = vk::CompositeAlphaFlagBitsKHR::ePostMultiplied;
|
||||||
|
}
|
||||||
|
|
||||||
|
ENGINE_INFO("supportedCompositeAlpha = 0x{:x}, chosen = 0x{:x}",
|
||||||
|
static_cast<u32>(caps.supportedCompositeAlpha),
|
||||||
|
static_cast<u32>(compositeAlpha));
|
||||||
|
|
||||||
createInfo.setSurface(surface)
|
createInfo.setSurface(surface)
|
||||||
.setMinImageCount(imageCount)
|
.setMinImageCount(imageCount)
|
||||||
.setImageFormat(surfaceFormat.format)
|
.setImageFormat(surfaceFormat.format)
|
||||||
@@ -60,7 +75,7 @@ namespace Engine
|
|||||||
.setImageUsage(vk::ImageUsageFlagBits::eColorAttachment)
|
.setImageUsage(vk::ImageUsageFlagBits::eColorAttachment)
|
||||||
.setImageSharingMode(vk::SharingMode::eExclusive)
|
.setImageSharingMode(vk::SharingMode::eExclusive)
|
||||||
.setPreTransform(caps.currentTransform)
|
.setPreTransform(caps.currentTransform)
|
||||||
.setCompositeAlpha(vk::CompositeAlphaFlagBitsKHR::eOpaque)
|
.setCompositeAlpha(compositeAlpha)
|
||||||
.setPresentMode(presentMode)
|
.setPresentMode(presentMode)
|
||||||
.setClipped(VK_TRUE)
|
.setClipped(VK_TRUE)
|
||||||
.setOldSwapchain(nullptr);
|
.setOldSwapchain(nullptr);
|
||||||
@@ -95,10 +110,11 @@ namespace Engine
|
|||||||
m_ImageViews[i] = vkDevice.createImageView(viewInfo);
|
m_ImageViews[i] = vkDevice.createImageView(viewInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
ENGINE_INFO("Swapchain created: {}x{}, {} images, format={}",
|
ENGINE_INFO("Swapchain created: {}x{}, {} images, format={}, present={}",
|
||||||
m_Extent.width, m_Extent.height,
|
m_Extent.width, m_Extent.height,
|
||||||
m_Images.size(),
|
m_Images.size(),
|
||||||
vk::to_string(m_Format));
|
vk::to_string(m_Format),
|
||||||
|
vk::to_string(presentMode));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Swapchain::Destroy()
|
void Swapchain::Destroy()
|
||||||
@@ -151,9 +167,18 @@ namespace Engine
|
|||||||
|
|
||||||
vk::PresentModeKHR Swapchain::ChoosePresentMode(const std::vector<vk::PresentModeKHR>& modes)
|
vk::PresentModeKHR Swapchain::ChoosePresentMode(const std::vector<vk::PresentModeKHR>& modes)
|
||||||
{
|
{
|
||||||
|
// 实测 Wayland/NVIDIA:Mailbox 不阻塞应用循环(compositor 取最新
|
||||||
|
// 缓冲,应用层计数可远超刷新率);Immediate 常不可用而退到 FIFO。
|
||||||
|
// 因此映射按"用户预期"定义:
|
||||||
|
// vsync 开 → FIFO:应用被 pacing 到刷新率(真正的垂直同步)
|
||||||
|
// vsync 关 → Mailbox:应用循环不限速(Wayland 下无撕裂)
|
||||||
|
const vk::PresentModeKHR preferred = m_VSync
|
||||||
|
? vk::PresentModeKHR::eFifo
|
||||||
|
: vk::PresentModeKHR::eMailbox;
|
||||||
|
|
||||||
for (const auto& m : modes)
|
for (const auto& m : modes)
|
||||||
{
|
{
|
||||||
if (m == vk::PresentModeKHR::eMailbox)
|
if (m == preferred)
|
||||||
{
|
{
|
||||||
return m;
|
return m;
|
||||||
}
|
}
|
||||||
|
|||||||
+241
-53
@@ -5,80 +5,88 @@
|
|||||||
#include <Engine/Vulkan/Texture.h>
|
#include <Engine/Vulkan/Texture.h>
|
||||||
#include <Engine/Vulkan/Device.h>
|
#include <Engine/Vulkan/Device.h>
|
||||||
#include <Engine/Vulkan/MemoryAllocator.h>
|
#include <Engine/Vulkan/MemoryAllocator.h>
|
||||||
|
#include <Engine/Assets/DdsLoader.h>
|
||||||
#include <Engine/Core/Log.h>
|
#include <Engine/Core/Log.h>
|
||||||
|
|
||||||
#include <stb/stb_image.h>
|
#include <stb/stb_image.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cctype>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
#include "Engine/Core/Stats.h"
|
#include "Engine/Core/Stats.h"
|
||||||
|
|
||||||
namespace Engine
|
namespace Engine
|
||||||
{
|
{
|
||||||
Texture::Texture(Device& device, MemoryAllocator& allocator, const std::string& path)
|
Texture::Texture(Device& device, MemoryAllocator& allocator, const std::string& path,
|
||||||
|
bool srgb)
|
||||||
: m_Device(device) , m_Allocator(allocator)
|
: m_Device(device) , m_Allocator(allocator)
|
||||||
{
|
{
|
||||||
// 1. CPU 加载图片
|
// 按扩展名分发:DDS(BC 压缩直传 + mip 链)vs stbi(无压缩单 mip)
|
||||||
int width = 0, height = 0, channels = 0;
|
const std::string ext = path.size() >= 4
|
||||||
stbi_uc* pixels = stbi_load(path.c_str(), &width, &height, &channels, STBI_rgb_alpha);
|
? path.substr(path.size() - 4) : "";
|
||||||
|
std::string lower;
|
||||||
|
for (char c : ext) lower += static_cast<char>(std::tolower(c));
|
||||||
|
|
||||||
if (!pixels)
|
if (lower == ".dds") {
|
||||||
{
|
InitFromDds(path, srgb);
|
||||||
ENGINE_ERROR("Failed to load texture: {}", path);
|
} else {
|
||||||
return;
|
// 1. CPU 加载图片
|
||||||
|
int width = 0, height = 0, channels = 0;
|
||||||
|
stbi_uc* pixels = stbi_load(path.c_str(), &width, &height, &channels,
|
||||||
|
STBI_rgb_alpha);
|
||||||
|
|
||||||
|
if (!pixels) {
|
||||||
|
ENGINE_ERROR("Failed to load texture: {}", path);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
InitFromPixels(pixels, static_cast<u32>(width), static_cast<u32>(height), srgb);
|
||||||
|
|
||||||
|
stbi_image_free(pixels);
|
||||||
|
|
||||||
|
ENGINE_INFO("Texture loaded: {} ({}x{})", path, m_Width, m_Height);
|
||||||
}
|
}
|
||||||
|
|
||||||
InitFromPixels(pixels, static_cast<u32>(width), static_cast<u32>(height));
|
|
||||||
|
|
||||||
stbi_image_free(pixels);
|
|
||||||
|
|
||||||
ENGINE_INFO("Texture loaded: {} ({}x{})", path, m_Width, m_Height);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Texture::Texture(Device& device, MemoryAllocator& allocator, ProceduralTag,
|
Texture::Texture(Device& device, MemoryAllocator& allocator, ProceduralTag,
|
||||||
const std::array<u8, 4>& rgba)
|
const std::array<u8, 4>& rgba, bool srgb)
|
||||||
: m_Device(device) , m_Allocator(allocator)
|
: m_Device(device) , m_Allocator(allocator)
|
||||||
{
|
{
|
||||||
const u8 pixels[4] = { rgba[0], rgba[1], rgba[2], rgba[3] };
|
const u8 pixels[4] = { rgba[0], rgba[1], rgba[2], rgba[3] };
|
||||||
InitFromPixels(pixels, 1, 1);
|
InitFromPixels(pixels, 1, 1, srgb);
|
||||||
}
|
}
|
||||||
|
|
||||||
Scope<Texture> Texture::CreateSolidColor(Device& device, MemoryAllocator& allocator,
|
Scope<Texture> Texture::CreateSolidColor(Device& device, MemoryAllocator& allocator,
|
||||||
u8 r, u8 g, u8 b)
|
u8 r, u8 g, u8 b, bool srgb)
|
||||||
{
|
{
|
||||||
return Scope<Texture>(new Texture(device, allocator, ProceduralTag{}, {r, g, b, 255}));
|
return Scope<Texture>(new Texture(device, allocator, ProceduralTag{},
|
||||||
|
{r, g, b, 255}, srgb));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Texture::InitFromPixels(const void* pixels, u32 width, u32 height)
|
void Texture::InitFromPixels(const void* pixels, u32 width, u32 height, bool srgb)
|
||||||
{
|
{
|
||||||
m_Width = width;
|
m_Width = width;
|
||||||
m_Height = height;
|
m_Height = height;
|
||||||
|
m_Format = srgb ? vk::Format::eR8G8B8A8Srgb : vk::Format::eR8G8B8A8Unorm;
|
||||||
|
m_MipLevels = 1;
|
||||||
vk::DeviceSize imageSize = static_cast<vk::DeviceSize>(width) *
|
vk::DeviceSize imageSize = static_cast<vk::DeviceSize>(width) *
|
||||||
static_cast<vk::DeviceSize>(height) * 4; // RGBA8
|
static_cast<vk::DeviceSize>(height) * 4; // RGBA8
|
||||||
|
|
||||||
// 2. 创建 Staging Buffer(HOST_VISIBLE)
|
// 2. 创建 Staging Buffer(HOST_VISIBLE,VMA 持久映射)
|
||||||
vk::BufferCreateInfo stagingInfo;
|
auto stagingAlloc = m_Allocator.CreateBuffer(
|
||||||
stagingInfo.setSize(imageSize)
|
imageSize,
|
||||||
.setUsage(vk::BufferUsageFlagBits::eTransferSrc)
|
vk::BufferUsageFlagBits::eTransferSrc,
|
||||||
.setSharingMode(vk::SharingMode::eExclusive);
|
|
||||||
|
|
||||||
vk::Buffer stagingBuffer = m_Device.GetDevice().createBuffer(stagingInfo);
|
|
||||||
|
|
||||||
auto stagingReqs = m_Device.GetDevice().getBufferMemoryRequirements(stagingBuffer);
|
|
||||||
auto stagingAlloc = m_Allocator.Allocate(
|
|
||||||
stagingReqs,
|
|
||||||
vk::MemoryPropertyFlagBits::eHostVisible |
|
vk::MemoryPropertyFlagBits::eHostVisible |
|
||||||
vk::MemoryPropertyFlagBits::eHostCoherent);
|
vk::MemoryPropertyFlagBits::eHostCoherent);
|
||||||
|
|
||||||
m_Device.GetDevice().bindBufferMemory(stagingBuffer, stagingAlloc.Memory, stagingAlloc.Offset);
|
|
||||||
|
|
||||||
// 3. 拷贝像素到 Staging Buffer
|
// 3. 拷贝像素到 Staging Buffer
|
||||||
std::memcpy(stagingAlloc.MappedPtr, pixels, static_cast<size_t>(imageSize));
|
std::memcpy(stagingAlloc.MappedPtr, pixels, static_cast<size_t>(imageSize));
|
||||||
|
|
||||||
// 4. 创建 Device Local Image
|
// 4. 创建 Device Local Image
|
||||||
vk::ImageCreateInfo imageInfo;
|
vk::ImageCreateInfo imageInfo;
|
||||||
imageInfo.setImageType(vk::ImageType::e2D)
|
imageInfo.setImageType(vk::ImageType::e2D)
|
||||||
.setFormat(vk::Format::eR8G8B8A8Srgb)
|
.setFormat(m_Format)
|
||||||
.setExtent({m_Width, m_Height, 1})
|
.setExtent({m_Width, m_Height, 1})
|
||||||
.setMipLevels(1)
|
.setMipLevels(1)
|
||||||
.setArrayLayers(1)
|
.setArrayLayers(1)
|
||||||
@@ -88,24 +96,18 @@ namespace Engine
|
|||||||
.setSharingMode(vk::SharingMode::eExclusive)
|
.setSharingMode(vk::SharingMode::eExclusive)
|
||||||
.setInitialLayout(vk::ImageLayout::eUndefined);
|
.setInitialLayout(vk::ImageLayout::eUndefined);
|
||||||
|
|
||||||
m_Image = m_Device.GetDevice().createImage(imageInfo);
|
m_ImageAllocation = m_Allocator.CreateImage(imageInfo,
|
||||||
|
vk::MemoryPropertyFlagBits::eDeviceLocal);
|
||||||
auto imageReqs = m_Device.GetDevice().getImageMemoryRequirements(m_Image);
|
m_Image = m_ImageAllocation.Image;
|
||||||
auto imageAlloc = m_Allocator.Allocate(imageReqs, vk::MemoryPropertyFlagBits::eDeviceLocal);
|
|
||||||
|
|
||||||
m_ImageAllocation = imageAlloc;
|
|
||||||
|
|
||||||
m_Memory = imageAlloc.Memory;
|
|
||||||
m_Device.GetDevice().bindImageMemory(m_Image, m_Memory, imageAlloc.Offset);
|
|
||||||
|
|
||||||
// 5. 创建 ImageView
|
// 5. 创建 ImageView
|
||||||
vk::ImageViewCreateInfo viewInfo;
|
vk::ImageViewCreateInfo viewInfo;
|
||||||
viewInfo.setImage(m_Image)
|
viewInfo.setImage(m_Image)
|
||||||
.setViewType(vk::ImageViewType::e2D)
|
.setViewType(vk::ImageViewType::e2D)
|
||||||
.setFormat(vk::Format::eR8G8B8A8Srgb)
|
.setFormat(m_Format)
|
||||||
.setSubresourceRange({
|
.setSubresourceRange({
|
||||||
vk::ImageAspectFlagBits::eColor,
|
vk::ImageAspectFlagBits::eColor,
|
||||||
0, 1, 0, 1
|
0, m_MipLevels, 0, 1
|
||||||
});
|
});
|
||||||
|
|
||||||
m_View = m_Device.GetDevice().createImageView(viewInfo);
|
m_View = m_Device.GetDevice().createImageView(viewInfo);
|
||||||
@@ -122,7 +124,9 @@ namespace Engine
|
|||||||
.setBorderColor(vk::BorderColor::eIntOpaqueBlack)
|
.setBorderColor(vk::BorderColor::eIntOpaqueBlack)
|
||||||
.setUnnormalizedCoordinates(VK_FALSE)
|
.setUnnormalizedCoordinates(VK_FALSE)
|
||||||
.setCompareEnable(VK_FALSE)
|
.setCompareEnable(VK_FALSE)
|
||||||
.setMipmapMode(vk::SamplerMipmapMode::eLinear);
|
.setMipmapMode(vk::SamplerMipmapMode::eLinear)
|
||||||
|
.setMinLod(0.0f)
|
||||||
|
.setMaxLod(static_cast<f32>(m_MipLevels));
|
||||||
|
|
||||||
m_Sampler = m_Device.GetDevice().createSampler(samplerInfo);
|
m_Sampler = m_Device.GetDevice().createSampler(samplerInfo);
|
||||||
|
|
||||||
@@ -168,7 +172,7 @@ namespace Engine
|
|||||||
.setImageOffset({0, 0, 0})
|
.setImageOffset({0, 0, 0})
|
||||||
.setImageExtent({m_Width, m_Height, 1});
|
.setImageExtent({m_Width, m_Height, 1});
|
||||||
|
|
||||||
cmd.copyBufferToImage(stagingBuffer, m_Image,
|
cmd.copyBufferToImage(stagingAlloc.Buffer, m_Image,
|
||||||
vk::ImageLayout::eTransferDstOptimal, region);
|
vk::ImageLayout::eTransferDstOptimal, region);
|
||||||
|
|
||||||
// 6.3 布局:TRANSFER_DST → SHADER_READ_ONLY
|
// 6.3 布局:TRANSFER_DST → SHADER_READ_ONLY
|
||||||
@@ -202,28 +206,212 @@ namespace Engine
|
|||||||
ENGINE_ERROR("Texture upload: waitForFences failed");
|
ENGINE_ERROR("Texture upload: waitForFences failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 8. 清理临时对象
|
// 8. 清理临时对象(staging 资源与分配经 VMA 成对释放)
|
||||||
m_Device.GetDevice().destroyFence(fence);
|
m_Device.GetDevice().destroyFence(fence);
|
||||||
m_Device.GetDevice().destroyCommandPool(pool);
|
m_Device.GetDevice().destroyCommandPool(pool);
|
||||||
m_Device.GetDevice().destroyBuffer(stagingBuffer);
|
m_Allocator.Destroy(stagingAlloc);
|
||||||
m_Allocator.Free(stagingAlloc);
|
|
||||||
// staging 内存由 MemoryAllocator 统一释放
|
|
||||||
|
|
||||||
// 显存占用 = 宽 * 高 * 4 字节(RGBA8)
|
// 显存占用 = 宽 * 高 * 4 字节(RGBA8)
|
||||||
m_TextureBytes = static_cast<u64>(m_Width) * m_Height * 4;
|
m_TextureBytes = static_cast<u64>(m_Width) * m_Height * 4;
|
||||||
STATS.AddTexture(m_TextureBytes);
|
STATS.AddTexture(m_TextureBytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DDS → Vulkan 格式映射。sRGB 语义由贴图槽位决定(渲染侧职责):
|
||||||
|
// 颜色图(BaseColor/Emissive)→ SRGB 变体
|
||||||
|
// 数据图(Normal/Specular 等线性槽位)→ UNORM
|
||||||
|
vk::Format ToVkFormat(Assets::Dds::DdsFormat f, bool srgb)
|
||||||
|
{
|
||||||
|
using DF = Assets::Dds::DdsFormat;
|
||||||
|
switch (f) {
|
||||||
|
case DF::BC1: return srgb ? vk::Format::eBc1RgbaSrgbBlock
|
||||||
|
: vk::Format::eBc1RgbaUnormBlock;
|
||||||
|
case DF::BC2: return srgb ? vk::Format::eBc2SrgbBlock
|
||||||
|
: vk::Format::eBc2UnormBlock;
|
||||||
|
case DF::BC3: return srgb ? vk::Format::eBc3SrgbBlock
|
||||||
|
: vk::Format::eBc3UnormBlock;
|
||||||
|
case DF::BC4: return vk::Format::eBc4UnormBlock;
|
||||||
|
case DF::BC5: return vk::Format::eBc5UnormBlock;
|
||||||
|
case DF::BC6H: return vk::Format::eBc6HUfloatBlock;
|
||||||
|
case DF::BC7: return srgb ? vk::Format::eBc7SrgbBlock
|
||||||
|
: vk::Format::eBc7UnormBlock;
|
||||||
|
case DF::RGBA8: return srgb ? vk::Format::eR8G8B8A8Srgb
|
||||||
|
: vk::Format::eR8G8B8A8Unorm;
|
||||||
|
case DF::BGRA8: return srgb ? vk::Format::eB8G8R8A8Srgb
|
||||||
|
: vk::Format::eB8G8R8A8Unorm;
|
||||||
|
}
|
||||||
|
return vk::Format::eUndefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 MipDim(u32 v, u32 level)
|
||||||
|
{
|
||||||
|
return std::max(1u, v >> level);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Texture::InitFromDds(const std::string& path, bool srgb)
|
||||||
|
{
|
||||||
|
Assets::Dds::DdsData data;
|
||||||
|
std::string err;
|
||||||
|
if (!Assets::Dds::Load(path, data, err)) {
|
||||||
|
ENGINE_ERROR("DDS: {} ({})", err, path);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_Width = data.Width;
|
||||||
|
m_Height = data.Height;
|
||||||
|
m_MipLevels = data.MipLevels;
|
||||||
|
m_Format = ToVkFormat(data.Format, srgb);
|
||||||
|
|
||||||
|
ENGINE_INFO("DDS loaded: {} ({}x{}, {} mips, {})", path,
|
||||||
|
m_Width, m_Height, m_MipLevels, vk::to_string(m_Format));
|
||||||
|
|
||||||
|
// 1. staging:各 mip 紧凑排布,记偏移
|
||||||
|
vk::DeviceSize total = 0;
|
||||||
|
for (const auto& mip : data.Mips) {
|
||||||
|
total += mip.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto stagingAlloc = m_Allocator.CreateBuffer(
|
||||||
|
total,
|
||||||
|
vk::BufferUsageFlagBits::eTransferSrc,
|
||||||
|
vk::MemoryPropertyFlagBits::eHostVisible |
|
||||||
|
vk::MemoryPropertyFlagBits::eHostCoherent);
|
||||||
|
|
||||||
|
std::vector<vk::DeviceSize> offsets(m_MipLevels);
|
||||||
|
{
|
||||||
|
vk::DeviceSize off = 0;
|
||||||
|
for (u32 mip = 0; mip < m_MipLevels; ++mip) {
|
||||||
|
offsets[mip] = off;
|
||||||
|
std::memcpy(stagingAlloc.MappedPtr + off,
|
||||||
|
data.Mips[mip].data(), data.Mips[mip].size());
|
||||||
|
off += data.Mips[mip].size();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. device local image(带完整 mip 链)
|
||||||
|
vk::ImageCreateInfo imageInfo;
|
||||||
|
imageInfo.setImageType(vk::ImageType::e2D)
|
||||||
|
.setFormat(m_Format)
|
||||||
|
.setExtent({m_Width, m_Height, 1})
|
||||||
|
.setMipLevels(m_MipLevels)
|
||||||
|
.setArrayLayers(1)
|
||||||
|
.setSamples(vk::SampleCountFlagBits::e1)
|
||||||
|
.setTiling(vk::ImageTiling::eOptimal)
|
||||||
|
.setUsage(vk::ImageUsageFlagBits::eTransferDst |
|
||||||
|
vk::ImageUsageFlagBits::eSampled)
|
||||||
|
.setSharingMode(vk::SharingMode::eExclusive)
|
||||||
|
.setInitialLayout(vk::ImageLayout::eUndefined);
|
||||||
|
|
||||||
|
m_ImageAllocation = m_Allocator.CreateImage(imageInfo,
|
||||||
|
vk::MemoryPropertyFlagBits::eDeviceLocal);
|
||||||
|
m_Image = m_ImageAllocation.Image;
|
||||||
|
|
||||||
|
// 3. view / sampler(覆盖整个 mip 链)
|
||||||
|
m_View = m_Device.GetDevice().createImageView(
|
||||||
|
vk::ImageViewCreateInfo{}
|
||||||
|
.setImage(m_Image)
|
||||||
|
.setViewType(vk::ImageViewType::e2D)
|
||||||
|
.setFormat(m_Format)
|
||||||
|
.setSubresourceRange({vk::ImageAspectFlagBits::eColor,
|
||||||
|
0, m_MipLevels, 0, 1}));
|
||||||
|
|
||||||
|
m_Sampler = m_Device.GetDevice().createSampler(
|
||||||
|
vk::SamplerCreateInfo{}
|
||||||
|
.setMagFilter(vk::Filter::eLinear)
|
||||||
|
.setMinFilter(vk::Filter::eLinear)
|
||||||
|
.setMipmapMode(vk::SamplerMipmapMode::eLinear)
|
||||||
|
.setAddressModeU(vk::SamplerAddressMode::eRepeat)
|
||||||
|
.setAddressModeV(vk::SamplerAddressMode::eRepeat)
|
||||||
|
.setAddressModeW(vk::SamplerAddressMode::eRepeat)
|
||||||
|
.setAnisotropyEnable(VK_FALSE)
|
||||||
|
.setBorderColor(vk::BorderColor::eIntOpaqueBlack)
|
||||||
|
.setUnnormalizedCoordinates(VK_FALSE)
|
||||||
|
.setCompareEnable(VK_FALSE)
|
||||||
|
.setMinLod(0.0f)
|
||||||
|
.setMaxLod(static_cast<f32>(m_MipLevels)));
|
||||||
|
|
||||||
|
// 4. 一次性命令缓冲:整图 → TransferDst,逐 mip 拷贝 → ShaderRead
|
||||||
|
vk::CommandPoolCreateInfo poolInfo;
|
||||||
|
poolInfo.setQueueFamilyIndex(m_Device.GetGraphicsFamily())
|
||||||
|
.setFlags(vk::CommandPoolCreateFlagBits::eTransient);
|
||||||
|
vk::CommandPool pool = m_Device.GetDevice().createCommandPool(poolInfo);
|
||||||
|
|
||||||
|
vk::CommandBufferAllocateInfo allocInfo;
|
||||||
|
allocInfo.setCommandPool(pool)
|
||||||
|
.setLevel(vk::CommandBufferLevel::ePrimary)
|
||||||
|
.setCommandBufferCount(1);
|
||||||
|
vk::CommandBuffer cmd =
|
||||||
|
m_Device.GetDevice().allocateCommandBuffers(allocInfo)[0];
|
||||||
|
|
||||||
|
cmd.begin(vk::CommandBufferBeginInfo{
|
||||||
|
vk::CommandBufferUsageFlagBits::eOneTimeSubmit});
|
||||||
|
|
||||||
|
vk::ImageMemoryBarrier2 toTransfer;
|
||||||
|
toTransfer.setSrcStageMask(vk::PipelineStageFlagBits2::eNone)
|
||||||
|
.setSrcAccessMask(vk::AccessFlagBits2::eNone)
|
||||||
|
.setDstStageMask(vk::PipelineStageFlagBits2::eTransfer)
|
||||||
|
.setDstAccessMask(vk::AccessFlagBits2::eTransferWrite)
|
||||||
|
.setOldLayout(vk::ImageLayout::eUndefined)
|
||||||
|
.setNewLayout(vk::ImageLayout::eTransferDstOptimal)
|
||||||
|
.setImage(m_Image)
|
||||||
|
.setSubresourceRange({vk::ImageAspectFlagBits::eColor,
|
||||||
|
0, m_MipLevels, 0, 1});
|
||||||
|
cmd.pipelineBarrier2(vk::DependencyInfo{}.setImageMemoryBarriers(toTransfer));
|
||||||
|
|
||||||
|
// BC 数据按块紧凑排布 → bufferRowLength/Height 留 0(紧凑)
|
||||||
|
for (u32 mip = 0; mip < m_MipLevels; ++mip) {
|
||||||
|
vk::BufferImageCopy region;
|
||||||
|
region.setBufferOffset(offsets[mip])
|
||||||
|
.setBufferRowLength(0)
|
||||||
|
.setBufferImageHeight(0)
|
||||||
|
.setImageSubresource({vk::ImageAspectFlagBits::eColor,
|
||||||
|
mip, 0, 1})
|
||||||
|
.setImageOffset({0, 0, 0})
|
||||||
|
.setImageExtent({MipDim(m_Width, mip),
|
||||||
|
MipDim(m_Height, mip), 1});
|
||||||
|
cmd.copyBufferToImage(stagingAlloc.Buffer, m_Image,
|
||||||
|
vk::ImageLayout::eTransferDstOptimal, region);
|
||||||
|
}
|
||||||
|
|
||||||
|
vk::ImageMemoryBarrier2 toShader;
|
||||||
|
toShader.setSrcStageMask(vk::PipelineStageFlagBits2::eTransfer)
|
||||||
|
.setSrcAccessMask(vk::AccessFlagBits2::eTransferWrite)
|
||||||
|
.setDstStageMask(vk::PipelineStageFlagBits2::eFragmentShader)
|
||||||
|
.setDstAccessMask(vk::AccessFlagBits2::eShaderRead)
|
||||||
|
.setOldLayout(vk::ImageLayout::eTransferDstOptimal)
|
||||||
|
.setNewLayout(vk::ImageLayout::eShaderReadOnlyOptimal)
|
||||||
|
.setImage(m_Image)
|
||||||
|
.setSubresourceRange({vk::ImageAspectFlagBits::eColor,
|
||||||
|
0, m_MipLevels, 0, 1});
|
||||||
|
cmd.pipelineBarrier2(vk::DependencyInfo{}.setImageMemoryBarriers(toShader));
|
||||||
|
|
||||||
|
cmd.end();
|
||||||
|
|
||||||
|
// 5. 提交并等待,清理 staging
|
||||||
|
vk::Fence fence = m_Device.GetDevice().createFence({});
|
||||||
|
m_Device.GetGraphicsQueue().submit(
|
||||||
|
vk::SubmitInfo{}.setCommandBuffers(cmd), fence);
|
||||||
|
if (m_Device.GetDevice().waitForFences(fence, VK_TRUE, UINT64_MAX) !=
|
||||||
|
vk::Result::eSuccess) {
|
||||||
|
ENGINE_ERROR("DDS upload: waitForFences failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
m_Device.GetDevice().destroyFence(fence);
|
||||||
|
m_Device.GetDevice().destroyCommandPool(pool);
|
||||||
|
m_Allocator.Destroy(stagingAlloc);
|
||||||
|
|
||||||
|
m_TextureBytes = total;
|
||||||
|
STATS.AddTexture(m_TextureBytes);
|
||||||
|
}
|
||||||
|
|
||||||
Texture::~Texture()
|
Texture::~Texture()
|
||||||
{
|
{
|
||||||
const auto& vkDevice = m_Device.GetDevice();
|
const auto& vkDevice = m_Device.GetDevice();
|
||||||
|
|
||||||
vkDevice.destroySampler(m_Sampler);
|
vkDevice.destroySampler(m_Sampler);
|
||||||
vkDevice.destroyImageView(m_View);
|
vkDevice.destroyImageView(m_View);
|
||||||
vkDevice.destroyImage(m_Image);
|
|
||||||
|
|
||||||
// 归还 image 的显存
|
// image 资源与显存经 VMA 成对释放
|
||||||
m_Allocator.Free(m_ImageAllocation);
|
m_Allocator.Destroy(m_ImageAllocation);
|
||||||
|
|
||||||
STATS.RemoveTexture(m_TextureBytes);
|
STATS.RemoveTexture(m_TextureBytes);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,15 +10,15 @@
|
|||||||
namespace Engine {
|
namespace Engine {
|
||||||
|
|
||||||
UniformBuffer::UniformBuffer(Device& device, MemoryAllocator& allocator,
|
UniformBuffer::UniformBuffer(Device& device, MemoryAllocator& allocator,
|
||||||
u64 size, u32 frameCount)
|
u64 size, u32 frameCount,
|
||||||
|
vk::BufferUsageFlags usage)
|
||||||
: m_Size(size)
|
: m_Size(size)
|
||||||
{
|
{
|
||||||
m_Buffers.reserve(frameCount);
|
m_Buffers.reserve(frameCount);
|
||||||
for (u32 i = 0; i < frameCount; ++i) {
|
for (u32 i = 0; i < frameCount; ++i) {
|
||||||
// Uniform Buffer 需要 CPU 每帧更新,所以用 HOST_VISIBLE | HOST_COHERENT
|
// CPU 每帧更新,所以用 HOST_VISIBLE | HOST_COHERENT
|
||||||
m_Buffers.push_back(CreateScope<Buffer>(
|
m_Buffers.push_back(CreateScope<Buffer>(
|
||||||
device, allocator, size,
|
device, allocator, size, usage,
|
||||||
vk::BufferUsageFlagBits::eUniformBuffer,
|
|
||||||
vk::MemoryPropertyFlagBits::eHostVisible |
|
vk::MemoryPropertyFlagBits::eHostVisible |
|
||||||
vk::MemoryPropertyFlagBits::eHostCoherent));
|
vk::MemoryPropertyFlagBits::eHostCoherent));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
//
|
||||||
|
// Created by atdunbg on 2026/9/23.
|
||||||
|
//
|
||||||
|
// VMA 实现编译单元:整个引擎唯一一份 vk_mem_alloc 实现。
|
||||||
|
//
|
||||||
|
// 静态链接 Vulkan loader(项目已链接 Vulkan::Vulkan),与 vulkan-hpp 的
|
||||||
|
// dynamic loader 互不干扰(各自持有函数表)。
|
||||||
|
//
|
||||||
|
|
||||||
|
#define VMA_STATIC_VULKAN_FUNCTIONS 1
|
||||||
|
#define VMA_IMPLEMENTATION
|
||||||
|
#include <vk_mem_alloc.h>
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
//
|
|
||||||
// Created by atdunbg on 2026/9/20.
|
|
||||||
//
|
|
||||||
|
|
||||||
#define STB_IMAGE_IMPLEMENTATION
|
|
||||||
#include <stb/stb_image.h>
|
|
||||||
Submodule
+1
Submodule Lib/VulkanMemoryAllocator added at 3aa921224c
Submodule
+1
Submodule Lib/tracy added at 30997d5ca6
+8
-6
@@ -1,17 +1,19 @@
|
|||||||
#version 450
|
#version 450
|
||||||
|
|
||||||
// Shadow pass vertex:光源视角写深度。
|
// Shadow pass vertex:光源视角写深度。
|
||||||
// push constant = { lightVP, model },与 Forward pass 用同一份
|
// GPUDriven:模型矩阵经 gl_InstanceIndex 从实例缓冲取(与 Forward
|
||||||
// lightVP(UBO.lightSpace)和同一份 model(TransformComponent.world),
|
// 共用同一份数据),push 只带 lightVP(全 pass 一次)。
|
||||||
// 保证阴影贴图与主视角几何严格一致。
|
|
||||||
|
|
||||||
layout(location = 0) in vec3 inPosition;
|
layout(location = 0) in vec3 inPosition;
|
||||||
|
|
||||||
layout(push_constant) uniform ShadowPush {
|
layout(std430, set = 0, binding = 1) readonly buffer ObjectBuffer {
|
||||||
|
mat4 models[];
|
||||||
|
} objects;
|
||||||
|
|
||||||
|
layout(push_constant) uniform PushConstants {
|
||||||
mat4 lightVP;
|
mat4 lightVP;
|
||||||
mat4 model;
|
|
||||||
} pc;
|
} pc;
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
gl_Position = pc.lightVP * pc.model * vec4(inPosition, 1.0);
|
gl_Position = pc.lightVP * objects.models[gl_InstanceIndex] * vec4(inPosition, 1.0);
|
||||||
}
|
}
|
||||||
|
|||||||
+70
-15
@@ -10,47 +10,102 @@ struct Light {
|
|||||||
layout(location = 0) in vec2 fragUV;
|
layout(location = 0) in vec2 fragUV;
|
||||||
layout(location = 1) in vec3 fragNormal;
|
layout(location = 1) in vec3 fragNormal;
|
||||||
layout(location = 2) in vec3 fragWorldPos;
|
layout(location = 2) in vec3 fragWorldPos;
|
||||||
layout(location = 3) in vec3 fragLightSpacePos;
|
layout(location = 3) in float fragViewDepth;
|
||||||
|
layout(location = 4) in vec2 fragVelocity;
|
||||||
|
layout(location = 5) in vec3 fragTangent;
|
||||||
|
|
||||||
layout(location = 0) out vec4 outColor;
|
layout(location = 0) out vec4 outColor;
|
||||||
|
layout(location = 1) out vec2 outVelocity; // TAA velocity 附件
|
||||||
|
|
||||||
layout(set = 0, binding = 0) uniform UniformBufferObject {
|
layout(set = 0, binding = 0) uniform UniformBufferObject {
|
||||||
mat4 view;
|
mat4 view;
|
||||||
mat4 proj;
|
mat4 proj;
|
||||||
mat4 lightSpace;
|
mat4 lightSpace[3]; // CSM 各级联光空间 VP
|
||||||
|
vec4 cascadeSplits; // xyz = 级联 0..2 远边界(视图深度)
|
||||||
vec4 ambientColor;
|
vec4 ambientColor;
|
||||||
Light lights[MAX_LIGHTS];
|
Light lights[MAX_LIGHTS];
|
||||||
|
mat4 projNoJitter;
|
||||||
|
mat4 prevView;
|
||||||
|
mat4 prevProj;
|
||||||
|
vec4 cameraPos;
|
||||||
} ubo;
|
} ubo;
|
||||||
|
|
||||||
layout(set = 1, binding = 0) uniform sampler2D texSampler;
|
// 材质纹理槽(binding = Material::Slot)
|
||||||
layout(set = 2, binding = 0) uniform sampler2D shadowMap;
|
layout(set = 1, binding = 0) uniform sampler2D albedoMap;
|
||||||
|
layout(set = 1, binding = 1) uniform sampler2D normalMap;
|
||||||
|
layout(set = 1, binding = 2) uniform sampler2D specularMap;
|
||||||
|
layout(set = 1, binding = 3) uniform sampler2D emissiveMap;
|
||||||
|
|
||||||
// 第 0 盏灯(KeyLight)的阴影因子:0.3 影中 / 1.0 受光。
|
layout(set = 2, binding = 0) uniform sampler2D shadowMap0; // CSM 级联 0(最近)
|
||||||
// 采样器为 clamp-to-border 白色:光视锥外自动 1.0(无阴影)。
|
layout(set = 2, binding = 1) uniform sampler2D shadowMap1; // 级联 1
|
||||||
float ShadowFactor(vec3 N) {
|
layout(set = 2, binding = 2) uniform sampler2D shadowMap2; // 级联 2(最远)
|
||||||
// NDC [-1,1] → UV [0,1](ortho 的 w=1,无需透视除法)
|
|
||||||
vec2 uv = fragLightSpacePos.xy * 0.5 + 0.5;
|
// 第 0 盏灯(KeyLight)的 CSM 阴影因子:0.3 影中 / 1.0 受光。
|
||||||
float closest = texture(shadowMap, uv).r;
|
// 级联按视图深度选择;采样器均 clamp-to-border 白色:
|
||||||
|
// 光视锥外自动 1.0(无阴影),省出界分支。
|
||||||
|
float ShadowFactor(vec3 N, float viewDepth) {
|
||||||
|
// 级联选择:第一个 viewDepth < split 的级联
|
||||||
|
int cascade = 2;
|
||||||
|
for (int i = 0; i < 3; ++i) {
|
||||||
|
if (viewDepth < ubo.cascadeSplits[i]) { cascade = i; break; }
|
||||||
|
}
|
||||||
|
|
||||||
|
vec4 ls = ubo.lightSpace[cascade] * vec4(fragWorldPos, 1.0);
|
||||||
|
vec3 proj = ls.xyz / ls.w;
|
||||||
|
// NDC [-1,1] → UV [0,1](ortho 的 w=1)
|
||||||
|
vec2 uv = proj.xy * 0.5 + 0.5;
|
||||||
|
|
||||||
|
float closest;
|
||||||
|
if (cascade == 0) closest = texture(shadowMap0, uv).r;
|
||||||
|
else if (cascade == 1) closest = texture(shadowMap1, uv).r;
|
||||||
|
else closest = texture(shadowMap2, uv).r;
|
||||||
|
|
||||||
// 斜率缩放 bias:掠射面用更大的 bias 压 acne
|
// 斜率缩放 bias:掠射面用更大的 bias 压 acne
|
||||||
vec3 L = normalize(-ubo.lights[0].direction.xyz);
|
vec3 L = normalize(-ubo.lights[0].direction.xyz);
|
||||||
float bias = max(0.004 * (1.0 - dot(N, L)), 0.0012);
|
float bias = max(0.004 * (1.0 - dot(N, L)), 0.0012);
|
||||||
|
|
||||||
return (fragLightSpacePos.z - bias > closest) ? 0.3 : 1.0;
|
return (proj.z - bias > closest) ? 0.3 : 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 法线贴图 → 世界空间法线(TBN 构建)。
|
||||||
|
// 无切线数据(零向量)或平法线贴图时退回几何法线。
|
||||||
|
vec3 MappedNormal(vec3 geomN, vec3 T, vec2 uv) {
|
||||||
|
if (dot(T, T) < 1e-4) return geomN;
|
||||||
|
|
||||||
|
vec3 N = geomN;
|
||||||
|
vec3 nT = normalize(T - N * dot(N, T)); // 正交化
|
||||||
|
vec3 B = cross(N, nT);
|
||||||
|
vec3 sampled = texture(normalMap, uv).xyz * 2.0 - 1.0;
|
||||||
|
if (dot(sampled, sampled) < 1e-4) return geomN;
|
||||||
|
|
||||||
|
return normalize(nT * sampled.x + B * sampled.y + N * sampled.z);
|
||||||
}
|
}
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
vec3 albedo = texture(texSampler, fragUV).rgb;
|
vec3 albedo = texture(albedoMap, fragUV).rgb;
|
||||||
vec3 N = normalize(fragNormal);
|
float specIntensity = texture(specularMap, fragUV).r;
|
||||||
|
vec3 emissive = texture(emissiveMap, fragUV).rgb;
|
||||||
|
|
||||||
|
vec3 N = MappedNormal(normalize(fragNormal), fragTangent, fragUV);
|
||||||
|
vec3 V = normalize(ubo.cameraPos.xyz - fragWorldPos);
|
||||||
|
|
||||||
vec3 result = ubo.ambientColor.rgb * albedo;
|
vec3 result = ubo.ambientColor.rgb * albedo;
|
||||||
|
|
||||||
for (int i = 0; i < MAX_LIGHTS; ++i) {
|
for (int i = 0; i < MAX_LIGHTS; ++i) {
|
||||||
vec3 L = normalize(-ubo.lights[i].direction.xyz);
|
vec3 L = normalize(-ubo.lights[i].direction.xyz);
|
||||||
float diff = max(dot(N, L), 0.0);
|
float diff = max(dot(N, L), 0.0);
|
||||||
float shade = (i == 0) ? ShadowFactor(N) : 1.0;
|
// Blinn-Phong 高光(半程向量;specular 图做强度系数)
|
||||||
result += ubo.lights[i].color.rgb * ubo.lights[i].color.a * diff * albedo * shade;
|
vec3 H = normalize(L + V);
|
||||||
|
float spec = pow(max(dot(N, H), 0.0), 32.0) * specIntensity;
|
||||||
|
|
||||||
|
float shade = (i == 0) ? ShadowFactor(N, fragViewDepth) : 1.0;
|
||||||
|
result += ubo.lights[i].color.rgb * ubo.lights[i].color.a *
|
||||||
|
(albedo * diff + vec3(spec)) * shade;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 自发光不经光照/阴影直接叠加
|
||||||
|
result += emissive;
|
||||||
|
|
||||||
outColor = vec4(result, 1.0);
|
outColor = vec4(result, 1.0);
|
||||||
|
outVelocity = fragVelocity;
|
||||||
}
|
}
|
||||||
|
|||||||
+40
-11
@@ -10,34 +10,63 @@ struct Light {
|
|||||||
layout(location = 0) in vec3 inPosition;
|
layout(location = 0) in vec3 inPosition;
|
||||||
layout(location = 1) in vec3 inNormal;
|
layout(location = 1) in vec3 inNormal;
|
||||||
layout(location = 2) in vec2 inUV;
|
layout(location = 2) in vec2 inUV;
|
||||||
|
layout(location = 3) in vec3 inTangent;
|
||||||
|
|
||||||
layout(location = 0) out vec2 fragUV;
|
layout(location = 0) out vec2 fragUV;
|
||||||
layout(location = 1) out vec3 fragNormal;
|
layout(location = 1) out vec3 fragNormal;
|
||||||
layout(location = 2) out vec3 fragWorldPos;
|
layout(location = 2) out vec3 fragWorldPos;
|
||||||
layout(location = 3) out vec3 fragLightSpacePos;
|
layout(location = 3) out float fragViewDepth; // CSM:视图深度(级联选择)
|
||||||
|
layout(location = 4) out vec2 fragVelocity; // TAA:clip 空间运动向量
|
||||||
|
layout(location = 5) out vec3 fragTangent; // 世界空间切线(法线贴图)
|
||||||
|
|
||||||
layout(push_constant) uniform PushConstants {
|
// GPUDriven:模型矩阵从实例缓冲按实例索引取。
|
||||||
mat4 model;
|
// 同一 mesh 的 N 个实体 = 1 次 drawIndexed(instanceCount=N),
|
||||||
} pc;
|
// draw 侧用 firstInstance 指向该 batch 在实例数组中的起始下标。
|
||||||
|
layout(std430, set = 0, binding = 1) readonly buffer ObjectBuffer {
|
||||||
|
mat4 models[]; // 连续 mat4 数组(16B 对齐,std430/std140 布局一致)
|
||||||
|
} objects;
|
||||||
|
|
||||||
|
// 上一帧实例矩阵(与 ObjectBuffer 同下标 = 同一实例的上一帧姿态)
|
||||||
|
layout(std430, set = 0, binding = 2) readonly buffer PrevObjectBuffer {
|
||||||
|
mat4 prevModels[];
|
||||||
|
} prevObjects;
|
||||||
|
|
||||||
layout(set = 0, binding = 0) uniform UniformBufferObject {
|
layout(set = 0, binding = 0) uniform UniformBufferObject {
|
||||||
mat4 view;
|
mat4 view;
|
||||||
mat4 proj;
|
mat4 proj;
|
||||||
mat4 lightSpace;
|
mat4 lightSpace[3]; // CSM 各级联光空间 VP
|
||||||
|
vec4 cascadeSplits; // xyz = 级联 0..2 远边界(视图深度)
|
||||||
vec4 ambientColor;
|
vec4 ambientColor;
|
||||||
Light lights[MAX_LIGHTS];
|
Light lights[MAX_LIGHTS];
|
||||||
|
mat4 projNoJitter;
|
||||||
|
mat4 prevView;
|
||||||
|
mat4 prevProj;
|
||||||
|
vec4 cameraPos;
|
||||||
} ubo;
|
} ubo;
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
vec4 worldPos = pc.model * vec4(inPosition, 1.0);
|
mat4 model = objects.models[gl_InstanceIndex];
|
||||||
|
|
||||||
|
vec4 worldPos = model * vec4(inPosition, 1.0);
|
||||||
gl_Position = ubo.proj * ubo.view * worldPos;
|
gl_Position = ubo.proj * ubo.view * worldPos;
|
||||||
|
|
||||||
fragWorldPos = worldPos.xyz;
|
// ---- TAA velocity:当前帧与上一帧的 clip 空间位置差(UV 空间)----
|
||||||
// 光空间坐标(含 Vulkan [0,1] 深度重映射,与 shadow pass 写入端共用同一矩阵)
|
// 抖动只进光栅化 gl_Position,不进 velocity(否则抖动本身被当成运动)
|
||||||
fragLightSpacePos = (ubo.lightSpace * worldPos).xyz;
|
vec4 currClip = ubo.projNoJitter * ubo.view * worldPos;
|
||||||
|
vec4 prevWorldPos = prevObjects.prevModels[gl_InstanceIndex] * vec4(inPosition, 1.0);
|
||||||
|
vec4 prevClip = ubo.prevProj * ubo.prevView * prevWorldPos;
|
||||||
|
fragVelocity = (currClip.xy / currClip.w - prevClip.xy / prevClip.w) * 0.5;
|
||||||
|
|
||||||
mat3 normalMatrix = mat3(transpose(inverse(pc.model)));
|
fragWorldPos = worldPos.xyz;
|
||||||
|
// CSM:视图深度(正值,与 cascadeSplits 同尺度)
|
||||||
|
fragViewDepth = -(ubo.view * worldPos).z;
|
||||||
|
|
||||||
|
mat3 normalMatrix = mat3(transpose(inverse(model)));
|
||||||
fragNormal = normalize(normalMatrix * inNormal);
|
fragNormal = normalize(normalMatrix * inNormal);
|
||||||
|
// 切线随模型变换(法线贴图 TBN;无切线数据保持零,frag 侧退回几何法线)
|
||||||
|
fragTangent = normalize(mat3(model) * inTangent);
|
||||||
|
// 零向量 normalize 产生 NaN,这里直接透传零,frag 判长度
|
||||||
|
fragTangent = dot(inTangent, inTangent) > 0.0 ? fragTangent : vec3(0.0);
|
||||||
|
|
||||||
fragUV = inUV;
|
fragUV = inUV;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
#version 450
|
||||||
|
|
||||||
|
// TAA resolve:时域抗锯齿。
|
||||||
|
// 1. velocity 反推历史像素位置,取历史累积色
|
||||||
|
// 2. 邻域 clamp(3×3 AABB)压制鬼影:历史色超出当前帧邻域范围的部分丢弃
|
||||||
|
// 3. mix(cur, hist, alpha) 时域累积
|
||||||
|
// 输出写入 TAAColor,它同时也是下一帧的历史(ReadHistory 自引用)。
|
||||||
|
|
||||||
|
layout(location = 0) in vec2 fragUV;
|
||||||
|
layout(location = 0) out vec4 outColor;
|
||||||
|
|
||||||
|
layout(set = 0, binding = 0) uniform sampler2D curImage; // 当前帧 HDR
|
||||||
|
layout(set = 0, binding = 1) uniform sampler2D velocityImage; // 运动向量(UV 空间)
|
||||||
|
layout(set = 0, binding = 2) uniform sampler2D historyImage; // 上一帧累积结果
|
||||||
|
|
||||||
|
// 全屏 pass 通用 push(16B,程序级统一声明,VUID-08878)
|
||||||
|
layout(push_constant) uniform PushConstants {
|
||||||
|
float exposure; // 与其它全屏 pass 布局一致(本 pass 不用)
|
||||||
|
float alpha; // 历史权重(0.9 = 90% 历史)
|
||||||
|
float param1;
|
||||||
|
float param2;
|
||||||
|
} pc;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec4 cur = texture(curImage, fragUV);
|
||||||
|
vec2 vel = texture(velocityImage, fragUV).xy;
|
||||||
|
vec2 histUV = fragUV - vel;
|
||||||
|
|
||||||
|
vec3 result = cur.rgb;
|
||||||
|
|
||||||
|
// 历史采样:越出画面(相机快速平移)直接用当前帧,不累积
|
||||||
|
if (all(greaterThanEqual(histUV, vec2(0.0))) &&
|
||||||
|
all(lessThanEqual(histUV, vec2(1.0)))) {
|
||||||
|
vec3 hist = texture(historyImage, histUV).rgb;
|
||||||
|
|
||||||
|
// 邻域 clamp:当前帧 3×3 邻域的 min/max 包围盒
|
||||||
|
vec2 ts = 1.0 / vec2(textureSize(curImage, 0));
|
||||||
|
vec3 minC = cur.rgb;
|
||||||
|
vec3 maxC = cur.rgb;
|
||||||
|
for (int y = -1; y <= 1; ++y) {
|
||||||
|
for (int x = -1; x <= 1; ++x) {
|
||||||
|
vec3 c = texture(curImage, fragUV + vec2(x, y) * ts).rgb;
|
||||||
|
minC = min(minC, c);
|
||||||
|
maxC = max(maxC, c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vec3 clamped = clamp(hist, minC, maxC);
|
||||||
|
result = mix(cur.rgb, clamped, pc.alpha);
|
||||||
|
}
|
||||||
|
|
||||||
|
outColor = vec4(result, cur.a);
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
#version 450
|
||||||
|
|
||||||
|
// TemporalDiff:帧历史链路调试视图。
|
||||||
|
// 输出 |上一帧 HDR − 当前帧 HDR| × 增益:
|
||||||
|
// - 静止画面应接近纯黑(两帧内容一致)
|
||||||
|
// - 运动的实体边缘发光(时域差异可见)
|
||||||
|
// - 全屏噪声 / 错位残影 = 帧历史 barrier 或槽位镜像绑定错误
|
||||||
|
// 这是 RenderGraph 帧历史资源(ReadHistory)的验证消费端。
|
||||||
|
|
||||||
|
layout(location = 0) in vec2 fragUV;
|
||||||
|
layout(location = 0) out vec4 outColor;
|
||||||
|
|
||||||
|
layout(set = 0, binding = 0) uniform sampler2D prevImage; // 帧历史(上一帧槽位镜像)
|
||||||
|
layout(set = 0, binding = 1) uniform sampler2D currImage; // 当前帧
|
||||||
|
|
||||||
|
// 全屏 pass 通用 push(16B,程序级统一声明,VUID-08878)
|
||||||
|
layout(push_constant) uniform PushConstants {
|
||||||
|
float exposure; // 与其它全屏 pass 布局一致(本 pass 不用)
|
||||||
|
float gain; // 差异增益
|
||||||
|
float param1;
|
||||||
|
float param2;
|
||||||
|
} pc;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec3 prev = texture(prevImage, fragUV).rgb;
|
||||||
|
vec3 curr = texture(currImage, fragUV).rgb;
|
||||||
|
outColor = vec4(abs(prev - curr) * pc.gain, 1.0);
|
||||||
|
}
|
||||||
+13
-4
@@ -10,13 +10,14 @@ layout(location = 0) out vec4 outColor;
|
|||||||
|
|
||||||
layout(set = 0, binding = 0) uniform sampler2D hdrImage;
|
layout(set = 0, binding = 0) uniform sampler2D hdrImage;
|
||||||
layout(set = 0, binding = 1) uniform sampler2D bloomImage;
|
layout(set = 0, binding = 1) uniform sampler2D bloomImage;
|
||||||
|
layout(set = 0, binding = 2) uniform sampler2D temporalDiffImage; // 帧历史差异(调试视图)
|
||||||
|
|
||||||
// 全屏 pass 通用 push(16B,程序级统一声明,VUID-08878)
|
// 全屏 pass 通用 push(16B,程序级统一声明,VUID-08878)
|
||||||
layout(push_constant) uniform PushConstants {
|
layout(push_constant) uniform PushConstants {
|
||||||
float exposure; // 曝光系数
|
float exposure; // 曝光系数
|
||||||
float aces; // >0.5 启用 ACES 曲线,否则纯 gamma
|
float aces; // >0.5 启用 ACES 曲线,否则纯 gamma
|
||||||
float bloomStrength; // bloom 叠加强度(0 = 关闭)
|
float bloomStrength; // bloom 叠加强度(0 = 关闭)
|
||||||
float _pad0;
|
float debugView; // >0.5 显示帧历史差异视图(TemporalDiff)
|
||||||
} pc;
|
} pc;
|
||||||
|
|
||||||
vec3 acesFilm(vec3 x) {
|
vec3 acesFilm(vec3 x) {
|
||||||
@@ -24,9 +25,17 @@ vec3 acesFilm(vec3 x) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
|
// 调试视图:帧历史差异(静止=黑,运动边缘发光)
|
||||||
|
if (pc.debugView > 0.5) {
|
||||||
|
vec3 d = clamp(texture(temporalDiffImage, fragUV).rgb, 0.0, 1.0);
|
||||||
|
outColor = vec4(pow(d, vec3(1.0 / 2.2)), 1.0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// bloom 在 HDR 域叠加(半分辨率模糊图线性采样上采样)
|
// bloom 在 HDR 域叠加(半分辨率模糊图线性采样上采样)
|
||||||
vec3 hdr = texture(hdrImage, fragUV).rgb
|
vec4 hdrTex = texture(hdrImage, fragUV);
|
||||||
+ texture(bloomImage, fragUV).rgb * pc.bloomStrength;
|
vec3 hdr = hdrTex.rgb + texture(bloomImage, fragUV).rgb * pc.bloomStrength;
|
||||||
|
float alpha = hdrTex.a;
|
||||||
|
|
||||||
hdr *= pc.exposure;
|
hdr *= pc.exposure;
|
||||||
|
|
||||||
@@ -34,5 +43,5 @@ void main() {
|
|||||||
? acesFilm(hdr)
|
? acesFilm(hdr)
|
||||||
: clamp(hdr, 0.0, 1.0);
|
: clamp(hdr, 0.0, 1.0);
|
||||||
|
|
||||||
outColor = vec4(pow(mapped, vec3(1.0 / 2.2)), 1.0);
|
outColor = vec4(pow(mapped, vec3(1.0 / 2.2)) * alpha, alpha);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user