feat: 引入 RenderGraph、Application 框架、ECS 与资产句柄系统
架构分层(自上而下):
App(SandboxApp) → Application(生命周期+事件) → Scene(ECS) / AssetManager(句柄) → Renderer → RenderGraph → Vulkan 模块
- RenderGraph:声明式帧图。虚拟资源+生成号句柄、死 pass 剔除、
拓扑排序、资源生命周期、barrier 编译期预算(Synchronization2)、
物理镜像池按帧槽位隔离(帧间竞争从根上消除)
- 帧同步修复:per-frame-slot 命令缓冲与 UBO ring(原先单份存在
数据竞争);统一 kMaxFramesInFlight / FrameContext
- Application:生命周期钩子(OnInit/OnUpdate/OnRender/OnShutdown)
+ 事件系统(GLFW 回调 → POD 事件 → 类型化钩子);main 只 new 派生类
- ECS:自研稀疏集 Registry(Entity=(index,generation) 防悬空,
O(1) 增删查,Each 取最小池遍历);Scene 延迟销毁队列;
IdComponent{UUID} 稳定外部身份
- Assets:AssetManager + AssetHandle。组件存句柄不存裸指针/Ref
(单一所有者 + 多使用者),Renderer 删除 LoadModel 改为纯消费
- 修复退出析构:成员析构前 GPU waitIdle(资产层先于 Renderer
析构导致 VUID-00922 资源销毁时仍在使用)
- 引入 entt 子模块备用
validation 全程零错误零警告(RTX 5060 实测)
This commit is contained in:
@@ -10,3 +10,6 @@
|
|||||||
[submodule "Lib/assimp"]
|
[submodule "Lib/assimp"]
|
||||||
path = Lib/assimp
|
path = Lib/assimp
|
||||||
url = https://github.com/assimp/assimp.git
|
url = https://github.com/assimp/assimp.git
|
||||||
|
[submodule "Lib/entt"]
|
||||||
|
path = Lib/entt
|
||||||
|
url = https://github.com/skypjack/entt.git
|
||||||
|
|||||||
+20
-7
@@ -3,19 +3,25 @@ add_executable(HelloVulkan main.cpp)
|
|||||||
target_link_libraries(HelloVulkan PRIVATE Engine)
|
target_link_libraries(HelloVulkan PRIVATE Engine)
|
||||||
|
|
||||||
|
|
||||||
###### 自动构建shader ######
|
###### 自动构建 shader ######
|
||||||
find_program(GLSLC glslc REQUIRED)
|
find_program(GLSLC glslc REQUIRED)
|
||||||
|
|
||||||
set(SHADER_SRC_DIR ${CMAKE_SOURCE_DIR}/shaders)
|
set(SHADER_SRC_DIR ${CMAKE_SOURCE_DIR}/shaders)
|
||||||
set(SHADER_OUT_DIR ${CMAKE_BINARY_DIR}/bin/shaders)
|
set(SHADER_OUT_DIR ${CMAKE_BINARY_DIR}/bin/shaders)
|
||||||
|
|
||||||
# 定义一个 shader 编译目标:<name>(glsl_file stage)
|
function(auto_compile_shader glsl_file) # glsl_file 是相对 SHADER_SRC_DIR 的路径
|
||||||
function(auto_compile_shader glsl_file stage)
|
# 推断 stage:'.vert' -> 'vert'
|
||||||
# string(REGEX REPLACE "\\.glsl$" "" base "${glsl_file}")
|
get_filename_component(stage ${glsl_file} EXT)
|
||||||
|
string(SUBSTRING ${stage} 1 -1 stage)
|
||||||
|
|
||||||
set(spv ${SHADER_OUT_DIR}/${glsl_file}.spv)
|
set(spv ${SHADER_OUT_DIR}/${glsl_file}.spv)
|
||||||
|
get_filename_component(spv_dir ${spv} DIRECTORY) # 保留子目录
|
||||||
|
|
||||||
add_custom_command(
|
add_custom_command(
|
||||||
OUTPUT ${spv}
|
OUTPUT ${spv}
|
||||||
COMMAND ${GLSLC} -fshader-stage=${stage} -o ${spv} ${SHADER_SRC_DIR}/${glsl_file}
|
COMMAND ${CMAKE_COMMAND} -E make_directory ${spv_dir}
|
||||||
|
COMMAND ${GLSLC} -fshader-stage=${stage}
|
||||||
|
-o ${spv} ${SHADER_SRC_DIR}/${glsl_file}
|
||||||
DEPENDS ${SHADER_SRC_DIR}/${glsl_file}
|
DEPENDS ${SHADER_SRC_DIR}/${glsl_file}
|
||||||
VERBATIM)
|
VERBATIM)
|
||||||
set_source_files_properties(${spv} PROPERTIES GENERATED TRUE)
|
set_source_files_properties(${spv} PROPERTIES GENERATED TRUE)
|
||||||
@@ -23,5 +29,12 @@ function(auto_compile_shader glsl_file stage)
|
|||||||
set_property(DIRECTORY APPEND PROPERTY HZ_SHADER_SPV_FILES ${spv})
|
set_property(DIRECTORY APPEND PROPERTY HZ_SHADER_SPV_FILES ${spv})
|
||||||
endfunction()
|
endfunction()
|
||||||
|
|
||||||
auto_compile_shader(simple_shader.vert vert)
|
file(GLOB_RECURSE SHADER_SOURCES
|
||||||
auto_compile_shader(simple_shader.frag frag)
|
CONFIGURE_DEPENDS # 新增 shader 时自动重配
|
||||||
|
RELATIVE ${SHADER_SRC_DIR}
|
||||||
|
${SHADER_SRC_DIR}/*.vert
|
||||||
|
${SHADER_SRC_DIR}/*.frag)
|
||||||
|
|
||||||
|
foreach (f ${SHADER_SOURCES})
|
||||||
|
auto_compile_shader(${f})
|
||||||
|
endforeach()
|
||||||
+118
-10
@@ -1,23 +1,131 @@
|
|||||||
//
|
//
|
||||||
// Created by atdunbg on 2026/9/20.
|
// Created by atdunbg on 2026/9/20.
|
||||||
//
|
//
|
||||||
|
// SandboxApp:第一个基于 Application 框架的程序。
|
||||||
|
//
|
||||||
|
// main 的全部职责:定义一个 Application 派生类并运行它。
|
||||||
|
// 窗口、渲染器、事件泵、帧计时、场景容器全部由框架管理;
|
||||||
|
// 这里只写"程序逻辑":创建实体、逐帧更新组件、事件响应。
|
||||||
|
//
|
||||||
|
|
||||||
#include <Engine/Core/Window.h>
|
#include <Engine/Core/Application.h>
|
||||||
#include <Engine/Renderer/Renderer.h>
|
|
||||||
#include <Engine/Core/Log.h>
|
#include <Engine/Core/Log.h>
|
||||||
|
#include <Engine/Core/Stats.h>
|
||||||
|
#include <Engine/ECS/Scene.h>
|
||||||
|
#include <Engine/ECS/Components.h>
|
||||||
|
#include <Engine/Assets/AssetManager.h>
|
||||||
|
|
||||||
#include "Engine/Core/Stats.h"
|
#include <glm/glm.hpp>
|
||||||
|
#include <glm/gtc/matrix_transform.hpp>
|
||||||
|
|
||||||
|
#include <GLFW/glfw3.h>
|
||||||
|
|
||||||
|
using namespace Engine;
|
||||||
|
using namespace Engine::ECS;
|
||||||
|
|
||||||
|
class SandboxApp final : public Application
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
SandboxApp()
|
||||||
|
: Application("HelloVulkan", 1280, 720)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
// ---- 生命周期 ----
|
||||||
|
|
||||||
|
void OnInit() override
|
||||||
|
{
|
||||||
|
Scene& scene = GetScene();
|
||||||
|
|
||||||
|
// 模型资产(AssetManager 负责加载/缓存/生命周期,返回句柄)
|
||||||
|
auto backpack = GetAssets().LoadModel("assets/models/backpack/backpack.obj");
|
||||||
|
|
||||||
|
// 相机实体(primary = 当前渲染视角)
|
||||||
|
m_CameraEntity = scene.CreateEntity("MainCamera");
|
||||||
|
auto& camera = scene.AddComponent<CameraComponent>(m_CameraEntity);
|
||||||
|
camera.primary = true;
|
||||||
|
camera.camera.SetPosition(glm::vec3(0.0f, 0.0f, 7.0f));
|
||||||
|
|
||||||
|
// 背包实体 × 3
|
||||||
|
for (int i = 0; i < 3; ++i) {
|
||||||
|
Entity e = scene.CreateEntity("Backpack" + std::to_string(i));
|
||||||
|
|
||||||
|
auto& transform = scene.AddComponent<TransformComponent>(e);
|
||||||
|
transform.world = glm::translate(
|
||||||
|
glm::mat4(1.0f),
|
||||||
|
glm::vec3((i - 1) * 1.5f, 0.0f, 0.0f));
|
||||||
|
|
||||||
|
scene.AddComponent<MeshRendererComponent>(e, MeshRendererComponent{backpack});
|
||||||
|
|
||||||
|
m_Spinners.push_back(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 灯光实体
|
||||||
|
Entity keyLight = scene.CreateEntity("KeyLight");
|
||||||
|
scene.AddComponent<LightComponent>(keyLight,
|
||||||
|
LightComponent{glm::vec3(-0.5f, -1.0f, -0.3f),
|
||||||
|
glm::vec4(1.0f, 0.95f, 0.9f, 1.0f)});
|
||||||
|
|
||||||
|
Entity fillLight = scene.CreateEntity("FillLight");
|
||||||
|
scene.AddComponent<LightComponent>(fillLight,
|
||||||
|
LightComponent{glm::vec3(0.5f, -0.5f, 0.3f),
|
||||||
|
glm::vec4(0.3f, 0.5f, 1.0f, 0.8f)});
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnUpdate(f32 deltaTime) override
|
||||||
|
{
|
||||||
|
Scene& scene = GetScene();
|
||||||
|
m_Time += deltaTime;
|
||||||
|
|
||||||
|
// 相机(WASD/Space/Shift 移动)
|
||||||
|
scene.GetComponent<CameraComponent>(m_CameraEntity)
|
||||||
|
.camera.Update(GetWindow(), deltaTime);
|
||||||
|
|
||||||
|
// 旋转动画:直接写实体组件
|
||||||
|
for (size_t i = 0; i < m_Spinners.size(); ++i) {
|
||||||
|
f32 angle = m_Time * glm::radians(60.0f) + static_cast<f32>(i) * 0.7f;
|
||||||
|
|
||||||
|
auto& transform = scene.GetComponent<TransformComponent>(m_Spinners[i]);
|
||||||
|
glm::mat4 t = glm::translate(
|
||||||
|
glm::mat4(1.0f),
|
||||||
|
glm::vec3((static_cast<f32>(i) - 1.0f) * 1.5f, 0.0f, 0.0f));
|
||||||
|
glm::mat4 r = glm::rotate(glm::mat4(1.0f), angle, glm::vec3(0.0f, 1.0f, 0.0f));
|
||||||
|
glm::mat4 s = glm::scale(glm::mat4(1.0f), glm::vec3(0.5));
|
||||||
|
|
||||||
|
transform.world = t * r * s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- 事件 ----
|
||||||
|
|
||||||
|
void OnKeyPress(const Event& event) override
|
||||||
|
{
|
||||||
|
if (event.key == GLFW_KEY_F1) {
|
||||||
|
STATS.PrintComparison();
|
||||||
|
}
|
||||||
|
if (event.key == GLFW_KEY_ESCAPE) {
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void OnWindowResize(const Event& event) override
|
||||||
|
{
|
||||||
|
ENGINE_INFO("App: window resized to {}x{}", event.width, event.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Entity m_CameraEntity{};
|
||||||
|
std::vector<Entity> m_Spinners;
|
||||||
|
f32 m_Time = 0.0f;
|
||||||
|
};
|
||||||
|
|
||||||
int main()
|
int main()
|
||||||
{
|
{
|
||||||
try {
|
SandboxApp app;
|
||||||
Engine::Window window("HelloVulkan", 1280, 720);
|
|
||||||
Engine::Renderer renderer(window);
|
|
||||||
|
|
||||||
while (!window.ShouldClose()) {
|
try {
|
||||||
window.PollEvents();
|
app.Run();
|
||||||
renderer.Render();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (const std::exception& e) {
|
catch (const std::exception& e) {
|
||||||
ENGINE_FATAL("Exception: {}", e.what());
|
ENGINE_FATAL("Exception: {}", e.what());
|
||||||
|
|||||||
@@ -0,0 +1,37 @@
|
|||||||
|
//
|
||||||
|
// 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
|
||||||
@@ -0,0 +1,173 @@
|
|||||||
|
//
|
||||||
|
// Created by atdunbg on 2026/9/22.
|
||||||
|
//
|
||||||
|
// AssetManager:引擎资产层。
|
||||||
|
//
|
||||||
|
// 职责划分(对照 Renderer):
|
||||||
|
// - 资产层:路径 → 解析/加载 → 缓存去重 → 句柄分发 + 生命周期管理
|
||||||
|
// - 渲染层:拿已解析资产创建/绑定 GPU 资源
|
||||||
|
//
|
||||||
|
// Model 的构造仍需要 GPU 上下文(buffer/image upload),这是现阶段的
|
||||||
|
// 折中:资产层"使用"GPU 层的接口,但 Renderer 不再知道磁盘和缓存。
|
||||||
|
// 后续演进:把 Model 拆成 CPU 解析数据 + GPU 上传两段,实现真正的
|
||||||
|
// 异步加载(IO 线程解析 → 主线程/上传队列提交),本文件结构已为此留好位置。
|
||||||
|
//
|
||||||
|
// 组件存句柄不存指针的理由:
|
||||||
|
// shared_ptr 会引入共享所有权 → 原子引用计数(swap-remove 拷贝时)
|
||||||
|
// + 不可预测的销毁时机(GPU 可能还在用)+ 热重载被引用计数卡死。
|
||||||
|
// 句柄 + 单一所有者(本类)是引擎级标准形态。
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef HELLOVULKAN_ASSETMANAGER_H
|
||||||
|
#define HELLOVULKAN_ASSETMANAGER_H
|
||||||
|
|
||||||
|
#include <Engine/Core/Types.h>
|
||||||
|
#include <Engine/Core/Log.h>
|
||||||
|
#include <Engine/Assets/AssetHandle.h>
|
||||||
|
#include <Engine/Renderer/Model.h>
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace Engine {
|
||||||
|
|
||||||
|
class Device;
|
||||||
|
class MemoryAllocator;
|
||||||
|
class DescriptorManager;
|
||||||
|
|
||||||
|
namespace Assets {
|
||||||
|
|
||||||
|
// ---------- 通用资产缓存(不感知 Vulkan / 磁盘细节由 loader 决定) ----------
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
class AssetCache {
|
||||||
|
public:
|
||||||
|
// loader:path → 资产实例(失败返回 nullptr)
|
||||||
|
using LoaderFn = std::function<Scope<T>(const std::string&)>;
|
||||||
|
|
||||||
|
// 加载(带路径去重)。失败返回无效句柄。
|
||||||
|
AssetHandle<T> Load(const std::string& path, const LoaderFn& loader)
|
||||||
|
{
|
||||||
|
if (auto it = m_ByPath.find(path); it != m_ByPath.end()) {
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
Scope<T> asset = loader(path);
|
||||||
|
if (!asset) {
|
||||||
|
ENGINE_ERROR("AssetCache: failed to load '{}'", path);
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
AssetHandle<T> h;
|
||||||
|
if (!m_Free.empty()) {
|
||||||
|
h.index = m_Free.back();
|
||||||
|
m_Free.pop_back();
|
||||||
|
h.generation = m_Entries[h.index].generation;
|
||||||
|
m_Entries[h.index] = Entry{path, h.generation, std::move(asset), true};
|
||||||
|
} else {
|
||||||
|
h.index = static_cast<u32>(m_Entries.size());
|
||||||
|
h.generation = 1;
|
||||||
|
m_Entries.push_back(Entry{path, h.generation, std::move(asset), true});
|
||||||
|
}
|
||||||
|
|
||||||
|
m_ByPath.emplace(path, h);
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 句柄 → 资产指针。悬空句柄 FATAL(bug 信号);已卸载返回 nullptr。
|
||||||
|
T* Get(AssetHandle<T> h) const
|
||||||
|
{
|
||||||
|
if (!h.Valid() || h.index >= m_Entries.size()) {
|
||||||
|
ENGINE_FATAL("AssetCache::Get: invalid handle (index={}, gen={})",
|
||||||
|
h.index, h.generation);
|
||||||
|
}
|
||||||
|
const Entry& e = m_Entries[h.index];
|
||||||
|
if (e.generation != h.generation) {
|
||||||
|
ENGINE_FATAL("AssetCache::Get: stale handle for '{}' (gen {} != {})",
|
||||||
|
e.path, h.generation, e.generation);
|
||||||
|
}
|
||||||
|
return e.alive ? e.asset.get() : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 卸载:句柄即刻失效(generation 递增),路径缓存清除。
|
||||||
|
// 注意:GPU 侧何时安全释放由上层(Renderer fence)保证——
|
||||||
|
// 本类只管所有权,不管 GPU 生命周期。
|
||||||
|
void Unload(AssetHandle<T> h)
|
||||||
|
{
|
||||||
|
if (!h.Valid() || h.index >= m_Entries.size()) return;
|
||||||
|
|
||||||
|
Entry& e = m_Entries[h.index];
|
||||||
|
if (e.generation != h.generation || !e.alive) return;
|
||||||
|
|
||||||
|
m_ByPath.erase(e.path);
|
||||||
|
e.path.clear();
|
||||||
|
e.asset.reset();
|
||||||
|
e.alive = false;
|
||||||
|
++e.generation;
|
||||||
|
m_Free.push_back(h.index);
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string& Path(AssetHandle<T> h) const
|
||||||
|
{
|
||||||
|
static const std::string kEmpty;
|
||||||
|
if (!h.Valid() || h.index >= m_Entries.size()) return kEmpty;
|
||||||
|
const Entry& e = m_Entries[h.index];
|
||||||
|
return (e.generation == h.generation && e.alive) ? e.path : kEmpty;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 Count() const
|
||||||
|
{
|
||||||
|
return static_cast<u32>(m_Entries.size()) - static_cast<u32>(m_Free.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct Entry {
|
||||||
|
std::string path;
|
||||||
|
u32 generation = 1;
|
||||||
|
Scope<T> asset;
|
||||||
|
bool alive = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<Entry> m_Entries;
|
||||||
|
std::vector<u32> m_Free;
|
||||||
|
std::unordered_map<std::string, AssetHandle<T>> m_ByPath;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---------- AssetManager:全引擎资产入口 ----------
|
||||||
|
// (ModelHandle 别名来自 AssetHandle.h)
|
||||||
|
|
||||||
|
class AssetManager {
|
||||||
|
public:
|
||||||
|
// GPU 上下文由组合根(Application)在 Renderer 装配完成后注入
|
||||||
|
AssetManager(Device& device, MemoryAllocator& allocator,
|
||||||
|
DescriptorManager& descriptors,
|
||||||
|
vk::DescriptorSetLayout textureLayout);
|
||||||
|
~AssetManager() = default;
|
||||||
|
|
||||||
|
AssetManager(const AssetManager&) = delete;
|
||||||
|
AssetManager& operator=(const AssetManager&) = delete;
|
||||||
|
|
||||||
|
ModelHandle LoadModel(const std::string& path);
|
||||||
|
Model* GetModel(ModelHandle h) const { return m_Models.Get(h); }
|
||||||
|
void UnloadModel(ModelHandle h) { m_Models.Unload(h); }
|
||||||
|
|
||||||
|
AssetCache<Model>& Models() { return m_Models; }
|
||||||
|
|
||||||
|
// 后续扩展点:TextureHandle / MaterialHandle / ShaderHandle
|
||||||
|
// 各自一个 AssetCache<T> + 便捷方法,模式与 Model 完全一致。
|
||||||
|
|
||||||
|
private:
|
||||||
|
Device& m_Device;
|
||||||
|
MemoryAllocator& m_Allocator;
|
||||||
|
DescriptorManager& m_Descriptors;
|
||||||
|
vk::DescriptorSetLayout m_TextureLayout;
|
||||||
|
|
||||||
|
AssetCache<Model> m_Models;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Assets
|
||||||
|
} // namespace Engine
|
||||||
|
|
||||||
|
#endif //HELLOVULKAN_ASSETMANAGER_H
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
//
|
||||||
|
// Created by atdunbg on 2026/9/22.
|
||||||
|
//
|
||||||
|
// Application:程序生命周期基类。
|
||||||
|
//
|
||||||
|
// 分层目标:main 不再知道 Window/Renderer 的存在——
|
||||||
|
// main 只 new 一个派生类并 Run();
|
||||||
|
// 派生类通过生命周期钩子(OnInit/OnUpdate/OnRender/OnShutdown)
|
||||||
|
// 和事件钩子(OnEvent + 类型化事件)定义程序行为;
|
||||||
|
// runtime、editor 等不同程序形态只是不同的派生类。
|
||||||
|
//
|
||||||
|
// 主循环固定为:PollEvents → OnUpdate(dt) → OnRender → 判退出。
|
||||||
|
// dt 由框架统一计时(含首帧与卡顿钳制),派生层无需自己管理。
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef HELLOVULKAN_APPLICATION_H
|
||||||
|
#define HELLOVULKAN_APPLICATION_H
|
||||||
|
|
||||||
|
#include <Engine/Core/Types.h>
|
||||||
|
#include <Engine/Core/Event.h>
|
||||||
|
#include <chrono>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace Engine {
|
||||||
|
|
||||||
|
class Window;
|
||||||
|
class Renderer;
|
||||||
|
|
||||||
|
namespace ECS { class Scene; }
|
||||||
|
namespace Assets { class AssetManager; }
|
||||||
|
|
||||||
|
class Application {
|
||||||
|
public:
|
||||||
|
Application(std::string title, u32 width, u32 height);
|
||||||
|
virtual ~Application();
|
||||||
|
|
||||||
|
Application(const Application&) = delete;
|
||||||
|
Application& operator=(const Application&) = delete;
|
||||||
|
|
||||||
|
// 进入主循环(阻塞直到 Close() 或窗口关闭)
|
||||||
|
void Run();
|
||||||
|
|
||||||
|
// 请求退出(当帧结束后生效)
|
||||||
|
void Close() { m_Running = false; }
|
||||||
|
|
||||||
|
// ---- 供派生类访问的模块 ----
|
||||||
|
Window& GetWindow() const { return *m_Window; }
|
||||||
|
Renderer& GetRenderer() const { return *m_Renderer; }
|
||||||
|
ECS::Scene& GetScene() const { return *m_Scene; }
|
||||||
|
Assets::AssetManager& GetAssets() const { return *m_Assets; }
|
||||||
|
f32 GetDeltaTime() const { return m_DeltaTime; }
|
||||||
|
u64 GetFrameCount() const { return m_FrameCount; }
|
||||||
|
|
||||||
|
protected:
|
||||||
|
// ---- 生命周期钩子 ----
|
||||||
|
|
||||||
|
// 框架装配完成(Window/Renderer 可用),派生类做初始化(建场景等)
|
||||||
|
virtual void OnInit() {}
|
||||||
|
|
||||||
|
// 每帧逻辑更新(输入、动画、物理……)
|
||||||
|
virtual void OnUpdate(f32 deltaTime) {}
|
||||||
|
|
||||||
|
// 每帧渲染;默认实现驱动 Renderer 主循环,派生类可覆写
|
||||||
|
virtual void OnRender();
|
||||||
|
|
||||||
|
// 主循环结束后、析构前(保存、清理场景等)
|
||||||
|
virtual void OnShutdown() {}
|
||||||
|
|
||||||
|
// ---- 事件钩子 ----
|
||||||
|
// OnEvent 收到所有事件(原始入口,适合做全局日志/拦截);
|
||||||
|
// 类型化钩子按事件类型分别回调,派生类按需覆写。
|
||||||
|
virtual void OnEvent(const Event& event) {}
|
||||||
|
virtual void OnWindowClose(const Event& event);
|
||||||
|
virtual void OnWindowResize(const Event& event) {}
|
||||||
|
virtual void OnWindowFocus(const Event& event) {}
|
||||||
|
virtual void OnWindowLostFocus(const Event& event) {}
|
||||||
|
virtual void OnKeyPress(const Event& event) {}
|
||||||
|
virtual void OnKeyRelease(const Event& event) {}
|
||||||
|
virtual void OnMouseButtonPress(const Event& event) {}
|
||||||
|
virtual void OnMouseButtonRelease(const Event& event) {}
|
||||||
|
virtual void OnMouseMove(const Event& event) {}
|
||||||
|
virtual void OnMouseScroll(const Event& event) {}
|
||||||
|
|
||||||
|
private:
|
||||||
|
void DispatchEvent(const Event& event);
|
||||||
|
|
||||||
|
Scope<Window> m_Window;
|
||||||
|
Scope<ECS::Scene> m_Scene;
|
||||||
|
Scope<Renderer> m_Renderer;
|
||||||
|
Scope<Assets::AssetManager> m_Assets;
|
||||||
|
|
||||||
|
bool m_Running = false;
|
||||||
|
f32 m_DeltaTime = 0.0f;
|
||||||
|
u64 m_FrameCount = 0;
|
||||||
|
|
||||||
|
std::chrono::steady_clock::time_point m_LastFrameTime;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Engine
|
||||||
|
|
||||||
|
#endif //HELLOVULKAN_APPLICATION_H
|
||||||
@@ -0,0 +1,76 @@
|
|||||||
|
//
|
||||||
|
// Created by atdunbg on 2026/9/22.
|
||||||
|
//
|
||||||
|
// Event:轻量级事件。
|
||||||
|
//
|
||||||
|
// 设计取舍:不用继承体系(每事件一个类 + dynamic_cast 分发),
|
||||||
|
// 用单个 POD 结构 + 类型枚举。事件每帧可能产生成百上千条
|
||||||
|
// (MouseMove 尤其频繁),POD 按值传递/排队最便宜;
|
||||||
|
// 类型安全由字段约定保证,规模上来后再演进为 variant/队列池。
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef HELLOVULKAN_EVENT_H
|
||||||
|
#define HELLOVULKAN_EVENT_H
|
||||||
|
|
||||||
|
#include <Engine/Core/Types.h>
|
||||||
|
|
||||||
|
namespace Engine {
|
||||||
|
|
||||||
|
enum class EventType : u8 {
|
||||||
|
None = 0,
|
||||||
|
|
||||||
|
// 窗口
|
||||||
|
WindowClose,
|
||||||
|
WindowResize,
|
||||||
|
WindowFocus,
|
||||||
|
WindowLostFocus,
|
||||||
|
|
||||||
|
// 键盘
|
||||||
|
KeyPress,
|
||||||
|
KeyRelease,
|
||||||
|
|
||||||
|
// 鼠标
|
||||||
|
MouseButtonPress,
|
||||||
|
MouseButtonRelease,
|
||||||
|
MouseMove,
|
||||||
|
MouseScroll,
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Event {
|
||||||
|
EventType type = EventType::None;
|
||||||
|
|
||||||
|
// 键盘(GLFW key code)/ 鼠标按钮
|
||||||
|
i32 key = 0;
|
||||||
|
i32 button = 0;
|
||||||
|
|
||||||
|
// 鼠标位置 / 滚轮
|
||||||
|
f32 x = 0.0f;
|
||||||
|
f32 y = 0.0f;
|
||||||
|
f32 deltaX = 0.0f;
|
||||||
|
f32 deltaY = 0.0f;
|
||||||
|
|
||||||
|
// 窗口尺寸
|
||||||
|
i32 width = 0;
|
||||||
|
i32 height = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
inline const char* ToString(EventType type)
|
||||||
|
{
|
||||||
|
switch (type) {
|
||||||
|
case EventType::WindowClose: return "WindowClose";
|
||||||
|
case EventType::WindowResize: return "WindowResize";
|
||||||
|
case EventType::WindowFocus: return "WindowFocus";
|
||||||
|
case EventType::WindowLostFocus: return "WindowLostFocus";
|
||||||
|
case EventType::KeyPress: return "KeyPress";
|
||||||
|
case EventType::KeyRelease: return "KeyRelease";
|
||||||
|
case EventType::MouseButtonPress: return "MouseButtonPress";
|
||||||
|
case EventType::MouseButtonRelease: return "MouseButtonRelease";
|
||||||
|
case EventType::MouseMove: return "MouseMove";
|
||||||
|
case EventType::MouseScroll: return "MouseScroll";
|
||||||
|
default: return "None";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Engine
|
||||||
|
|
||||||
|
#endif //HELLOVULKAN_EVENT_H
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
//
|
||||||
|
// Created by atdunbg on 2026/9/22.
|
||||||
|
//
|
||||||
|
// FrameContext:一帧在 GPU 侧的全部上下文。
|
||||||
|
// 引擎中所有 per-frame 资源(命令缓冲、UBO ring、描述符池等)
|
||||||
|
// 都以 frameSlot 为索引,保证 kMaxFramesInFlight 帧并发时互不踩踏。
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef HELLOVULKAN_FRAMECONTEXT_H
|
||||||
|
#define HELLOVULKAN_FRAMECONTEXT_H
|
||||||
|
|
||||||
|
#include <Engine/Core/Types.h>
|
||||||
|
#include <vulkan/vulkan.hpp>
|
||||||
|
|
||||||
|
namespace Engine {
|
||||||
|
|
||||||
|
// 引擎统一的最大并发帧数。
|
||||||
|
// 所有 per-frame 资源必须以此为 ring 长度,不允许再出现"单独一个"的帧资源。
|
||||||
|
inline constexpr u32 kMaxFramesInFlight = 2;
|
||||||
|
|
||||||
|
struct FrameContext {
|
||||||
|
u32 frameSlot = 0; // 帧-in-flight 槽位 [0, kMaxFramesInFlight)
|
||||||
|
u32 imageIndex = 0; // 本次 acquire 到的 swapchain image 索引
|
||||||
|
vk::CommandBuffer cmd;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Engine
|
||||||
|
|
||||||
|
#endif //HELLOVULKAN_FRAMECONTEXT_H
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
//
|
||||||
|
// Created by atdunbg on 2026/9/22.
|
||||||
|
//
|
||||||
|
// UUID:64 位随机唯一标识,实体的稳定外部身份。
|
||||||
|
//
|
||||||
|
// 用途:序列化、跨场景引用、编辑器对象选择——这些场景需要
|
||||||
|
// "实体销毁重建后 ID 不变"或"独立于槽位分配"的稳定标识。
|
||||||
|
// ECS 内部遍历仍用 (index, generation) 句柄(性能),UUID 只做外部身份。
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef HELLOVULKAN_UUID_H
|
||||||
|
#define HELLOVULKAN_UUID_H
|
||||||
|
|
||||||
|
#include <Engine/Core/Types.h>
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <random>
|
||||||
|
|
||||||
|
namespace Engine {
|
||||||
|
|
||||||
|
struct UUID {
|
||||||
|
UUID() = default;
|
||||||
|
explicit UUID(u64 value) : m_Value(value) {}
|
||||||
|
|
||||||
|
// 生成全局唯一 ID(非确定性种子,跨进程不重复)
|
||||||
|
static UUID New()
|
||||||
|
{
|
||||||
|
static std::mt19937_64 rng{std::random_device{}()};
|
||||||
|
return UUID{rng()};
|
||||||
|
}
|
||||||
|
|
||||||
|
u64 Value() const { return m_Value; }
|
||||||
|
bool Valid() const { return m_Value != 0; }
|
||||||
|
|
||||||
|
bool operator==(const UUID& o) const = default;
|
||||||
|
bool operator!=(const UUID& o) const = default;
|
||||||
|
|
||||||
|
private:
|
||||||
|
u64 m_Value = 0; // 0 保留为"无效"
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Engine
|
||||||
|
|
||||||
|
namespace std {
|
||||||
|
template<> struct hash<Engine::UUID> {
|
||||||
|
size_t operator()(const Engine::UUID& id) const noexcept {
|
||||||
|
return std::hash<std::uint64_t>{}(id.Value());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif //HELLOVULKAN_UUID_H
|
||||||
@@ -11,6 +11,7 @@
|
|||||||
#include <functional>
|
#include <functional>
|
||||||
|
|
||||||
#include "Types.h"
|
#include "Types.h"
|
||||||
|
#include "Event.h"
|
||||||
|
|
||||||
struct GLFWwindow;
|
struct GLFWwindow;
|
||||||
|
|
||||||
@@ -27,6 +28,11 @@ namespace Engine {
|
|||||||
bool ShouldClose() const;
|
bool ShouldClose() const;
|
||||||
void PollEvents();
|
void PollEvents();
|
||||||
|
|
||||||
|
// 事件出口:GLFW 回调 → Event → 上层回调(由 Application 注册)。
|
||||||
|
// 未注册时事件直接丢弃。
|
||||||
|
using EventCallbackFn = std::function<void(const Event&)>;
|
||||||
|
void SetEventCallback(EventCallbackFn callback) { m_EventCallback = std::move(callback); }
|
||||||
|
|
||||||
vk::SurfaceKHR CreateSurface(vk::Instance instance);
|
vk::SurfaceKHR CreateSurface(vk::Instance instance);
|
||||||
void DestroySurface(vk::Instance instance, vk::SurfaceKHR surface);
|
void DestroySurface(vk::Instance instance, vk::SurfaceKHR surface);
|
||||||
|
|
||||||
@@ -57,7 +63,15 @@ namespace Engine {
|
|||||||
std::string m_Title;
|
std::string m_Title;
|
||||||
|
|
||||||
bool m_FramebufferResized = false;
|
bool m_FramebufferResized = false;
|
||||||
|
EventCallbackFn m_EventCallback;
|
||||||
|
|
||||||
|
// GLFW 静态回调(通过 window user pointer 找回 this)
|
||||||
static void FramebufferResizeCallback(GLFWwindow* window, int width, int height);
|
static void FramebufferResizeCallback(GLFWwindow* window, int width, int height);
|
||||||
|
static void KeyCallback(GLFWwindow* window, int key, int scancode, int action, int mods);
|
||||||
|
static void MouseButtonCallback(GLFWwindow* window, int button, int action, int mods);
|
||||||
|
static void CursorPosCallback(GLFWwindow* window, double xpos, double ypos);
|
||||||
|
static void ScrollCallback(GLFWwindow* window, double xoffset, double yoffset);
|
||||||
|
static void WindowFocusCallback(GLFWwindow* window, int focused);
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Engine
|
} // namespace Engine
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
//
|
||||||
|
// Created by atdunbg on 2026/9/22.
|
||||||
|
//
|
||||||
|
// Components:内置组件集合。
|
||||||
|
// 组件是纯数据(POD 倾向),逻辑在系统/应用层。
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef HELLOVULKAN_COMPONENTS_H
|
||||||
|
#define HELLOVULKAN_COMPONENTS_H
|
||||||
|
|
||||||
|
#include <Engine/Core/Types.h>
|
||||||
|
#include <Engine/Core/UUID.h>
|
||||||
|
#include <Engine/Renderer/Camera.h>
|
||||||
|
#include <Engine/Assets/AssetHandle.h>
|
||||||
|
|
||||||
|
#include <glm/glm.hpp>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace Engine {
|
||||||
|
class Model;
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace Engine::ECS {
|
||||||
|
|
||||||
|
// 实体稳定身份:CreateEntity 时自动分配,用于序列化/跨引用。
|
||||||
|
// ECS 内部遍历不用它(用 index+generation 句柄,性能)。
|
||||||
|
struct IdComponent {
|
||||||
|
UUID uuid;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 实体名(调试/查询用;大规模场景建议演进为哈希 id)
|
||||||
|
struct NameComponent {
|
||||||
|
std::string name;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 世界变换。当前直接存世界矩阵(渲染热路径友好);
|
||||||
|
// 需要 SRT 编辑时再加 LocalTransform 组件 + 层级同步系统。
|
||||||
|
struct TransformComponent {
|
||||||
|
glm::mat4 world{1.0f};
|
||||||
|
};
|
||||||
|
|
||||||
|
// 网格渲染体:引用共享的 Model 资产。
|
||||||
|
// 存句柄不存指针/Ref:所有权归 AssetManager,悬空由句柄 generation 拦截。
|
||||||
|
struct MeshRendererComponent {
|
||||||
|
Assets::ModelHandle model;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 相机。一个场景中 primary=true 的实体是当前渲染视角。
|
||||||
|
struct CameraComponent {
|
||||||
|
Camera camera;
|
||||||
|
bool primary = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 方向光
|
||||||
|
struct LightComponent {
|
||||||
|
glm::vec3 direction{0.0f, -1.0f, 0.0f};
|
||||||
|
glm::vec4 color{1.0f, 1.0f, 1.0f, 1.0f};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Engine::ECS
|
||||||
|
|
||||||
|
#endif //HELLOVULKAN_COMPONENTS_H
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
//
|
||||||
|
// Created by atdunbg on 2026/9/22.
|
||||||
|
//
|
||||||
|
// Entity:实体句柄 = (池内索引, 生成号)。
|
||||||
|
//
|
||||||
|
// 生成号用于甄别悬空引用:实体销毁后索引会回收复用,
|
||||||
|
// 旧的 Entity 句柄因 generation 不匹配会被 Registry 拒绝,
|
||||||
|
// 从机制上杜绝"引用到复用后的错误实体"这类隐式 bug。
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef HELLOVULKAN_ENTITY_H
|
||||||
|
#define HELLOVULKAN_ENTITY_H
|
||||||
|
|
||||||
|
#include <Engine/Core/Types.h>
|
||||||
|
|
||||||
|
namespace Engine {
|
||||||
|
|
||||||
|
struct Entity {
|
||||||
|
static constexpr u32 kInvalid = 0xFFFFFFFF;
|
||||||
|
|
||||||
|
u32 index = kInvalid; // 实体槽位索引
|
||||||
|
u32 generation = 0; // 槽位复用计数
|
||||||
|
|
||||||
|
bool Valid() const { return index != kInvalid; }
|
||||||
|
explicit operator bool() const { return Valid(); }
|
||||||
|
bool operator==(const Entity& o) const = default;
|
||||||
|
bool operator!=(const Entity& o) const = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Engine
|
||||||
|
|
||||||
|
#endif //HELLOVULKAN_ENTITY_H
|
||||||
@@ -0,0 +1,312 @@
|
|||||||
|
//
|
||||||
|
// Created by atdunbg on 2026/9/22.
|
||||||
|
//
|
||||||
|
// Registry:稀疏集 ECS 核心。
|
||||||
|
//
|
||||||
|
// 数据结构(与 EnTT 同族,自研实现):
|
||||||
|
// - 每个组件类型一个 ComponentPool<T>:sparse 索引表 + dense 实体/组件数组
|
||||||
|
// - Add/Remove/Get 全部 O(1);Remove 用 swap-remove(O(1),代价是
|
||||||
|
// dense 顺序会变——渲染遍历只要求"每帧确定",不要求跨帧稳定)
|
||||||
|
// - Each<A,B,...> 以最小的池为主表遍历,其余池做 O(1) Contains 探测,
|
||||||
|
// 复杂度 O(min(|A|,|B|,...))——大场景下的关键保证
|
||||||
|
//
|
||||||
|
// 约束(引擎级正确性):
|
||||||
|
// - 悬空句柄:generation 不匹配直接 FATAL,不静默
|
||||||
|
// - 遍历中禁止结构变更(Add/Remove 组件、Create/Destroy 实体),
|
||||||
|
// 会使池失效——销毁走延迟队列(Scene::DestroyEntity → FlushDestroyQueue)
|
||||||
|
// - 全程无虚调用开销在热路径上(Get/Add 直接走模板池)
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef HELLOVULKAN_REGISTRY_H
|
||||||
|
#define HELLOVULKAN_REGISTRY_H
|
||||||
|
|
||||||
|
#include <Engine/Core/Types.h>
|
||||||
|
#include <Engine/Core/Log.h>
|
||||||
|
#include <Engine/ECS/Entity.h>
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <tuple>
|
||||||
|
#include <vector>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace Engine::ECS {
|
||||||
|
|
||||||
|
// ---------- 组件类型 ID(编译期静态分配) ----------
|
||||||
|
|
||||||
|
using ComponentTypeId = u32;
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
inline ComponentTypeId NextComponentTypeId()
|
||||||
|
{
|
||||||
|
static ComponentTypeId counter = 0;
|
||||||
|
return counter++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
ComponentTypeId ComponentTypeIdOf()
|
||||||
|
{
|
||||||
|
static const ComponentTypeId id = detail::NextComponentTypeId();
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- 类型擦除池接口(Registry 管理生命周期 + 视图主表遍历用) ----------
|
||||||
|
|
||||||
|
class IPool {
|
||||||
|
public:
|
||||||
|
virtual ~IPool() = default;
|
||||||
|
virtual u32 Size() const = 0;
|
||||||
|
virtual Entity EntityAt(u32 denseIndex) const = 0;
|
||||||
|
virtual bool Remove(Entity e) = 0; // 不存在返回 false
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---------- 稀疏集组件池 ----------
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
class ComponentPool final : public IPool {
|
||||||
|
public:
|
||||||
|
static constexpr u32 kInvalid = 0xFFFFFFFF;
|
||||||
|
|
||||||
|
T& Add(Entity e, T component)
|
||||||
|
{
|
||||||
|
if (Contains(e)) {
|
||||||
|
ENGINE_FATAL("ComponentPool: entity ({},{}) already has this component",
|
||||||
|
e.index, e.generation);
|
||||||
|
}
|
||||||
|
if (e.index >= m_Sparse.size()) {
|
||||||
|
m_Sparse.resize(e.index + 1, kInvalid);
|
||||||
|
}
|
||||||
|
|
||||||
|
m_Sparse[e.index] = static_cast<u32>(m_Components.size());
|
||||||
|
m_Entities.push_back(e);
|
||||||
|
m_Components.push_back(std::move(component));
|
||||||
|
return m_Components.back();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Contains(Entity e) const
|
||||||
|
{
|
||||||
|
return e.index < m_Sparse.size() &&
|
||||||
|
m_Sparse[e.index] != kInvalid &&
|
||||||
|
m_Entities[m_Sparse[e.index]] == e;
|
||||||
|
}
|
||||||
|
|
||||||
|
T& Get(Entity e)
|
||||||
|
{
|
||||||
|
if (!Contains(e)) {
|
||||||
|
ENGINE_FATAL("ComponentPool: entity ({},{}) does not have this component",
|
||||||
|
e.index, e.generation);
|
||||||
|
}
|
||||||
|
return m_Components[m_Sparse[e.index]];
|
||||||
|
}
|
||||||
|
|
||||||
|
const T& Get(Entity e) const
|
||||||
|
{
|
||||||
|
return const_cast<ComponentPool*>(this)->Get(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Remove(Entity e) override
|
||||||
|
{
|
||||||
|
if (!Contains(e)) return false;
|
||||||
|
|
||||||
|
const u32 denseIdx = m_Sparse[e.index];
|
||||||
|
const u32 lastIdx = static_cast<u32>(m_Components.size()) - 1;
|
||||||
|
|
||||||
|
if (denseIdx != lastIdx) {
|
||||||
|
// swap-remove:尾元素补位
|
||||||
|
m_Entities[denseIdx] = m_Entities[lastIdx];
|
||||||
|
m_Components[denseIdx] = std::move(m_Components[lastIdx]);
|
||||||
|
m_Sparse[m_Entities[denseIdx].index] = denseIdx;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_Sparse[e.index] = kInvalid;
|
||||||
|
m_Entities.pop_back();
|
||||||
|
m_Components.pop_back();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 Size() const override { return static_cast<u32>(m_Components.size()); }
|
||||||
|
Entity EntityAt(u32 denseIndex) const override { return m_Entities[denseIndex]; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<u32> m_Sparse; // 实体索引 -> dense 下标(kInvalid = 无)
|
||||||
|
std::vector<Entity> m_Entities; // dense:与 m_Components 平行
|
||||||
|
std::vector<T> m_Components; // dense:组件数据(连续内存,缓存友好)
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---------- Registry ----------
|
||||||
|
|
||||||
|
class Registry {
|
||||||
|
public:
|
||||||
|
Registry() = default;
|
||||||
|
~Registry() = default;
|
||||||
|
|
||||||
|
Registry(const Registry&) = delete;
|
||||||
|
Registry& operator=(const Registry&) = delete;
|
||||||
|
|
||||||
|
// ---- 实体生命周期 ----
|
||||||
|
|
||||||
|
Entity Create()
|
||||||
|
{
|
||||||
|
if (!m_FreeIndices.empty()) {
|
||||||
|
const u32 index = m_FreeIndices.back();
|
||||||
|
m_FreeIndices.pop_back();
|
||||||
|
m_Records[index].alive = true; // generation 已在 Destroy 时递增
|
||||||
|
return Entity{index, m_Records[index].generation};
|
||||||
|
}
|
||||||
|
|
||||||
|
m_Records.push_back({0, true});
|
||||||
|
return Entity{static_cast<u32>(m_Records.size() - 1), 0};
|
||||||
|
}
|
||||||
|
|
||||||
|
void Destroy(Entity e)
|
||||||
|
{
|
||||||
|
if (!IsAlive(e)) {
|
||||||
|
ENGINE_FATAL("Registry::Destroy: stale entity ({},{})",
|
||||||
|
e.index, e.generation);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& pool : m_Pools) {
|
||||||
|
if (pool) pool->Remove(e); // 不在池中就跳过
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& record = m_Records[e.index];
|
||||||
|
record.alive = false;
|
||||||
|
++record.generation;
|
||||||
|
m_FreeIndices.push_back(e.index);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsAlive(Entity e) const
|
||||||
|
{
|
||||||
|
return e.Valid() && e.index < m_Records.size() &&
|
||||||
|
m_Records[e.index].alive &&
|
||||||
|
m_Records[e.index].generation == e.generation;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- 组件操作 ----
|
||||||
|
|
||||||
|
template<typename T, typename... Args>
|
||||||
|
T& Add(Entity e, Args&&... args)
|
||||||
|
{
|
||||||
|
if (!IsAlive(e)) {
|
||||||
|
ENGINE_FATAL("Registry::Add: stale entity ({},{})", e.index, e.generation);
|
||||||
|
}
|
||||||
|
return AssurePool<T>().Add(e, T{std::forward<Args>(args)...});
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
T& Get(Entity e)
|
||||||
|
{
|
||||||
|
if (!IsAlive(e)) {
|
||||||
|
ENGINE_FATAL("Registry::Get: stale entity ({},{})", e.index, e.generation);
|
||||||
|
}
|
||||||
|
auto* pool = GetPool<T>();
|
||||||
|
if (!pool) {
|
||||||
|
ENGINE_FATAL("Registry::Get: no component pool of this type exists");
|
||||||
|
}
|
||||||
|
return pool->Get(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
T* TryGet(Entity e)
|
||||||
|
{
|
||||||
|
if (!IsAlive(e)) return nullptr;
|
||||||
|
auto* pool = GetPool<T>();
|
||||||
|
return (pool && pool->Contains(e)) ? &pool->Get(e) : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
bool Has(Entity e)
|
||||||
|
{
|
||||||
|
if (!IsAlive(e)) return false;
|
||||||
|
auto* pool = GetPool<T>();
|
||||||
|
return pool && pool->Contains(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
bool Remove(Entity e)
|
||||||
|
{
|
||||||
|
if (!IsAlive(e)) return false;
|
||||||
|
auto* pool = GetPool<T>();
|
||||||
|
return pool && pool->Remove(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- 遍历(视图)----
|
||||||
|
// fn(Entity, Ts&...);以最小的池为主表,复杂度 O(min pool size)。
|
||||||
|
// 遍历中禁止结构变更(见文件头约束)。
|
||||||
|
template<typename... Ts, typename Fn>
|
||||||
|
void Each(Fn&& fn)
|
||||||
|
{
|
||||||
|
static_assert(sizeof...(Ts) > 0, "Each requires at least one component type");
|
||||||
|
|
||||||
|
std::tuple<ComponentPool<Ts>*...> pools{GetPool<Ts>()...};
|
||||||
|
|
||||||
|
// 任一类型池不存在 → 空集
|
||||||
|
bool allExist = true;
|
||||||
|
std::apply([&allExist](auto*... p) {
|
||||||
|
((p || (allExist = false)) && ...);
|
||||||
|
}, pools);
|
||||||
|
if (!allExist) return;
|
||||||
|
|
||||||
|
// 主池:取最小者
|
||||||
|
const std::array<IPool*, sizeof...(Ts)> basePools{GetPool<Ts>()...};
|
||||||
|
IPool* primary = nullptr;
|
||||||
|
u32 minSize = 0xFFFFFFFF;
|
||||||
|
for (auto* p : basePools) {
|
||||||
|
if (p && p->Size() < minSize) {
|
||||||
|
minSize = p->Size();
|
||||||
|
primary = p;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!primary) return;
|
||||||
|
|
||||||
|
for (u32 i = 0; i < primary->Size(); ++i) {
|
||||||
|
const Entity e = primary->EntityAt(i);
|
||||||
|
if (!IsAlive(e)) continue;
|
||||||
|
|
||||||
|
bool all = true;
|
||||||
|
std::apply([&](auto*... p) {
|
||||||
|
all = (p->Contains(e) && ...); // 短路折叠
|
||||||
|
}, pools);
|
||||||
|
if (!all) continue;
|
||||||
|
|
||||||
|
std::apply([&](auto*... p) {
|
||||||
|
fn(e, p->Get(e)...);
|
||||||
|
}, pools);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
template<typename T>
|
||||||
|
ComponentPool<T>* GetPool()
|
||||||
|
{
|
||||||
|
const ComponentTypeId id = ComponentTypeIdOf<T>();
|
||||||
|
if (id >= m_Pools.size() || !m_Pools[id]) return nullptr;
|
||||||
|
return static_cast<ComponentPool<T>*>(m_Pools[id].get());
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
ComponentPool<T>& AssurePool()
|
||||||
|
{
|
||||||
|
const ComponentTypeId id = ComponentTypeIdOf<T>();
|
||||||
|
if (id >= m_Pools.size()) {
|
||||||
|
m_Pools.resize(id + 1);
|
||||||
|
}
|
||||||
|
if (!m_Pools[id]) {
|
||||||
|
m_Pools[id] = std::make_unique<ComponentPool<T>>();
|
||||||
|
}
|
||||||
|
return static_cast<ComponentPool<T>&>(*m_Pools[id]);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct EntityRecord {
|
||||||
|
u32 generation = 0;
|
||||||
|
bool alive = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<EntityRecord> m_Records;
|
||||||
|
std::vector<u32> m_FreeIndices; // 可复用槽位
|
||||||
|
std::vector<std::unique_ptr<IPool>> m_Pools; // 按 ComponentTypeId 索引
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Engine::ECS
|
||||||
|
|
||||||
|
#endif //HELLOVULKAN_REGISTRY_H
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
//
|
||||||
|
// Created by atdunbg on 2026/9/22.
|
||||||
|
//
|
||||||
|
// Scene:世界容器 = Registry + 场景级设置 + 安全的生命周期封装。
|
||||||
|
//
|
||||||
|
// 销毁策略:DestroyEntity 只入队,FlushDestroyQueue 在帧尾统一执行,
|
||||||
|
// 保证遍历(Each)过程中组件池结构不变——这是 ECS 正确性的关键约束。
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef HELLOVULKAN_SCENE_H
|
||||||
|
#define HELLOVULKAN_SCENE_H
|
||||||
|
|
||||||
|
#include <Engine/Core/Types.h>
|
||||||
|
#include <Engine/ECS/Registry.h>
|
||||||
|
#include <Engine/ECS/Components.h>
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace Engine::ECS {
|
||||||
|
|
||||||
|
class Scene {
|
||||||
|
public:
|
||||||
|
Scene() = default;
|
||||||
|
~Scene() = default;
|
||||||
|
|
||||||
|
Scene(const Scene&) = delete;
|
||||||
|
Scene& operator=(const Scene&) = delete;
|
||||||
|
|
||||||
|
// ---- 场景级设置 ----
|
||||||
|
struct Settings {
|
||||||
|
glm::vec4 ambientColor{0.15f, 0.15f, 0.2f, 0.0f};
|
||||||
|
};
|
||||||
|
Settings& GetSettings() { return m_Settings; }
|
||||||
|
|
||||||
|
// ---- 实体 ----
|
||||||
|
|
||||||
|
Entity CreateEntity(std::string name = {});
|
||||||
|
void DestroyEntity(Entity e); // 延迟:帧尾统一销毁
|
||||||
|
void FlushDestroyQueue(); // 每帧末尾由 Application 调用
|
||||||
|
bool IsAlive(Entity e) const { return m_Registry.IsAlive(e); }
|
||||||
|
|
||||||
|
// 按稳定 UUID 查实体(序列化/跨引用场景);找不到返回无效句柄
|
||||||
|
Entity FindEntityByUUID(UUID uuid);
|
||||||
|
|
||||||
|
Registry& GetRegistry() { return m_Registry; }
|
||||||
|
|
||||||
|
// ---- 组件便捷接口(转发 Registry)----
|
||||||
|
|
||||||
|
template<typename T, typename... Args>
|
||||||
|
T& AddComponent(Entity e, Args&&... args)
|
||||||
|
{
|
||||||
|
return m_Registry.Add<T>(e, std::forward<Args>(args)...);
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
T& GetComponent(Entity e) { return m_Registry.Get<T>(e); }
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
T* TryGetComponent(Entity e) { return m_Registry.TryGet<T>(e); }
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
bool HasComponent(Entity e) { return m_Registry.Has<T>(e); }
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
void RemoveComponent(Entity e) { m_Registry.Remove<T>(e); }
|
||||||
|
|
||||||
|
template<typename... Ts, typename Fn>
|
||||||
|
void Each(Fn&& fn) { m_Registry.Each<Ts...>(std::forward<Fn>(fn)); }
|
||||||
|
|
||||||
|
// ---- 常用查询 ----
|
||||||
|
|
||||||
|
// primary 相机(渲染视角来源);无则返回 nullptr
|
||||||
|
CameraComponent* GetPrimaryCamera();
|
||||||
|
|
||||||
|
private:
|
||||||
|
Registry m_Registry;
|
||||||
|
std::vector<Entity> m_PendingDestroy;
|
||||||
|
Settings m_Settings;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Engine::ECS
|
||||||
|
|
||||||
|
#endif //HELLOVULKAN_SCENE_H
|
||||||
@@ -0,0 +1,299 @@
|
|||||||
|
//
|
||||||
|
// Created by atdunbg on 2026/9/22.
|
||||||
|
//
|
||||||
|
// RenderGraph:声明式帧图内核。
|
||||||
|
//
|
||||||
|
// 设计原则(面向引擎,非 demo):
|
||||||
|
// 1. 声明与执行分离:图结构(pass + 虚拟资源)只声明一次并 Compile,
|
||||||
|
// 每帧 Execute 零图结构开销、零堆分配(barrier 列表全部编译期预算)。
|
||||||
|
// 2. 虚拟资源 + 生成号句柄:上层永远不持有裸 vulkan 句柄,
|
||||||
|
// 生命周期、layout、物理内存全部由图管理。
|
||||||
|
// 3. 物理资源池:真实 GpuImage 按 (format, extent, usage) 池化复用,
|
||||||
|
// 每个虚拟资源按帧槽位拥有 kMaxFramesInFlight 个物理实例,
|
||||||
|
// 从根上避免帧间读写竞争,同时杜绝每帧重建 GPU 资源。
|
||||||
|
// 4. 自动同步:layout/访问状态链在 Compile 期解算,产出批量
|
||||||
|
// vk::ImageMemoryBarrier2(Synchronization2),上层不再手写 barrier。
|
||||||
|
//
|
||||||
|
// 已知边界(后续演进点):
|
||||||
|
// - 暂不支持异步 compute queue / 跨队列信号量
|
||||||
|
// - 暂不支持内存别名(需 VMA)与帧历史资源(TAA 等)
|
||||||
|
// - 同一 pass 内对同一资源"读+写"(input attachment / RWM)未建模
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef HELLOVULKAN_RENDERGRAPH_H
|
||||||
|
#define HELLOVULKAN_RENDERGRAPH_H
|
||||||
|
|
||||||
|
#include <Engine/Core/Types.h>
|
||||||
|
#include <Engine/Core/FrameContext.h>
|
||||||
|
#include <Engine/Vulkan/MemoryAllocator.h>
|
||||||
|
#include <vulkan/vulkan.hpp>
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace Engine::RG {
|
||||||
|
|
||||||
|
// ---------- 句柄 ----------
|
||||||
|
// 带生成号的索引句柄:图永远不会删除条目(只追加),
|
||||||
|
// 生成号用于上层误持过期句柄时的校验,防止 use-after-free 式错误。
|
||||||
|
|
||||||
|
inline constexpr u32 kInvalidHandle = 0xFFFFFFFF;
|
||||||
|
|
||||||
|
template<typename Tag>
|
||||||
|
struct Handle {
|
||||||
|
u32 index = kInvalidHandle;
|
||||||
|
u32 generation = 0;
|
||||||
|
|
||||||
|
bool Valid() const { return index != kInvalidHandle; }
|
||||||
|
explicit operator bool() const { return Valid(); }
|
||||||
|
};
|
||||||
|
|
||||||
|
struct TextureTag;
|
||||||
|
using TextureHandle = Handle<TextureTag>;
|
||||||
|
|
||||||
|
// ---------- 资源使用方式 ----------
|
||||||
|
// 声明 pass 怎么用资源;layout/access/stage 映射和 image usage
|
||||||
|
// 都由这里推导,上层不需要(也不允许)手写。
|
||||||
|
|
||||||
|
enum class ResourceUse : u8 {
|
||||||
|
ColorAttachment,
|
||||||
|
DepthAttachment,
|
||||||
|
ShaderRead,
|
||||||
|
TransferSrc,
|
||||||
|
TransferDst,
|
||||||
|
Present, // 仅用于 swapchain 导入资源:transition 到 ePresentSrcKHR
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---------- 虚拟纹理描述 ----------
|
||||||
|
|
||||||
|
struct TextureDesc {
|
||||||
|
vk::Format format = vk::Format::eUndefined;
|
||||||
|
u32 width = 0; // 0 = 跟随目标(swapchain)尺寸
|
||||||
|
u32 height = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---------- 物理镜像 ----------
|
||||||
|
|
||||||
|
struct PhysicalImage {
|
||||||
|
vk::Image image{};
|
||||||
|
vk::ImageView view{};
|
||||||
|
vk::Extent2D extent{};
|
||||||
|
vk::Format format{};
|
||||||
|
vk::ImageUsageFlags usage{};
|
||||||
|
MemoryAllocator::Allocation allocation{};
|
||||||
|
bool valid = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---------- Pass 上下文(Execute 回调收到的) ----------
|
||||||
|
|
||||||
|
struct RenderGraphContext {
|
||||||
|
vk::CommandBuffer cmd;
|
||||||
|
FrameContext frame;
|
||||||
|
vk::Extent2D extent{};
|
||||||
|
|
||||||
|
// 按句柄取当前帧槽位的物理资源(image/view)
|
||||||
|
vk::ImageView View(TextureHandle h) const;
|
||||||
|
vk::Image Image(TextureHandle h) const;
|
||||||
|
|
||||||
|
class RenderGraph* graph = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
using ExecuteFn = std::function<void(RenderGraphContext&)>;
|
||||||
|
|
||||||
|
// ---------- 图内部结构 ----------
|
||||||
|
|
||||||
|
struct TextureEntry {
|
||||||
|
std::string name;
|
||||||
|
u32 index = 0; // 自身在 m_Textures 中的下标
|
||||||
|
TextureDesc desc;
|
||||||
|
bool imported = false; // swapchain 导入资源无物理实例
|
||||||
|
u32 generation = 1;
|
||||||
|
|
||||||
|
// Compile 期推导
|
||||||
|
vk::ImageUsageFlags usage{};
|
||||||
|
vk::ImageAspectFlagBits aspect = vk::ImageAspectFlagBits::eColor;
|
||||||
|
i32 firstPass = -1;
|
||||||
|
i32 lastPass = -1;
|
||||||
|
|
||||||
|
// 物理实例:每个帧槽位一个,created 资源才拥有
|
||||||
|
std::vector<PhysicalImage*> physical;
|
||||||
|
|
||||||
|
// 导入资源每帧绑定
|
||||||
|
vk::Image importedImage{};
|
||||||
|
vk::ImageView importedView{};
|
||||||
|
|
||||||
|
// 帧槽位状态:物理镜像是否已被使用过(决定 entry barrier 变体)
|
||||||
|
struct SlotState {
|
||||||
|
bool used = false;
|
||||||
|
};
|
||||||
|
std::vector<SlotState> slots;
|
||||||
|
|
||||||
|
// Compile 期状态链解算用(执行序上的"当前"状态)
|
||||||
|
vk::ImageLayout chainLayout = vk::ImageLayout::eUndefined;
|
||||||
|
vk::PipelineStageFlags2 chainStage{};
|
||||||
|
vk::AccessFlags2 chainAccess{};
|
||||||
|
bool chainWasWrite = false;
|
||||||
|
|
||||||
|
// 稳态:上一轮执行结束时(lastPass 之后)的状态。
|
||||||
|
// 同一帧槽位再次进入图时,entry barrier 从这里出发。
|
||||||
|
vk::ImageLayout restingLayout = vk::ImageLayout::eUndefined;
|
||||||
|
vk::PipelineStageFlags2 restingStage{};
|
||||||
|
vk::AccessFlags2 restingAccess{};
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PassNode {
|
||||||
|
std::string name;
|
||||||
|
struct Use {
|
||||||
|
u32 resIndex;
|
||||||
|
ResourceUse use;
|
||||||
|
vk::ShaderStageFlags readStage; // ShaderRead 时的消费 stage
|
||||||
|
};
|
||||||
|
std::vector<Use> uses;
|
||||||
|
ExecuteFn exec;
|
||||||
|
bool sideEffect = false; // 无读写但必须执行(compute 写 buffer 等)
|
||||||
|
bool culled = false;
|
||||||
|
|
||||||
|
// Compile 期预算好的 barrier:每个资源两个变体
|
||||||
|
// steady = 该槽位已被使用过(从 resting 状态出发)
|
||||||
|
// first = 该槽位首次使用(从 Undefined 出发)
|
||||||
|
// imported 资源恒用 first(acquire 后 layout 语义上为 Undefined)
|
||||||
|
struct BarrierPair {
|
||||||
|
u32 resIndex;
|
||||||
|
vk::ImageMemoryBarrier2 steady;
|
||||||
|
vk::ImageMemoryBarrier2 first;
|
||||||
|
};
|
||||||
|
std::vector<BarrierPair> barriers;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ---------- RenderGraph ----------
|
||||||
|
|
||||||
|
class RenderGraph {
|
||||||
|
public:
|
||||||
|
RenderGraph(Device& device, MemoryAllocator& allocator, u32 framesInFlight);
|
||||||
|
~RenderGraph();
|
||||||
|
|
||||||
|
RenderGraph(const RenderGraph&) = delete;
|
||||||
|
RenderGraph& operator=(const RenderGraph&) = delete;
|
||||||
|
|
||||||
|
// ---- 声明阶段(AddPass 之后不得再声明资源;全部声明完成后 Compile)----
|
||||||
|
|
||||||
|
// 声明一张图拥有的瞬时纹理(此时不分配任何 GPU 内存)
|
||||||
|
TextureHandle CreateTexture(std::string name, const TextureDesc& desc);
|
||||||
|
|
||||||
|
// 导入 swapchain 颜色附件(每帧通过 SetImportedImage 绑定物理 image)
|
||||||
|
TextureHandle ImportSwapchain(std::string name, vk::Format format);
|
||||||
|
|
||||||
|
// 每帧 acquire 后调用,把当帧 swapchain image 绑到导入资源
|
||||||
|
void SetImportedImage(TextureHandle h, vk::Image image, vk::ImageView view);
|
||||||
|
|
||||||
|
// ---- Pass 声明 ----
|
||||||
|
|
||||||
|
class PassBuilder {
|
||||||
|
public:
|
||||||
|
PassBuilder& Write(TextureHandle h, ResourceUse use);
|
||||||
|
PassBuilder& Read(TextureHandle h, ResourceUse use,
|
||||||
|
vk::ShaderStageFlags stage = vk::ShaderStageFlagBits::eFragment);
|
||||||
|
PassBuilder& SideEffect();
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend class RenderGraph;
|
||||||
|
PassBuilder(RenderGraph& graph, u32 passIndex)
|
||||||
|
: m_Graph(graph), m_PassIndex(passIndex) {}
|
||||||
|
|
||||||
|
RenderGraph& m_Graph;
|
||||||
|
u32 m_PassIndex;
|
||||||
|
};
|
||||||
|
|
||||||
|
[[nodiscard]] PassBuilder AddPass(std::string name, ExecuteFn exec);
|
||||||
|
|
||||||
|
// ---- 编译与执行 ----
|
||||||
|
|
||||||
|
// 编译:死 pass 剔除 → 拓扑排序 → 生命周期 → barrier 预算 → 物理分配。
|
||||||
|
// 目标尺寸变化时调用 SetTargetExtent 会自动重编译并轮换物理资源。
|
||||||
|
void Compile();
|
||||||
|
|
||||||
|
void SetTargetExtent(vk::Extent2D extent);
|
||||||
|
vk::Extent2D GetTargetExtent() const { return m_TargetExtent; }
|
||||||
|
|
||||||
|
// 每帧执行:按编译好的顺序录命令。要求 cmd 处于 recording 状态。
|
||||||
|
void Execute(const FrameContext& frame);
|
||||||
|
|
||||||
|
// ---- 统计 ----
|
||||||
|
|
||||||
|
u32 GetPassCount() const;
|
||||||
|
u32 GetActivePassCount() const;
|
||||||
|
u32 GetTextureCount() const;
|
||||||
|
u32 GetBarrierCount() const;
|
||||||
|
u32 GetPooledImageCount() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
friend struct RenderGraphContext;
|
||||||
|
|
||||||
|
// 编译子步骤
|
||||||
|
void ResolveUsage(TextureEntry& entry);
|
||||||
|
void CullPasses();
|
||||||
|
void Toposort();
|
||||||
|
void ComputeLifetimes();
|
||||||
|
void PrecomputeBarriers();
|
||||||
|
void AllocatePhysical();
|
||||||
|
void ReleasePhysical();
|
||||||
|
void PurgePool(vk::Extent2D target);
|
||||||
|
PhysicalImage* AcquirePhysical(vk::Format format, vk::Extent2D extent,
|
||||||
|
vk::ImageUsageFlags usage,
|
||||||
|
vk::ImageAspectFlagBits aspect);
|
||||||
|
|
||||||
|
TextureEntry& GetEntry(TextureHandle h);
|
||||||
|
const TextureEntry& GetEntry(TextureHandle h) const;
|
||||||
|
void CheckHandle(TextureHandle h) const;
|
||||||
|
void CheckMutable() const; // 声明期校验:Compile 后禁止再改图
|
||||||
|
|
||||||
|
static vk::ImageLayout UseLayout(ResourceUse use);
|
||||||
|
static vk::PipelineStageFlags2 UseDstStage(const PassNode::Use& use);
|
||||||
|
static vk::PipelineStageFlags2 UseSrcStage(ResourceUse use);
|
||||||
|
static vk::AccessFlags2 UseDstAccess(const PassNode::Use& use);
|
||||||
|
static vk::AccessFlags2 UseSrcAccess(ResourceUse use);
|
||||||
|
|
||||||
|
Device& m_Device;
|
||||||
|
MemoryAllocator& m_Allocator;
|
||||||
|
u32 m_FramesInFlight;
|
||||||
|
bool m_Compiled = false;
|
||||||
|
|
||||||
|
std::vector<TextureEntry> m_Textures;
|
||||||
|
std::vector<PassNode> m_Passes;
|
||||||
|
u32 m_ActivePassCount = 0;
|
||||||
|
|
||||||
|
vk::Extent2D m_TargetExtent{};
|
||||||
|
|
||||||
|
// 拓扑排序后的执行序(存 pass 下标)
|
||||||
|
std::vector<u32> m_ExecutionOrder;
|
||||||
|
|
||||||
|
// 物理资源池:key = (format, extent, usage),value = 空闲镜像列表
|
||||||
|
struct PoolKey {
|
||||||
|
u32 format;
|
||||||
|
u32 width;
|
||||||
|
u32 height;
|
||||||
|
u32 usage;
|
||||||
|
bool operator==(const PoolKey& o) const {
|
||||||
|
return format == o.format && width == o.width &&
|
||||||
|
height == o.height && usage == o.usage;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
struct PoolKeyHash {
|
||||||
|
size_t operator()(const PoolKey& k) const {
|
||||||
|
size_t h = std::hash<u64>{}(
|
||||||
|
(static_cast<u64>(k.format) << 32) ^ k.usage);
|
||||||
|
h ^= std::hash<u64>{}((static_cast<u64>(k.width) << 32) ^ k.height) << 1;
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
std::unordered_map<PoolKey, std::vector<Scope<PhysicalImage>>, PoolKeyHash> m_Pool;
|
||||||
|
|
||||||
|
// Execute 用的 barrier 暂存(成员变量,避免每帧堆分配)
|
||||||
|
std::vector<vk::ImageMemoryBarrier2> m_ScratchBarriers;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace Engine::RG
|
||||||
|
|
||||||
|
#endif //HELLOVULKAN_RENDERGRAPH_H
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
//
|
||||||
|
// Created by atdunbg on 2026/9/22.
|
||||||
|
//
|
||||||
|
// Camera:独立的相机模块。
|
||||||
|
// 职责:输入驱动的运动更新 + 视图/投影矩阵计算。
|
||||||
|
// 不再依赖具体渲染器——Renderer/RenderGraph 只消费它的矩阵输出。
|
||||||
|
//
|
||||||
|
|
||||||
|
#ifndef HELLOVULKAN_CAMERA_H
|
||||||
|
#define HELLOVULKAN_CAMERA_H
|
||||||
|
|
||||||
|
#include <Engine/Core/Types.h>
|
||||||
|
#include <glm/glm.hpp>
|
||||||
|
|
||||||
|
namespace Engine {
|
||||||
|
|
||||||
|
class Window;
|
||||||
|
|
||||||
|
class Camera {
|
||||||
|
public:
|
||||||
|
// 每帧调用:读取窗口输入,更新位置与朝向
|
||||||
|
void Update(Window& window, f32 deltaTime);
|
||||||
|
|
||||||
|
glm::mat4 GetView() const;
|
||||||
|
|
||||||
|
// Vulkan 投影:内部完成 Y 翻转(clip space Y 向下)和 [0,1] 深度
|
||||||
|
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 SetMouseSensitivity(f32 sens) { m_MouseSensitivity = sens; }
|
||||||
|
|
||||||
|
// 鼠标视角默认关闭(需要先做 cursor capture 再打开)
|
||||||
|
void SetMouseLookEnabled(bool enabled) { m_MouseLookEnabled = enabled; }
|
||||||
|
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:
|
||||||
|
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_MouseSensitivity = 0.1f;
|
||||||
|
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
|
||||||
|
|
||||||
|
#endif //HELLOVULKAN_CAMERA_H
|
||||||
@@ -8,14 +8,13 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <Engine/Core/Types.h>
|
#include <Engine/Core/Types.h>
|
||||||
|
#include <Engine/Core/FrameContext.h>
|
||||||
|
#include <Engine/RenderGraph/RenderGraph.h>
|
||||||
#include <vulkan/vulkan.hpp>
|
#include <vulkan/vulkan.hpp>
|
||||||
#include <chrono>
|
#include <vector>
|
||||||
|
|
||||||
#include "Engine/Vulkan/Texture.h"
|
|
||||||
#include <glm/glm.hpp>
|
#include <glm/glm.hpp>
|
||||||
|
|
||||||
#include "Model.h"
|
|
||||||
|
|
||||||
namespace Engine {
|
namespace Engine {
|
||||||
|
|
||||||
class Window;
|
class Window;
|
||||||
@@ -24,16 +23,20 @@ namespace Engine {
|
|||||||
class DescriptorManager;
|
class DescriptorManager;
|
||||||
class ShaderManager;
|
class ShaderManager;
|
||||||
class Swapchain;
|
class Swapchain;
|
||||||
class CommandManager;
|
|
||||||
class SyncManager;
|
class SyncManager;
|
||||||
class Buffer;
|
class CommandManager;
|
||||||
class UniformBuffer;
|
class UniformBuffer;
|
||||||
|
class Model;
|
||||||
|
|
||||||
struct Object {
|
namespace ECS { class Scene; }
|
||||||
glm::mat4 transform = glm::mat4(1.0f);
|
namespace Assets { class AssetManager; }
|
||||||
Model* model = nullptr;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
// Renderer 职责(收敛后):
|
||||||
|
// 1. 装配底层模块(Device/Swapchain/Sync/Command/Descriptor/Shader)
|
||||||
|
// 2. 声明并驱动 RenderGraph(pass 结构在这里组装)
|
||||||
|
// 3. Render(Scene&, AssetManager&):从 ECS 场景读取相机/灯光/渲染体,
|
||||||
|
// 经资产层解析句柄后绘制
|
||||||
|
// 不拥有场景,不加载资产(资产归 AssetManager,磁盘 IO 与这里无关)。
|
||||||
class Renderer {
|
class Renderer {
|
||||||
public:
|
public:
|
||||||
Renderer(Window& window);
|
Renderer(Window& window);
|
||||||
@@ -42,15 +45,21 @@ namespace Engine {
|
|||||||
Renderer(const Renderer&) = delete;
|
Renderer(const Renderer&) = delete;
|
||||||
Renderer& operator=(const Renderer&) = delete;
|
Renderer& operator=(const Renderer&) = delete;
|
||||||
|
|
||||||
void Render();
|
void Render(ECS::Scene& scene, Assets::AssetManager& assets);
|
||||||
|
|
||||||
|
// ---- 组合根(Application)装配其他模块时需要的访问器 ----
|
||||||
|
Device& GetDevice() const { return *m_Device; }
|
||||||
|
MemoryAllocator& GetAllocator() const { return *m_Allocator; }
|
||||||
|
DescriptorManager& GetDescriptorManager() const { return *m_DescriptorManager; }
|
||||||
|
vk::DescriptorSetLayout GetTextureSetLayout() const { return m_TextureSetLayout; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void RecordCommands(vk::CommandBuffer cmd, u32 imageIndex);
|
void BuildGraph(); // 声明 pass 结构 + 首次 Compile
|
||||||
void TransitionImageLayout(vk::CommandBuffer cmd, vk::Image image,
|
void HandleResize();
|
||||||
vk::ImageLayout oldLayout, vk::ImageLayout newLayout);
|
|
||||||
|
|
||||||
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;
|
||||||
@@ -58,38 +67,27 @@ namespace Engine {
|
|||||||
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<Model> m_Model;
|
|
||||||
std::vector<Object> m_Objects;
|
|
||||||
Scope<UniformBuffer> m_UniformBuffer;
|
Scope<UniformBuffer> m_UniformBuffer;
|
||||||
|
Scope<RG::RenderGraph> m_RenderGraph;
|
||||||
|
|
||||||
|
// ---- 管线装配(shader object 方案,无 vk::Pipeline)----
|
||||||
vk::PipelineLayout m_PipelineLayout;
|
vk::PipelineLayout m_PipelineLayout;
|
||||||
|
|
||||||
vk::DescriptorSetLayout m_UBOSetLayout;
|
vk::DescriptorSetLayout m_UBOSetLayout;
|
||||||
vk::DescriptorSet m_UBODescriptorSet;
|
|
||||||
vk::DescriptorSetLayout m_TextureSetLayout;
|
vk::DescriptorSetLayout m_TextureSetLayout;
|
||||||
|
std::vector<vk::DescriptorSet> m_UBODescriptorSets; // 每帧槽位一个
|
||||||
|
vk::ShaderEXT m_VertShader{};
|
||||||
|
vk::ShaderEXT m_FragShader{};
|
||||||
|
|
||||||
|
// ---- RenderGraph 资源句柄 ----
|
||||||
|
RG::TextureHandle m_Backbuffer{};
|
||||||
|
RG::TextureHandle m_Depth{};
|
||||||
|
|
||||||
vk::ShaderEXT m_VertShader;
|
// ---- 当帧绑定(Render() 期间有效,供 pass 回调读取)----
|
||||||
vk::ShaderEXT m_FragShader;
|
ECS::Scene* m_BoundScene = nullptr;
|
||||||
|
Assets::AssetManager* m_BoundAssets = nullptr;
|
||||||
|
|
||||||
vk::Extent2D m_Extent;
|
vk::Extent2D m_Extent{};
|
||||||
vk::Format m_Format;
|
|
||||||
|
|
||||||
static constexpr u32 kMaxFramesInFlight = 2;
|
|
||||||
u32 m_CurrentFrame = 0;
|
u32 m_CurrentFrame = 0;
|
||||||
|
|
||||||
|
|
||||||
// 相机
|
|
||||||
glm::vec3 m_CameraPos = glm::vec3(0.0f, 0.0f, 7.0f);
|
|
||||||
f32 m_CameraYaw = -90.0f; // 朝向 -Z
|
|
||||||
f32 m_CameraPitch = 0.0f;
|
|
||||||
f32 m_CameraSpeed = 2.5f;
|
|
||||||
f32 m_MouseSensitivity = 0.1f;
|
|
||||||
f32 m_DeltaTime = 0.016f;
|
|
||||||
f32 m_LastFrameTime = 0.0f;
|
|
||||||
|
|
||||||
std::chrono::steady_clock::time_point m_StartTime;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Engine
|
} // namespace Engine
|
||||||
|
|||||||
@@ -5,32 +5,40 @@
|
|||||||
#ifndef HELLOVULKAN_COMMANDMANAGER_H
|
#ifndef HELLOVULKAN_COMMANDMANAGER_H
|
||||||
#define HELLOVULKAN_COMMANDMANAGER_H
|
#define HELLOVULKAN_COMMANDMANAGER_H
|
||||||
|
|
||||||
|
#include <Engine/Core/Types.h>
|
||||||
|
#include <Engine/Core/FrameContext.h>
|
||||||
#include <vulkan/vulkan.hpp>
|
#include <vulkan/vulkan.hpp>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace Engine {
|
namespace Engine {
|
||||||
|
|
||||||
class Device;
|
class Device;
|
||||||
|
|
||||||
|
// 每个帧槽位独立命令缓冲。
|
||||||
|
//
|
||||||
|
// 关键正确性约束:kMaxFramesInFlight 帧并发时,第 N 帧录制时第 N-1 帧的
|
||||||
|
// 命令缓冲可能仍在 GPU 上执行,因此绝不能共享/复用同一个 command buffer。
|
||||||
|
// 每个 frameSlot 一个 buffer,reset 只发生在"该槽位 fence 已确认完成"之后。
|
||||||
class CommandManager {
|
class CommandManager {
|
||||||
public:
|
public:
|
||||||
CommandManager(Device& device);
|
CommandManager(Device& device, u32 frameCount);
|
||||||
~CommandManager();
|
~CommandManager();
|
||||||
|
|
||||||
CommandManager(const CommandManager&) = delete;
|
CommandManager(const CommandManager&) = delete;
|
||||||
CommandManager& operator=(const CommandManager&) = delete;
|
CommandManager& operator=(const CommandManager&) = delete;
|
||||||
|
|
||||||
// 开始录制
|
// 开始录制指定帧槽位的命令缓冲
|
||||||
vk::CommandBuffer BeginFrame();
|
vk::CommandBuffer BeginFrame(u32 frameSlot);
|
||||||
|
|
||||||
// 结束录制
|
// 结束录制
|
||||||
void EndFrame();
|
void EndFrame(u32 frameSlot);
|
||||||
|
|
||||||
vk::CommandBuffer GetBuffer() const { return m_Buffer; }
|
vk::CommandBuffer GetBuffer(u32 frameSlot) const { return m_Buffers[frameSlot]; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Device& m_Device;
|
Device& m_Device;
|
||||||
vk::CommandPool m_Pool;
|
vk::CommandPool m_Pool;
|
||||||
vk::CommandBuffer m_Buffer;
|
std::vector<vk::CommandBuffer> m_Buffers;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Engine
|
} // namespace Engine
|
||||||
|
|||||||
@@ -34,6 +34,11 @@ public:
|
|||||||
|
|
||||||
uint32_t FindMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags props) const;
|
uint32_t FindMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags props) const;
|
||||||
|
|
||||||
|
// 在候选列表中找到第一个满足 tiling+features 的格式,找不到则 FATAL
|
||||||
|
vk::Format FindSupportedFormat(const std::vector<vk::Format>& candidates,
|
||||||
|
vk::ImageTiling tiling,
|
||||||
|
vk::FormatFeatureFlags features) const;
|
||||||
|
|
||||||
// 1.4 特性查询
|
// 1.4 特性查询
|
||||||
bool IsDynamicRenderingSupported() const { return m_DynamicRenderingSupported; }
|
bool IsDynamicRenderingSupported() const { return m_DynamicRenderingSupported; }
|
||||||
bool IsShaderObjectSupported() const { return m_ShaderObjectSupported; }
|
bool IsShaderObjectSupported() const { return m_ShaderObjectSupported; }
|
||||||
|
|||||||
@@ -33,10 +33,8 @@ namespace Engine
|
|||||||
const std::vector<vk::Image>& GetImages() const { return m_Images; }
|
const std::vector<vk::Image>& GetImages() const { return m_Images; }
|
||||||
const std::vector<vk::ImageView>& GetImageViews() const { return m_ImageViews; }
|
const std::vector<vk::ImageView>& GetImageViews() const { return m_ImageViews; }
|
||||||
|
|
||||||
vk::ImageView GetDepthImageView() const { return m_DepthImageView; }
|
// 深度图不在 Swapchain 管理——渲染目标(含深度)统一由 RenderGraph 创建。
|
||||||
vk::Format GetDepthFormat() const { return m_DepthFormat; }
|
// Swapchain 只负责"呈现"这一件事。
|
||||||
vk::Image GetDepthImage() const { return m_DepthImage; }
|
|
||||||
|
|
||||||
void Recreate(Window& window);
|
void Recreate(Window& window);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -47,10 +45,6 @@ namespace Engine
|
|||||||
vk::PresentModeKHR ChoosePresentMode(const std::vector<vk::PresentModeKHR>& modes);
|
vk::PresentModeKHR ChoosePresentMode(const std::vector<vk::PresentModeKHR>& modes);
|
||||||
vk::Extent2D ChooseExtent(const vk::SurfaceCapabilitiesKHR& caps, Window& window);
|
vk::Extent2D ChooseExtent(const vk::SurfaceCapabilitiesKHR& caps, Window& window);
|
||||||
|
|
||||||
void CreateDepthResources();
|
|
||||||
void DestroyDepthResources();
|
|
||||||
vk::Format FindDepthFormat();
|
|
||||||
|
|
||||||
|
|
||||||
Device& m_Device;
|
Device& m_Device;
|
||||||
|
|
||||||
@@ -58,11 +52,6 @@ namespace Engine
|
|||||||
std::vector<vk::Image> m_Images;
|
std::vector<vk::Image> m_Images;
|
||||||
std::vector<vk::ImageView> m_ImageViews;
|
std::vector<vk::ImageView> m_ImageViews;
|
||||||
|
|
||||||
vk::Image m_DepthImage;
|
|
||||||
vk::DeviceMemory m_DepthMemory;
|
|
||||||
vk::ImageView m_DepthImageView;
|
|
||||||
vk::Format m_DepthFormat;
|
|
||||||
|
|
||||||
MemoryAllocator& m_Allocator;
|
MemoryAllocator& m_Allocator;
|
||||||
|
|
||||||
vk::Format m_Format;
|
vk::Format m_Format;
|
||||||
|
|||||||
@@ -5,33 +5,42 @@
|
|||||||
#ifndef HELLOVULKAN_UNIFORMBUFFER_H
|
#ifndef HELLOVULKAN_UNIFORMBUFFER_H
|
||||||
#define HELLOVULKAN_UNIFORMBUFFER_H
|
#define HELLOVULKAN_UNIFORMBUFFER_H
|
||||||
|
|
||||||
|
|
||||||
#include <Engine/Core/Types.h>
|
#include <Engine/Core/Types.h>
|
||||||
|
#include <Engine/Core/FrameContext.h>
|
||||||
#include <Engine/Vulkan/Buffer.h>
|
#include <Engine/Vulkan/Buffer.h>
|
||||||
#include <vulkan/vulkan.hpp>
|
#include <vulkan/vulkan.hpp>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace Engine {
|
namespace Engine {
|
||||||
|
|
||||||
class Device;
|
class Device;
|
||||||
class MemoryAllocator;
|
class MemoryAllocator;
|
||||||
|
|
||||||
|
// Per-frame UBO ring:每个帧槽位一个独立 buffer。
|
||||||
|
//
|
||||||
|
// 正确性:CPU 在第 N 帧写 UBO 时,GPU 可能仍在读取第 N-1 帧的同槽位数据。
|
||||||
|
// 共享单个 buffer 会产生数据竞争;ring 化后每槽位的写入严格发生在
|
||||||
|
// 该槽位 fence 确认完成之后。
|
||||||
|
// 后续演进方向:单个大 ring buffer + 动态 offset(省描述符、省内存),
|
||||||
|
// 接口已按"写入哪个槽位"设计,届时只是内部实现替换。
|
||||||
class UniformBuffer {
|
class UniformBuffer {
|
||||||
public:
|
public:
|
||||||
UniformBuffer(Device& device, MemoryAllocator& allocator, u64 size);
|
UniformBuffer(Device& device, MemoryAllocator& allocator,
|
||||||
~UniformBuffer();
|
u64 size, u32 frameCount);
|
||||||
|
~UniformBuffer() = default;
|
||||||
|
|
||||||
UniformBuffer(const UniformBuffer&) = delete;
|
UniformBuffer(const UniformBuffer&) = delete;
|
||||||
UniformBuffer& operator=(const UniformBuffer&) = delete;
|
UniformBuffer& operator=(const UniformBuffer&) = delete;
|
||||||
|
|
||||||
vk::Buffer Get() const { return m_Buffer->Get(); }
|
vk::Buffer Get(u32 frameSlot) const { return m_Buffers[frameSlot]->Get(); }
|
||||||
u64 GetSize() const { return m_Size; }
|
u64 GetSize() const { return m_Size; }
|
||||||
|
|
||||||
// 每帧更新数据
|
// 写入指定帧槽位的 UBO(仅在该槽位 fence 确认完成后调用)
|
||||||
void Update(const void* data, u64 size);
|
void Update(u32 frameSlot, const void* data, u64 size);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Scope<Buffer> m_Buffer;
|
std::vector<Scope<Buffer>> m_Buffers;
|
||||||
u64 m_Size;
|
u64 m_Size = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace Engine
|
} // namespace Engine
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
//
|
||||||
|
// Created by atdunbg on 2026/9/22.
|
||||||
|
//
|
||||||
|
|
||||||
|
#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>
|
||||||
|
|
||||||
|
namespace Engine::Assets {
|
||||||
|
|
||||||
|
AssetManager::AssetManager(Device& device, MemoryAllocator& allocator,
|
||||||
|
DescriptorManager& descriptors,
|
||||||
|
vk::DescriptorSetLayout textureLayout)
|
||||||
|
: m_Device(device), m_Allocator(allocator),
|
||||||
|
m_Descriptors(descriptors), m_TextureLayout(textureLayout)
|
||||||
|
{
|
||||||
|
ENGINE_INFO("AssetManager created");
|
||||||
|
}
|
||||||
|
|
||||||
|
ModelHandle AssetManager::LoadModel(const std::string& path)
|
||||||
|
{
|
||||||
|
return m_Models.Load(path, [this](const std::string& p) -> Scope<Model> {
|
||||||
|
// GPU 上传在此发生:资产层调用渲染层接口完成 buffer/image 创建
|
||||||
|
return CreateScope<Model>(
|
||||||
|
m_Device, m_Allocator, m_Descriptors, m_TextureLayout, p);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Engine::Assets
|
||||||
@@ -0,0 +1,115 @@
|
|||||||
|
//
|
||||||
|
// Created by atdunbg on 2026/9/22.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <Engine/Core/Application.h>
|
||||||
|
#include <Engine/Core/Window.h>
|
||||||
|
#include <Engine/Core/Log.h>
|
||||||
|
#include <Engine/ECS/Scene.h>
|
||||||
|
#include <Engine/Renderer/Renderer.h>
|
||||||
|
#include <Engine/Assets/AssetManager.h>
|
||||||
|
#include <Engine/Vulkan/Device.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
namespace Engine {
|
||||||
|
|
||||||
|
Application::Application(std::string title, u32 width, u32 height)
|
||||||
|
{
|
||||||
|
m_Window = CreateScope<Window>(std::move(title), width, height);
|
||||||
|
|
||||||
|
// 事件通道:GLFW 回调 → 本类 DispatchEvent → 派生类钩子
|
||||||
|
m_Window->SetEventCallback(
|
||||||
|
[this](const Event& e) { DispatchEvent(e); });
|
||||||
|
|
||||||
|
// 场景(ECS 世界)+ 渲染器
|
||||||
|
m_Scene = CreateScope<ECS::Scene>();
|
||||||
|
m_Renderer = CreateScope<Renderer>(*m_Window);
|
||||||
|
|
||||||
|
// 资产层:复用渲染器的 GPU 上下文;
|
||||||
|
// 声明在 Renderer 之后 → 析构先于 Renderer(它引用其子模块)
|
||||||
|
m_Assets = CreateScope<Assets::AssetManager>(
|
||||||
|
m_Renderer->GetDevice(),
|
||||||
|
m_Renderer->GetAllocator(),
|
||||||
|
m_Renderer->GetDescriptorManager(),
|
||||||
|
m_Renderer->GetTextureSetLayout());
|
||||||
|
}
|
||||||
|
|
||||||
|
Application::~Application()
|
||||||
|
{
|
||||||
|
// 关键:必须在成员析构之前等 GPU 空闲。
|
||||||
|
//
|
||||||
|
// 成员析构序(后声明先析构)是 m_Assets → m_Renderer → m_Scene → m_Window:
|
||||||
|
// 资产层持有全部 GPU buffer/texture/sampler,会先于 Renderer 析构,
|
||||||
|
// 而 Renderer 的 waitIdle 在它自己的析构里——那时资产已经销毁了。
|
||||||
|
// 若最后一帧命令缓冲仍在 GPU 上执行,就会出现
|
||||||
|
// VUID-vkDestroyBuffer-buffer-00922(资源销毁时仍在使用)。
|
||||||
|
// 这里显式等待,保证成员按任意顺序析构都是安全的。
|
||||||
|
if (m_Renderer) {
|
||||||
|
m_Renderer->GetDevice().GetDevice().waitIdle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::Run()
|
||||||
|
{
|
||||||
|
ENGINE_INFO("Application starting");
|
||||||
|
|
||||||
|
m_Running = true;
|
||||||
|
m_LastFrameTime = std::chrono::steady_clock::now();
|
||||||
|
|
||||||
|
OnInit();
|
||||||
|
|
||||||
|
while (m_Running && !m_Window->ShouldClose()) {
|
||||||
|
m_Window->PollEvents();
|
||||||
|
|
||||||
|
auto now = std::chrono::steady_clock::now();
|
||||||
|
f32 dt = std::chrono::duration<f32>(now - m_LastFrameTime).count();
|
||||||
|
// 钳制卡顿(断点/窗口拖动导致的超大 dt),避免物理/动画穿模
|
||||||
|
m_DeltaTime = std::clamp(dt, 0.0f, 0.25f);
|
||||||
|
m_LastFrameTime = now;
|
||||||
|
|
||||||
|
OnUpdate(m_DeltaTime);
|
||||||
|
|
||||||
|
// 延迟销毁在渲染前统一 flush,保证渲染遍历时池结构不变
|
||||||
|
m_Scene->FlushDestroyQueue();
|
||||||
|
|
||||||
|
OnRender();
|
||||||
|
|
||||||
|
++m_FrameCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
OnShutdown();
|
||||||
|
ENGINE_INFO("Application stopped ({} frames)", m_FrameCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::OnRender()
|
||||||
|
{
|
||||||
|
m_Renderer->Render(*m_Scene, *m_Assets);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::DispatchEvent(const Event& event)
|
||||||
|
{
|
||||||
|
OnEvent(event);
|
||||||
|
|
||||||
|
switch (event.type) {
|
||||||
|
case EventType::WindowClose: OnWindowClose(event); break;
|
||||||
|
case EventType::WindowResize: OnWindowResize(event); break;
|
||||||
|
case EventType::WindowFocus: OnWindowFocus(event); break;
|
||||||
|
case EventType::WindowLostFocus: OnWindowLostFocus(event); break;
|
||||||
|
case EventType::KeyPress: OnKeyPress(event); break;
|
||||||
|
case EventType::KeyRelease: OnKeyRelease(event); break;
|
||||||
|
case EventType::MouseButtonPress: OnMouseButtonPress(event); break;
|
||||||
|
case EventType::MouseButtonRelease: OnMouseButtonRelease(event); break;
|
||||||
|
case EventType::MouseMove: OnMouseMove(event); break;
|
||||||
|
case EventType::MouseScroll: OnMouseScroll(event); break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Application::OnWindowClose(const Event& event)
|
||||||
|
{
|
||||||
|
// 默认行为:直接退出。需要"退出确认"的程序覆写本钩子。
|
||||||
|
Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Engine
|
||||||
@@ -14,6 +14,73 @@ namespace Engine
|
|||||||
{
|
{
|
||||||
auto* self = static_cast<Window*>(glfwGetWindowUserPointer(window));
|
auto* self = static_cast<Window*>(glfwGetWindowUserPointer(window));
|
||||||
self->m_FramebufferResized = true;
|
self->m_FramebufferResized = true;
|
||||||
|
|
||||||
|
if (self->m_EventCallback) {
|
||||||
|
Event e;
|
||||||
|
e.type = EventType::WindowResize;
|
||||||
|
e.width = width;
|
||||||
|
e.height = height;
|
||||||
|
self->m_EventCallback(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Window::KeyCallback(GLFWwindow* window, int key, int scancode, int action, int mods)
|
||||||
|
{
|
||||||
|
auto* self = static_cast<Window*>(glfwGetWindowUserPointer(window));
|
||||||
|
if (!self->m_EventCallback || key == GLFW_KEY_UNKNOWN) return;
|
||||||
|
|
||||||
|
Event e;
|
||||||
|
e.key = key;
|
||||||
|
e.type = (action == GLFW_PRESS) ? EventType::KeyPress
|
||||||
|
: (action == GLFW_RELEASE) ? EventType::KeyRelease
|
||||||
|
: EventType::None; // GLFW_REPEAT 暂不透出
|
||||||
|
if (e.type != EventType::None) self->m_EventCallback(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Window::MouseButtonCallback(GLFWwindow* window, int button, int action, int mods)
|
||||||
|
{
|
||||||
|
auto* self = static_cast<Window*>(glfwGetWindowUserPointer(window));
|
||||||
|
if (!self->m_EventCallback) return;
|
||||||
|
|
||||||
|
Event e;
|
||||||
|
e.button = button;
|
||||||
|
e.type = (action == GLFW_PRESS) ? EventType::MouseButtonPress
|
||||||
|
: EventType::MouseButtonRelease;
|
||||||
|
self->m_EventCallback(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Window::CursorPosCallback(GLFWwindow* window, double xpos, double ypos)
|
||||||
|
{
|
||||||
|
auto* self = static_cast<Window*>(glfwGetWindowUserPointer(window));
|
||||||
|
if (!self->m_EventCallback) return;
|
||||||
|
|
||||||
|
Event e;
|
||||||
|
e.type = EventType::MouseMove;
|
||||||
|
e.x = static_cast<f32>(xpos);
|
||||||
|
e.y = static_cast<f32>(ypos);
|
||||||
|
self->m_EventCallback(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Window::ScrollCallback(GLFWwindow* window, double xoffset, double yoffset)
|
||||||
|
{
|
||||||
|
auto* self = static_cast<Window*>(glfwGetWindowUserPointer(window));
|
||||||
|
if (!self->m_EventCallback) return;
|
||||||
|
|
||||||
|
Event e;
|
||||||
|
e.type = EventType::MouseScroll;
|
||||||
|
e.deltaX = static_cast<f32>(xoffset);
|
||||||
|
e.deltaY = static_cast<f32>(yoffset);
|
||||||
|
self->m_EventCallback(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Window::WindowFocusCallback(GLFWwindow* window, int focused)
|
||||||
|
{
|
||||||
|
auto* self = static_cast<Window*>(glfwGetWindowUserPointer(window));
|
||||||
|
if (!self->m_EventCallback) return;
|
||||||
|
|
||||||
|
Event e;
|
||||||
|
e.type = focused ? EventType::WindowFocus : EventType::WindowLostFocus;
|
||||||
|
self->m_EventCallback(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
Window::Window(const std::string& title, u32 width, u32 height)
|
Window::Window(const std::string& title, u32 width, u32 height)
|
||||||
@@ -42,6 +109,11 @@ namespace Engine
|
|||||||
|
|
||||||
glfwSetWindowUserPointer(m_Window, this);
|
glfwSetWindowUserPointer(m_Window, this);
|
||||||
glfwSetFramebufferSizeCallback(m_Window, FramebufferResizeCallback);
|
glfwSetFramebufferSizeCallback(m_Window, FramebufferResizeCallback);
|
||||||
|
glfwSetKeyCallback(m_Window, KeyCallback);
|
||||||
|
glfwSetMouseButtonCallback(m_Window, MouseButtonCallback);
|
||||||
|
glfwSetCursorPosCallback(m_Window, CursorPosCallback);
|
||||||
|
glfwSetScrollCallback(m_Window, ScrollCallback);
|
||||||
|
glfwSetWindowFocusCallback(m_Window, WindowFocusCallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
Window::~Window()
|
Window::~Window()
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
//
|
||||||
|
// Created by atdunbg on 2026/9/22.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <Engine/ECS/Scene.h>
|
||||||
|
#include <Engine/Core/Log.h>
|
||||||
|
|
||||||
|
namespace Engine::ECS {
|
||||||
|
|
||||||
|
Entity Scene::CreateEntity(std::string name)
|
||||||
|
{
|
||||||
|
Entity e = m_Registry.Create();
|
||||||
|
// IdComponent 自动挂载:每个实体出生即有稳定 UUID
|
||||||
|
m_Registry.Add<IdComponent>(e, IdComponent{UUID::New()});
|
||||||
|
if (!name.empty()) {
|
||||||
|
m_Registry.Add<NameComponent>(e, NameComponent{std::move(name)});
|
||||||
|
}
|
||||||
|
return e;
|
||||||
|
}
|
||||||
|
|
||||||
|
Entity Scene::FindEntityByUUID(UUID uuid)
|
||||||
|
{
|
||||||
|
if (!uuid.Valid()) return {};
|
||||||
|
|
||||||
|
Entity found{};
|
||||||
|
m_Registry.Each<IdComponent>([&](Entity e, IdComponent& id) {
|
||||||
|
if (!found && id.uuid == uuid) found = e;
|
||||||
|
});
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Scene::DestroyEntity(Entity e)
|
||||||
|
{
|
||||||
|
if (!m_Registry.IsAlive(e)) {
|
||||||
|
ENGINE_FATAL("Scene::DestroyEntity: stale entity ({},{})",
|
||||||
|
e.index, e.generation);
|
||||||
|
}
|
||||||
|
m_PendingDestroy.push_back(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Scene::FlushDestroyQueue()
|
||||||
|
{
|
||||||
|
if (m_PendingDestroy.empty()) return;
|
||||||
|
|
||||||
|
for (Entity e : m_PendingDestroy) {
|
||||||
|
if (m_Registry.IsAlive(e)) {
|
||||||
|
m_Registry.Destroy(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_PendingDestroy.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
CameraComponent* Scene::GetPrimaryCamera()
|
||||||
|
{
|
||||||
|
CameraComponent* found = nullptr;
|
||||||
|
m_Registry.Each<CameraComponent>([&](Entity, CameraComponent& cc) {
|
||||||
|
if (!found && cc.primary) found = &cc;
|
||||||
|
});
|
||||||
|
return found;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Engine::ECS
|
||||||
@@ -0,0 +1,781 @@
|
|||||||
|
//
|
||||||
|
// Created by atdunbg on 2026/9/22.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <Engine/RenderGraph/RenderGraph.h>
|
||||||
|
#include <Engine/Vulkan/Device.h>
|
||||||
|
#include <Engine/Core/Log.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <set>
|
||||||
|
|
||||||
|
namespace Engine::RG {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
bool IsWriteUse(ResourceUse use)
|
||||||
|
{
|
||||||
|
switch (use) {
|
||||||
|
case ResourceUse::ColorAttachment:
|
||||||
|
case ResourceUse::DepthAttachment:
|
||||||
|
case ResourceUse::TransferDst:
|
||||||
|
case ResourceUse::Present:
|
||||||
|
return true;
|
||||||
|
case ResourceUse::ShaderRead:
|
||||||
|
case ResourceUse::TransferSrc:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
// ---------- 构造 / 析构 ----------
|
||||||
|
|
||||||
|
RenderGraph::RenderGraph(Device& device, MemoryAllocator& allocator, u32 framesInFlight)
|
||||||
|
: m_Device(device), m_Allocator(allocator), m_FramesInFlight(framesInFlight)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderGraph::~RenderGraph()
|
||||||
|
{
|
||||||
|
ReleasePhysical();
|
||||||
|
|
||||||
|
const auto& vkDevice = m_Device.GetDevice();
|
||||||
|
for (auto& [key, images] : m_Pool) {
|
||||||
|
for (auto& img : images) {
|
||||||
|
if (!img->valid) continue;
|
||||||
|
vkDevice.destroyImageView(img->view);
|
||||||
|
vkDevice.destroyImage(img->image);
|
||||||
|
m_Allocator.Free(img->allocation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m_Pool.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- 资源声明 ----------
|
||||||
|
|
||||||
|
TextureHandle RenderGraph::CreateTexture(std::string name, const TextureDesc& desc)
|
||||||
|
{
|
||||||
|
CheckMutable();
|
||||||
|
|
||||||
|
TextureHandle h{static_cast<u32>(m_Textures.size()), 1};
|
||||||
|
|
||||||
|
TextureEntry entry;
|
||||||
|
entry.name = std::move(name);
|
||||||
|
entry.index = h.index;
|
||||||
|
entry.desc = desc;
|
||||||
|
entry.imported = false;
|
||||||
|
entry.generation = h.generation;
|
||||||
|
entry.physical.resize(m_FramesInFlight, nullptr);
|
||||||
|
entry.slots.resize(m_FramesInFlight);
|
||||||
|
|
||||||
|
m_Textures.push_back(std::move(entry));
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
|
||||||
|
TextureHandle RenderGraph::ImportSwapchain(std::string name, vk::Format format)
|
||||||
|
{
|
||||||
|
CheckMutable();
|
||||||
|
|
||||||
|
TextureHandle h{static_cast<u32>(m_Textures.size()), 1};
|
||||||
|
|
||||||
|
TextureEntry entry;
|
||||||
|
entry.name = std::move(name);
|
||||||
|
entry.index = h.index;
|
||||||
|
entry.desc.format = format;
|
||||||
|
entry.imported = true;
|
||||||
|
entry.generation = h.generation;
|
||||||
|
entry.physical.resize(m_FramesInFlight, nullptr);
|
||||||
|
entry.slots.resize(m_FramesInFlight);
|
||||||
|
|
||||||
|
m_Textures.push_back(std::move(entry));
|
||||||
|
return h;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderGraph::SetImportedImage(TextureHandle h, vk::Image image, vk::ImageView view)
|
||||||
|
{
|
||||||
|
auto& entry = GetEntry(h);
|
||||||
|
if (!entry.imported) {
|
||||||
|
ENGINE_FATAL("RenderGraph: SetImportedImage on non-imported resource '{}'", entry.name);
|
||||||
|
}
|
||||||
|
entry.importedImage = image;
|
||||||
|
entry.importedView = view;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- Pass 声明 ----------
|
||||||
|
|
||||||
|
RenderGraph::PassBuilder RenderGraph::AddPass(std::string name, ExecuteFn exec)
|
||||||
|
{
|
||||||
|
CheckMutable();
|
||||||
|
|
||||||
|
PassNode node;
|
||||||
|
node.name = std::move(name);
|
||||||
|
node.exec = std::move(exec);
|
||||||
|
|
||||||
|
m_Passes.push_back(std::move(node));
|
||||||
|
return PassBuilder(*this, static_cast<u32>(m_Passes.size() - 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderGraph::PassBuilder& RenderGraph::PassBuilder::Write(TextureHandle h, ResourceUse use)
|
||||||
|
{
|
||||||
|
m_Graph.CheckMutable();
|
||||||
|
m_Graph.m_Passes[m_PassIndex].uses.push_back({h.index, use, {}});
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderGraph::PassBuilder& RenderGraph::PassBuilder::Read(TextureHandle h, ResourceUse use,
|
||||||
|
vk::ShaderStageFlags stage)
|
||||||
|
{
|
||||||
|
m_Graph.CheckMutable();
|
||||||
|
m_Graph.m_Passes[m_PassIndex].uses.push_back({h.index, use, stage});
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderGraph::PassBuilder& RenderGraph::PassBuilder::SideEffect()
|
||||||
|
{
|
||||||
|
m_Graph.CheckMutable();
|
||||||
|
m_Graph.m_Passes[m_PassIndex].sideEffect = true;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- 编译 ----------
|
||||||
|
|
||||||
|
void RenderGraph::Compile()
|
||||||
|
{
|
||||||
|
if (m_TargetExtent.width == 0 || m_TargetExtent.height == 0) {
|
||||||
|
ENGINE_FATAL("RenderGraph: Compile before SetTargetExtent");
|
||||||
|
}
|
||||||
|
|
||||||
|
ReleasePhysical();
|
||||||
|
|
||||||
|
// 清理上一次编译的中间结果
|
||||||
|
for (auto& entry : m_Textures) {
|
||||||
|
entry.usage = {};
|
||||||
|
entry.firstPass = -1;
|
||||||
|
entry.lastPass = -1;
|
||||||
|
entry.chainLayout = vk::ImageLayout::eUndefined;
|
||||||
|
entry.chainStage = {};
|
||||||
|
entry.chainAccess = {};
|
||||||
|
entry.chainWasWrite = false;
|
||||||
|
}
|
||||||
|
for (auto& pass : m_Passes) {
|
||||||
|
pass.culled = false;
|
||||||
|
pass.barriers.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& entry : m_Textures) ResolveUsage(entry);
|
||||||
|
|
||||||
|
CullPasses();
|
||||||
|
Toposort();
|
||||||
|
ComputeLifetimes();
|
||||||
|
PrecomputeBarriers();
|
||||||
|
AllocatePhysical();
|
||||||
|
|
||||||
|
m_Compiled = true;
|
||||||
|
m_ActivePassCount = static_cast<u32>(
|
||||||
|
std::count_if(m_Passes.begin(), m_Passes.end(),
|
||||||
|
[](const PassNode& p) { return !p.culled; }));
|
||||||
|
|
||||||
|
ENGINE_INFO("RenderGraph compiled: {} passes ({} active), {} textures, {} barriers, {} gpu images",
|
||||||
|
m_Passes.size(), m_ActivePassCount, m_Textures.size(),
|
||||||
|
GetBarrierCount(), GetPooledImageCount());
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderGraph::SetTargetExtent(vk::Extent2D extent)
|
||||||
|
{
|
||||||
|
if (extent == m_TargetExtent) return;
|
||||||
|
|
||||||
|
// 尺寸变化:调用方必须保证 GPU 空闲(通常在 swapchain recreate 的 waitIdle 之后)
|
||||||
|
ReleasePhysical();
|
||||||
|
PurgePool(extent);
|
||||||
|
|
||||||
|
// 物理镜像全部换新,槽位状态清零
|
||||||
|
for (auto& entry : m_Textures) {
|
||||||
|
for (auto& slot : entry.slots) slot.used = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_TargetExtent = extent;
|
||||||
|
Compile();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderGraph::ResolveUsage(TextureEntry& entry)
|
||||||
|
{
|
||||||
|
if (entry.imported) return;
|
||||||
|
|
||||||
|
vk::ImageUsageFlags usage{};
|
||||||
|
for (const auto& pass : m_Passes) {
|
||||||
|
for (const auto& use : pass.uses) {
|
||||||
|
if (use.resIndex != entry.index) continue;
|
||||||
|
switch (use.use) {
|
||||||
|
case ResourceUse::ColorAttachment:
|
||||||
|
case ResourceUse::Present:
|
||||||
|
usage |= vk::ImageUsageFlagBits::eColorAttachment;
|
||||||
|
break;
|
||||||
|
case ResourceUse::DepthAttachment:
|
||||||
|
usage |= vk::ImageUsageFlagBits::eDepthStencilAttachment;
|
||||||
|
entry.aspect = vk::ImageAspectFlagBits::eDepth;
|
||||||
|
break;
|
||||||
|
case ResourceUse::ShaderRead:
|
||||||
|
usage |= vk::ImageUsageFlagBits::eSampled;
|
||||||
|
break;
|
||||||
|
case ResourceUse::TransferSrc:
|
||||||
|
usage |= vk::ImageUsageFlagBits::eTransferSrc;
|
||||||
|
break;
|
||||||
|
case ResourceUse::TransferDst:
|
||||||
|
usage |= vk::ImageUsageFlagBits::eTransferDst;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
entry.usage = usage;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderGraph::CullPasses()
|
||||||
|
{
|
||||||
|
// 引用计数剔除:一个 pass 存活当且仅当
|
||||||
|
// a) 声明了 sideEffect,或
|
||||||
|
// b) 它读写某个导入资源(对外可见),或
|
||||||
|
// c) 它写出的某个资源被某个存活 pass 读取。
|
||||||
|
// 迭代到不动点(最多 pass 数轮)。
|
||||||
|
|
||||||
|
std::vector<bool> kept(m_Passes.size(), false);
|
||||||
|
std::vector<bool> readByKept(m_Textures.size(), false);
|
||||||
|
|
||||||
|
bool changed = true;
|
||||||
|
while (changed) {
|
||||||
|
changed = false;
|
||||||
|
for (u32 i = 0; i < m_Passes.size(); ++i) {
|
||||||
|
if (kept[i]) continue;
|
||||||
|
const auto& pass = m_Passes[i];
|
||||||
|
|
||||||
|
bool keep = pass.sideEffect;
|
||||||
|
for (const auto& use : pass.uses) {
|
||||||
|
if (IsWriteUse(use.use) && readByKept[use.resIndex]) {
|
||||||
|
keep = true;
|
||||||
|
} else if (m_Textures[use.resIndex].imported) {
|
||||||
|
keep = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!keep) continue;
|
||||||
|
|
||||||
|
kept[i] = true;
|
||||||
|
changed = true;
|
||||||
|
for (const auto& use : pass.uses) {
|
||||||
|
if (!IsWriteUse(use.use)) readByKept[use.resIndex] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (u32 i = 0; i < m_Passes.size(); ++i) {
|
||||||
|
m_Passes[i].culled = !kept[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderGraph::Toposort()
|
||||||
|
{
|
||||||
|
// writer → reader 建边(去重),Kahn 拓扑排序。
|
||||||
|
// 声明顺序通常已是拓扑序,但引擎级实现必须显式校验,
|
||||||
|
// 避免上层重构 pass 顺序时引入环。
|
||||||
|
|
||||||
|
std::vector<std::vector<u32>> writers(m_Textures.size());
|
||||||
|
for (u32 i = 0; i < m_Passes.size(); ++i) {
|
||||||
|
if (m_Passes[i].culled) continue;
|
||||||
|
for (const auto& use : m_Passes[i].uses) {
|
||||||
|
if (IsWriteUse(use.use)) {
|
||||||
|
writers[use.resIndex].push_back(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<std::set<u32>> adj(m_Passes.size());
|
||||||
|
std::vector<u32> indegree(m_Passes.size(), 0);
|
||||||
|
|
||||||
|
for (u32 q = 0; q < m_Passes.size(); ++q) {
|
||||||
|
if (m_Passes[q].culled) continue;
|
||||||
|
std::set<u32> seenWriters;
|
||||||
|
for (const auto& use : m_Passes[q].uses) {
|
||||||
|
if (IsWriteUse(use.use)) continue;
|
||||||
|
for (u32 p : writers[use.resIndex]) {
|
||||||
|
if (p == q || m_Passes[p].culled) continue;
|
||||||
|
if (seenWriters.insert(p).second) {
|
||||||
|
adj[p].insert(q);
|
||||||
|
++indegree[q];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
m_ExecutionOrder.clear();
|
||||||
|
m_ExecutionOrder.reserve(m_Passes.size());
|
||||||
|
|
||||||
|
// 同层按声明序出队,保证确定性
|
||||||
|
std::set<u32> ready;
|
||||||
|
for (u32 i = 0; i < m_Passes.size(); ++i) {
|
||||||
|
if (!m_Passes[i].culled && indegree[i] == 0) ready.insert(i);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (!ready.empty()) {
|
||||||
|
u32 p = *ready.begin();
|
||||||
|
ready.erase(ready.begin());
|
||||||
|
m_ExecutionOrder.push_back(p);
|
||||||
|
|
||||||
|
for (u32 q : adj[p]) {
|
||||||
|
if (--indegree[q] == 0) ready.insert(q);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 activeCount = 0;
|
||||||
|
for (const auto& pass : m_Passes) {
|
||||||
|
if (!pass.culled) ++activeCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_ExecutionOrder.size() != activeCount) {
|
||||||
|
ENGINE_FATAL("RenderGraph: cycle detected in pass declarations");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderGraph::ComputeLifetimes()
|
||||||
|
{
|
||||||
|
for (u32 pos = 0; pos < m_ExecutionOrder.size(); ++pos) {
|
||||||
|
const u32 passIdx = m_ExecutionOrder[pos];
|
||||||
|
for (const auto& use : m_Passes[passIdx].uses) {
|
||||||
|
auto& entry = m_Textures[use.resIndex];
|
||||||
|
if (entry.firstPass < 0) entry.firstPass = static_cast<i32>(pos);
|
||||||
|
entry.lastPass = static_cast<i32>(pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderGraph::PrecomputeBarriers()
|
||||||
|
{
|
||||||
|
// 两阶段:
|
||||||
|
// Pass A:按执行序推演每个资源的状态链,得到稳态(resting)。
|
||||||
|
// Pass B:真正生成 barrier。
|
||||||
|
//
|
||||||
|
// entry barrier(资源在该 pass 首次被使用)有两个变体:
|
||||||
|
// first — 该物理实例的槽位尚未用过:old = Undefined(无历史依赖)
|
||||||
|
// steady — 槽位已用过:old = resting(上一轮执行结束时的状态)。
|
||||||
|
// 同一槽位两次进入图之间有 CPU fence 全等保,状态链闭合。
|
||||||
|
// imported 资源(swapchain)acquire 后 layout 语义上总是 Undefined,恒用 first。
|
||||||
|
//
|
||||||
|
// 非 entry:layout 变化或存在写读/读写/写写依赖时插入(read-read 无害跳过)。
|
||||||
|
|
||||||
|
// ---- Pass A:状态链推演 ----
|
||||||
|
for (const u32 passIdx : m_ExecutionOrder) {
|
||||||
|
for (const auto& use : m_Passes[passIdx].uses) {
|
||||||
|
auto& entry = m_Textures[use.resIndex];
|
||||||
|
entry.chainLayout = UseLayout(use.use);
|
||||||
|
entry.chainStage = UseDstStage(use);
|
||||||
|
entry.chainAccess = UseDstAccess(use);
|
||||||
|
entry.chainWasWrite = IsWriteUse(use.use);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (auto& entry : m_Textures) {
|
||||||
|
if (entry.imported || entry.firstPass < 0) continue;
|
||||||
|
entry.restingLayout = entry.chainLayout;
|
||||||
|
entry.restingStage = entry.chainStage;
|
||||||
|
entry.restingAccess = entry.chainAccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- Pass B:barrier 生成 ----
|
||||||
|
for (auto& entry : m_Textures) {
|
||||||
|
entry.chainLayout = vk::ImageLayout::eUndefined;
|
||||||
|
entry.chainStage = {};
|
||||||
|
entry.chainAccess = {};
|
||||||
|
entry.chainWasWrite = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (u32 pos = 0; pos < m_ExecutionOrder.size(); ++pos) {
|
||||||
|
auto& pass = m_Passes[m_ExecutionOrder[pos]];
|
||||||
|
|
||||||
|
for (const auto& use : pass.uses) {
|
||||||
|
auto& entry = m_Textures[use.resIndex];
|
||||||
|
|
||||||
|
const vk::ImageLayout desiredLayout = UseLayout(use.use);
|
||||||
|
const vk::PipelineStageFlags2 dstStage = UseDstStage(use);
|
||||||
|
const vk::AccessFlags2 dstAccess = UseDstAccess(use);
|
||||||
|
const bool curIsWrite = IsWriteUse(use.use);
|
||||||
|
|
||||||
|
auto makeBarrier = [&](vk::ImageLayout oldLayout,
|
||||||
|
vk::PipelineStageFlags2 srcStage,
|
||||||
|
vk::AccessFlags2 srcAccess) {
|
||||||
|
vk::ImageMemoryBarrier2 b;
|
||||||
|
b.setSrcStageMask(srcStage)
|
||||||
|
.setSrcAccessMask(srcAccess)
|
||||||
|
.setDstStageMask(dstStage)
|
||||||
|
.setDstAccessMask(dstAccess)
|
||||||
|
.setOldLayout(oldLayout)
|
||||||
|
.setNewLayout(desiredLayout)
|
||||||
|
// image 句柄由 AllocatePhysical/每帧导入回填
|
||||||
|
.setSubresourceRange({entry.aspect, 0, 1, 0, 1});
|
||||||
|
return b;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (entry.firstPass == static_cast<i32>(pos)) {
|
||||||
|
// entry barrier:两个变体
|
||||||
|
vk::ImageMemoryBarrier2 first = makeBarrier(
|
||||||
|
vk::ImageLayout::eUndefined,
|
||||||
|
vk::PipelineStageFlagBits2::eNone,
|
||||||
|
vk::AccessFlagBits2::eNone);
|
||||||
|
|
||||||
|
vk::ImageMemoryBarrier2 steady;
|
||||||
|
if (entry.imported) {
|
||||||
|
// swapchain:每帧 acquire 后都是 Undefined,两变体相同
|
||||||
|
steady = first;
|
||||||
|
} else {
|
||||||
|
steady = makeBarrier(entry.restingLayout,
|
||||||
|
entry.restingStage,
|
||||||
|
entry.restingAccess);
|
||||||
|
}
|
||||||
|
|
||||||
|
pass.barriers.push_back({use.resIndex, steady, first});
|
||||||
|
entry.chainLayout = desiredLayout;
|
||||||
|
entry.chainStage = dstStage;
|
||||||
|
entry.chainAccess = dstAccess;
|
||||||
|
entry.chainWasWrite = curIsWrite;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 非 entry:layout 变化,或存在写读/读写/写写依赖时插入
|
||||||
|
bool need = entry.chainLayout != desiredLayout ||
|
||||||
|
entry.chainWasWrite || curIsWrite;
|
||||||
|
if (!need) continue;
|
||||||
|
|
||||||
|
vk::ImageMemoryBarrier2 b = makeBarrier(
|
||||||
|
entry.chainLayout, entry.chainStage, entry.chainAccess);
|
||||||
|
|
||||||
|
pass.barriers.push_back({use.resIndex, b, b});
|
||||||
|
entry.chainLayout = desiredLayout;
|
||||||
|
entry.chainStage = dstStage;
|
||||||
|
entry.chainAccess = dstAccess;
|
||||||
|
entry.chainWasWrite = curIsWrite;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderGraph::AllocatePhysical()
|
||||||
|
{
|
||||||
|
for (auto& entry : m_Textures) {
|
||||||
|
if (entry.imported || entry.firstPass < 0) continue;
|
||||||
|
|
||||||
|
vk::Extent2D extent{
|
||||||
|
entry.desc.width ? entry.desc.width : m_TargetExtent.width,
|
||||||
|
entry.desc.height ? entry.desc.height : m_TargetExtent.height
|
||||||
|
};
|
||||||
|
|
||||||
|
for (u32 slot = 0; slot < m_FramesInFlight; ++slot) {
|
||||||
|
entry.physical[slot] = AcquirePhysical(
|
||||||
|
entry.desc.format, extent, entry.usage, entry.aspect);
|
||||||
|
}
|
||||||
|
// barrier 的 image 句柄在 Execute 期按帧槽位绑定(见 Execute)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderGraph::ReleasePhysical()
|
||||||
|
{
|
||||||
|
for (auto& entry : m_Textures) {
|
||||||
|
if (entry.imported) continue;
|
||||||
|
for (u32 slot = 0; slot < entry.physical.size(); ++slot) {
|
||||||
|
auto* img = entry.physical[slot];
|
||||||
|
if (!img) continue;
|
||||||
|
|
||||||
|
PoolKey key{
|
||||||
|
static_cast<u32>(img->format),
|
||||||
|
img->extent.width, img->extent.height,
|
||||||
|
static_cast<u32>(img->usage)
|
||||||
|
};
|
||||||
|
m_Pool[key].push_back(Scope<PhysicalImage>(img));
|
||||||
|
entry.physical[slot] = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderGraph::PurgePool(vk::Extent2D target)
|
||||||
|
{
|
||||||
|
const auto& vkDevice = m_Device.GetDevice();
|
||||||
|
|
||||||
|
for (auto it = m_Pool.begin(); it != m_Pool.end();) {
|
||||||
|
if (it->first.width != target.width || it->first.height != target.height) {
|
||||||
|
for (auto& img : it->second) {
|
||||||
|
if (!img->valid) continue;
|
||||||
|
vkDevice.destroyImageView(img->view);
|
||||||
|
vkDevice.destroyImage(img->image);
|
||||||
|
m_Allocator.Free(img->allocation);
|
||||||
|
}
|
||||||
|
it = m_Pool.erase(it);
|
||||||
|
} else {
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
PhysicalImage* RenderGraph::AcquirePhysical(vk::Format format, vk::Extent2D extent,
|
||||||
|
vk::ImageUsageFlags usage,
|
||||||
|
vk::ImageAspectFlagBits aspect)
|
||||||
|
{
|
||||||
|
PoolKey key{static_cast<u32>(format), extent.width, extent.height,
|
||||||
|
static_cast<u32>(usage)};
|
||||||
|
|
||||||
|
// 池中取空闲镜像
|
||||||
|
auto it = m_Pool.find(key);
|
||||||
|
if (it != m_Pool.end() && !it->second.empty()) {
|
||||||
|
PhysicalImage* img = it->second.back().release();
|
||||||
|
it->second.pop_back();
|
||||||
|
return img;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新建:所有权归 TextureEntry.physical,Release 时归还池
|
||||||
|
auto img = CreateScope<PhysicalImage>();
|
||||||
|
img->extent = extent;
|
||||||
|
img->format = format;
|
||||||
|
img->usage = usage;
|
||||||
|
img->valid = true;
|
||||||
|
|
||||||
|
const auto& vkDevice = m_Device.GetDevice();
|
||||||
|
|
||||||
|
vk::ImageCreateInfo imageInfo;
|
||||||
|
imageInfo.setImageType(vk::ImageType::e2D)
|
||||||
|
.setFormat(format)
|
||||||
|
.setExtent({extent.width, extent.height, 1})
|
||||||
|
.setMipLevels(1)
|
||||||
|
.setArrayLayers(1)
|
||||||
|
.setSamples(vk::SampleCountFlagBits::e1)
|
||||||
|
.setTiling(vk::ImageTiling::eOptimal)
|
||||||
|
.setUsage(usage)
|
||||||
|
.setSharingMode(vk::SharingMode::eExclusive)
|
||||||
|
.setInitialLayout(vk::ImageLayout::eUndefined);
|
||||||
|
|
||||||
|
img->image = vkDevice.createImage(imageInfo);
|
||||||
|
|
||||||
|
auto reqs = vkDevice.getImageMemoryRequirements(img->image);
|
||||||
|
img->allocation = m_Allocator.Allocate(reqs, vk::MemoryPropertyFlagBits::eDeviceLocal);
|
||||||
|
vkDevice.bindImageMemory(img->image, img->allocation.Memory, img->allocation.Offset);
|
||||||
|
|
||||||
|
vk::ImageViewCreateInfo viewInfo;
|
||||||
|
viewInfo.setImage(img->image)
|
||||||
|
.setViewType(vk::ImageViewType::e2D)
|
||||||
|
.setFormat(format)
|
||||||
|
.setSubresourceRange({aspect, 0, 1, 0, 1});
|
||||||
|
|
||||||
|
img->view = vkDevice.createImageView(viewInfo);
|
||||||
|
|
||||||
|
return img.release();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- 执行 ----------
|
||||||
|
|
||||||
|
void RenderGraph::Execute(const FrameContext& frame)
|
||||||
|
{
|
||||||
|
for (const u32 passIdx : m_ExecutionOrder) {
|
||||||
|
auto& pass = m_Passes[passIdx];
|
||||||
|
if (pass.culled) continue;
|
||||||
|
|
||||||
|
// barrier:按槽位状态挑选变体,批量提交
|
||||||
|
m_ScratchBarriers.clear();
|
||||||
|
for (const auto& bp : pass.barriers) {
|
||||||
|
auto& entry = m_Textures[bp.resIndex];
|
||||||
|
|
||||||
|
bool useFirst;
|
||||||
|
if (entry.imported) {
|
||||||
|
useFirst = true; // 恒为 Undefined 起点
|
||||||
|
} else {
|
||||||
|
auto& slot = entry.slots[frame.frameSlot];
|
||||||
|
useFirst = !slot.used;
|
||||||
|
slot.used = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
vk::ImageMemoryBarrier2 b = useFirst ? bp.first : bp.steady;
|
||||||
|
// image 句柄在执行期绑定:imported 用当帧 acquire 的 image,
|
||||||
|
// created 用当前帧槽位的物理镜像(每个槽位各有一份)
|
||||||
|
b.setImage(entry.imported
|
||||||
|
? entry.importedImage
|
||||||
|
: entry.physical[frame.frameSlot]->image);
|
||||||
|
m_ScratchBarriers.push_back(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_ScratchBarriers.empty()) {
|
||||||
|
vk::DependencyInfo dep;
|
||||||
|
dep.setImageMemoryBarriers(m_ScratchBarriers);
|
||||||
|
frame.cmd.pipelineBarrier2(dep);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pass.exec) {
|
||||||
|
RenderGraphContext ctx;
|
||||||
|
ctx.cmd = frame.cmd;
|
||||||
|
ctx.frame = frame;
|
||||||
|
ctx.extent = m_TargetExtent;
|
||||||
|
ctx.graph = this;
|
||||||
|
pass.exec(ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- RenderGraphContext ----------
|
||||||
|
|
||||||
|
vk::ImageView RenderGraphContext::View(TextureHandle h) const
|
||||||
|
{
|
||||||
|
const auto& entry = graph->GetEntry(h);
|
||||||
|
if (entry.imported) return entry.importedView;
|
||||||
|
return entry.physical[frame.frameSlot]->view;
|
||||||
|
}
|
||||||
|
|
||||||
|
vk::Image RenderGraphContext::Image(TextureHandle h) const
|
||||||
|
{
|
||||||
|
const auto& entry = graph->GetEntry(h);
|
||||||
|
if (entry.imported) return entry.importedImage;
|
||||||
|
return entry.physical[frame.frameSlot]->image;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- 工具 / 统计 ----------
|
||||||
|
|
||||||
|
TextureEntry& RenderGraph::GetEntry(TextureHandle h)
|
||||||
|
{
|
||||||
|
CheckHandle(h);
|
||||||
|
return m_Textures[h.index];
|
||||||
|
}
|
||||||
|
|
||||||
|
const TextureEntry& RenderGraph::GetEntry(TextureHandle h) const
|
||||||
|
{
|
||||||
|
CheckHandle(h);
|
||||||
|
return m_Textures[h.index];
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderGraph::CheckHandle(TextureHandle h) const
|
||||||
|
{
|
||||||
|
if (!h.Valid() || h.index >= m_Textures.size() ||
|
||||||
|
m_Textures[h.index].generation != h.generation) {
|
||||||
|
ENGINE_FATAL("RenderGraph: invalid texture handle (index={}, gen={})",
|
||||||
|
h.index, h.generation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderGraph::CheckMutable() const
|
||||||
|
{
|
||||||
|
// 图结构(资源/pass/use)只在声明期可变;Compile 后 barrier 已预算,
|
||||||
|
// 静默接受修改会产生错误同步,直接拒绝。
|
||||||
|
if (m_Compiled) {
|
||||||
|
ENGINE_FATAL("RenderGraph: graph is compiled; structural changes are not allowed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
vk::ImageLayout RenderGraph::UseLayout(ResourceUse use)
|
||||||
|
{
|
||||||
|
switch (use) {
|
||||||
|
case ResourceUse::ColorAttachment: return vk::ImageLayout::eColorAttachmentOptimal;
|
||||||
|
case ResourceUse::DepthAttachment: return vk::ImageLayout::eDepthAttachmentOptimal;
|
||||||
|
case ResourceUse::ShaderRead: return vk::ImageLayout::eShaderReadOnlyOptimal;
|
||||||
|
case ResourceUse::TransferSrc: return vk::ImageLayout::eTransferSrcOptimal;
|
||||||
|
case ResourceUse::TransferDst: return vk::ImageLayout::eTransferDstOptimal;
|
||||||
|
case ResourceUse::Present: return vk::ImageLayout::ePresentSrcKHR;
|
||||||
|
}
|
||||||
|
return vk::ImageLayout::eUndefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
vk::PipelineStageFlags2 RenderGraph::UseDstStage(const PassNode::Use& use)
|
||||||
|
{
|
||||||
|
switch (use.use) {
|
||||||
|
case ResourceUse::ColorAttachment:
|
||||||
|
return vk::PipelineStageFlagBits2::eColorAttachmentOutput;
|
||||||
|
case ResourceUse::DepthAttachment:
|
||||||
|
return vk::PipelineStageFlagBits2::eEarlyFragmentTests |
|
||||||
|
vk::PipelineStageFlagBits2::eLateFragmentTests;
|
||||||
|
case ResourceUse::ShaderRead:
|
||||||
|
// ShaderStageFlags 与 PipelineStageFlags2 位值兼容,需显式转换
|
||||||
|
return vk::PipelineStageFlags2(
|
||||||
|
static_cast<VkPipelineStageFlags2>(
|
||||||
|
static_cast<VkShaderStageFlags>(use.readStage)));
|
||||||
|
case ResourceUse::TransferSrc:
|
||||||
|
case ResourceUse::TransferDst:
|
||||||
|
return vk::PipelineStageFlagBits2::eTransfer;
|
||||||
|
case ResourceUse::Present:
|
||||||
|
return vk::PipelineStageFlagBits2::eNone;
|
||||||
|
}
|
||||||
|
return vk::PipelineStageFlagBits2::eNone;
|
||||||
|
}
|
||||||
|
|
||||||
|
vk::PipelineStageFlags2 RenderGraph::UseSrcStage(ResourceUse use)
|
||||||
|
{
|
||||||
|
switch (use) {
|
||||||
|
case ResourceUse::ColorAttachment:
|
||||||
|
case ResourceUse::Present:
|
||||||
|
return vk::PipelineStageFlagBits2::eColorAttachmentOutput;
|
||||||
|
case ResourceUse::DepthAttachment:
|
||||||
|
return vk::PipelineStageFlagBits2::eEarlyFragmentTests |
|
||||||
|
vk::PipelineStageFlagBits2::eLateFragmentTests;
|
||||||
|
case ResourceUse::ShaderRead:
|
||||||
|
return vk::PipelineStageFlagBits2::eFragmentShader;
|
||||||
|
case ResourceUse::TransferSrc:
|
||||||
|
case ResourceUse::TransferDst:
|
||||||
|
return vk::PipelineStageFlagBits2::eTransfer;
|
||||||
|
}
|
||||||
|
return vk::PipelineStageFlagBits2::eNone;
|
||||||
|
}
|
||||||
|
|
||||||
|
vk::AccessFlags2 RenderGraph::UseDstAccess(const PassNode::Use& use)
|
||||||
|
{
|
||||||
|
switch (use.use) {
|
||||||
|
case ResourceUse::ColorAttachment:
|
||||||
|
return vk::AccessFlagBits2::eColorAttachmentWrite;
|
||||||
|
case ResourceUse::DepthAttachment:
|
||||||
|
return vk::AccessFlagBits2::eDepthStencilAttachmentWrite;
|
||||||
|
case ResourceUse::ShaderRead:
|
||||||
|
return vk::AccessFlagBits2::eShaderRead;
|
||||||
|
case ResourceUse::TransferSrc:
|
||||||
|
return vk::AccessFlagBits2::eTransferRead;
|
||||||
|
case ResourceUse::TransferDst:
|
||||||
|
return vk::AccessFlagBits2::eTransferWrite;
|
||||||
|
case ResourceUse::Present:
|
||||||
|
// layout-only 转换(交给 presentation engine),无访问掩码
|
||||||
|
return vk::AccessFlagBits2::eNone;
|
||||||
|
}
|
||||||
|
return vk::AccessFlagBits2::eNone;
|
||||||
|
}
|
||||||
|
|
||||||
|
vk::AccessFlags2 RenderGraph::UseSrcAccess(ResourceUse use)
|
||||||
|
{
|
||||||
|
switch (use) {
|
||||||
|
case ResourceUse::ColorAttachment:
|
||||||
|
case ResourceUse::Present:
|
||||||
|
return vk::AccessFlagBits2::eColorAttachmentWrite;
|
||||||
|
case ResourceUse::DepthAttachment:
|
||||||
|
return vk::AccessFlagBits2::eDepthStencilAttachmentWrite;
|
||||||
|
case ResourceUse::ShaderRead:
|
||||||
|
return vk::AccessFlagBits2::eShaderRead;
|
||||||
|
case ResourceUse::TransferSrc:
|
||||||
|
return vk::AccessFlagBits2::eTransferRead;
|
||||||
|
case ResourceUse::TransferDst:
|
||||||
|
return vk::AccessFlagBits2::eTransferWrite;
|
||||||
|
}
|
||||||
|
return vk::AccessFlagBits2::eNone;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 RenderGraph::GetPassCount() const { return static_cast<u32>(m_Passes.size()); }
|
||||||
|
u32 RenderGraph::GetActivePassCount() const { return m_ActivePassCount; }
|
||||||
|
u32 RenderGraph::GetTextureCount() const { return static_cast<u32>(m_Textures.size()); }
|
||||||
|
|
||||||
|
u32 RenderGraph::GetBarrierCount() const
|
||||||
|
{
|
||||||
|
u32 total = 0;
|
||||||
|
for (const auto& pass : m_Passes) {
|
||||||
|
total += static_cast<u32>(pass.barriers.size());
|
||||||
|
}
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
|
u32 RenderGraph::GetPooledImageCount() const
|
||||||
|
{
|
||||||
|
// 池中空闲 + 租借中(挂在资源上的物理镜像)
|
||||||
|
u32 total = 0;
|
||||||
|
for (const auto& [key, images] : m_Pool) {
|
||||||
|
total += static_cast<u32>(images.size());
|
||||||
|
}
|
||||||
|
for (const auto& entry : m_Textures) {
|
||||||
|
if (entry.imported) continue;
|
||||||
|
for (auto* img : entry.physical) {
|
||||||
|
if (img) ++total;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Engine::RG
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
//
|
||||||
|
// Created by atdunbg on 2026/9/22.
|
||||||
|
//
|
||||||
|
|
||||||
|
#include <Engine/Renderer/Camera.h>
|
||||||
|
#include <Engine/Core/Window.h>
|
||||||
|
|
||||||
|
#include <glm/gtc/matrix_transform.hpp>
|
||||||
|
|
||||||
|
#include "GLFW/glfw3.h"
|
||||||
|
|
||||||
|
namespace Engine {
|
||||||
|
|
||||||
|
void Camera::Update(Window& window, f32 deltaTime)
|
||||||
|
{
|
||||||
|
// 鼠标旋转(需显式启用,配合 cursor capture 使用)
|
||||||
|
if (m_MouseLookEnabled)
|
||||||
|
{
|
||||||
|
f32 mouseDX = 0, mouseDY = 0;
|
||||||
|
window.GetMouseDelta(mouseDX, mouseDY);
|
||||||
|
|
||||||
|
if (mouseDX != 0 || mouseDY != 0)
|
||||||
|
{
|
||||||
|
m_Yaw += mouseDX * m_MouseSensitivity;
|
||||||
|
m_Pitch -= mouseDY * m_MouseSensitivity;
|
||||||
|
m_Pitch = glm::clamp(m_Pitch, -89.0f, 89.0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UpdateBasis();
|
||||||
|
|
||||||
|
f32 speed = m_MoveSpeed * deltaTime;
|
||||||
|
if (window.IsKeyPressed(GLFW_KEY_W)) m_Position += m_Forward * speed;
|
||||||
|
if (window.IsKeyPressed(GLFW_KEY_S)) m_Position -= m_Forward * speed;
|
||||||
|
if (window.IsKeyPressed(GLFW_KEY_A)) m_Position -= m_Right * speed;
|
||||||
|
if (window.IsKeyPressed(GLFW_KEY_D)) m_Position += m_Right * speed;
|
||||||
|
if (window.IsKeyPressed(GLFW_KEY_SPACE)) m_Position.y += speed;
|
||||||
|
if (window.IsKeyPressed(GLFW_KEY_LEFT_SHIFT)) m_Position.y -= speed;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Camera::UpdateBasis()
|
||||||
|
{
|
||||||
|
m_Forward.x = cosf(glm::radians(m_Yaw)) * cosf(glm::radians(m_Pitch));
|
||||||
|
m_Forward.y = sinf(glm::radians(m_Pitch));
|
||||||
|
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
|
||||||
|
{
|
||||||
|
auto proj = glm::perspective(glm::radians(45.0f), aspect, nearZ, farZ);
|
||||||
|
proj[1][1] *= -1.0f; // Vulkan Y 翻转
|
||||||
|
return proj;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace Engine
|
||||||
+287
-373
@@ -4,6 +4,11 @@
|
|||||||
|
|
||||||
#include <Engine/Renderer/Renderer.h>
|
#include <Engine/Renderer/Renderer.h>
|
||||||
#include <Engine/Renderer/Material.h>
|
#include <Engine/Renderer/Material.h>
|
||||||
|
#include <Engine/Renderer/Model.h>
|
||||||
|
#include <Engine/Renderer/Vertex.h>
|
||||||
|
#include <Engine/ECS/Scene.h>
|
||||||
|
#include <Engine/ECS/Components.h>
|
||||||
|
#include <Engine/Assets/AssetManager.h>
|
||||||
#include <Engine/Vulkan/Device.h>
|
#include <Engine/Vulkan/Device.h>
|
||||||
#include <Engine/Vulkan/MemoryAllocator.h>
|
#include <Engine/Vulkan/MemoryAllocator.h>
|
||||||
#include <Engine/Vulkan/DescriptorManager.h>
|
#include <Engine/Vulkan/DescriptorManager.h>
|
||||||
@@ -20,26 +25,30 @@
|
|||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include "Engine/Core/Stats.h"
|
|
||||||
#include "GLFW/glfw3.h"
|
|
||||||
|
|
||||||
namespace Engine
|
namespace Engine
|
||||||
{
|
{
|
||||||
|
using namespace ECS; // TransformComponent / Entity 等直接使用
|
||||||
|
|
||||||
struct Light {
|
namespace
|
||||||
glm::vec4 direction;
|
{
|
||||||
glm::vec4 color; // rgb = 颜色,a = 强度
|
struct Light {
|
||||||
};
|
glm::vec4 direction; // xyz = 方向,w 未用
|
||||||
|
glm::vec4 color; // rgb = 颜色,a = 强度
|
||||||
|
};
|
||||||
|
|
||||||
struct UniformBufferObject {
|
struct UniformBufferObject {
|
||||||
glm::mat4 view;
|
glm::mat4 view;
|
||||||
glm::mat4 proj;
|
glm::mat4 proj;
|
||||||
glm::vec4 ambientColor;
|
glm::vec4 ambientColor;
|
||||||
Light lights[4];
|
Light lights[4];
|
||||||
};
|
};
|
||||||
|
|
||||||
|
constexpr u32 kUboBinding = 0;
|
||||||
|
constexpr u32 kUBOSetIndex = 0;
|
||||||
|
constexpr u32 kTextureSetIndex = 1;
|
||||||
|
constexpr u32 kMaxLights = 4;
|
||||||
|
}
|
||||||
|
|
||||||
Renderer::Renderer(Window& window)
|
Renderer::Renderer(Window& window)
|
||||||
: m_Window(window)
|
: m_Window(window)
|
||||||
@@ -55,7 +64,8 @@ namespace Engine
|
|||||||
|
|
||||||
// 4. Set 0: UBO
|
// 4. Set 0: UBO
|
||||||
std::vector<DescriptorBinding> uboBindings = {
|
std::vector<DescriptorBinding> uboBindings = {
|
||||||
{0, vk::DescriptorType::eUniformBuffer, vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment },
|
{kUboBinding, vk::DescriptorType::eUniformBuffer,
|
||||||
|
vk::ShaderStageFlagBits::eVertex | vk::ShaderStageFlagBits::eFragment},
|
||||||
};
|
};
|
||||||
m_UBOSetLayout = m_DescriptorManager->CreateLayout(uboBindings);
|
m_UBOSetLayout = m_DescriptorManager->CreateLayout(uboBindings);
|
||||||
|
|
||||||
@@ -66,277 +76,350 @@ namespace Engine
|
|||||||
m_TextureSetLayout = m_DescriptorManager->CreateLayout(texBindings);
|
m_TextureSetLayout = m_DescriptorManager->CreateLayout(texBindings);
|
||||||
|
|
||||||
// 6. PipelineLayout:两个 set
|
// 6. PipelineLayout:两个 set
|
||||||
std::vector<vk::DescriptorSetLayout> layouts = {
|
|
||||||
m_UBOSetLayout, m_TextureSetLayout
|
|
||||||
};
|
|
||||||
m_PipelineLayout = m_DescriptorManager->CreatePipelineLayout(
|
m_PipelineLayout = m_DescriptorManager->CreatePipelineLayout(
|
||||||
layouts, sizeof(glm::mat4));
|
{m_UBOSetLayout, m_TextureSetLayout}, sizeof(glm::mat4));
|
||||||
|
|
||||||
// 7. ShaderManager
|
// 7. ShaderManager(VK_EXT_shader_object)
|
||||||
m_ShaderManager = CreateScope<ShaderManager>(*m_Device);
|
m_ShaderManager = CreateScope<ShaderManager>(*m_Device);
|
||||||
|
|
||||||
std::string shaderDir = "shaders/";
|
std::string shaderDir = "shaders/";
|
||||||
auto vert = m_ShaderManager->CreateFromFile(
|
auto vert = m_ShaderManager->CreateFromFile(
|
||||||
shaderDir + "simple_shader.vert.spv",
|
shaderDir + "simple_shader.vert.spv",
|
||||||
vk::ShaderStageFlagBits::eVertex,
|
vk::ShaderStageFlagBits::eVertex,
|
||||||
layouts, sizeof(glm::mat4));
|
{m_UBOSetLayout, m_TextureSetLayout}, sizeof(glm::mat4));
|
||||||
auto frag = m_ShaderManager->CreateFromFile(
|
auto frag = m_ShaderManager->CreateFromFile(
|
||||||
shaderDir + "simple_shader.frag.spv",
|
shaderDir + "simple_shader.frag.spv",
|
||||||
vk::ShaderStageFlagBits::eFragment,
|
vk::ShaderStageFlagBits::eFragment,
|
||||||
layouts, sizeof(glm::mat4));
|
{m_UBOSetLayout, m_TextureSetLayout}, sizeof(glm::mat4));
|
||||||
|
|
||||||
m_VertShader = vert.Handle;
|
m_VertShader = vert.Handle;
|
||||||
m_FragShader = frag.Handle;
|
m_FragShader = frag.Handle;
|
||||||
|
|
||||||
if (!m_VertShader || !m_FragShader)
|
if (!m_VertShader || !m_FragShader) {
|
||||||
{
|
|
||||||
ENGINE_FATAL("Failed to create shader objects");
|
ENGINE_FATAL("Failed to create shader objects");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 8. Swapchain
|
// 8. Swapchain(只管呈现;深度图由 RenderGraph 创建)
|
||||||
m_Swapchain = CreateScope<Swapchain>(*m_Device, *m_Allocator, window);
|
m_Swapchain = CreateScope<Swapchain>(*m_Device, *m_Allocator, m_Window);
|
||||||
m_Extent = m_Swapchain->GetExtent();
|
m_Extent = m_Swapchain->GetExtent();
|
||||||
m_Format = m_Swapchain->GetFormat();
|
|
||||||
|
|
||||||
// 9. SyncManager
|
// 9. Sync / Command(per-frame)
|
||||||
m_SyncManager = CreateScope<SyncManager>(*m_Device, kMaxFramesInFlight, m_Swapchain->GetImageCount());
|
m_SyncManager = CreateScope<SyncManager>(
|
||||||
|
*m_Device, kMaxFramesInFlight, m_Swapchain->GetImageCount());
|
||||||
|
m_CommandManager = CreateScope<CommandManager>(*m_Device, kMaxFramesInFlight);
|
||||||
|
|
||||||
// 10. CommandManager
|
// 10. Per-frame UBO ring + 每槽位的 UBO descriptor set
|
||||||
m_CommandManager = CreateScope<CommandManager>(*m_Device);
|
|
||||||
|
|
||||||
// 11. Uniform Buffer
|
|
||||||
m_UniformBuffer = CreateScope<UniformBuffer>(
|
m_UniformBuffer = CreateScope<UniformBuffer>(
|
||||||
*m_Device, *m_Allocator, sizeof(UniformBufferObject));
|
*m_Device, *m_Allocator, sizeof(UniformBufferObject), kMaxFramesInFlight);
|
||||||
|
|
||||||
// 12. UBO DescriptorSet
|
m_UBODescriptorSets.resize(kMaxFramesInFlight);
|
||||||
m_UBODescriptorSet = m_DescriptorManager->AllocateDescriptorSet(m_UBOSetLayout);
|
for (u32 slot = 0; slot < kMaxFramesInFlight; ++slot) {
|
||||||
m_DescriptorManager->UpdateUniformBuffer(
|
m_UBODescriptorSets[slot] =
|
||||||
m_UBODescriptorSet, 0, m_UniformBuffer->Get(), m_UniformBuffer->GetSize());
|
m_DescriptorManager->AllocateDescriptorSet(m_UBOSetLayout);
|
||||||
|
m_DescriptorManager->UpdateUniformBuffer(
|
||||||
// 13. Model(模型自己加载纹理)
|
m_UBODescriptorSets[slot], kUboBinding,
|
||||||
m_Model = CreateScope<Model>(
|
m_UniformBuffer->Get(slot), m_UniformBuffer->GetSize());
|
||||||
*m_Device, *m_Allocator, *m_DescriptorManager,
|
|
||||||
m_TextureSetLayout,
|
|
||||||
"assets/models/backpack/backpack.obj");
|
|
||||||
|
|
||||||
// 14. 创建几个物体,摆一排
|
|
||||||
for (int i = 0; i < 3; ++i) {
|
|
||||||
Object obj;
|
|
||||||
obj.model = m_Model.get();
|
|
||||||
obj.transform = glm::translate(
|
|
||||||
glm::mat4(1.0f),
|
|
||||||
glm::vec3((i - 1) * 1.5f, 0.0f, 0.0f));
|
|
||||||
m_Objects.push_back(obj);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
m_StartTime = std::chrono::steady_clock::now();
|
// 11. RenderGraph:声明 + 编译(场景在 Render(Scene&) 时按帧绑定)
|
||||||
|
BuildGraph();
|
||||||
|
|
||||||
ENGINE_INFO("Renderer initialized");
|
ENGINE_INFO("Renderer initialized");
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Renderer::~Renderer()
|
Renderer::~Renderer()
|
||||||
{
|
{
|
||||||
m_Device->GetDevice().waitIdle();
|
m_Device->GetDevice().waitIdle();
|
||||||
|
|
||||||
if (m_VertShader)
|
if (m_VertShader) {
|
||||||
{
|
|
||||||
m_Device->GetDevice().destroyShaderEXT(m_VertShader);
|
m_Device->GetDevice().destroyShaderEXT(m_VertShader);
|
||||||
}
|
}
|
||||||
if (m_FragShader)
|
if (m_FragShader) {
|
||||||
{
|
|
||||||
m_Device->GetDevice().destroyShaderEXT(m_FragShader);
|
m_Device->GetDevice().destroyShaderEXT(m_FragShader);
|
||||||
}
|
}
|
||||||
|
|
||||||
m_Device->GetDevice().destroyPipelineLayout(m_PipelineLayout);
|
m_Device->GetDevice().destroyPipelineLayout(m_PipelineLayout);
|
||||||
|
|
||||||
m_Device->GetDevice().destroyDescriptorSetLayout(m_UBOSetLayout);
|
m_Device->GetDevice().destroyDescriptorSetLayout(m_UBOSetLayout);
|
||||||
m_Device->GetDevice().destroyDescriptorSetLayout(m_TextureSetLayout);
|
m_Device->GetDevice().destroyDescriptorSetLayout(m_TextureSetLayout);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Renderer::BuildGraph()
|
||||||
void Renderer::Render()
|
|
||||||
{
|
{
|
||||||
|
m_RenderGraph = CreateScope<RG::RenderGraph>(
|
||||||
|
*m_Device, *m_Allocator, kMaxFramesInFlight);
|
||||||
|
|
||||||
if (m_Window.WasResized())
|
// 导入 swapchain 颜色附件
|
||||||
{
|
m_Backbuffer = m_RenderGraph->ImportSwapchain(
|
||||||
|
"Backbuffer", m_Swapchain->GetFormat());
|
||||||
|
|
||||||
|
// 深度图:目标尺寸,格式由 Device 能力决定
|
||||||
|
const vk::Format depthFormat = m_Device->FindSupportedFormat(
|
||||||
|
{vk::Format::eD32Sfloat, vk::Format::eD32SfloatS8Uint,
|
||||||
|
vk::Format::eD24UnormS8Uint},
|
||||||
|
vk::ImageTiling::eOptimal,
|
||||||
|
vk::FormatFeatureFlagBits::eDepthStencilAttachment);
|
||||||
|
|
||||||
|
m_Depth = m_RenderGraph->CreateTexture(
|
||||||
|
"MainDepth", RG::TextureDesc{depthFormat});
|
||||||
|
|
||||||
|
// ---- Forward pass:直接渲染到 swapchain ----
|
||||||
|
// 场景数据每帧经 m_BoundScene 从 ECS 读取
|
||||||
|
m_RenderGraph->AddPass("Forward",
|
||||||
|
[this](RG::RenderGraphContext& ctx) {
|
||||||
|
const vk::CommandBuffer cmd = ctx.cmd;
|
||||||
|
|
||||||
|
vk::ClearValue clearValue;
|
||||||
|
clearValue.color = vk::ClearColorValue(
|
||||||
|
std::array<float, 4>{0.1f, 0.1f, 0.1f, 1.0f});
|
||||||
|
|
||||||
|
vk::RenderingAttachmentInfo colorAttachment;
|
||||||
|
colorAttachment.setImageView(ctx.View(m_Backbuffer))
|
||||||
|
.setImageLayout(vk::ImageLayout::eColorAttachmentOptimal)
|
||||||
|
.setLoadOp(vk::AttachmentLoadOp::eClear)
|
||||||
|
.setStoreOp(vk::AttachmentStoreOp::eStore)
|
||||||
|
.setClearValue(clearValue);
|
||||||
|
|
||||||
|
vk::RenderingAttachmentInfo depthAttachment;
|
||||||
|
depthAttachment.setImageView(ctx.View(m_Depth))
|
||||||
|
.setImageLayout(vk::ImageLayout::eDepthAttachmentOptimal)
|
||||||
|
.setLoadOp(vk::AttachmentLoadOp::eClear)
|
||||||
|
.setStoreOp(vk::AttachmentStoreOp::eDontCare)
|
||||||
|
.setClearValue(vk::ClearValue(
|
||||||
|
vk::ClearDepthStencilValue(1.0f, 0)));
|
||||||
|
|
||||||
|
vk::RenderingInfo renderingInfo;
|
||||||
|
renderingInfo.setRenderArea({{0, 0}, ctx.extent})
|
||||||
|
.setLayerCount(1)
|
||||||
|
.setColorAttachments(colorAttachment)
|
||||||
|
.setPDepthAttachment(&depthAttachment);
|
||||||
|
|
||||||
|
cmd.beginRendering(renderingInfo);
|
||||||
|
|
||||||
|
// 绑定 shader object
|
||||||
|
std::array<vk::ShaderStageFlagBits, 2> stages = {
|
||||||
|
vk::ShaderStageFlagBits::eVertex,
|
||||||
|
vk::ShaderStageFlagBits::eFragment
|
||||||
|
};
|
||||||
|
std::array<vk::ShaderEXT, 2> shaders = {m_VertShader, m_FragShader};
|
||||||
|
cmd.bindShadersEXT(stages, shaders);
|
||||||
|
|
||||||
|
// Set 0:本帧槽位的 UBO
|
||||||
|
cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics,
|
||||||
|
m_PipelineLayout, kUBOSetIndex,
|
||||||
|
m_UBODescriptorSets[ctx.frame.frameSlot], {});
|
||||||
|
|
||||||
|
// ---- 动态状态 ----
|
||||||
|
cmd.setRasterizerDiscardEnable(VK_FALSE);
|
||||||
|
|
||||||
|
cmd.setDepthTestEnable(VK_TRUE);
|
||||||
|
cmd.setDepthWriteEnable(VK_TRUE);
|
||||||
|
cmd.setDepthCompareOp(vk::CompareOp::eLess);
|
||||||
|
cmd.setDepthBoundsTestEnable(VK_FALSE);
|
||||||
|
|
||||||
|
cmd.setStencilTestEnable(VK_FALSE);
|
||||||
|
cmd.setDepthBiasEnable(VK_FALSE);
|
||||||
|
cmd.setPolygonModeEXT(vk::PolygonMode::eFill);
|
||||||
|
cmd.setCullMode(vk::CullModeFlagBits::eNone);
|
||||||
|
cmd.setFrontFace(vk::FrontFace::eCounterClockwise);
|
||||||
|
cmd.setPrimitiveTopology(vk::PrimitiveTopology::eTriangleList);
|
||||||
|
cmd.setPrimitiveRestartEnable(VK_FALSE);
|
||||||
|
|
||||||
|
auto binding = Vertex::GetBindingDescription();
|
||||||
|
auto attributes = Vertex::GetAttributeDescriptions();
|
||||||
|
cmd.setVertexInputEXT(binding, attributes);
|
||||||
|
|
||||||
|
cmd.setRasterizationSamplesEXT(vk::SampleCountFlagBits::e1);
|
||||||
|
vk::SampleMask sampleMask = 0xFFFFFFFF;
|
||||||
|
cmd.setSampleMaskEXT(vk::SampleCountFlagBits::e1, sampleMask);
|
||||||
|
cmd.setAlphaToCoverageEnableEXT(VK_FALSE);
|
||||||
|
cmd.setAlphaToOneEnableEXT(VK_FALSE);
|
||||||
|
|
||||||
|
vk::Viewport viewport(0.0f, 0.0f,
|
||||||
|
static_cast<float>(ctx.extent.width),
|
||||||
|
static_cast<float>(ctx.extent.height),
|
||||||
|
0.0f, 1.0f);
|
||||||
|
cmd.setViewportWithCount(viewport);
|
||||||
|
|
||||||
|
vk::Rect2D scissor({0, 0}, ctx.extent);
|
||||||
|
cmd.setScissorWithCount(scissor);
|
||||||
|
|
||||||
|
vk::Bool32 blendEnable = VK_FALSE;
|
||||||
|
cmd.setColorBlendEnableEXT(0, blendEnable);
|
||||||
|
|
||||||
|
vk::ColorBlendEquationEXT blendEquation;
|
||||||
|
blendEquation.setSrcColorBlendFactor(vk::BlendFactor::eOne)
|
||||||
|
.setDstColorBlendFactor(vk::BlendFactor::eZero)
|
||||||
|
.setColorBlendOp(vk::BlendOp::eAdd)
|
||||||
|
.setSrcAlphaBlendFactor(vk::BlendFactor::eOne)
|
||||||
|
.setDstAlphaBlendFactor(vk::BlendFactor::eZero)
|
||||||
|
.setAlphaBlendOp(vk::BlendOp::eAdd);
|
||||||
|
cmd.setColorBlendEquationEXT(0, blendEquation);
|
||||||
|
|
||||||
|
vk::ColorComponentFlags colorWriteMask =
|
||||||
|
vk::ColorComponentFlagBits::eR |
|
||||||
|
vk::ColorComponentFlagBits::eG |
|
||||||
|
vk::ColorComponentFlagBits::eB |
|
||||||
|
vk::ColorComponentFlagBits::eA;
|
||||||
|
cmd.setColorWriteMaskEXT(0, colorWriteMask);
|
||||||
|
|
||||||
|
// ---- 绘制:遍历 ECS 渲染体 ----
|
||||||
|
// Each 以 Transform/MeshRenderer 中较小的池为主表——
|
||||||
|
// 大场景下遍历开销与最小池规模成正比
|
||||||
|
m_BoundScene->Each<TransformComponent, MeshRendererComponent>(
|
||||||
|
[&](Entity, TransformComponent& transform,
|
||||||
|
MeshRendererComponent& meshRenderer) {
|
||||||
|
// 句柄 → 资产(悬空句柄会被 AssetCache FATAL 拦截)
|
||||||
|
Model* model = m_BoundAssets->GetModel(meshRenderer.model);
|
||||||
|
if (!model) return;
|
||||||
|
|
||||||
|
cmd.pushConstants(m_PipelineLayout,
|
||||||
|
vk::ShaderStageFlagBits::eVertex,
|
||||||
|
0, sizeof(glm::mat4), &transform.world);
|
||||||
|
|
||||||
|
for (auto& mesh : model->GetMeshes()) {
|
||||||
|
cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics,
|
||||||
|
m_PipelineLayout, kTextureSetIndex,
|
||||||
|
mesh->GetMaterial()->GetDescriptorSet(), {});
|
||||||
|
mesh->Draw(cmd);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
cmd.endRendering();
|
||||||
|
})
|
||||||
|
.Write(m_Backbuffer, RG::ResourceUse::ColorAttachment)
|
||||||
|
.Write(m_Depth, RG::ResourceUse::DepthAttachment);
|
||||||
|
|
||||||
|
// ---- Present pass:transition 到 ePresentSrcKHR ----
|
||||||
|
m_RenderGraph->AddPass("Present",
|
||||||
|
[](RG::RenderGraphContext&) {
|
||||||
|
// present 的 layout 转换由 RG 的 barrier 完成,
|
||||||
|
// 实际 vkQueuePresentKHR 由 Renderer 驱动。
|
||||||
|
})
|
||||||
|
.Read(m_Backbuffer, RG::ResourceUse::Present);
|
||||||
|
|
||||||
|
m_RenderGraph->SetTargetExtent(m_Extent); // 触发首次 Compile
|
||||||
|
}
|
||||||
|
|
||||||
|
void Renderer::HandleResize()
|
||||||
|
{
|
||||||
|
m_Device->GetDevice().waitIdle();
|
||||||
|
|
||||||
|
m_Swapchain->Recreate(m_Window);
|
||||||
|
m_Extent = m_Swapchain->GetExtent();
|
||||||
|
|
||||||
|
// image 数量可能变化
|
||||||
|
m_SyncManager = CreateScope<SyncManager>(
|
||||||
|
*m_Device, kMaxFramesInFlight, m_Swapchain->GetImageCount());
|
||||||
|
|
||||||
|
// RG 重编译:物理镜像按新尺寸轮换
|
||||||
|
m_RenderGraph->SetTargetExtent(m_Extent);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Renderer::Render(ECS::Scene& scene, Assets::AssetManager& assets)
|
||||||
|
{
|
||||||
|
m_BoundScene = &scene;
|
||||||
|
m_BoundAssets = &assets;
|
||||||
|
|
||||||
|
if (m_Window.WasResized()) {
|
||||||
m_Window.ResetResizedFlag();
|
m_Window.ResetResizedFlag();
|
||||||
|
HandleResize();
|
||||||
m_Device->GetDevice().waitIdle();
|
|
||||||
|
|
||||||
m_Swapchain->Recreate(m_Window);
|
|
||||||
m_Extent = m_Swapchain->GetExtent();
|
|
||||||
m_SyncManager = CreateScope<SyncManager>(
|
|
||||||
*m_Device, kMaxFramesInFlight, m_Swapchain->GetImageCount());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
u32 frameIndex = m_CurrentFrame;
|
const u32 frameSlot = m_CurrentFrame;
|
||||||
const auto& vkDevice = m_Device->GetDevice();
|
const auto& vkDevice = m_Device->GetDevice();
|
||||||
|
|
||||||
// 1. 更新 MVP 矩阵
|
// 1. UBO:从 ECS 场景组装(primary 相机 + 灯光 + 环境色)
|
||||||
auto now = std::chrono::steady_clock::now();
|
auto* cameraComp = scene.GetPrimaryCamera();
|
||||||
float currentTime = std::chrono::duration<float>(now - m_StartTime).count();
|
if (!cameraComp) {
|
||||||
m_DeltaTime = currentTime - m_LastFrameTime;
|
// 无相机:跳过本帧(正常场景必有相机,这里只是安全网)
|
||||||
m_LastFrameTime = currentTime;
|
return;
|
||||||
|
|
||||||
// 更新每个物体的 transform
|
|
||||||
for (size_t i = 0; i < m_Objects.size(); ++i) {
|
|
||||||
float angle = currentTime * glm::radians(60.0f) + i * 0.7f;
|
|
||||||
|
|
||||||
glm::mat4 t = glm::translate(
|
|
||||||
glm::mat4(1.0f),
|
|
||||||
glm::vec3((static_cast<float>(i) - 1.0f) * 1.5f, 0.0f, 0.0f));
|
|
||||||
glm::mat4 r = glm::rotate(glm::mat4(1.0f), angle, glm::vec3(0.0f, 1.0f, 0.0f));
|
|
||||||
|
|
||||||
m_Objects[i].transform = t * r;
|
|
||||||
}
|
}
|
||||||
|
const Camera& camera = cameraComp->camera;
|
||||||
// 鼠标控制方向
|
|
||||||
/*
|
|
||||||
float mouseDX = 0, mouseDY = 0;
|
|
||||||
m_Window.GetMouseDelta(mouseDX, mouseDY);
|
|
||||||
if (mouseDX != 0 || mouseDY != 0)
|
|
||||||
{
|
|
||||||
m_CameraYaw += mouseDX * m_MouseSensitivity;
|
|
||||||
m_CameraPitch -= mouseDY * m_MouseSensitivity;
|
|
||||||
m_CameraPitch = glm::clamp(m_CameraPitch, -89.0f, 89.0f);
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
// 相机前方向量
|
|
||||||
glm::vec3 forward;
|
|
||||||
forward.x = cosf(glm::radians(m_CameraYaw)) * cos(glm::radians(m_CameraPitch));
|
|
||||||
forward.y = sinf(glm::radians(m_CameraPitch));
|
|
||||||
forward.z = sinf(glm::radians(m_CameraYaw)) * cos(glm::radians(m_CameraPitch));
|
|
||||||
forward = glm::normalize(forward);
|
|
||||||
|
|
||||||
glm::vec3 right = glm::normalize(glm::cross(forward, glm::vec3(0, 1, 0)));
|
|
||||||
glm::vec3 up = glm::normalize(glm::cross(right, forward));
|
|
||||||
|
|
||||||
// WASD 移动
|
|
||||||
float speed = m_CameraSpeed * m_DeltaTime;
|
|
||||||
if (m_Window.IsKeyPressed(GLFW_KEY_W)) m_CameraPos += forward * speed;
|
|
||||||
if (m_Window.IsKeyPressed(GLFW_KEY_S)) m_CameraPos -= forward * speed;
|
|
||||||
if (m_Window.IsKeyPressed(GLFW_KEY_A)) m_CameraPos -= right * speed;
|
|
||||||
if (m_Window.IsKeyPressed(GLFW_KEY_D)) m_CameraPos += right * speed;
|
|
||||||
if (m_Window.IsKeyPressed(GLFW_KEY_SPACE)) m_CameraPos.y += speed;
|
|
||||||
if (m_Window.IsKeyPressed(GLFW_KEY_LEFT_SHIFT)) m_CameraPos.y -= speed;
|
|
||||||
|
|
||||||
|
|
||||||
auto view = glm::lookAt(m_CameraPos,
|
|
||||||
m_CameraPos + forward,
|
|
||||||
up);
|
|
||||||
|
|
||||||
float aspect = static_cast<f32>(m_Extent.width) /
|
float aspect = static_cast<f32>(m_Extent.width) /
|
||||||
static_cast<f32>(m_Extent.height);
|
static_cast<f32>(m_Extent.height);
|
||||||
|
|
||||||
auto proj = glm::perspective(glm::radians(45.0f), aspect, 0.1f, 100.0f);
|
|
||||||
proj[1][1] *= -1.0f; // Vulkan Y 翻转
|
|
||||||
|
|
||||||
UniformBufferObject ubo{};
|
UniformBufferObject ubo{};
|
||||||
ubo.view = view;
|
ubo.view = camera.GetView();
|
||||||
ubo.proj = proj;
|
ubo.proj = camera.GetProjection(aspect, 0.1f, 100.0f);
|
||||||
|
ubo.ambientColor = scene.GetSettings().ambientColor;
|
||||||
|
|
||||||
ubo.ambientColor = glm::vec4(0.15f, 0.15f, 0.2f, 0.0f);
|
u32 lightCount = 0;
|
||||||
|
scene.Each<LightComponent>([&](Entity, LightComponent& light) {
|
||||||
|
if (lightCount >= kMaxLights) return;
|
||||||
|
ubo.lights[lightCount].direction =
|
||||||
|
glm::length(light.direction) > 0.0f
|
||||||
|
? glm::vec4(glm::normalize(light.direction), 0.0f)
|
||||||
|
: glm::vec4(0.0f);
|
||||||
|
ubo.lights[lightCount].color = light.color;
|
||||||
|
++lightCount;
|
||||||
|
});
|
||||||
|
for (u32 i = lightCount; i < kMaxLights; ++i) {
|
||||||
|
ubo.lights[i].direction = glm::vec4(0.0f);
|
||||||
|
ubo.lights[i].color = glm::vec4(0.0f);
|
||||||
|
}
|
||||||
|
|
||||||
// 主光:暖色,从左上方照下
|
// 此时本槽位的 fence 尚未 reset——先等它(保证 GPU 已完成本槽位上一轮),
|
||||||
ubo.lights[0].direction = glm::vec4(glm::normalize(glm::vec3(-0.5f, -1.0f, -0.3f)), 0.0f);
|
// 再写 UBO,写-读竞争关闭。
|
||||||
ubo.lights[0].color = glm::vec4(1.0f, 0.95f, 0.9f, 1.0f);
|
m_SyncManager->WaitFence(frameSlot);
|
||||||
|
m_UniformBuffer->Update(frameSlot, &ubo, sizeof(ubo));
|
||||||
|
m_SyncManager->ResetFence(frameSlot);
|
||||||
|
|
||||||
// 补光:冷色,从右下方照上
|
// 2. acquire swapchain image
|
||||||
ubo.lights[1].direction = glm::vec4(glm::normalize(glm::vec3(0.5f, -0.5f, 0.3f)), 0.0f);
|
|
||||||
ubo.lights[1].color = glm::vec4(0.3f, 0.5f, 1.0f, 0.8f);
|
|
||||||
|
|
||||||
// 未使用:强度 0
|
|
||||||
ubo.lights[2].direction = glm::vec4(0.0f, 0.0f, 0.0f, 0.0f);
|
|
||||||
ubo.lights[2].color = glm::vec4(0.0f, 0.0f, 0.0f, 0.0f);
|
|
||||||
|
|
||||||
ubo.lights[3].direction = glm::vec4(0.0f, 0.0f, 0.0f, 0.0f);
|
|
||||||
ubo.lights[3].color = glm::vec4(0.0f, 0.0f, 0.0f, 0.0f);
|
|
||||||
|
|
||||||
m_UniformBuffer->Update(&ubo, sizeof(ubo));
|
|
||||||
|
|
||||||
// 等待这一帧上一次完成
|
|
||||||
m_SyncManager->WaitFence(frameIndex);
|
|
||||||
|
|
||||||
// 2. 获取交换链 image
|
|
||||||
u32 imageIndex = 0;
|
u32 imageIndex = 0;
|
||||||
try
|
try {
|
||||||
{
|
|
||||||
const auto result = vkDevice.acquireNextImageKHR(
|
const auto result = vkDevice.acquireNextImageKHR(
|
||||||
m_Swapchain->Get(),
|
m_Swapchain->Get(),
|
||||||
std::numeric_limits<u64>::max(),
|
std::numeric_limits<u64>::max(),
|
||||||
m_SyncManager->GetImageAvailable(frameIndex));
|
m_SyncManager->GetImageAvailable(frameSlot));
|
||||||
|
|
||||||
if (result.result == vk::Result::eErrorOutOfDateKHR)
|
if (result.result == vk::Result::eErrorOutOfDateKHR) {
|
||||||
{
|
HandleResize();
|
||||||
m_Swapchain->Recreate(m_Window);
|
return;
|
||||||
return; // ← fence 没被 reset,下帧 wait 立即返回
|
|
||||||
}
|
}
|
||||||
imageIndex = result.value;
|
imageIndex = result.value;
|
||||||
}
|
}
|
||||||
catch (const vk::OutOfDateKHRError&)
|
catch (const vk::OutOfDateKHRError&) {
|
||||||
{
|
HandleResize();
|
||||||
m_Swapchain->Recreate(m_Window);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. acquire 成功, reset fence
|
// 3. 录制:FrameContext + RenderGraph
|
||||||
m_SyncManager->ResetFence(frameIndex);
|
FrameContext frame;
|
||||||
|
frame.frameSlot = frameSlot;
|
||||||
|
frame.imageIndex = imageIndex;
|
||||||
|
frame.cmd = m_CommandManager->BeginFrame(frameSlot);
|
||||||
|
|
||||||
// 4. 录制命令
|
m_RenderGraph->SetImportedImage(
|
||||||
auto cmd = m_CommandManager->BeginFrame();
|
m_Backbuffer,
|
||||||
|
m_Swapchain->GetImages()[imageIndex],
|
||||||
|
m_Swapchain->GetImageViews()[imageIndex]);
|
||||||
|
|
||||||
TransitionImageLayout(cmd, m_Swapchain->GetImages()[imageIndex],
|
m_RenderGraph->Execute(frame);
|
||||||
vk::ImageLayout::eUndefined,
|
|
||||||
vk::ImageLayout::eColorAttachmentOptimal);
|
|
||||||
|
|
||||||
// 深度转换
|
m_CommandManager->EndFrame(frameSlot);
|
||||||
vk::ImageMemoryBarrier2 depthBarrier;
|
|
||||||
depthBarrier.setSrcStageMask(vk::PipelineStageFlagBits2::eEarlyFragmentTests)
|
|
||||||
.setSrcAccessMask(vk::AccessFlagBits2::eDepthStencilAttachmentWrite)
|
|
||||||
.setDstStageMask(vk::PipelineStageFlagBits2::eEarlyFragmentTests)
|
|
||||||
.setDstAccessMask(vk::AccessFlagBits2::eDepthStencilAttachmentWrite)
|
|
||||||
.setOldLayout(vk::ImageLayout::eUndefined)
|
|
||||||
.setNewLayout(vk::ImageLayout::eDepthAttachmentOptimal)
|
|
||||||
.setImage(m_Swapchain->GetDepthImage())
|
|
||||||
.setSubresourceRange({
|
|
||||||
vk::ImageAspectFlagBits::eDepth,
|
|
||||||
0, 1, 0, 1
|
|
||||||
});
|
|
||||||
|
|
||||||
vk::DependencyInfo depthDep;
|
// 4. 提交
|
||||||
depthDep.setImageMemoryBarriers(depthBarrier);
|
|
||||||
cmd.pipelineBarrier2(depthDep);
|
|
||||||
|
|
||||||
RecordCommands(cmd, imageIndex);
|
|
||||||
|
|
||||||
TransitionImageLayout(cmd, m_Swapchain->GetImages()[imageIndex],
|
|
||||||
vk::ImageLayout::eColorAttachmentOptimal,
|
|
||||||
vk::ImageLayout::ePresentSrcKHR);
|
|
||||||
|
|
||||||
m_CommandManager->EndFrame();
|
|
||||||
|
|
||||||
// 5. 提交
|
|
||||||
std::array<vk::PipelineStageFlags, 1> waitStages = {
|
std::array<vk::PipelineStageFlags, 1> waitStages = {
|
||||||
vk::PipelineStageFlagBits::eColorAttachmentOutput
|
vk::PipelineStageFlagBits::eColorAttachmentOutput
|
||||||
};
|
};
|
||||||
std::array<vk::Semaphore, 1> waitSemaphores = {
|
std::array<vk::Semaphore, 1> waitSemaphores = {
|
||||||
m_SyncManager->GetImageAvailable(frameIndex)
|
m_SyncManager->GetImageAvailable(frameSlot)
|
||||||
};
|
};
|
||||||
std::array<vk::Semaphore, 1> signalSemaphores = {
|
std::array<vk::Semaphore, 1> signalSemaphores = {
|
||||||
m_SyncManager->GetRenderFinished(imageIndex)
|
m_SyncManager->GetRenderFinished(imageIndex)
|
||||||
};
|
};
|
||||||
|
|
||||||
vk::SubmitInfo submitInfo;
|
vk::SubmitInfo submitInfo;
|
||||||
submitInfo.setCommandBuffers(cmd)
|
submitInfo.setCommandBuffers(frame.cmd)
|
||||||
.setWaitSemaphores(waitSemaphores)
|
.setWaitSemaphores(waitSemaphores)
|
||||||
.setWaitDstStageMask(waitStages)
|
.setWaitDstStageMask(waitStages)
|
||||||
.setSignalSemaphores(signalSemaphores);
|
.setSignalSemaphores(signalSemaphores);
|
||||||
|
|
||||||
m_Device->GetGraphicsQueue().submit(submitInfo, m_SyncManager->GetFrameFence(frameIndex));
|
m_Device->GetGraphicsQueue().submit(
|
||||||
|
submitInfo, m_SyncManager->GetFrameFence(frameSlot));
|
||||||
|
|
||||||
// 6. 呈现
|
// 5. 呈现
|
||||||
std::array<vk::SwapchainKHR, 1> swapchains = {m_Swapchain->Get()};
|
std::array<vk::SwapchainKHR, 1> swapchains = {m_Swapchain->Get()};
|
||||||
std::array<vk::Semaphore, 1> presentWait = {
|
std::array<vk::Semaphore, 1> presentWait = {
|
||||||
m_SyncManager->GetRenderFinished(imageIndex)
|
m_SyncManager->GetRenderFinished(imageIndex)
|
||||||
@@ -347,186 +430,17 @@ namespace Engine
|
|||||||
.setSwapchains(swapchains)
|
.setSwapchains(swapchains)
|
||||||
.setWaitSemaphores(presentWait);
|
.setWaitSemaphores(presentWait);
|
||||||
|
|
||||||
try
|
try {
|
||||||
{
|
|
||||||
auto result = m_Device->GetPresentQueue().presentKHR(presentInfo);
|
auto result = m_Device->GetPresentQueue().presentKHR(presentInfo);
|
||||||
if (result == vk::Result::eSuboptimalKHR)
|
if (result == vk::Result::eSuboptimalKHR) {
|
||||||
{
|
HandleResize();
|
||||||
m_Swapchain->Recreate(m_Window);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (const vk::OutOfDateKHRError&)
|
catch (const vk::OutOfDateKHRError&) {
|
||||||
{
|
HandleResize();
|
||||||
m_Swapchain->Recreate(m_Window);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_Window.IsKeyPressed(GLFW_KEY_F1))
|
m_CurrentFrame = (m_CurrentFrame + 1) % kMaxFramesInFlight;
|
||||||
STATS.PrintComparison();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Renderer::RecordCommands(vk::CommandBuffer cmd, u32 imageIndex)
|
|
||||||
{
|
|
||||||
vk::ClearValue clearValue;
|
|
||||||
clearValue.color = vk::ClearColorValue(std::array<float, 4>{0.1f, 0.1f, 0.1f, 1.0f});
|
|
||||||
|
|
||||||
// 颜色附件
|
|
||||||
vk::RenderingAttachmentInfo colorAttachment;
|
|
||||||
colorAttachment.setImageView(m_Swapchain->GetImageViews()[imageIndex])
|
|
||||||
.setImageLayout(vk::ImageLayout::eColorAttachmentOptimal)
|
|
||||||
.setLoadOp(vk::AttachmentLoadOp::eClear)
|
|
||||||
.setStoreOp(vk::AttachmentStoreOp::eStore)
|
|
||||||
.setClearValue(clearValue);
|
|
||||||
|
|
||||||
// 深度附件
|
|
||||||
vk::RenderingAttachmentInfo depthAttachment;
|
|
||||||
depthAttachment.setImageView(m_Swapchain->GetDepthImageView())
|
|
||||||
.setImageLayout(vk::ImageLayout::eDepthAttachmentOptimal)
|
|
||||||
.setLoadOp(vk::AttachmentLoadOp::eClear)
|
|
||||||
.setStoreOp(vk::AttachmentStoreOp::eDontCare)
|
|
||||||
.setClearValue(vk::ClearValue(vk::ClearDepthStencilValue(1.0f, 0)));
|
|
||||||
|
|
||||||
vk::RenderingInfo renderingInfo;
|
|
||||||
renderingInfo.setRenderArea({{0, 0}, m_Extent})
|
|
||||||
.setLayerCount(1)
|
|
||||||
.setColorAttachments(colorAttachment)
|
|
||||||
.setPDepthAttachment(&depthAttachment);
|
|
||||||
|
|
||||||
cmd.beginRendering(renderingInfo);
|
|
||||||
|
|
||||||
// 绑定着色器
|
|
||||||
std::array<vk::ShaderStageFlagBits, 2> stages = {
|
|
||||||
vk::ShaderStageFlagBits::eVertex,
|
|
||||||
vk::ShaderStageFlagBits::eFragment
|
|
||||||
};
|
|
||||||
std::array<vk::ShaderEXT, 2> shaders = {m_VertShader, m_FragShader};
|
|
||||||
cmd.bindShadersEXT(stages, shaders);
|
|
||||||
|
|
||||||
// 绑定描述符集
|
|
||||||
cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics,
|
|
||||||
m_PipelineLayout, 0,
|
|
||||||
m_UBODescriptorSet, {});
|
|
||||||
|
|
||||||
// ===== 动态状态 =====
|
|
||||||
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);
|
|
||||||
|
|
||||||
// 顶点输入
|
|
||||||
/*
|
|
||||||
vk::VertexInputBindingDescription2EXT binding;
|
|
||||||
binding.setBinding(0)
|
|
||||||
.setStride(sizeof(f32) * 5)
|
|
||||||
.setInputRate(vk::VertexInputRate::eVertex)
|
|
||||||
.setDivisor(1);
|
|
||||||
|
|
||||||
std::array<vk::VertexInputAttributeDescription2EXT, 2> attributes;
|
|
||||||
|
|
||||||
// location 0: position
|
|
||||||
attributes[0].setLocation(0)
|
|
||||||
.setBinding(0)
|
|
||||||
.setFormat(vk::Format::eR32G32B32Sfloat)
|
|
||||||
.setOffset(0);
|
|
||||||
|
|
||||||
// location 1: uv
|
|
||||||
attributes[1].setLocation(1)
|
|
||||||
.setBinding(0)
|
|
||||||
.setFormat(vk::Format::eR32G32Sfloat)
|
|
||||||
.setOffset(sizeof(f32) * 3);
|
|
||||||
*/
|
|
||||||
|
|
||||||
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>(m_Extent.width),
|
|
||||||
static_cast<float>(m_Extent.height),
|
|
||||||
0.0f, 1.0f);
|
|
||||||
cmd.setViewportWithCount(viewport);
|
|
||||||
|
|
||||||
vk::Rect2D scissor({0, 0}, m_Extent);
|
|
||||||
cmd.setScissorWithCount(scissor);
|
|
||||||
|
|
||||||
vk::Bool32 blendEnable = VK_FALSE;
|
|
||||||
cmd.setColorBlendEnableEXT(0, blendEnable);
|
|
||||||
|
|
||||||
vk::ColorBlendEquationEXT blendEquation;
|
|
||||||
blendEquation.setSrcColorBlendFactor(vk::BlendFactor::eOne)
|
|
||||||
.setDstColorBlendFactor(vk::BlendFactor::eZero)
|
|
||||||
.setColorBlendOp(vk::BlendOp::eAdd)
|
|
||||||
.setSrcAlphaBlendFactor(vk::BlendFactor::eOne)
|
|
||||||
.setDstAlphaBlendFactor(vk::BlendFactor::eZero)
|
|
||||||
.setAlphaBlendOp(vk::BlendOp::eAdd);
|
|
||||||
cmd.setColorBlendEquationEXT(0, blendEquation);
|
|
||||||
|
|
||||||
vk::ColorComponentFlags colorWriteMask =
|
|
||||||
vk::ColorComponentFlagBits::eR |
|
|
||||||
vk::ColorComponentFlagBits::eG |
|
|
||||||
vk::ColorComponentFlagBits::eB |
|
|
||||||
vk::ColorComponentFlagBits::eA;
|
|
||||||
cmd.setColorWriteMaskEXT(0, colorWriteMask);
|
|
||||||
|
|
||||||
|
|
||||||
// 绑定顶点缓冲并绘制
|
|
||||||
for (auto& obj : m_Objects) {
|
|
||||||
cmd.pushConstants(m_PipelineLayout,
|
|
||||||
vk::ShaderStageFlagBits::eVertex,
|
|
||||||
0, sizeof(glm::mat4), &obj.transform);
|
|
||||||
|
|
||||||
for (auto& mesh : obj.model->GetMeshes()) {
|
|
||||||
cmd.bindDescriptorSets(vk::PipelineBindPoint::eGraphics,
|
|
||||||
m_PipelineLayout, 1,
|
|
||||||
mesh->GetMaterial()->GetDescriptorSet(), {});
|
|
||||||
mesh->Draw(cmd);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
cmd.bindVertexBuffers(0, m_VertexBuffer->Get(), {0});
|
|
||||||
cmd.bindIndexBuffer(m_IndexBuffer->Get(), 0, vk::IndexType::eUint32);
|
|
||||||
// cmd.draw(3, 1, 0, 0);
|
|
||||||
cmd.drawIndexed(36, 1, 0, 0, 0);
|
|
||||||
*/
|
|
||||||
|
|
||||||
cmd.endRendering();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Renderer::TransitionImageLayout(vk::CommandBuffer cmd, vk::Image image,
|
|
||||||
vk::ImageLayout oldLayout, vk::ImageLayout newLayout)
|
|
||||||
{
|
|
||||||
vk::ImageMemoryBarrier2 barrier;
|
|
||||||
barrier.setSrcStageMask(vk::PipelineStageFlagBits2::eColorAttachmentOutput)
|
|
||||||
.setSrcAccessMask(vk::AccessFlagBits2::eColorAttachmentWrite)
|
|
||||||
.setDstStageMask(vk::PipelineStageFlagBits2::eColorAttachmentOutput)
|
|
||||||
.setDstAccessMask(vk::AccessFlagBits2::eColorAttachmentWrite)
|
|
||||||
.setOldLayout(oldLayout)
|
|
||||||
.setNewLayout(newLayout)
|
|
||||||
.setImage(image)
|
|
||||||
.setSubresourceRange({
|
|
||||||
vk::ImageAspectFlagBits::eColor,
|
|
||||||
0, 1, 0, 1
|
|
||||||
});
|
|
||||||
|
|
||||||
vk::DependencyInfo dependencyInfo;
|
|
||||||
dependencyInfo.setImageMemoryBarriers(barrier);
|
|
||||||
|
|
||||||
cmd.pipelineBarrier2(dependencyInfo);
|
|
||||||
}
|
|
||||||
} // namespace Engine
|
} // namespace Engine
|
||||||
|
|||||||
@@ -8,25 +8,26 @@
|
|||||||
|
|
||||||
namespace Engine {
|
namespace Engine {
|
||||||
|
|
||||||
CommandManager::CommandManager(Device& device)
|
CommandManager::CommandManager(Device& device, u32 frameCount)
|
||||||
: m_Device(device)
|
: m_Device(device)
|
||||||
{
|
{
|
||||||
// 创建命令池
|
// eResetCommandBuffer:允许单独 reset 某个 buffer,而不是整池 reset。
|
||||||
|
// 若改为 per-frame pool + 整池 reset,可在帧槽 fence 等待后一次 reset,
|
||||||
|
// 驱动开销更低——当前规模下单池多 buffer 已足够。
|
||||||
vk::CommandPoolCreateInfo poolInfo;
|
vk::CommandPoolCreateInfo poolInfo;
|
||||||
poolInfo.setQueueFamilyIndex(m_Device.GetGraphicsFamily())
|
poolInfo.setQueueFamilyIndex(m_Device.GetGraphicsFamily())
|
||||||
.setFlags(vk::CommandPoolCreateFlagBits::eResetCommandBuffer);
|
.setFlags(vk::CommandPoolCreateFlagBits::eResetCommandBuffer);
|
||||||
|
|
||||||
m_Pool = m_Device.GetDevice().createCommandPool(poolInfo);
|
m_Pool = m_Device.GetDevice().createCommandPool(poolInfo);
|
||||||
|
|
||||||
// 分配一个命令缓冲
|
|
||||||
vk::CommandBufferAllocateInfo allocInfo;
|
vk::CommandBufferAllocateInfo allocInfo;
|
||||||
allocInfo.setCommandPool(m_Pool)
|
allocInfo.setCommandPool(m_Pool)
|
||||||
.setLevel(vk::CommandBufferLevel::ePrimary)
|
.setLevel(vk::CommandBufferLevel::ePrimary)
|
||||||
.setCommandBufferCount(1);
|
.setCommandBufferCount(frameCount);
|
||||||
|
|
||||||
m_Buffer = m_Device.GetDevice().allocateCommandBuffers(allocInfo)[0];
|
m_Buffers = m_Device.GetDevice().allocateCommandBuffers(allocInfo);
|
||||||
|
|
||||||
ENGINE_INFO("CommandManager created");
|
ENGINE_INFO("CommandManager created: {} frame buffers", frameCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
CommandManager::~CommandManager()
|
CommandManager::~CommandManager()
|
||||||
@@ -34,20 +35,21 @@ namespace Engine {
|
|||||||
m_Device.GetDevice().destroyCommandPool(m_Pool);
|
m_Device.GetDevice().destroyCommandPool(m_Pool);
|
||||||
}
|
}
|
||||||
|
|
||||||
vk::CommandBuffer CommandManager::BeginFrame()
|
vk::CommandBuffer CommandManager::BeginFrame(u32 frameSlot)
|
||||||
{
|
{
|
||||||
m_Buffer.reset();
|
auto cmd = m_Buffers[frameSlot];
|
||||||
|
cmd.reset();
|
||||||
|
|
||||||
vk::CommandBufferBeginInfo beginInfo;
|
vk::CommandBufferBeginInfo beginInfo;
|
||||||
beginInfo.setFlags(vk::CommandBufferUsageFlagBits::eOneTimeSubmit);
|
beginInfo.setFlags(vk::CommandBufferUsageFlagBits::eOneTimeSubmit);
|
||||||
m_Buffer.begin(beginInfo);
|
cmd.begin(beginInfo);
|
||||||
|
|
||||||
return m_Buffer;
|
return cmd;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CommandManager::EndFrame()
|
void CommandManager::EndFrame(u32 frameSlot)
|
||||||
{
|
{
|
||||||
m_Buffer.end();
|
m_Buffers[frameSlot].end();
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace Engine
|
} // namespace Engine
|
||||||
|
|||||||
@@ -310,6 +310,26 @@ uint32_t Device::FindMemoryType(uint32_t typeFilter, vk::MemoryPropertyFlags pro
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
vk::Format Device::FindSupportedFormat(const std::vector<vk::Format>& candidates,
|
||||||
|
vk::ImageTiling tiling,
|
||||||
|
vk::FormatFeatureFlags features) const
|
||||||
|
{
|
||||||
|
for (auto format : candidates) {
|
||||||
|
auto props = m_PhysicalDevice.getFormatProperties(format);
|
||||||
|
if (tiling == vk::ImageTiling::eLinear &&
|
||||||
|
(props.linearTilingFeatures & features) == features) {
|
||||||
|
return format;
|
||||||
|
}
|
||||||
|
if (tiling == vk::ImageTiling::eOptimal &&
|
||||||
|
(props.optimalTilingFeatures & features) == features) {
|
||||||
|
return format;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ENGINE_FATAL("No supported format found in candidate list");
|
||||||
|
return candidates.empty() ? vk::Format::eUndefined : candidates.front();
|
||||||
|
}
|
||||||
|
|
||||||
// ---------- 验证层检查 ----------
|
// ---------- 验证层检查 ----------
|
||||||
|
|
||||||
bool Device::CheckValidationLayerSupport()
|
bool Device::CheckValidationLayerSupport()
|
||||||
|
|||||||
@@ -99,14 +99,10 @@ namespace Engine
|
|||||||
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));
|
||||||
|
|
||||||
CreateDepthResources();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Swapchain::Destroy()
|
void Swapchain::Destroy()
|
||||||
{
|
{
|
||||||
DestroyDepthResources();
|
|
||||||
|
|
||||||
const auto& vkDevice = m_Device.GetDevice();
|
const auto& vkDevice = m_Device.GetDevice();
|
||||||
|
|
||||||
for (auto view : m_ImageViews)
|
for (auto view : m_ImageViews)
|
||||||
@@ -181,78 +177,4 @@ namespace Engine
|
|||||||
|
|
||||||
return extent;
|
return extent;
|
||||||
}
|
}
|
||||||
|
|
||||||
vk::Format Swapchain::FindDepthFormat()
|
|
||||||
{
|
|
||||||
// 优先 D32_SFLOAT,退回 D32_SFLOAT_S8_UINT,再退回 D24_UNORM_S8_UINT
|
|
||||||
std::array<vk::Format, 3> candidates = {
|
|
||||||
vk::Format::eD32Sfloat,
|
|
||||||
vk::Format::eD32SfloatS8Uint,
|
|
||||||
vk::Format::eD24UnormS8Uint
|
|
||||||
};
|
|
||||||
|
|
||||||
for (auto format : candidates)
|
|
||||||
{
|
|
||||||
auto props = m_Device.GetPhysicalDevice().getFormatProperties(format);
|
|
||||||
if (props.optimalTilingFeatures & vk::FormatFeatureFlagBits::eDepthStencilAttachment)
|
|
||||||
{
|
|
||||||
return format;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ENGINE_FATAL("No depth format supported");
|
|
||||||
return vk::Format::eD32Sfloat;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Swapchain::CreateDepthResources()
|
|
||||||
{
|
|
||||||
m_DepthFormat = FindDepthFormat();
|
|
||||||
|
|
||||||
vk::ImageCreateInfo imageInfo;
|
|
||||||
imageInfo.setImageType(vk::ImageType::e2D)
|
|
||||||
.setFormat(m_DepthFormat)
|
|
||||||
.setExtent({m_Extent.width, m_Extent.height, 1})
|
|
||||||
.setMipLevels(1)
|
|
||||||
.setArrayLayers(1)
|
|
||||||
.setSamples(vk::SampleCountFlagBits::e1)
|
|
||||||
.setTiling(vk::ImageTiling::eOptimal)
|
|
||||||
.setUsage(vk::ImageUsageFlagBits::eDepthStencilAttachment)
|
|
||||||
.setSharingMode(vk::SharingMode::eExclusive)
|
|
||||||
.setInitialLayout(vk::ImageLayout::eUndefined);
|
|
||||||
|
|
||||||
m_DepthImage = m_Device.GetDevice().createImage(imageInfo);
|
|
||||||
|
|
||||||
auto reqs = m_Device.GetDevice().getImageMemoryRequirements(m_DepthImage);
|
|
||||||
auto alloc = m_Allocator.Allocate(reqs, vk::MemoryPropertyFlagBits::eDeviceLocal);
|
|
||||||
|
|
||||||
m_DepthMemory = alloc.Memory;
|
|
||||||
m_Device.GetDevice().bindImageMemory(m_DepthImage, m_DepthMemory, alloc.Offset);
|
|
||||||
|
|
||||||
vk::ImageViewCreateInfo viewInfo;
|
|
||||||
viewInfo.setImage(m_DepthImage)
|
|
||||||
.setViewType(vk::ImageViewType::e2D)
|
|
||||||
.setFormat(m_DepthFormat)
|
|
||||||
.setSubresourceRange({
|
|
||||||
vk::ImageAspectFlagBits::eDepth,
|
|
||||||
0, 1, 0, 1
|
|
||||||
});
|
|
||||||
|
|
||||||
m_DepthImageView = m_Device.GetDevice().createImageView(viewInfo);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Swapchain::DestroyDepthResources()
|
|
||||||
{
|
|
||||||
const auto& vkDevice = m_Device.GetDevice();
|
|
||||||
if (m_DepthImageView)
|
|
||||||
{
|
|
||||||
vkDevice.destroyImageView(m_DepthImageView);
|
|
||||||
m_DepthImageView = nullptr;
|
|
||||||
}
|
|
||||||
if (m_DepthImage)
|
|
||||||
{
|
|
||||||
vkDevice.destroyImage(m_DepthImage);
|
|
||||||
m_DepthImage = nullptr;
|
|
||||||
}
|
|
||||||
// 内存由 MemoryAllocator 统一释放
|
|
||||||
}
|
|
||||||
} // namespace Engine
|
} // namespace Engine
|
||||||
|
|||||||
@@ -9,28 +9,26 @@
|
|||||||
|
|
||||||
namespace Engine {
|
namespace Engine {
|
||||||
|
|
||||||
UniformBuffer::UniformBuffer(Device& device, MemoryAllocator& allocator, u64 size)
|
UniformBuffer::UniformBuffer(Device& device, MemoryAllocator& allocator,
|
||||||
|
u64 size, u32 frameCount)
|
||||||
: m_Size(size)
|
: m_Size(size)
|
||||||
{
|
{
|
||||||
// Uniform Buffer 需要 CPU 每帧更新,所以用 HOST_VISIBLE | HOST_COHERENT
|
m_Buffers.reserve(frameCount);
|
||||||
m_Buffer = CreateScope<Buffer>(
|
for (u32 i = 0; i < frameCount; ++i) {
|
||||||
device, allocator,
|
// Uniform Buffer 需要 CPU 每帧更新,所以用 HOST_VISIBLE | HOST_COHERENT
|
||||||
size,
|
m_Buffers.push_back(CreateScope<Buffer>(
|
||||||
vk::BufferUsageFlagBits::eUniformBuffer,
|
device, allocator, size,
|
||||||
vk::MemoryPropertyFlagBits::eHostVisible |
|
vk::BufferUsageFlagBits::eUniformBuffer,
|
||||||
vk::MemoryPropertyFlagBits::eHostCoherent);
|
vk::MemoryPropertyFlagBits::eHostVisible |
|
||||||
|
vk::MemoryPropertyFlagBits::eHostCoherent));
|
||||||
|
}
|
||||||
|
|
||||||
ENGINE_INFO("UniformBuffer created: {} bytes", size);
|
ENGINE_INFO("UniformBuffer created: {} bytes x {} frames", size, frameCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
UniformBuffer::~UniformBuffer()
|
void UniformBuffer::Update(u32 frameSlot, const void* data, u64 size)
|
||||||
{
|
{
|
||||||
// Buffer 析构时自动销毁 vk::Buffer,内存由 MemoryAllocator 统一释放
|
m_Buffers[frameSlot]->Upload(data, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
void UniformBuffer::Update(const void* data, u64 size)
|
} // namespace Engine
|
||||||
{
|
|
||||||
m_Buffer->Upload(data, size);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace Engine
|
|
||||||
|
|||||||
Submodule
+1
Submodule Lib/entt added at 85c6bba014
Reference in New Issue
Block a user