Compare commits
10 Commits
f7e4f89cca
...
44e0d65f10
| Author | SHA1 | Date |
|---|---|---|
|
|
44e0d65f10 | |
|
|
e0b0a7883d | |
|
|
97be1b746a | |
|
|
717112c437 | |
|
|
ec6ced9db2 | |
|
|
8cd883ede7 | |
|
|
91e3e8fe57 | |
|
|
9041833430 | |
|
|
b4a3d6b14b | |
|
|
d387532738 |
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"permissions": {
|
||||||
|
"allow": [
|
||||||
|
"Bash(git diff:*)"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -31,8 +31,8 @@ void PlayerNode::onUpdate(float dt) {
|
||||||
|
|
||||||
setPosition(x, y);
|
setPosition(x, y);
|
||||||
|
|
||||||
// 根据移动方向旋转
|
// 根据移动方向旋转(顺时针,使用负角度)
|
||||||
float angle = time_ * 0.5f * 57.2958f + 90.0f;
|
float angle = -time_ * 0.5f * 57.2958f + 90.0f;
|
||||||
setRotation(angle);
|
setRotation(angle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -56,8 +56,8 @@ RotatingDecoration::RotatingDecoration() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void RotatingDecoration::onUpdate(float dt) {
|
void RotatingDecoration::onUpdate(float dt) {
|
||||||
// 自转
|
// 自转(顺时针,使用负角度)
|
||||||
setRotation(getRotation() + rotationSpeed_ * dt);
|
setRotation(getRotation() - rotationSpeed_ * dt);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
|
|
@ -89,6 +89,7 @@ void GameScene::onExit() {
|
||||||
Scene::onExit();
|
Scene::onExit();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
void GameScene::update(float dt) {
|
void GameScene::update(float dt) {
|
||||||
Scene::update(dt);
|
Scene::update(dt);
|
||||||
|
|
||||||
|
|
@ -100,7 +101,7 @@ void GameScene::update(float dt) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新装饰物
|
// 更新装饰物
|
||||||
for (auto& decoration : decorations_) {
|
for (auto &decoration : decorations_) {
|
||||||
decoration->onUpdate(dt);
|
decoration->onUpdate(dt);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -135,12 +136,9 @@ void GameScene::createDecorations() {
|
||||||
float r = std::abs(hue * 6.0f - 3.0f) - 1.0f;
|
float r = std::abs(hue * 6.0f - 3.0f) - 1.0f;
|
||||||
float g = 2.0f - std::abs(hue * 6.0f - 2.0f);
|
float g = 2.0f - std::abs(hue * 6.0f - 2.0f);
|
||||||
float b = 2.0f - std::abs(hue * 6.0f - 4.0f);
|
float b = 2.0f - std::abs(hue * 6.0f - 4.0f);
|
||||||
sprite->setColor(Color(
|
sprite->setColor(Color(std::max(0.0f, std::min(1.0f, r)),
|
||||||
std::max(0.0f, std::min(1.0f, r)),
|
|
||||||
std::max(0.0f, std::min(1.0f, g)),
|
std::max(0.0f, std::min(1.0f, g)),
|
||||||
std::max(0.0f, std::min(1.0f, b)),
|
std::max(0.0f, std::min(1.0f, b)), 1.0f));
|
||||||
1.0f
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
decorations_.push_back(decoration);
|
decorations_.push_back(decoration);
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
#version 320 es
|
||||||
|
precision highp float;
|
||||||
|
|
||||||
|
// 从顶点着色器输入
|
||||||
|
in vec2 vTexCoord;
|
||||||
|
in vec4 vColor;
|
||||||
|
in vec4 vTintColor;
|
||||||
|
in float vOpacity;
|
||||||
|
|
||||||
|
// 纹理采样器
|
||||||
|
uniform sampler2D uTexture;
|
||||||
|
|
||||||
|
// 输出颜色
|
||||||
|
out vec4 fragColor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 片段着色器入口
|
||||||
|
*
|
||||||
|
* 采样纹理并与顶点颜色、色调和透明度混合
|
||||||
|
*/
|
||||||
|
void main() {
|
||||||
|
// 采样纹理
|
||||||
|
vec4 texColor = texture(uTexture, vTexCoord);
|
||||||
|
|
||||||
|
// 如果纹理采样结果是黑色或透明,使用白色作为默认值
|
||||||
|
if (texColor.rgb == vec3(0.0) || texColor.a < 0.01) {
|
||||||
|
texColor = vec4(1.0, 1.0, 1.0, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 混合:纹理 * 顶点颜色 * 色调
|
||||||
|
fragColor = texColor * vColor * vTintColor;
|
||||||
|
|
||||||
|
// 应用透明度
|
||||||
|
fragColor.a *= vOpacity;
|
||||||
|
|
||||||
|
// Alpha 测试:丢弃几乎透明的像素
|
||||||
|
if (fragColor.a < 0.01) {
|
||||||
|
discard;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,48 @@
|
||||||
|
#version 320 es
|
||||||
|
precision highp float;
|
||||||
|
|
||||||
|
// 全局 UBO (binding = 0) - 每帧更新一次
|
||||||
|
layout(std140, binding = 0) uniform GlobalUBO {
|
||||||
|
mat4 uViewProjection;
|
||||||
|
vec4 uCameraPosition;
|
||||||
|
float uTime;
|
||||||
|
float uDeltaTime;
|
||||||
|
vec2 uScreenSize;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 材质 UBO (binding = 1) - 每物体更新
|
||||||
|
layout(std140, binding = 1) uniform MaterialUBO {
|
||||||
|
vec4 uColor;
|
||||||
|
vec4 uTintColor;
|
||||||
|
float uOpacity;
|
||||||
|
float uPadding[3]; // std140 对齐填充
|
||||||
|
};
|
||||||
|
|
||||||
|
// 模型矩阵作为单独的统一变量(每个物体设置)
|
||||||
|
uniform mat4 uModelMatrix;
|
||||||
|
|
||||||
|
// 顶点属性
|
||||||
|
layout(location = 0) in vec2 aPosition;
|
||||||
|
layout(location = 1) in vec2 aTexCoord;
|
||||||
|
layout(location = 2) in vec4 aColor;
|
||||||
|
|
||||||
|
// 输出到片段着色器
|
||||||
|
out vec2 vTexCoord;
|
||||||
|
out vec4 vColor;
|
||||||
|
out vec4 vTintColor;
|
||||||
|
out float vOpacity;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 顶点着色器入口
|
||||||
|
*
|
||||||
|
* 计算顶点在裁剪空间中的位置,
|
||||||
|
* 并传递纹理坐标和颜色到片段着色器
|
||||||
|
*/
|
||||||
|
void main() {
|
||||||
|
gl_Position = uViewProjection * uModelMatrix * vec4(aPosition, 0.0, 1.0);
|
||||||
|
vTexCoord = aTexCoord;
|
||||||
|
// 混合顶点颜色和材质 UBO 中的颜色
|
||||||
|
vColor = aColor * uColor;
|
||||||
|
vTintColor = uTintColor;
|
||||||
|
vOpacity = uOpacity;
|
||||||
|
}
|
||||||
|
|
@ -28,9 +28,25 @@ target("scene_graph_demo")
|
||||||
local nacptool = path.join(devkitPro, "tools/bin/nacptool.exe")
|
local nacptool = path.join(devkitPro, "tools/bin/nacptool.exe")
|
||||||
local elf2nro = path.join(devkitPro, "tools/bin/elf2nro.exe")
|
local elf2nro = path.join(devkitPro, "tools/bin/elf2nro.exe")
|
||||||
|
|
||||||
|
-- 确保 romfs 目录存在
|
||||||
|
local romfs = path.join(example_dir, "romfs")
|
||||||
|
if not os.isdir(romfs) then
|
||||||
|
os.mkdir(romfs)
|
||||||
|
end
|
||||||
|
|
||||||
|
-- 复制着色器文件到 romfs/shader 目录
|
||||||
|
local shader_source = path.join(example_dir, "../../shader")
|
||||||
|
local shader_target = path.join(romfs, "shader")
|
||||||
|
if os.isdir(shader_source) then
|
||||||
|
if not os.isdir(shader_target) then
|
||||||
|
os.mkdir(shader_target)
|
||||||
|
end
|
||||||
|
os.cp(path.join(shader_source, "*"), shader_target)
|
||||||
|
print("Copied shaders to romfs: " .. shader_target)
|
||||||
|
end
|
||||||
|
|
||||||
if os.isfile(nacptool) and os.isfile(elf2nro) then
|
if os.isfile(nacptool) and os.isfile(elf2nro) then
|
||||||
os.vrunv(nacptool, {"--create", "Scene Graph Demo", "Extra2D Team", "1.0.0", nacp_file})
|
os.vrunv(nacptool, {"--create", "Scene Graph Demo", "Extra2D Team", "1.0.0", nacp_file})
|
||||||
local romfs = path.join(example_dir, "romfs")
|
|
||||||
if os.isdir(romfs) then
|
if os.isdir(romfs) then
|
||||||
os.vrunv(elf2nro, {elf_file, nro_file, "--nacp=" .. nacp_file, "--romfsdir=" .. romfs})
|
os.vrunv(elf2nro, {elf_file, nro_file, "--nacp=" .. nacp_file, "--romfsdir=" .. romfs})
|
||||||
else
|
else
|
||||||
|
|
|
||||||
|
|
@ -4,24 +4,42 @@
|
||||||
#include <types/ptr/intrusive_ptr.h>
|
#include <types/ptr/intrusive_ptr.h>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <chrono>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 资源加载状态
|
||||||
|
*/
|
||||||
|
enum class AssetLoadState {
|
||||||
|
Unloaded, ///< 未加载
|
||||||
|
Loading, ///< 加载中
|
||||||
|
Loaded, ///< 已加载
|
||||||
|
Failed ///< 加载失败
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 密集存储的资源仓库
|
* @brief 密集存储的资源仓库
|
||||||
*
|
*
|
||||||
* 使用 Dense Vec 存储,类似 ECS 的组件存储。
|
* 使用 Dense Vec 存储,类似 ECS 的组件存储。
|
||||||
* 提供 O(1) 的插入、删除、访问。
|
* 提供 O(1) 的插入、删除、访问。
|
||||||
|
* 支持引用统计和 LRU(最近最少使用)缓存策略。
|
||||||
*
|
*
|
||||||
* @tparam T 资源类型
|
* @tparam T 资源类型
|
||||||
*/
|
*/
|
||||||
template<typename T>
|
template<typename T>
|
||||||
class AssetStorage {
|
class AssetStorage {
|
||||||
public:
|
public:
|
||||||
|
using TimePoint = std::chrono::steady_clock::time_point;
|
||||||
|
|
||||||
struct Slot {
|
struct Slot {
|
||||||
Ptr<T> asset;
|
Ptr<T> asset;
|
||||||
typename Handle<T>::Generation generation = 0;
|
typename Handle<T>::Generation generation = 0;
|
||||||
bool active = false;
|
bool active = false;
|
||||||
|
AssetLoadState loadState = AssetLoadState::Unloaded;
|
||||||
|
TimePoint lastAccessTime; ///< 最后访问时间(用于 LRU)
|
||||||
};
|
};
|
||||||
|
|
||||||
AssetStorage() = default;
|
AssetStorage() = default;
|
||||||
|
|
@ -37,18 +55,23 @@ public:
|
||||||
*/
|
*/
|
||||||
Handle<T> insert(Ptr<T> asset) {
|
Handle<T> insert(Ptr<T> asset) {
|
||||||
uint32_t index;
|
uint32_t index;
|
||||||
|
auto now = std::chrono::steady_clock::now();
|
||||||
|
|
||||||
if (!freeIndices_.empty()) {
|
if (!freeIndices_.empty()) {
|
||||||
index = freeIndices_.back();
|
index = freeIndices_.back();
|
||||||
freeIndices_.pop_back();
|
freeIndices_.pop_back();
|
||||||
slots_[index].asset = std::move(asset);
|
slots_[index].asset = std::move(asset);
|
||||||
slots_[index].active = true;
|
slots_[index].active = true;
|
||||||
|
slots_[index].loadState = AssetLoadState::Loaded;
|
||||||
|
slots_[index].lastAccessTime = now;
|
||||||
} else {
|
} else {
|
||||||
index = static_cast<uint32_t>(slots_.size());
|
index = static_cast<uint32_t>(slots_.size());
|
||||||
Slot slot;
|
Slot slot;
|
||||||
slot.asset = std::move(asset);
|
slot.asset = std::move(asset);
|
||||||
slot.generation = nextGeneration_++;
|
slot.generation = nextGeneration_++;
|
||||||
slot.active = true;
|
slot.active = true;
|
||||||
|
slot.loadState = AssetLoadState::Loaded;
|
||||||
|
slot.lastAccessTime = now;
|
||||||
slots_.push_back(std::move(slot));
|
slots_.push_back(std::move(slot));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -76,7 +99,18 @@ public:
|
||||||
* @param handle 资源句柄
|
* @param handle 资源句柄
|
||||||
* @return 资源指针
|
* @return 资源指针
|
||||||
*/
|
*/
|
||||||
T* get(Handle<T> handle) const {
|
T* get(Handle<T> handle) {
|
||||||
|
if (!isValid(handle)) return nullptr;
|
||||||
|
updateAccessTime(handle);
|
||||||
|
return slots_[handle.index()].asset.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取资源(返回指针,可能为 nullptr) - 常量版本,不更新访问时间
|
||||||
|
* @param handle 资源句柄
|
||||||
|
* @return 资源指针
|
||||||
|
*/
|
||||||
|
const T* get(Handle<T> handle) const {
|
||||||
if (!isValid(handle)) return nullptr;
|
if (!isValid(handle)) return nullptr;
|
||||||
return slots_[handle.index()].asset.get();
|
return slots_[handle.index()].asset.get();
|
||||||
}
|
}
|
||||||
|
|
@ -86,6 +120,17 @@ public:
|
||||||
* @param handle 资源句柄
|
* @param handle 资源句柄
|
||||||
* @return 资源智能指针
|
* @return 资源智能指针
|
||||||
*/
|
*/
|
||||||
|
Ptr<T> getPtr(Handle<T> handle) {
|
||||||
|
if (!isValid(handle)) return Ptr<T>();
|
||||||
|
updateAccessTime(handle);
|
||||||
|
return slots_[handle.index()].asset;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取资源(返回智能指针) - 常量版本,不更新访问时间
|
||||||
|
* @param handle 资源句柄
|
||||||
|
* @return 资源智能指针
|
||||||
|
*/
|
||||||
Ptr<T> getPtr(Handle<T> handle) const {
|
Ptr<T> getPtr(Handle<T> handle) const {
|
||||||
if (!isValid(handle)) return Ptr<T>();
|
if (!isValid(handle)) return Ptr<T>();
|
||||||
return slots_[handle.index()].asset;
|
return slots_[handle.index()].asset;
|
||||||
|
|
@ -104,6 +149,44 @@ public:
|
||||||
return slot.active && slot.generation == handle.generation();
|
return slot.active && slot.generation == handle.generation();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取资源加载状态
|
||||||
|
* @param handle 资源句柄
|
||||||
|
* @return 加载状态
|
||||||
|
*/
|
||||||
|
AssetLoadState getLoadState(Handle<T> handle) const {
|
||||||
|
if (!isValid(handle)) return AssetLoadState::Unloaded;
|
||||||
|
return slots_[handle.index()].loadState;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置资源加载状态
|
||||||
|
* @param handle 资源句柄
|
||||||
|
* @param state 加载状态
|
||||||
|
*/
|
||||||
|
void setLoadState(Handle<T> handle, AssetLoadState state) {
|
||||||
|
if (!isValid(handle)) return;
|
||||||
|
slots_[handle.index()].loadState = state;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查资源是否正在加载
|
||||||
|
* @param handle 资源句柄
|
||||||
|
* @return 是否正在加载
|
||||||
|
*/
|
||||||
|
bool isLoading(Handle<T> handle) const {
|
||||||
|
return getLoadState(handle) == AssetLoadState::Loading;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查资源是否已加载
|
||||||
|
* @param handle 资源句柄
|
||||||
|
* @return 是否已加载
|
||||||
|
*/
|
||||||
|
bool isLoaded(Handle<T> handle) const {
|
||||||
|
return getLoadState(handle) == AssetLoadState::Loaded;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 遍历所有资源
|
* @brief 遍历所有资源
|
||||||
* @param func 回调函数,签名 void(Handle<T>, T*)
|
* @param func 回调函数,签名 void(Handle<T>, T*)
|
||||||
|
|
@ -137,6 +220,111 @@ public:
|
||||||
*/
|
*/
|
||||||
size_t capacity() const { return slots_.size(); }
|
size_t capacity() const { return slots_.size(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取资源引用计数
|
||||||
|
* @param handle 资源句柄
|
||||||
|
* @return 引用计数
|
||||||
|
*/
|
||||||
|
uint32_t getRefCount(Handle<T> handle) const {
|
||||||
|
if (!isValid(handle)) return 0;
|
||||||
|
const auto& slot = slots_[handle.index()];
|
||||||
|
return slot.asset ? slot.asset.refCount() : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取资源最后访问时间
|
||||||
|
* @param handle 资源句柄
|
||||||
|
* @return 最后访问时间
|
||||||
|
*/
|
||||||
|
TimePoint getLastAccessTime(Handle<T> handle) const {
|
||||||
|
if (!isValid(handle)) return TimePoint();
|
||||||
|
return slots_[handle.index()].lastAccessTime;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 更新资源访问时间
|
||||||
|
* @param handle 资源句柄
|
||||||
|
*/
|
||||||
|
void updateAccessTime(Handle<T> handle) {
|
||||||
|
if (!isValid(handle)) return;
|
||||||
|
slots_[handle.index()].lastAccessTime = std::chrono::steady_clock::now();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 查找可以卸载的资源(引用计数为1的资源,只有AssetStorage自己持有引用)
|
||||||
|
* @param maxAgeSeconds 最大空闲时间(秒),默认300秒
|
||||||
|
* @return 可以卸载的资源句柄列表
|
||||||
|
*/
|
||||||
|
std::vector<Handle<T>> findUnloadableResources(int maxAgeSeconds = 300) const {
|
||||||
|
std::vector<Handle<T>> result;
|
||||||
|
auto now = std::chrono::steady_clock::now();
|
||||||
|
|
||||||
|
for (size_t i = 0; i < slots_.size(); ++i) {
|
||||||
|
const Slot& slot = slots_[i];
|
||||||
|
if (slot.active && slot.asset) {
|
||||||
|
// 引用计数为1表示只有 AssetStorage 自己持有引用
|
||||||
|
if (slot.asset.refCount() == 1) {
|
||||||
|
auto age = std::chrono::duration_cast<std::chrono::seconds>(
|
||||||
|
now - slot.lastAccessTime).count();
|
||||||
|
if (age >= maxAgeSeconds) {
|
||||||
|
result.emplace_back(static_cast<uint32_t>(i), slot.generation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 按访问时间排序,最久未使用的排在前面
|
||||||
|
std::sort(result.begin(), result.end(),
|
||||||
|
[this](const Handle<T>& a, const Handle<T>& b) {
|
||||||
|
return slots_[a.index()].lastAccessTime < slots_[b.index()].lastAccessTime;
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 卸载指定资源(只有引用计数为1时才会真正卸载)
|
||||||
|
* @param handle 资源句柄
|
||||||
|
* @return 是否成功卸载
|
||||||
|
*/
|
||||||
|
bool unloadResource(Handle<T> handle) {
|
||||||
|
if (!isValid(handle)) return false;
|
||||||
|
|
||||||
|
auto& slot = slots_[handle.index()];
|
||||||
|
if (!slot.asset) return false;
|
||||||
|
|
||||||
|
// 只有引用计数为1时才可以卸载(只有AssetStorage自己持有)
|
||||||
|
if (slot.asset.refCount() > 1) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
slot.asset.reset();
|
||||||
|
slot.loadState = AssetLoadState::Unloaded;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 批量卸载可卸载的资源
|
||||||
|
* @param maxCount 最大卸载数量,-1表示全部卸载
|
||||||
|
* @param maxAgeSeconds 最大空闲时间(秒)
|
||||||
|
* @return 实际卸载的资源数量
|
||||||
|
*/
|
||||||
|
size_t unloadOldResources(int maxCount = -1, int maxAgeSeconds = 300) {
|
||||||
|
auto unloadable = findUnloadableResources(maxAgeSeconds);
|
||||||
|
size_t unloaded = 0;
|
||||||
|
|
||||||
|
for (const auto& handle : unloadable) {
|
||||||
|
if (maxCount >= 0 && unloaded >= static_cast<size_t>(maxCount)) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (unloadResource(handle)) {
|
||||||
|
++unloaded;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return unloaded;
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::vector<Slot> slots_;
|
std::vector<Slot> slots_;
|
||||||
std::vector<uint32_t> freeIndices_;
|
std::vector<uint32_t> freeIndices_;
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <assets/asset_loader.h>
|
|
||||||
#include <assets/asset_storage.h>
|
#include <assets/asset_storage.h>
|
||||||
|
#include <assets/asset_loader.h>
|
||||||
#include <assets/handle.h>
|
#include <assets/handle.h>
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <chrono>
|
|
||||||
#include <condition_variable>
|
#include <condition_variable>
|
||||||
#include <event/events.h>
|
#include <event/events.h>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
|
@ -34,7 +33,8 @@ namespace extra2d {
|
||||||
* - Handle<T>: 轻量级句柄
|
* - Handle<T>: 轻量级句柄
|
||||||
*/
|
*/
|
||||||
class AssetsModule : public Module {
|
class AssetsModule : public Module {
|
||||||
E2D_REGISTER_MODULE(AssetsModule, "Assets", 2)
|
// 优先级为 3,在 FileModule (优先级 2) 之后初始化
|
||||||
|
E2D_REGISTER_MODULE(AssetsModule, "Assets", 3)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
AssetsModule();
|
AssetsModule();
|
||||||
|
|
@ -103,11 +103,52 @@ public:
|
||||||
*/
|
*/
|
||||||
template <typename T> bool isValid(Handle<T> handle) const;
|
template <typename T> bool isValid(Handle<T> handle) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查资源是否正在加载
|
||||||
|
*/
|
||||||
|
template <typename T> bool isLoading(Handle<T> handle) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查资源是否已加载
|
||||||
|
*/
|
||||||
|
template <typename T> bool isLoaded(Handle<T> handle) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取资源加载状态
|
||||||
|
*/
|
||||||
|
template <typename T> AssetLoadState getLoadState(Handle<T> handle) const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 移除资源
|
* @brief 移除资源
|
||||||
*/
|
*/
|
||||||
template <typename T> void remove(Handle<T> handle);
|
template <typename T> void remove(Handle<T> handle);
|
||||||
|
|
||||||
|
//===========================================================================
|
||||||
|
// 资源引用统计与智能卸载
|
||||||
|
//===========================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取资源引用计数
|
||||||
|
*/
|
||||||
|
template <typename T> uint32_t getRefCount(Handle<T> handle) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 查找可以卸载的资源
|
||||||
|
*/
|
||||||
|
template <typename T>
|
||||||
|
std::vector<Handle<T>> findUnloadableResources(int maxAgeSeconds = 300) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 卸载指定资源
|
||||||
|
*/
|
||||||
|
template <typename T> bool unloadResource(Handle<T> handle);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 批量卸载可卸载的资源
|
||||||
|
*/
|
||||||
|
template <typename T>
|
||||||
|
size_t unloadOldResources(int maxCount = -1, int maxAgeSeconds = 300);
|
||||||
|
|
||||||
//===========================================================================
|
//===========================================================================
|
||||||
// 批量加载(目录/包)
|
// 批量加载(目录/包)
|
||||||
//===========================================================================
|
//===========================================================================
|
||||||
|
|
@ -215,6 +256,12 @@ private:
|
||||||
void addFileWatch(const std::string &path, Handle<Texture> handle);
|
void addFileWatch(const std::string &path, Handle<Texture> handle);
|
||||||
void addFileWatch(const std::string &path, Handle<Shader> handle);
|
void addFileWatch(const std::string &path, Handle<Shader> handle);
|
||||||
|
|
||||||
|
private:
|
||||||
|
/**
|
||||||
|
* @brief 内部实现:添加文件监控(减少重复代码)
|
||||||
|
*/
|
||||||
|
void addFileWatchInternal(const std::string &path, FileWatchInfo &&info);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 重新加载纹理
|
* @brief 重新加载纹理
|
||||||
*/
|
*/
|
||||||
|
|
@ -470,6 +517,75 @@ template <> inline bool AssetsModule::isValid<Mesh>(Handle<Mesh> handle) const {
|
||||||
return meshes_.isValid(handle);
|
return meshes_.isValid(handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename T> bool AssetsModule::isLoading(Handle<T> handle) const {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
inline bool AssetsModule::isLoading<Texture>(Handle<Texture> handle) const {
|
||||||
|
return textures_.isLoading(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
inline bool AssetsModule::isLoading<Shader>(Handle<Shader> handle) const {
|
||||||
|
return shaders_.isLoading(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
inline bool AssetsModule::isLoading<Material>(Handle<Material> handle) const {
|
||||||
|
return materials_.isLoading(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <> inline bool AssetsModule::isLoading<Mesh>(Handle<Mesh> handle) const {
|
||||||
|
return meshes_.isLoading(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T> bool AssetsModule::isLoaded(Handle<T> handle) const {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
inline bool AssetsModule::isLoaded<Texture>(Handle<Texture> handle) const {
|
||||||
|
return textures_.isLoaded(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
inline bool AssetsModule::isLoaded<Shader>(Handle<Shader> handle) const {
|
||||||
|
return shaders_.isLoaded(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
inline bool AssetsModule::isLoaded<Material>(Handle<Material> handle) const {
|
||||||
|
return materials_.isLoaded(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <> inline bool AssetsModule::isLoaded<Mesh>(Handle<Mesh> handle) const {
|
||||||
|
return meshes_.isLoaded(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T> AssetLoadState AssetsModule::getLoadState(Handle<T> handle) const {
|
||||||
|
return AssetLoadState::Unloaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
inline AssetLoadState AssetsModule::getLoadState<Texture>(Handle<Texture> handle) const {
|
||||||
|
return textures_.getLoadState(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
inline AssetLoadState AssetsModule::getLoadState<Shader>(Handle<Shader> handle) const {
|
||||||
|
return shaders_.getLoadState(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
inline AssetLoadState AssetsModule::getLoadState<Material>(Handle<Material> handle) const {
|
||||||
|
return materials_.getLoadState(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <> inline AssetLoadState AssetsModule::getLoadState<Mesh>(Handle<Mesh> handle) const {
|
||||||
|
return meshes_.getLoadState(handle);
|
||||||
|
}
|
||||||
|
|
||||||
template <typename T> void AssetsModule::remove(Handle<T> handle) {}
|
template <typename T> void AssetsModule::remove(Handle<T> handle) {}
|
||||||
|
|
||||||
template <> inline void AssetsModule::remove<Texture>(Handle<Texture> handle) {
|
template <> inline void AssetsModule::remove<Texture>(Handle<Texture> handle) {
|
||||||
|
|
@ -489,6 +605,108 @@ template <> inline void AssetsModule::remove<Mesh>(Handle<Mesh> handle) {
|
||||||
meshes_.remove(handle);
|
meshes_.remove(handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template <typename T> uint32_t AssetsModule::getRefCount(Handle<T> handle) const {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
inline uint32_t AssetsModule::getRefCount<Texture>(Handle<Texture> handle) const {
|
||||||
|
return textures_.getRefCount(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
inline uint32_t AssetsModule::getRefCount<Shader>(Handle<Shader> handle) const {
|
||||||
|
return shaders_.getRefCount(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
inline uint32_t AssetsModule::getRefCount<Material>(Handle<Material> handle) const {
|
||||||
|
return materials_.getRefCount(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
inline uint32_t AssetsModule::getRefCount<Mesh>(Handle<Mesh> handle) const {
|
||||||
|
return meshes_.getRefCount(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
std::vector<Handle<T>> AssetsModule::findUnloadableResources(int maxAgeSeconds) const {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
inline std::vector<Handle<Texture>>
|
||||||
|
AssetsModule::findUnloadableResources<Texture>(int maxAgeSeconds) const {
|
||||||
|
return textures_.findUnloadableResources(maxAgeSeconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
inline std::vector<Handle<Shader>>
|
||||||
|
AssetsModule::findUnloadableResources<Shader>(int maxAgeSeconds) const {
|
||||||
|
return shaders_.findUnloadableResources(maxAgeSeconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
inline std::vector<Handle<Material>>
|
||||||
|
AssetsModule::findUnloadableResources<Material>(int maxAgeSeconds) const {
|
||||||
|
return materials_.findUnloadableResources(maxAgeSeconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
inline std::vector<Handle<Mesh>>
|
||||||
|
AssetsModule::findUnloadableResources<Mesh>(int maxAgeSeconds) const {
|
||||||
|
return meshes_.findUnloadableResources(maxAgeSeconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T> bool AssetsModule::unloadResource(Handle<T> handle) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
inline bool AssetsModule::unloadResource<Texture>(Handle<Texture> handle) {
|
||||||
|
return textures_.unloadResource(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
inline bool AssetsModule::unloadResource<Shader>(Handle<Shader> handle) {
|
||||||
|
return shaders_.unloadResource(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
inline bool AssetsModule::unloadResource<Material>(Handle<Material> handle) {
|
||||||
|
return materials_.unloadResource(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
inline bool AssetsModule::unloadResource<Mesh>(Handle<Mesh> handle) {
|
||||||
|
return meshes_.unloadResource(handle);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <typename T>
|
||||||
|
size_t AssetsModule::unloadOldResources(int maxCount, int maxAgeSeconds) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
inline size_t AssetsModule::unloadOldResources<Texture>(int maxCount, int maxAgeSeconds) {
|
||||||
|
return textures_.unloadOldResources(maxCount, maxAgeSeconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
inline size_t AssetsModule::unloadOldResources<Shader>(int maxCount, int maxAgeSeconds) {
|
||||||
|
return shaders_.unloadOldResources(maxCount, maxAgeSeconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
inline size_t AssetsModule::unloadOldResources<Material>(int maxCount, int maxAgeSeconds) {
|
||||||
|
return materials_.unloadOldResources(maxCount, maxAgeSeconds);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
inline size_t AssetsModule::unloadOldResources<Mesh>(int maxCount, int maxAgeSeconds) {
|
||||||
|
return meshes_.unloadOldResources(maxCount, maxAgeSeconds);
|
||||||
|
}
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
std::vector<Handle<T>> AssetsModule::loadDir(const std::string &directory,
|
std::vector<Handle<T>> AssetsModule::loadDir(const std::string &directory,
|
||||||
const std::string &pattern,
|
const std::string &pattern,
|
||||||
|
|
@ -509,6 +727,10 @@ template <>
|
||||||
void AssetsModule::loadAsync<Texture>(
|
void AssetsModule::loadAsync<Texture>(
|
||||||
const std::string &path, std::function<void(Handle<Texture>)> callback);
|
const std::string &path, std::function<void(Handle<Texture>)> callback);
|
||||||
|
|
||||||
|
template <>
|
||||||
|
void AssetsModule::loadAsync<Shader>(
|
||||||
|
const std::string &path, std::function<void(Handle<Shader>)> callback);
|
||||||
|
|
||||||
template <typename T>
|
template <typename T>
|
||||||
void AssetsModule::registerLoader(std::unique_ptr<AssetLoader<T>> loader) {}
|
void AssetsModule::registerLoader(std::unique_ptr<AssetLoader<T>> loader) {}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
#include <module/module.h>
|
#include <module/module.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <typeindex>
|
#include <typeindex>
|
||||||
|
#include <unordered_map>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
@ -67,9 +68,35 @@ public:
|
||||||
return registrations_;
|
return registrations_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 存储模块实例
|
||||||
|
* @param module 模块实例指针
|
||||||
|
*/
|
||||||
|
void storeModuleInstance(Module* module);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取模块实例
|
||||||
|
* @tparam T 模块类型
|
||||||
|
* @return 模块实例指针,未找到返回 nullptr
|
||||||
|
*/
|
||||||
|
template <typename T>
|
||||||
|
T* getModule() {
|
||||||
|
auto it = instances_.find(std::type_index(typeid(T)));
|
||||||
|
if (it != instances_.end()) {
|
||||||
|
return static_cast<T*>(it->second);
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 清除所有存储的模块实例
|
||||||
|
*/
|
||||||
|
void clearInstances();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
ModuleRegistry() = default;
|
ModuleRegistry() = default;
|
||||||
std::vector<ModuleInfo> registrations_;
|
std::vector<ModuleInfo> registrations_;
|
||||||
|
std::unordered_map<std::type_index, Module*> instances_;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -108,4 +135,14 @@ public: \
|
||||||
#define E2D_REGISTER_MODULE_SIMPLE(ClassName) \
|
#define E2D_REGISTER_MODULE_SIMPLE(ClassName) \
|
||||||
E2D_REGISTER_MODULE(ClassName, #ClassName, 100)
|
E2D_REGISTER_MODULE(ClassName, #ClassName, 100)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取模块实例的辅助函数
|
||||||
|
* @tparam T 模块类型
|
||||||
|
* @return 模块实例指针
|
||||||
|
*/
|
||||||
|
template <typename T>
|
||||||
|
T* getModule() {
|
||||||
|
return ModuleRegistry::instance().getModule<T>();
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace extra2d
|
} // namespace extra2d
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@
|
||||||
|
|
||||||
#include <module/module.h>
|
#include <module/module.h>
|
||||||
#include <module/module_registry.h>
|
#include <module/module_registry.h>
|
||||||
#include <types/base/types.h>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <types/base/types.h>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
@ -27,7 +27,7 @@ struct FileData {
|
||||||
std::string error;
|
std::string error;
|
||||||
|
|
||||||
operator bool() const { return ok; }
|
operator bool() const { return ok; }
|
||||||
const uint8* ptr() const { return data.data(); }
|
const uint8 *ptr() const { return data.data(); }
|
||||||
size_t size() const { return data.size(); }
|
size_t size() const { return data.size(); }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -38,20 +38,20 @@ struct FileData {
|
||||||
* 非单例设计,通过 Context 管理生命周期
|
* 非单例设计,通过 Context 管理生命周期
|
||||||
*/
|
*/
|
||||||
class FileModule : public Module {
|
class FileModule : public Module {
|
||||||
// 自动注册到模块系统,优先级为 20(系统模块)
|
// 优先级为 2,系统级模块,在 TimerModule (优先级 4) 之前初始化
|
||||||
E2D_REGISTER_MODULE(FileModule, "File", 20)
|
E2D_REGISTER_MODULE(FileModule, "File", 2)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
FileModule();
|
FileModule();
|
||||||
~FileModule() override;
|
~FileModule() override;
|
||||||
|
|
||||||
// 禁止拷贝
|
// 禁止拷贝
|
||||||
FileModule(const FileModule&) = delete;
|
FileModule(const FileModule &) = delete;
|
||||||
FileModule& operator=(const FileModule&) = delete;
|
FileModule &operator=(const FileModule &) = delete;
|
||||||
|
|
||||||
// 允许移动
|
// 允许移动
|
||||||
FileModule(FileModule&&) noexcept;
|
FileModule(FileModule &&) noexcept;
|
||||||
FileModule& operator=(FileModule&&) noexcept;
|
FileModule &operator=(FileModule &&) noexcept;
|
||||||
|
|
||||||
// Module 接口实现
|
// Module 接口实现
|
||||||
bool init() override;
|
bool init() override;
|
||||||
|
|
@ -60,77 +60,77 @@ public:
|
||||||
/**
|
/**
|
||||||
* @brief 检查文件是否存在
|
* @brief 检查文件是否存在
|
||||||
*/
|
*/
|
||||||
bool exists(const std::string& path) const;
|
bool exists(const std::string &path) const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 检查是否为目录
|
* @brief 检查是否为目录
|
||||||
*/
|
*/
|
||||||
bool isDir(const std::string& path) const;
|
bool isDir(const std::string &path) const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 读取整个文件到内存
|
* @brief 读取整个文件到内存
|
||||||
*/
|
*/
|
||||||
FileData read(const std::string& path) const;
|
FileData read(const std::string &path) const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 读取文件为字符串
|
* @brief 读取文件为字符串
|
||||||
*/
|
*/
|
||||||
std::string readString(const std::string& path) const;
|
std::string readString(const std::string &path) const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 写入数据到文件
|
* @brief 写入数据到文件
|
||||||
*/
|
*/
|
||||||
bool write(const std::string& path, const void* data, size_t size) const;
|
bool write(const std::string &path, const void *data, size_t size) const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 写入字符串到文件
|
* @brief 写入字符串到文件
|
||||||
*/
|
*/
|
||||||
bool writeString(const std::string& path, const std::string& content) const;
|
bool writeString(const std::string &path, const std::string &content) const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 追加数据到文件
|
* @brief 追加数据到文件
|
||||||
*/
|
*/
|
||||||
bool append(const std::string& path, const void* data, size_t size) const;
|
bool append(const std::string &path, const void *data, size_t size) const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 删除文件
|
* @brief 删除文件
|
||||||
*/
|
*/
|
||||||
bool remove(const std::string& path) const;
|
bool remove(const std::string &path) const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 创建目录
|
* @brief 创建目录
|
||||||
*/
|
*/
|
||||||
bool mkdir(const std::string& path) const;
|
bool mkdir(const std::string &path) const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 列出目录内容
|
* @brief 列出目录内容
|
||||||
*/
|
*/
|
||||||
std::vector<FileInfo> listDir(const std::string& path) const;
|
std::vector<FileInfo> listDir(const std::string &path) const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 获取文件大小
|
* @brief 获取文件大小
|
||||||
*/
|
*/
|
||||||
int64 fileSize(const std::string& path) const;
|
int64 fileSize(const std::string &path) const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 获取文件扩展名
|
* @brief 获取文件扩展名
|
||||||
*/
|
*/
|
||||||
std::string ext(const std::string& path) const;
|
std::string ext(const std::string &path) const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 获取文件名(不含路径)
|
* @brief 获取文件名(不含路径)
|
||||||
*/
|
*/
|
||||||
std::string fileName(const std::string& path) const;
|
std::string fileName(const std::string &path) const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 获取文件所在目录
|
* @brief 获取文件所在目录
|
||||||
*/
|
*/
|
||||||
std::string dirName(const std::string& path) const;
|
std::string dirName(const std::string &path) const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 连接路径
|
* @brief 连接路径
|
||||||
*/
|
*/
|
||||||
std::string join(const std::string& a, const std::string& b) const;
|
std::string join(const std::string &a, const std::string &b) const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 获取可写目录(用于存档等)
|
* @brief 获取可写目录(用于存档等)
|
||||||
|
|
@ -140,21 +140,40 @@ public:
|
||||||
/**
|
/**
|
||||||
* @brief 设置资源根目录
|
* @brief 设置资源根目录
|
||||||
*/
|
*/
|
||||||
void setAssetRoot(const std::string& root) { assetRoot_ = root; }
|
void setAssetRoot(const std::string &root) { assetRoot_ = root; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 获取资源根目录
|
* @brief 获取资源根目录
|
||||||
*/
|
*/
|
||||||
const std::string& assetRoot() const { return assetRoot_; }
|
const std::string &assetRoot() const { return assetRoot_; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 获取资源完整路径
|
* @brief 获取资源完整路径
|
||||||
*/
|
*/
|
||||||
std::string assetPath(const std::string& relPath) const;
|
std::string assetPath(const std::string &relPath) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查路径是否为 RomFS 路径
|
||||||
|
* @param path 文件路径
|
||||||
|
* @return 是否以 "romfs:/" 开头
|
||||||
|
*/
|
||||||
|
bool isRomfsPath(const std::string &path) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置 RomFS 资源根目录(用于 Switch 平台)
|
||||||
|
* @param root RomFS 根目录,默认为 "romfs:/"
|
||||||
|
*/
|
||||||
|
void setRomfsRoot(const std::string &root) { romfsRoot_ = root; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取 RomFS 资源根目录
|
||||||
|
*/
|
||||||
|
const std::string &romfsRoot() const { return romfsRoot_; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::string assetRoot_;
|
std::string assetRoot_;
|
||||||
std::string writableDir_;
|
std::string writableDir_;
|
||||||
|
std::string romfsRoot_ = "romfs:/";
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace extra2d
|
} // namespace extra2d
|
||||||
|
|
|
||||||
|
|
@ -172,20 +172,18 @@ using TouchCb = std::function<void(const TouchPoint &)>;
|
||||||
* 使用新的 Module 基类,支持自动注册
|
* 使用新的 Module 基类,支持自动注册
|
||||||
*/
|
*/
|
||||||
class InputModule : public Module {
|
class InputModule : public Module {
|
||||||
// 自动注册到模块系统,优先级为 10
|
// 优先级为 7,在 TimerModule (优先级 6) 之后初始化
|
||||||
E2D_REGISTER_MODULE(InputModule, "Input", 10)
|
E2D_REGISTER_MODULE(InputModule, "Input", 7)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
InputModule();
|
InputModule();
|
||||||
~InputModule() override;
|
~InputModule() override;
|
||||||
|
|
||||||
// 禁止拷贝
|
// 禁止拷贝和移动
|
||||||
InputModule(const InputModule &) = delete;
|
InputModule(const InputModule &) = delete;
|
||||||
InputModule &operator=(const InputModule &) = delete;
|
InputModule &operator=(const InputModule &) = delete;
|
||||||
|
InputModule(InputModule &&) = delete;
|
||||||
// 允许移动
|
InputModule &operator=(InputModule &&) = delete;
|
||||||
InputModule(InputModule &&) noexcept;
|
|
||||||
InputModule &operator=(InputModule &&) noexcept;
|
|
||||||
|
|
||||||
// Module 接口实现
|
// Module 接口实现
|
||||||
bool init() override;
|
bool init() override;
|
||||||
|
|
|
||||||
|
|
@ -33,13 +33,11 @@ public:
|
||||||
WindowModule();
|
WindowModule();
|
||||||
~WindowModule() override;
|
~WindowModule() override;
|
||||||
|
|
||||||
// 禁止拷贝
|
// 禁止拷贝和移动
|
||||||
WindowModule(const WindowModule &) = delete;
|
WindowModule(const WindowModule &) = delete;
|
||||||
WindowModule &operator=(const WindowModule &) = delete;
|
WindowModule &operator=(const WindowModule &) = delete;
|
||||||
|
WindowModule(WindowModule &&) = delete;
|
||||||
// 允许移动
|
WindowModule &operator=(WindowModule &&) = delete;
|
||||||
WindowModule(WindowModule &&) noexcept;
|
|
||||||
WindowModule &operator=(WindowModule &&) noexcept;
|
|
||||||
|
|
||||||
// Module 接口实现
|
// Module 接口实现
|
||||||
bool init() override;
|
bool init() override;
|
||||||
|
|
@ -121,28 +119,6 @@ public:
|
||||||
*/
|
*/
|
||||||
bool shouldClose() const { return shouldClose_; }
|
bool shouldClose() const { return shouldClose_; }
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 创建 OpenGL ES 上下文
|
|
||||||
* @return 是否成功
|
|
||||||
*/
|
|
||||||
bool createGLContext();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 销毁 OpenGL ES 上下文
|
|
||||||
*/
|
|
||||||
void destroyGLContext();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 交换缓冲区(呈现渲染结果)
|
|
||||||
*/
|
|
||||||
void swapBuffers();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取 OpenGL ES 上下文句柄
|
|
||||||
* @return SDL_GLContext 句柄
|
|
||||||
*/
|
|
||||||
void* getGLContext() const;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void handleWindowEvent(const SDL_WindowEvent &evt);
|
void handleWindowEvent(const SDL_WindowEvent &evt);
|
||||||
|
|
||||||
|
|
@ -162,7 +138,6 @@ private:
|
||||||
void onRenderPresent();
|
void onRenderPresent();
|
||||||
|
|
||||||
SDL_Window *window_ = nullptr;
|
SDL_Window *window_ = nullptr;
|
||||||
SDL_GLContext glContext_ = nullptr;
|
|
||||||
bool shouldClose_ = false;
|
bool shouldClose_ = false;
|
||||||
|
|
||||||
CloseCb onClose_;
|
CloseCb onClose_;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,424 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <memory>
|
||||||
|
#include <renderer/rhi/rhi.h>
|
||||||
|
#include <renderer/rhi/rhi_command_list.h>
|
||||||
|
#include <renderer/uniform_buffer.h>
|
||||||
|
#include <types/math/color.h>
|
||||||
|
#include <types/math/transform.h>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// 前向声明
|
||||||
|
class CommandQueue;
|
||||||
|
class Material;
|
||||||
|
class Mesh;
|
||||||
|
class UniformBufferManager;
|
||||||
|
template <typename T> class Ptr;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 绘制命令键
|
||||||
|
*
|
||||||
|
* 64位排序键,用于高效排序绘制命令
|
||||||
|
* 布局:| 材质ID (32位) | 深度 (16位) | 层 (8位) | 标志 (8位) |
|
||||||
|
*/
|
||||||
|
struct DrawKey {
|
||||||
|
uint64_t value;
|
||||||
|
|
||||||
|
DrawKey() : value(0) {}
|
||||||
|
explicit DrawKey(uint64_t v) : value(v) {}
|
||||||
|
|
||||||
|
// 构建排序键
|
||||||
|
static DrawKey make(uint32_t materialId, uint16_t depth, uint8_t layer,
|
||||||
|
uint8_t flags = 0) {
|
||||||
|
DrawKey key;
|
||||||
|
key.value = (static_cast<uint64_t>(materialId) << 32) |
|
||||||
|
(static_cast<uint64_t>(depth) << 16) |
|
||||||
|
(static_cast<uint64_t>(layer) << 8) |
|
||||||
|
static_cast<uint64_t>(flags);
|
||||||
|
return key;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t getMaterialId() const { return static_cast<uint32_t>(value >> 32); }
|
||||||
|
uint16_t getDepth() const {
|
||||||
|
return static_cast<uint16_t>((value >> 16) & 0xFFFF);
|
||||||
|
}
|
||||||
|
uint8_t getLayer() const { return static_cast<uint8_t>((value >> 8) & 0xFF); }
|
||||||
|
uint8_t getFlags() const { return static_cast<uint8_t>(value & 0xFF); }
|
||||||
|
|
||||||
|
bool operator<(const DrawKey &other) const { return value < other.value; }
|
||||||
|
bool operator>(const DrawKey &other) const { return value > other.value; }
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 绘制命令
|
||||||
|
*
|
||||||
|
* 封装一次绘制调用的所有信息
|
||||||
|
*/
|
||||||
|
struct DrawCommand {
|
||||||
|
DrawKey key; // 排序键
|
||||||
|
PipelineHandle pipeline; // 管线状态
|
||||||
|
BufferHandle vertexBuffer; // 顶点缓冲区
|
||||||
|
BufferHandle indexBuffer; // 索引缓冲区(可选)
|
||||||
|
uint32_t vertexCount; // 顶点数量
|
||||||
|
uint32_t indexCount; // 索引数量
|
||||||
|
std::array<TextureHandle, 8> textures; // 纹理数组
|
||||||
|
uint32_t textureCount; // 纹理数量
|
||||||
|
BufferHandle materialUBO; // 材质 UBO
|
||||||
|
uint32_t materialUBOSize; // 材质 UBO 大小
|
||||||
|
uint32_t materialUBOOffset; // 材质 UBO 在全局缓冲区中的偏移
|
||||||
|
|
||||||
|
// 变换和颜色数据(用于设置 shader uniform)
|
||||||
|
Mat4 modelMatrix; // 模型矩阵
|
||||||
|
Color color; // 颜色
|
||||||
|
|
||||||
|
DrawCommand()
|
||||||
|
: vertexCount(0), indexCount(0), textureCount(0),
|
||||||
|
materialUBOSize(0), materialUBOOffset(0),
|
||||||
|
modelMatrix(glm::identity<Mat4>()), color(Color::White) {}
|
||||||
|
|
||||||
|
// 检查是否使用索引绘制
|
||||||
|
bool isIndexed() const { return indexCount > 0; }
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 命令批次
|
||||||
|
*
|
||||||
|
* 合并具有相同管线和纹理的绘制命令
|
||||||
|
*/
|
||||||
|
struct CommandBatch {
|
||||||
|
PipelineHandle pipeline; // 共享管线
|
||||||
|
std::array<TextureHandle, 8> textures; // 共享纹理
|
||||||
|
uint32_t textureCount;
|
||||||
|
uint32_t startIndex; // 批次起始命令索引
|
||||||
|
uint32_t count; // 批次命令数量
|
||||||
|
|
||||||
|
CommandBatch() : textureCount(0), startIndex(0), count(0) {}
|
||||||
|
|
||||||
|
// 检查是否与命令兼容
|
||||||
|
bool isCompatibleWith(const DrawCommand &cmd) const {
|
||||||
|
if (pipeline != cmd.pipeline)
|
||||||
|
return false;
|
||||||
|
if (textureCount != cmd.textureCount)
|
||||||
|
return false;
|
||||||
|
for (uint32_t i = 0; i < textureCount; ++i) {
|
||||||
|
if (textures[i] != cmd.textures[i])
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 命令排序器
|
||||||
|
*
|
||||||
|
* 对绘制命令进行排序以优化渲染性能
|
||||||
|
* 排序优先级:材质(管线)> 深度 > 层
|
||||||
|
* 使用对象池复用命令内存,减少每帧分配
|
||||||
|
*/
|
||||||
|
class CommandSorter {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief 默认构造函数
|
||||||
|
*/
|
||||||
|
CommandSorter();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 析构函数
|
||||||
|
*/
|
||||||
|
~CommandSorter();
|
||||||
|
|
||||||
|
// 禁止拷贝
|
||||||
|
CommandSorter(const CommandSorter&) = delete;
|
||||||
|
CommandSorter& operator=(const CommandSorter&) = delete;
|
||||||
|
|
||||||
|
// 允许移动
|
||||||
|
CommandSorter(CommandSorter&&) noexcept;
|
||||||
|
CommandSorter& operator=(CommandSorter&&) noexcept;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 添加绘制命令
|
||||||
|
* @param cmd 绘制命令
|
||||||
|
* @return 命令索引
|
||||||
|
*/
|
||||||
|
uint32_t addCommand(const DrawCommand &cmd);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 排序所有命令
|
||||||
|
*/
|
||||||
|
void sort();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取排序后的命令
|
||||||
|
* @param index 命令索引
|
||||||
|
* @return 命令引用
|
||||||
|
*/
|
||||||
|
const DrawCommand &getCommand(uint32_t index) const {
|
||||||
|
return commands_[sortedIndices_[index]];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取命令数量
|
||||||
|
* @return 命令数量
|
||||||
|
*/
|
||||||
|
uint32_t getCount() const { return commandCount_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 清空所有命令(保留内存)
|
||||||
|
*/
|
||||||
|
void clear();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 预分配命令缓冲区
|
||||||
|
* @param capacity 预分配的容量
|
||||||
|
*/
|
||||||
|
void reserve(uint32_t capacity);
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<DrawCommand> commands_; // 命令缓冲区(复用)
|
||||||
|
std::vector<uint32_t> sortedIndices_; // 排序索引缓冲区(复用)
|
||||||
|
uint32_t commandCount_ = 0; // 当前命令数量
|
||||||
|
static constexpr uint32_t INITIAL_CAPACITY = 1024; // 初始容量
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 命令批处理器
|
||||||
|
*
|
||||||
|
* 将连续的兼容命令合并为批次
|
||||||
|
* 减少状态切换开销
|
||||||
|
*/
|
||||||
|
class CommandBatcher {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief 处理命令列表,生成批次
|
||||||
|
* @param sorter 已排序的命令排序器
|
||||||
|
*/
|
||||||
|
void process(const CommandSorter &sorter);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取批次数量
|
||||||
|
* @return 批次数量
|
||||||
|
*/
|
||||||
|
uint32_t getBatchCount() const {
|
||||||
|
return static_cast<uint32_t>(batches_.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取批次
|
||||||
|
* @param index 批次索引
|
||||||
|
* @return 批次引用
|
||||||
|
*/
|
||||||
|
const CommandBatch &getBatch(uint32_t index) const { return batches_[index]; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取批次中的命令
|
||||||
|
* @param batchIndex 批次索引
|
||||||
|
* @param cmdIndex 命令在批次中的索引
|
||||||
|
* @return 命令引用
|
||||||
|
*/
|
||||||
|
const DrawCommand &getCommand(uint32_t batchIndex, uint32_t cmdIndex) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 清空所有批次
|
||||||
|
*/
|
||||||
|
void clear();
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<CommandBatch> batches_;
|
||||||
|
const CommandSorter *sorter_ = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 命令队列
|
||||||
|
*
|
||||||
|
* 管理一帧的所有渲染命令
|
||||||
|
* 提供提交、排序、批处理和执行功能
|
||||||
|
*/
|
||||||
|
class CommandQueue {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief 默认构造函数
|
||||||
|
*/
|
||||||
|
CommandQueue();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 析构函数
|
||||||
|
*/
|
||||||
|
~CommandQueue();
|
||||||
|
|
||||||
|
// 禁止拷贝
|
||||||
|
CommandQueue(const CommandQueue&) = delete;
|
||||||
|
CommandQueue& operator=(const CommandQueue&) = delete;
|
||||||
|
|
||||||
|
// 允许移动
|
||||||
|
CommandQueue(CommandQueue&&) noexcept;
|
||||||
|
CommandQueue& operator=(CommandQueue&&) noexcept;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 初始化命令队列
|
||||||
|
* @return 初始化是否成功
|
||||||
|
*/
|
||||||
|
bool initialize();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 关闭命令队列
|
||||||
|
*/
|
||||||
|
void shutdown();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 开始一帧的录制
|
||||||
|
*/
|
||||||
|
void beginFrame();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 结束一帧的录制
|
||||||
|
*/
|
||||||
|
void endFrame();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 提交绘制命令
|
||||||
|
* @param material 材质
|
||||||
|
* @param mesh 网格
|
||||||
|
* @param transform 变换
|
||||||
|
* @param color 颜色
|
||||||
|
*/
|
||||||
|
void submitDraw(Ptr<Material> material, Ptr<Mesh> mesh,
|
||||||
|
const struct Transform &transform, const Color &color);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 提交清除命令
|
||||||
|
* @param color 清除颜色
|
||||||
|
* @param flags 清除标志
|
||||||
|
*/
|
||||||
|
void submitClear(const Color &color, uint32_t flags);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置视口
|
||||||
|
* @param x 视口 X
|
||||||
|
* @param y 视口 Y
|
||||||
|
* @param width 视口宽度
|
||||||
|
* @param height 视口高度
|
||||||
|
*/
|
||||||
|
void setViewport(int32_t x, int32_t y, int32_t width, int32_t height);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 执行所有命令
|
||||||
|
*
|
||||||
|
* 排序、批处理并执行所有提交的命令
|
||||||
|
* @param frameIndex 当前帧索引(用于双缓冲)
|
||||||
|
*/
|
||||||
|
void execute(uint32_t frameIndex);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 更新全局 UBO 数据
|
||||||
|
* @param viewProjection 视图投影矩阵
|
||||||
|
* @param deltaTime 帧时间
|
||||||
|
* @param screenWidth 屏幕宽度
|
||||||
|
* @param screenHeight 屏幕高度
|
||||||
|
* @param frameIndex 当前帧索引(用于双缓冲)
|
||||||
|
*/
|
||||||
|
void updateGlobalUBO(const Mat4& viewProjection, float deltaTime,
|
||||||
|
uint32_t screenWidth, uint32_t screenHeight, uint32_t frameIndex);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取当前命令数量
|
||||||
|
* @return 命令数量
|
||||||
|
*/
|
||||||
|
uint32_t getCommandCount() const { return sorter_.getCount(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取命令列表
|
||||||
|
* @return 命令列表指针
|
||||||
|
*/
|
||||||
|
RHICommandList* getCommandList() const { return commandList_.get(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取渲染统计信息
|
||||||
|
* @return 渲染统计
|
||||||
|
*/
|
||||||
|
const RenderStats& getStats() const { return stats_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 重置统计信息
|
||||||
|
*/
|
||||||
|
void resetStats() { stats_ = {}; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
CommandSorter sorter_;
|
||||||
|
CommandBatcher batcher_;
|
||||||
|
RHIContext *context_ = nullptr;
|
||||||
|
std::unique_ptr<RHICommandList> commandList_;
|
||||||
|
|
||||||
|
// UBO 管理器
|
||||||
|
std::unique_ptr<UniformBufferManager> uboManager_;
|
||||||
|
|
||||||
|
// 渲染统计
|
||||||
|
RenderStats stats_;
|
||||||
|
|
||||||
|
// 全局 UBO 数据 - 必须与着色器中的 std140 布局完全匹配
|
||||||
|
// layout(std140, binding = 0) uniform GlobalUBO {
|
||||||
|
// mat4 uViewProjection; // 64 bytes, offset 0
|
||||||
|
// vec4 uCameraPosition; // 16 bytes, offset 64
|
||||||
|
// float uTime; // 4 bytes, offset 80
|
||||||
|
// float uDeltaTime; // 4 bytes, offset 84
|
||||||
|
// vec2 uScreenSize; // 8 bytes, offset 88
|
||||||
|
// }; // 总大小: 96 bytes (std140 对齐)
|
||||||
|
struct alignas(16) GlobalUBOData {
|
||||||
|
float viewProjection[16]; // 64 bytes, offset 0
|
||||||
|
float cameraPosition[4]; // 16 bytes, offset 64
|
||||||
|
float time; // 4 bytes, offset 80
|
||||||
|
float deltaTime; // 4 bytes, offset 84
|
||||||
|
float screenSize[2]; // 8 bytes, offset 88
|
||||||
|
} globalUBOData_;
|
||||||
|
|
||||||
|
// 材质 UBO 数据缓冲区
|
||||||
|
std::vector<uint8_t> materialUBOData_;
|
||||||
|
|
||||||
|
// 当前材质 ID 计数器
|
||||||
|
uint32_t nextMaterialId_ = 1;
|
||||||
|
|
||||||
|
// 材质到 ID 的映射
|
||||||
|
std::unordered_map<Material *, uint32_t> materialIds_;
|
||||||
|
|
||||||
|
// 当前材质 UBO 缓冲区
|
||||||
|
UniformBuffer* currentMaterialUBO_ = nullptr;
|
||||||
|
uint32_t materialUBOBufferOffset_ = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取或创建材质 ID
|
||||||
|
* @param material 材质指针
|
||||||
|
* @return 材质 ID
|
||||||
|
*/
|
||||||
|
uint32_t getMaterialId(Material *material);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 执行单个批次
|
||||||
|
* @param batchIndex 批次索引
|
||||||
|
* @param batch 命令批次
|
||||||
|
* @param frameIndex 当前帧索引(用于双缓冲)
|
||||||
|
*/
|
||||||
|
void executeBatch(uint32_t batchIndex, const CommandBatch &batch, uint32_t frameIndex);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 分配材质 UBO 空间(使用 CPU 缓冲区)
|
||||||
|
* @param size 需要的空间大小
|
||||||
|
* @return 分配的 UBO 指针(nullptr 表示使用 CPU 缓冲区)和偏移量
|
||||||
|
*/
|
||||||
|
std::pair<UniformBuffer*, uint32_t> allocateMaterialUBO(uint32_t size);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 将材质 UBO CPU 缓冲区数据刷新到 GPU
|
||||||
|
*/
|
||||||
|
void flushMaterialUBOToGPU();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取当前材质 UBO
|
||||||
|
* @return 当前材质 UBO 指针
|
||||||
|
*/
|
||||||
|
UniformBuffer* getCurrentMaterialUBO() const;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,321 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <renderer/rhi/rhi.h>
|
||||||
|
#include <renderer/rhi/rhi_types.h>
|
||||||
|
#include <types/math/vec2.h>
|
||||||
|
#include <types/math/color.h>
|
||||||
|
#include <types/math/mat4.h>
|
||||||
|
#include <vector>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 实例数据
|
||||||
|
*
|
||||||
|
* 单个实例的属性数据,用于实例化渲染
|
||||||
|
* 布局遵循 std140 对齐规则
|
||||||
|
*
|
||||||
|
* 优化说明:
|
||||||
|
* - 旋转使用预计算的 cos/sin 值,避免在着色器中进行三角函数计算
|
||||||
|
* - 属性布局优化,便于GPU访问
|
||||||
|
*/
|
||||||
|
struct InstanceData {
|
||||||
|
// 第一组 16 字节 - 变换数据
|
||||||
|
Vec2 position; // 位置偏移 (8 bytes)
|
||||||
|
float rotationCos; // 旋转角度余弦值 (4 bytes) - 预计算避免着色器三角函数
|
||||||
|
float rotationSin; // 旋转角度正弦值 (4 bytes) - 预计算避免着色器三角函数
|
||||||
|
|
||||||
|
// 第二组 16 字节 - 缩放和预留
|
||||||
|
Vec2 scale; // 缩放 (8 bytes)
|
||||||
|
float padding1[2]; // 填充到 16 字节对齐 (8 bytes)
|
||||||
|
|
||||||
|
// 第三组 16 字节 - 颜色
|
||||||
|
Color color; // 颜色 (16 bytes) - r, g, b, a
|
||||||
|
|
||||||
|
// 第四组 16 字节 - UV坐标
|
||||||
|
float uvX; // UV 起始 X (4 bytes)
|
||||||
|
float uvY; // UV 起始 Y (4 bytes)
|
||||||
|
float uvWidth; // UV 宽度 (4 bytes)
|
||||||
|
float uvHeight; // UV 高度 (4 bytes)
|
||||||
|
|
||||||
|
InstanceData()
|
||||||
|
: position(0.0f, 0.0f)
|
||||||
|
, rotationCos(1.0f) // cos(0) = 1
|
||||||
|
, rotationSin(0.0f) // sin(0) = 0
|
||||||
|
, scale(1.0f, 1.0f)
|
||||||
|
, padding1{0.0f, 0.0f}
|
||||||
|
, color(Color::White)
|
||||||
|
, uvX(0.0f)
|
||||||
|
, uvY(0.0f)
|
||||||
|
, uvWidth(1.0f)
|
||||||
|
, uvHeight(1.0f) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置旋转角度(自动计算cos/sin)
|
||||||
|
* @param angle 旋转角度(弧度)
|
||||||
|
*/
|
||||||
|
void setRotation(float angle) {
|
||||||
|
rotationCos = std::cos(angle);
|
||||||
|
rotationSin = std::sin(angle);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取旋转角度(通过atan2计算,仅用于调试)
|
||||||
|
* @return 旋转角度(弧度)
|
||||||
|
*/
|
||||||
|
float getRotation() const {
|
||||||
|
return std::atan2(rotationSin, rotationCos);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static_assert(sizeof(InstanceData) == 64, "InstanceData size should be 64 bytes for std140 alignment");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 脏区域标记
|
||||||
|
*
|
||||||
|
* 标记需要更新的实例数据区域,支持部分更新GPU缓冲区
|
||||||
|
*/
|
||||||
|
struct DirtyRange {
|
||||||
|
uint32_t start; // 起始实例索引
|
||||||
|
uint32_t count; // 实例数量
|
||||||
|
|
||||||
|
DirtyRange(uint32_t s = 0, uint32_t c = 0) : start(s), count(c) {}
|
||||||
|
|
||||||
|
bool isValid() const { return count > 0; }
|
||||||
|
uint32_t end() const { return start + count; }
|
||||||
|
|
||||||
|
// 检查是否与另一个区域重叠或相邻
|
||||||
|
bool canMerge(const DirtyRange& other) const {
|
||||||
|
return (start <= other.end() && other.start <= end()) ||
|
||||||
|
(end() + 1 >= other.start && other.end() + 1 >= start);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 合并两个区域
|
||||||
|
void merge(const DirtyRange& other) {
|
||||||
|
uint32_t newStart = std::min(start, other.start);
|
||||||
|
uint32_t newEnd = std::max(end(), other.end());
|
||||||
|
start = newStart;
|
||||||
|
count = newEnd - newStart;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 实例缓冲区
|
||||||
|
*
|
||||||
|
* 管理实例化渲染的实例数据缓冲区
|
||||||
|
* 支持动态更新和双缓冲
|
||||||
|
*
|
||||||
|
* 优化特性:
|
||||||
|
* - 脏标记系统:只更新变化的实例数据,减少PCIe带宽占用
|
||||||
|
* - 批量更新:合并相邻的脏区域,减少GPU上传次数
|
||||||
|
*/
|
||||||
|
class InstanceBuffer {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief 默认构造函数
|
||||||
|
*/
|
||||||
|
InstanceBuffer();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 析构函数
|
||||||
|
*/
|
||||||
|
~InstanceBuffer();
|
||||||
|
|
||||||
|
// 禁止拷贝
|
||||||
|
InstanceBuffer(const InstanceBuffer&) = delete;
|
||||||
|
InstanceBuffer& operator=(const InstanceBuffer&) = delete;
|
||||||
|
|
||||||
|
// 允许移动
|
||||||
|
InstanceBuffer(InstanceBuffer&& other) noexcept;
|
||||||
|
InstanceBuffer& operator=(InstanceBuffer&& other) noexcept;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 初始化实例缓冲区
|
||||||
|
* @param maxInstances 最大实例数量
|
||||||
|
* @return 初始化是否成功
|
||||||
|
*/
|
||||||
|
bool initialize(uint32_t maxInstances);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 关闭缓冲区
|
||||||
|
*/
|
||||||
|
void shutdown();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 更新实例数据(全量更新,标记整个缓冲区为脏)
|
||||||
|
* @param instances 实例数据数组
|
||||||
|
* @param count 实例数量
|
||||||
|
* @return 更新是否成功
|
||||||
|
*/
|
||||||
|
bool updateInstances(const InstanceData* instances, uint32_t count);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 更新部分实例数据(增量更新)
|
||||||
|
* @param instances 实例数据数组
|
||||||
|
* @param start 起始实例索引
|
||||||
|
* @param count 实例数量
|
||||||
|
* @return 更新是否成功
|
||||||
|
*/
|
||||||
|
bool updateInstancesRange(const InstanceData* instances, uint32_t start, uint32_t count);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 添加单个实例
|
||||||
|
* @param instance 实例数据
|
||||||
|
* @return 实例索引
|
||||||
|
*/
|
||||||
|
uint32_t addInstance(const InstanceData& instance);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 标记实例范围为脏(下次updateGPU时更新)
|
||||||
|
* @param start 起始实例索引
|
||||||
|
* @param count 实例数量
|
||||||
|
*/
|
||||||
|
void markDirty(uint32_t start, uint32_t count);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 标记整个缓冲区为脏
|
||||||
|
*/
|
||||||
|
void markAllDirty();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 将脏数据上传到GPU
|
||||||
|
* @return 上传是否成功
|
||||||
|
*/
|
||||||
|
bool updateGPU();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 清除所有实例
|
||||||
|
*/
|
||||||
|
void clear();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取当前实例数量
|
||||||
|
* @return 实例数量
|
||||||
|
*/
|
||||||
|
uint32_t getInstanceCount() const { return instanceCount_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取最大实例数量
|
||||||
|
* @return 最大实例数量
|
||||||
|
*/
|
||||||
|
uint32_t getMaxInstances() const { return maxInstances_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取 RHI 缓冲区句柄
|
||||||
|
* @return 缓冲区句柄
|
||||||
|
*/
|
||||||
|
BufferHandle getBufferHandle() const { return bufferHandle_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取 RHI 缓冲区指针
|
||||||
|
* @return 缓冲区指针
|
||||||
|
*/
|
||||||
|
RHIBuffer* getRHIBuffer() const { return bufferHandle_.get(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查是否有效
|
||||||
|
* @return 是否有效
|
||||||
|
*/
|
||||||
|
bool isValid() const { return bufferHandle_.isValid(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取脏区域数量
|
||||||
|
* @return 脏区域数量
|
||||||
|
*/
|
||||||
|
uint32_t getDirtyRangeCount() const { return static_cast<uint32_t>(dirtyRanges_.size()); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查是否有脏数据
|
||||||
|
* @return 是否有脏数据
|
||||||
|
*/
|
||||||
|
bool hasDirtyData() const { return !dirtyRanges_.empty(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
BufferHandle bufferHandle_; // RHI 缓冲区句柄
|
||||||
|
uint32_t maxInstances_ = 0; // 最大实例数量
|
||||||
|
uint32_t instanceCount_ = 0; // 当前实例数量
|
||||||
|
std::vector<InstanceData> cpuBuffer_; // CPU 端缓冲区
|
||||||
|
std::vector<DirtyRange> dirtyRanges_; // 脏区域列表
|
||||||
|
|
||||||
|
// 最大脏区域数量,超过则合并为全量更新
|
||||||
|
static constexpr uint32_t MAX_DIRTY_RANGES = 8;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 添加脏区域,自动合并重叠区域
|
||||||
|
* @param range 脏区域
|
||||||
|
*/
|
||||||
|
void addDirtyRange(const DirtyRange& range);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 合并所有脏区域为一个
|
||||||
|
*/
|
||||||
|
void mergeAllDirtyRanges();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 清空脏区域列表
|
||||||
|
*/
|
||||||
|
void clearDirtyRanges();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 实例缓冲区管理器
|
||||||
|
*
|
||||||
|
* 管理多个实例缓冲区的分配和回收
|
||||||
|
*/
|
||||||
|
class InstanceBufferManager {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief 默认构造函数
|
||||||
|
*/
|
||||||
|
InstanceBufferManager();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 析构函数
|
||||||
|
*/
|
||||||
|
~InstanceBufferManager();
|
||||||
|
|
||||||
|
// 禁止拷贝
|
||||||
|
InstanceBufferManager(const InstanceBufferManager&) = delete;
|
||||||
|
InstanceBufferManager& operator=(const InstanceBufferManager&) = delete;
|
||||||
|
|
||||||
|
// 允许移动
|
||||||
|
InstanceBufferManager(InstanceBufferManager&& other) noexcept;
|
||||||
|
InstanceBufferManager& operator=(InstanceBufferManager&& other) noexcept;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 初始化管理器
|
||||||
|
* @return 初始化是否成功
|
||||||
|
*/
|
||||||
|
bool initialize();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 关闭管理器
|
||||||
|
*/
|
||||||
|
void shutdown();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取或创建实例缓冲区
|
||||||
|
* @param minSize 最小容量(实例数量)
|
||||||
|
* @return 实例缓冲区指针
|
||||||
|
*/
|
||||||
|
InstanceBuffer* acquireBuffer(uint32_t minSize);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 回收实例缓冲区
|
||||||
|
* @param buffer 缓冲区指针
|
||||||
|
*/
|
||||||
|
void releaseBuffer(InstanceBuffer* buffer);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 重置所有缓冲区
|
||||||
|
*/
|
||||||
|
void reset();
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<std::unique_ptr<InstanceBuffer>> bufferPool_;
|
||||||
|
uint32_t currentBufferIndex_ = 0;
|
||||||
|
static constexpr uint32_t DEFAULT_BUFFER_SIZE = 1024;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <renderer/rhi/rhi.h>
|
||||||
|
#include <renderer/shader.h>
|
||||||
|
#include <renderer/texture.h>
|
||||||
#include <types/ptr/ref_counted.h>
|
#include <types/ptr/ref_counted.h>
|
||||||
#include <types/ptr/intrusive_ptr.h>
|
#include <types/ptr/intrusive_ptr.h>
|
||||||
#include <types/math/vec2.h>
|
#include <types/math/vec2.h>
|
||||||
#include <types/math/color.h>
|
#include <types/math/color.h>
|
||||||
#include <renderer/render_types.h>
|
|
||||||
#include <renderer/shader.h>
|
|
||||||
#include <renderer/uniform_buffer.h>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
@ -15,7 +15,37 @@ namespace extra2d {
|
||||||
|
|
||||||
// 前向声明
|
// 前向声明
|
||||||
class Material;
|
class Material;
|
||||||
class Texture;
|
|
||||||
|
/**
|
||||||
|
* @brief 材质参数类型
|
||||||
|
*/
|
||||||
|
enum class MaterialParamType : uint8_t {
|
||||||
|
Float, // 浮点数
|
||||||
|
Vec2, // 二维向量
|
||||||
|
Vec3, // 三维向量
|
||||||
|
Vec4, // 四维向量
|
||||||
|
Color, // 颜色
|
||||||
|
Mat4, // 4x4 矩阵
|
||||||
|
Texture // 纹理
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取材质参数类型的大小
|
||||||
|
* @param type 参数类型
|
||||||
|
* @return 参数大小(字节)
|
||||||
|
*/
|
||||||
|
inline uint32_t getMaterialParamSize(MaterialParamType type) {
|
||||||
|
switch (type) {
|
||||||
|
case MaterialParamType::Float: return sizeof(float);
|
||||||
|
case MaterialParamType::Vec2: return sizeof(float) * 2;
|
||||||
|
case MaterialParamType::Vec3: return sizeof(float) * 3;
|
||||||
|
case MaterialParamType::Vec4: return sizeof(float) * 4;
|
||||||
|
case MaterialParamType::Color: return sizeof(float) * 4;
|
||||||
|
case MaterialParamType::Mat4: return sizeof(float) * 16;
|
||||||
|
case MaterialParamType::Texture: return sizeof(uint32_t);
|
||||||
|
default: return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 材质参数信息
|
* @brief 材质参数信息
|
||||||
|
|
@ -30,7 +60,7 @@ struct MaterialParamInfo {
|
||||||
* @brief 纹理槽位信息
|
* @brief 纹理槽位信息
|
||||||
*/
|
*/
|
||||||
struct TextureSlot {
|
struct TextureSlot {
|
||||||
Handle<Texture> handle;
|
Ptr<Texture> texture;
|
||||||
uint32_t slot;
|
uint32_t slot;
|
||||||
std::string uniformName;
|
std::string uniformName;
|
||||||
};
|
};
|
||||||
|
|
@ -79,7 +109,9 @@ public:
|
||||||
bool isFinalized() const { return finalized_; }
|
bool isFinalized() const { return finalized_; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::unordered_map<std::string, MaterialParamInfo> params_;
|
// 使用vector保持参数添加顺序,确保与着色器中的声明顺序一致
|
||||||
|
std::vector<std::pair<std::string, MaterialParamInfo>> params_;
|
||||||
|
std::unordered_map<std::string, size_t> paramIndexMap_; // 用于快速查找
|
||||||
uint32_t bufferSize_ = 0;
|
uint32_t bufferSize_ = 0;
|
||||||
bool finalized_ = false;
|
bool finalized_ = false;
|
||||||
};
|
};
|
||||||
|
|
@ -87,7 +119,7 @@ private:
|
||||||
/**
|
/**
|
||||||
* @brief 材质类
|
* @brief 材质类
|
||||||
*
|
*
|
||||||
* 作为着色器和纹理的中间层,管理:
|
* 基于 RHI 的材质包装类,管理:
|
||||||
* - 着色器程序
|
* - 着色器程序
|
||||||
* - 材质参数(通过 UBO 上传)
|
* - 材质参数(通过 UBO 上传)
|
||||||
* - 纹理绑定
|
* - 纹理绑定
|
||||||
|
|
@ -112,10 +144,7 @@ private:
|
||||||
* material->setFloat("uOpacity", 1.0f);
|
* material->setFloat("uOpacity", 1.0f);
|
||||||
*
|
*
|
||||||
* // 设置纹理
|
* // 设置纹理
|
||||||
* material->setTexture("uTexture", textureHandle, 0);
|
* material->setTexture("uTexture", texture, 0);
|
||||||
*
|
|
||||||
* // 注册到渲染器
|
|
||||||
* MaterialHandle handle = renderer->registerMaterial(material);
|
|
||||||
* @endcode
|
* @endcode
|
||||||
*/
|
*/
|
||||||
class Material : public RefCounted {
|
class Material : public RefCounted {
|
||||||
|
|
@ -147,6 +176,12 @@ public:
|
||||||
*/
|
*/
|
||||||
Ptr<Shader> getShader() const { return shader_; }
|
Ptr<Shader> getShader() const { return shader_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取 RHI 管线句柄
|
||||||
|
* @return RHI 管线句柄
|
||||||
|
*/
|
||||||
|
PipelineHandle getPipeline() const;
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
// 参数设置
|
// 参数设置
|
||||||
// ========================================
|
// ========================================
|
||||||
|
|
@ -168,7 +203,10 @@ public:
|
||||||
/**
|
/**
|
||||||
* @brief 设置 vec4 参数
|
* @brief 设置 vec4 参数
|
||||||
* @param name 参数名称
|
* @param name 参数名称
|
||||||
* @param value 值
|
* @param x X 分量
|
||||||
|
* @param y Y 分量
|
||||||
|
* @param z Z 分量
|
||||||
|
* @param w W 分量
|
||||||
*/
|
*/
|
||||||
void setVec4(const std::string& name, float x, float y, float z, float w);
|
void setVec4(const std::string& name, float x, float y, float z, float w);
|
||||||
|
|
||||||
|
|
@ -193,10 +231,10 @@ public:
|
||||||
/**
|
/**
|
||||||
* @brief 设置纹理
|
* @brief 设置纹理
|
||||||
* @param uniformName 着色器中的采样器 uniform 名称
|
* @param uniformName 着色器中的采样器 uniform 名称
|
||||||
* @param texture 纹理句柄
|
* @param texture 纹理
|
||||||
* @param slot 纹理槽位(0-15)
|
* @param slot 纹理槽位(0-15)
|
||||||
*/
|
*/
|
||||||
void setTexture(const std::string& uniformName, Handle<Texture> texture, uint32_t slot);
|
void setTexture(const std::string& uniformName, Ptr<Texture> texture, uint32_t slot);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 获取所有纹理槽位
|
* @brief 获取所有纹理槽位
|
||||||
|
|
@ -225,17 +263,11 @@ public:
|
||||||
*/
|
*/
|
||||||
uint32_t getDataSize() const { return static_cast<uint32_t>(data_.size()); }
|
uint32_t getDataSize() const { return static_cast<uint32_t>(data_.size()); }
|
||||||
|
|
||||||
// ========================================
|
|
||||||
// 应用材质(渲染时调用)
|
|
||||||
// ========================================
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 应用材质
|
* @brief 获取材质布局
|
||||||
*
|
* @return 材质布局
|
||||||
* 绑定着色器、上传 UBO 数据
|
|
||||||
* @param uboManager UBO 管理器
|
|
||||||
*/
|
*/
|
||||||
void apply(UniformBufferManager& uboManager);
|
Ptr<MaterialLayout> getLayout() const { return layout_; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Ptr<MaterialLayout> layout_; // 材质布局
|
Ptr<MaterialLayout> layout_; // 材质布局
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,12 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <renderer/rhi/rhi.h>
|
||||||
#include <types/math/color.h>
|
#include <types/math/color.h>
|
||||||
#include <types/math/rect.h>
|
#include <types/math/rect.h>
|
||||||
#include <types/math/vec2.h>
|
#include <types/math/vec2.h>
|
||||||
#include <types/ptr/intrusive_ptr.h>
|
#include <types/ptr/intrusive_ptr.h>
|
||||||
#include <types/ptr/ref_counted.h>
|
#include <types/ptr/ref_counted.h>
|
||||||
|
|
||||||
// 前向声明 OpenGL 类型
|
|
||||||
typedef unsigned int GLuint;
|
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -20,14 +18,14 @@ struct Vertex {
|
||||||
Color color; // 颜色
|
Color color; // 颜色
|
||||||
|
|
||||||
Vertex() = default;
|
Vertex() = default;
|
||||||
Vertex(const Vec2 &pos, const Vec2 &uv, const Color &col)
|
Vertex(const Vec2& pos, const Vec2& uv, const Color& col)
|
||||||
: position(pos), texCoord(uv), color(col) {}
|
: position(pos), texCoord(uv), color(col) {}
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 网格类
|
* @brief 网格类
|
||||||
*
|
*
|
||||||
* 管理 OpenGL 顶点数组对象和缓冲区
|
* 基于 RHI 的网格包装类,管理顶点缓冲区和索引缓冲区
|
||||||
* 支持静态和动态顶点数据
|
* 支持静态和动态顶点数据
|
||||||
*/
|
*/
|
||||||
class Mesh : public RefCounted {
|
class Mesh : public RefCounted {
|
||||||
|
|
@ -47,14 +45,14 @@ public:
|
||||||
* @param vertices 顶点数组
|
* @param vertices 顶点数组
|
||||||
* @param count 顶点数量
|
* @param count 顶点数量
|
||||||
*/
|
*/
|
||||||
void setVertices(const Vertex *vertices, uint32_t count);
|
void setVertices(const Vertex* vertices, uint32_t count);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 设置索引数据
|
* @brief 设置索引数据
|
||||||
* @param indices 索引数组
|
* @param indices 索引数组
|
||||||
* @param count 索引数量
|
* @param count 索引数量
|
||||||
*/
|
*/
|
||||||
void setIndices(const uint16_t *indices, uint32_t count);
|
void setIndices(const uint16_t* indices, uint32_t count);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 更新部分顶点数据
|
* @brief 更新部分顶点数据
|
||||||
|
|
@ -62,28 +60,7 @@ public:
|
||||||
* @param count 顶点数量
|
* @param count 顶点数量
|
||||||
* @param offset 偏移量
|
* @param offset 偏移量
|
||||||
*/
|
*/
|
||||||
void updateVertices(const Vertex *vertices, uint32_t count, uint32_t offset);
|
void updateVertices(const Vertex* vertices, uint32_t count, uint32_t offset);
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 绑定网格
|
|
||||||
*/
|
|
||||||
void bind() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 解绑网格
|
|
||||||
*/
|
|
||||||
void unbind() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 绘制网格
|
|
||||||
*/
|
|
||||||
void draw() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 实例化绘制
|
|
||||||
* @param instanceCount 实例数量
|
|
||||||
*/
|
|
||||||
void drawInstanced(uint32_t instanceCount) const;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 获取顶点数量
|
* @brief 获取顶点数量
|
||||||
|
|
@ -97,19 +74,36 @@ public:
|
||||||
*/
|
*/
|
||||||
uint32_t getIndexCount() const { return indexCount_; }
|
uint32_t getIndexCount() const { return indexCount_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取顶点缓冲区句柄
|
||||||
|
* @return 顶点缓冲区句柄
|
||||||
|
*/
|
||||||
|
BufferHandle getVertexBuffer() const { return vertexBuffer_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取索引缓冲区句柄
|
||||||
|
* @return 索引缓冲区句柄
|
||||||
|
*/
|
||||||
|
BufferHandle getIndexBuffer() const { return indexBuffer_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查是否有索引数据
|
||||||
|
* @return 是否有索引数据
|
||||||
|
*/
|
||||||
|
bool hasIndices() const { return indexCount_ > 0; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 创建标准四边形
|
* @brief 创建标准四边形
|
||||||
* @param size 四边形大小
|
* @param size 四边形大小
|
||||||
* @param uv 纹理坐标范围
|
* @param uv 纹理坐标范围
|
||||||
* @return 网格对象
|
* @return 网格对象
|
||||||
*/
|
*/
|
||||||
static Ptr<Mesh> createQuad(const Vec2 &size,
|
static Ptr<Mesh> createQuad(const Vec2& size,
|
||||||
const Rect &uv = Rect(0, 0, 1, 1));
|
const Rect& uv = Rect(0, 0, 1, 1));
|
||||||
|
|
||||||
private:
|
private:
|
||||||
GLuint vao_ = 0; // 顶点数组对象
|
BufferHandle vertexBuffer_; // 顶点缓冲区句柄
|
||||||
GLuint vbo_ = 0; // 顶点缓冲区
|
BufferHandle indexBuffer_; // 索引缓冲区句柄
|
||||||
GLuint ibo_ = 0; // 索引缓冲区
|
|
||||||
uint32_t vertexCount_ = 0; // 顶点数量
|
uint32_t vertexCount_ = 0; // 顶点数量
|
||||||
uint32_t indexCount_ = 0; // 索引数量
|
uint32_t indexCount_ = 0; // 索引数量
|
||||||
uint32_t vertexCapacity_ = 0; // 顶点缓冲区容量
|
uint32_t vertexCapacity_ = 0; // 顶点缓冲区容量
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,366 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <renderer/rhi/rhi.h>
|
||||||
|
#include <renderer/command_queue.h>
|
||||||
|
#include <types/math/color.h>
|
||||||
|
#include <types/math/rect.h>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// 前向声明
|
||||||
|
class RenderGraph;
|
||||||
|
class RenderPass;
|
||||||
|
class RenderGraphBuilder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 渲染图资源类型
|
||||||
|
*/
|
||||||
|
enum class RenderGraphResourceType : uint8_t {
|
||||||
|
Texture, // 纹理
|
||||||
|
Buffer, // 缓冲区
|
||||||
|
RenderTarget // 渲染目标
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 渲染图资源句柄
|
||||||
|
*/
|
||||||
|
struct RenderGraphResourceHandle {
|
||||||
|
uint32_t index = static_cast<uint32_t>(-1);
|
||||||
|
RenderGraphResourceType type = RenderGraphResourceType::Texture;
|
||||||
|
|
||||||
|
bool isValid() const { return index != static_cast<uint32_t>(-1); }
|
||||||
|
static RenderGraphResourceHandle invalid() { return RenderGraphResourceHandle{}; }
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 纹理描述
|
||||||
|
*/
|
||||||
|
struct RenderGraphTextureDesc {
|
||||||
|
uint32_t width = 0;
|
||||||
|
uint32_t height = 0;
|
||||||
|
TextureFormat format = TextureFormat::RGBA8;
|
||||||
|
TextureUsage usage = TextureUsage::Sampled;
|
||||||
|
bool isRenderTarget = false;
|
||||||
|
std::string name;
|
||||||
|
|
||||||
|
static RenderGraphTextureDesc create2D(uint32_t w, uint32_t h, TextureFormat fmt,
|
||||||
|
const std::string& name = "") {
|
||||||
|
RenderGraphTextureDesc desc;
|
||||||
|
desc.width = w;
|
||||||
|
desc.height = h;
|
||||||
|
desc.format = fmt;
|
||||||
|
desc.name = name;
|
||||||
|
return desc;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 渲染目标描述
|
||||||
|
*/
|
||||||
|
struct RenderGraphRenderTargetDesc {
|
||||||
|
RenderGraphResourceHandle colorTarget;
|
||||||
|
RenderGraphResourceHandle depthTarget;
|
||||||
|
Rect clearRect;
|
||||||
|
Color clearColor = Color::Black;
|
||||||
|
float clearDepth = 1.0f;
|
||||||
|
uint8_t clearStencil = 0;
|
||||||
|
bool clearColorFlag = true;
|
||||||
|
bool clearDepthFlag = false;
|
||||||
|
bool clearStencilFlag = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 渲染通道类型
|
||||||
|
*/
|
||||||
|
enum class RenderPassType : uint8_t {
|
||||||
|
Graphics, // 图形渲染
|
||||||
|
Compute, // 计算
|
||||||
|
Copy // 拷贝/传输
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 渲染通道执行上下文
|
||||||
|
*/
|
||||||
|
struct RenderPassContext {
|
||||||
|
RHIContext* context = nullptr;
|
||||||
|
CommandQueue* commandQueue = nullptr;
|
||||||
|
RenderGraph* graph = nullptr;
|
||||||
|
float deltaTime = 0.0f;
|
||||||
|
uint32_t frameIndex = 0;
|
||||||
|
|
||||||
|
// 获取纹理句柄
|
||||||
|
TextureHandle getTexture(RenderGraphResourceHandle handle) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 渲染通道基类
|
||||||
|
*/
|
||||||
|
class RenderPass {
|
||||||
|
public:
|
||||||
|
virtual ~RenderPass() = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取通道名称
|
||||||
|
*/
|
||||||
|
virtual const char* getName() const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取通道类型
|
||||||
|
*/
|
||||||
|
virtual RenderPassType getType() const { return RenderPassType::Graphics; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 声明资源依赖
|
||||||
|
* @param builder 渲染图构建器
|
||||||
|
*/
|
||||||
|
virtual void declareResources(RenderGraphBuilder& builder) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 执行渲染通道
|
||||||
|
* @param ctx 执行上下文
|
||||||
|
*/
|
||||||
|
virtual void execute(const RenderPassContext& ctx) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置是否启用
|
||||||
|
*/
|
||||||
|
void setEnabled(bool enabled) { enabled_ = enabled; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查是否启用
|
||||||
|
*/
|
||||||
|
bool isEnabled() const { return enabled_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool enabled_ = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 渲染图构建器
|
||||||
|
*
|
||||||
|
* 用于声明渲染通道的资源依赖
|
||||||
|
*/
|
||||||
|
class RenderGraphBuilder {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief 创建纹理
|
||||||
|
* @param desc 纹理描述
|
||||||
|
* @return 资源句柄
|
||||||
|
*/
|
||||||
|
RenderGraphResourceHandle createTexture(const RenderGraphTextureDesc& desc);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 读取资源
|
||||||
|
* @param handle 资源句柄
|
||||||
|
*/
|
||||||
|
void readResource(RenderGraphResourceHandle handle);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 写入资源
|
||||||
|
* @param handle 资源句柄
|
||||||
|
*/
|
||||||
|
void writeResource(RenderGraphResourceHandle handle);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置渲染目标
|
||||||
|
* @param desc 渲染目标描述
|
||||||
|
*/
|
||||||
|
void setRenderTarget(const RenderGraphRenderTargetDesc& desc);
|
||||||
|
|
||||||
|
// 内部使用
|
||||||
|
void setCurrentPass(RenderPass* pass) { currentPass_ = pass; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
RenderPass* currentPass_ = nullptr;
|
||||||
|
std::vector<RenderGraphTextureDesc> textureDescs_;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 渲染图
|
||||||
|
*
|
||||||
|
* 管理渲染通道的依赖关系和执行顺序
|
||||||
|
* 提供声明式渲染流程定义
|
||||||
|
*/
|
||||||
|
class RenderGraph {
|
||||||
|
public:
|
||||||
|
RenderGraph();
|
||||||
|
~RenderGraph();
|
||||||
|
|
||||||
|
// 禁止拷贝
|
||||||
|
RenderGraph(const RenderGraph&) = delete;
|
||||||
|
RenderGraph& operator=(const RenderGraph&) = delete;
|
||||||
|
|
||||||
|
// 允许移动
|
||||||
|
RenderGraph(RenderGraph&&) noexcept;
|
||||||
|
RenderGraph& operator=(RenderGraph&&) noexcept;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 初始化渲染图
|
||||||
|
* @return 初始化是否成功
|
||||||
|
*/
|
||||||
|
bool initialize();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 关闭渲染图
|
||||||
|
*/
|
||||||
|
void shutdown();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 添加渲染通道
|
||||||
|
* @param pass 渲染通道(所有权转移)
|
||||||
|
* @return 渲染通道指针
|
||||||
|
*/
|
||||||
|
RenderPass* addPass(std::unique_ptr<RenderPass> pass);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 创建渲染通道(便捷方法)
|
||||||
|
* @param name 通道名称
|
||||||
|
* @param executeFunc 执行函数
|
||||||
|
* @return 渲染通道指针
|
||||||
|
*/
|
||||||
|
RenderPass* addLambdaPass(const std::string& name,
|
||||||
|
std::function<void(const RenderPassContext&)> executeFunc);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 编译渲染图
|
||||||
|
*
|
||||||
|
* 分析依赖关系,确定执行顺序,分配资源
|
||||||
|
*/
|
||||||
|
bool compile();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 执行渲染图
|
||||||
|
* @param deltaTime 帧时间
|
||||||
|
* @param viewProjection 视图投影矩阵
|
||||||
|
*/
|
||||||
|
void execute(float deltaTime, const Mat4& viewProjection = Mat4(1.0f));
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取纹理资源
|
||||||
|
* @param handle 资源句柄
|
||||||
|
* @return 纹理句柄
|
||||||
|
*/
|
||||||
|
TextureHandle getTexture(RenderGraphResourceHandle handle) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置输出尺寸
|
||||||
|
* @param width 宽度
|
||||||
|
* @param height 高度
|
||||||
|
*/
|
||||||
|
void setOutputSize(uint32_t width, uint32_t height);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取输出宽度
|
||||||
|
*/
|
||||||
|
uint32_t getOutputWidth() const { return outputWidth_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取输出高度
|
||||||
|
*/
|
||||||
|
uint32_t getOutputHeight() const { return outputHeight_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取命令队列
|
||||||
|
*/
|
||||||
|
CommandQueue* getCommandQueue() { return &commandQueue_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
// 渲染通道列表
|
||||||
|
std::vector<std::unique_ptr<RenderPass>> passes_;
|
||||||
|
|
||||||
|
// 编译后的执行顺序
|
||||||
|
std::vector<RenderPass*> executionOrder_;
|
||||||
|
|
||||||
|
// 资源管理
|
||||||
|
std::unordered_map<uint32_t, TextureHandle> textures_;
|
||||||
|
uint32_t nextResourceId_ = 1;
|
||||||
|
|
||||||
|
// 构建器
|
||||||
|
RenderGraphBuilder builder_;
|
||||||
|
|
||||||
|
// 命令队列
|
||||||
|
CommandQueue commandQueue_;
|
||||||
|
|
||||||
|
// 输出尺寸
|
||||||
|
uint32_t outputWidth_ = 1280;
|
||||||
|
uint32_t outputHeight_ = 720;
|
||||||
|
|
||||||
|
// 帧计数
|
||||||
|
uint32_t frameIndex_ = 0;
|
||||||
|
|
||||||
|
// 编译状态
|
||||||
|
bool compiled_ = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 创建内部资源
|
||||||
|
*/
|
||||||
|
void createResources();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 销毁内部资源
|
||||||
|
*/
|
||||||
|
void destroyResources();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 简单的几何渲染通道
|
||||||
|
*
|
||||||
|
* 用于常规的 2D 几何渲染
|
||||||
|
*/
|
||||||
|
class GeometryRenderPass : public RenderPass {
|
||||||
|
public:
|
||||||
|
GeometryRenderPass(const std::string& name);
|
||||||
|
|
||||||
|
const char* getName() const override { return name_.c_str(); }
|
||||||
|
void declareResources(RenderGraphBuilder& builder) override;
|
||||||
|
void execute(const RenderPassContext& ctx) override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置颜色目标
|
||||||
|
*/
|
||||||
|
void setColorTarget(RenderGraphResourceHandle handle) { colorTarget_ = handle; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置深度目标
|
||||||
|
*/
|
||||||
|
void setDepthTarget(RenderGraphResourceHandle handle) { depthTarget_ = handle; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string name_;
|
||||||
|
RenderGraphResourceHandle colorTarget_;
|
||||||
|
RenderGraphResourceHandle depthTarget_;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 后期处理渲染通道
|
||||||
|
*/
|
||||||
|
class PostProcessRenderPass : public RenderPass {
|
||||||
|
public:
|
||||||
|
PostProcessRenderPass(const std::string& name);
|
||||||
|
|
||||||
|
const char* getName() const override { return name_.c_str(); }
|
||||||
|
void declareResources(RenderGraphBuilder& builder) override;
|
||||||
|
void execute(const RenderPassContext& ctx) override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置输入纹理
|
||||||
|
*/
|
||||||
|
void setInputTexture(RenderGraphResourceHandle handle) { inputTexture_ = handle; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置输出目标
|
||||||
|
*/
|
||||||
|
void setOutputTarget(RenderGraphResourceHandle handle) { outputTarget_ = handle; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::string name_;
|
||||||
|
RenderGraphResourceHandle inputTexture_;
|
||||||
|
RenderGraphResourceHandle outputTarget_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -1,12 +1,11 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <assets/handle.h>
|
||||||
|
#include <cstdint>
|
||||||
#include <types/base/types.h>
|
#include <types/base/types.h>
|
||||||
#include <types/math/vec2.h>
|
|
||||||
#include <types/math/color.h>
|
#include <types/math/color.h>
|
||||||
#include <types/math/transform.h>
|
#include <types/math/transform.h>
|
||||||
#include <assets/handle.h>
|
#include <types/math/vec2.h>
|
||||||
#include <array>
|
|
||||||
#include <cstdint>
|
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
||||||
|
|
@ -15,79 +14,17 @@ class Material;
|
||||||
class Mesh;
|
class Mesh;
|
||||||
class Texture;
|
class Texture;
|
||||||
|
|
||||||
// 资源句柄类型(使用新的 Handle<T>)
|
// 注意:资源句柄直接使用 Handle<T>,与 RHI 的 TextureHandle 等区分
|
||||||
using MaterialHandle = Handle<Material>;
|
|
||||||
using MeshHandle = Handle<Mesh>;
|
|
||||||
using TextureHandle = Handle<Texture>;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 渲染命令类型
|
* @brief 渲染命令类型
|
||||||
*/
|
*/
|
||||||
enum class RenderCommandType : uint8_t {
|
enum class RenderCommandType : uint8_t {
|
||||||
DrawMesh, // 绘制网格
|
DrawMesh, // 绘制网格
|
||||||
DrawMeshInstanced, // 实例化绘制
|
|
||||||
SetViewport, // 设置视口
|
SetViewport, // 设置视口
|
||||||
Clear // 清除缓冲区
|
Clear // 清除缓冲区
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 纹理格式
|
|
||||||
*/
|
|
||||||
enum class TextureFormat : uint8_t {
|
|
||||||
RGBA8, // 32位 RGBA
|
|
||||||
RGB8, // 24位 RGB
|
|
||||||
RGBA4, // 16位 RGBA
|
|
||||||
R8, // 8位 灰度
|
|
||||||
RG8 // 16位 RG
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取纹理格式的像素大小(字节)
|
|
||||||
* @param format 纹理格式
|
|
||||||
* @return 像素大小
|
|
||||||
*/
|
|
||||||
inline uint32_t getTexturePixelSize(TextureFormat format) {
|
|
||||||
switch (format) {
|
|
||||||
case TextureFormat::RGBA8: return 4;
|
|
||||||
case TextureFormat::RGB8: return 3;
|
|
||||||
case TextureFormat::RGBA4: return 2;
|
|
||||||
case TextureFormat::R8: return 1;
|
|
||||||
case TextureFormat::RG8: return 2;
|
|
||||||
default: return 4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 材质参数类型
|
|
||||||
*/
|
|
||||||
enum class MaterialParamType : uint8_t {
|
|
||||||
Float, // 浮点数
|
|
||||||
Vec2, // 二维向量
|
|
||||||
Vec3, // 三维向量
|
|
||||||
Vec4, // 四维向量
|
|
||||||
Color, // 颜色
|
|
||||||
Mat4, // 4x4 矩阵
|
|
||||||
Texture // 纹理
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取材质参数类型的大小
|
|
||||||
* @param type 参数类型
|
|
||||||
* @return 参数大小(字节)
|
|
||||||
*/
|
|
||||||
inline uint32_t getMaterialParamSize(MaterialParamType type) {
|
|
||||||
switch (type) {
|
|
||||||
case MaterialParamType::Float: return sizeof(float);
|
|
||||||
case MaterialParamType::Vec2: return sizeof(float) * 2;
|
|
||||||
case MaterialParamType::Vec3: return sizeof(float) * 3;
|
|
||||||
case MaterialParamType::Vec4: return sizeof(float) * 4;
|
|
||||||
case MaterialParamType::Color: return sizeof(float) * 4;
|
|
||||||
case MaterialParamType::Mat4: return sizeof(float) * 16;
|
|
||||||
case MaterialParamType::Texture: return sizeof(uint32_t) * 2; // Handle size
|
|
||||||
default: return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 渲染命令结构
|
* @brief 渲染命令结构
|
||||||
*
|
*
|
||||||
|
|
@ -100,22 +37,14 @@ struct RenderCommand {
|
||||||
|
|
||||||
// 绘制网格命令数据
|
// 绘制网格命令数据
|
||||||
struct DrawMeshData {
|
struct DrawMeshData {
|
||||||
MeshHandle mesh; // 网格句柄
|
Handle<Mesh> mesh; // 网格句柄
|
||||||
MaterialHandle material; // 材质句柄
|
Handle<Material> material; // 材质句柄
|
||||||
Vec2 pos; // 位置
|
Vec2 pos; // 位置
|
||||||
Vec2 scale; // 缩放
|
Vec2 scale; // 缩放
|
||||||
float rot; // 旋转角度
|
float rot; // 旋转角度
|
||||||
Color color; // 顶点颜色
|
Color color; // 顶点颜色
|
||||||
};
|
};
|
||||||
|
|
||||||
// 实例化绘制命令数据
|
|
||||||
struct DrawInstancedData {
|
|
||||||
MeshHandle mesh; // 网格句柄
|
|
||||||
MaterialHandle material; // 材质句柄
|
|
||||||
uint32_t instanceCount; // 实例数量
|
|
||||||
uint32_t instanceOffset; // 实例数据偏移
|
|
||||||
};
|
|
||||||
|
|
||||||
// 设置视口命令数据
|
// 设置视口命令数据
|
||||||
struct ViewportData {
|
struct ViewportData {
|
||||||
int32_t x, y; // 视口位置
|
int32_t x, y; // 视口位置
|
||||||
|
|
@ -130,7 +59,6 @@ struct RenderCommand {
|
||||||
|
|
||||||
union {
|
union {
|
||||||
DrawMeshData drawMesh;
|
DrawMeshData drawMesh;
|
||||||
DrawInstancedData drawInstanced;
|
|
||||||
ViewportData viewport;
|
ViewportData viewport;
|
||||||
ClearData clear;
|
ClearData clear;
|
||||||
};
|
};
|
||||||
|
|
@ -139,8 +67,8 @@ struct RenderCommand {
|
||||||
* @brief 默认构造函数
|
* @brief 默认构造函数
|
||||||
*/
|
*/
|
||||||
RenderCommand() : type(RenderCommandType::DrawMesh), sortKey(0) {
|
RenderCommand() : type(RenderCommandType::DrawMesh), sortKey(0) {
|
||||||
drawMesh.mesh = MeshHandle::invalid();
|
drawMesh.mesh = Handle<Mesh>::invalid();
|
||||||
drawMesh.material = MaterialHandle::invalid();
|
drawMesh.material = Handle<Material>::invalid();
|
||||||
drawMesh.pos = Vec2(0.0f, 0.0f);
|
drawMesh.pos = Vec2(0.0f, 0.0f);
|
||||||
drawMesh.scale = Vec2(1.0f, 1.0f);
|
drawMesh.scale = Vec2(1.0f, 1.0f);
|
||||||
drawMesh.rot = 0.0f;
|
drawMesh.rot = 0.0f;
|
||||||
|
|
@ -150,7 +78,7 @@ struct RenderCommand {
|
||||||
/**
|
/**
|
||||||
* @brief 从 Transform 设置绘制数据
|
* @brief 从 Transform 设置绘制数据
|
||||||
*/
|
*/
|
||||||
void setTransform(const Transform& t) {
|
void setTransform(const Transform &t) {
|
||||||
drawMesh.pos = t.pos;
|
drawMesh.pos = t.pos;
|
||||||
drawMesh.scale = t.scale;
|
drawMesh.scale = t.scale;
|
||||||
drawMesh.rot = t.rot;
|
drawMesh.rot = t.rot;
|
||||||
|
|
@ -159,9 +87,7 @@ struct RenderCommand {
|
||||||
/**
|
/**
|
||||||
* @brief 设置顶点颜色
|
* @brief 设置顶点颜色
|
||||||
*/
|
*/
|
||||||
void setColor(const Color& c) {
|
void setColor(const Color &c) { drawMesh.color = c; }
|
||||||
drawMesh.color = c;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 获取 Transform
|
* @brief 获取 Transform
|
||||||
|
|
@ -173,26 +99,21 @@ struct RenderCommand {
|
||||||
/**
|
/**
|
||||||
* @brief 获取顶点颜色
|
* @brief 获取顶点颜色
|
||||||
*/
|
*/
|
||||||
Color getColor() const {
|
Color getColor() const { return drawMesh.color; }
|
||||||
return drawMesh.color;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// 清除标志
|
// 清除标志
|
||||||
constexpr uint32_t CLEAR_COLOR_FLAG = 1 << 0;
|
constexpr uint32_t CLEAR_COLOR_FLAG = 1 << 0;
|
||||||
constexpr uint32_t CLEAR_DEPTH_FLAG = 1 << 1;
|
constexpr uint32_t CLEAR_DEPTH_FLAG = 1 << 1;
|
||||||
constexpr uint32_t CLEAR_STENCIL_FLAG = 1 << 2;
|
constexpr uint32_t CLEAR_STENCIL_FLAG = 1 << 2;
|
||||||
constexpr uint32_t CLEAR_ALL_FLAG = CLEAR_COLOR_FLAG | CLEAR_DEPTH_FLAG | CLEAR_STENCIL_FLAG;
|
constexpr uint32_t CLEAR_ALL_FLAG =
|
||||||
|
CLEAR_COLOR_FLAG | CLEAR_DEPTH_FLAG | CLEAR_STENCIL_FLAG;
|
||||||
|
|
||||||
// 最大渲染命令数
|
// 最大渲染命令数
|
||||||
constexpr uint32_t MAX_RENDER_COMMANDS = 10000;
|
constexpr uint32_t MAX_RENDER_COMMANDS = 10000;
|
||||||
|
|
||||||
// 最大实例数
|
// UBO 绑定槽位(已移至 RHI)
|
||||||
constexpr uint32_t MAX_INSTANCES = 256;
|
|
||||||
|
|
||||||
// UBO 绑定槽位
|
|
||||||
constexpr uint32_t GLOBAL_UBO_BINDING = 0; // 全局 UBO
|
constexpr uint32_t GLOBAL_UBO_BINDING = 0; // 全局 UBO
|
||||||
constexpr uint32_t MATERIAL_UBO_BINDING = 1; // 材质 UBO
|
constexpr uint32_t MATERIAL_UBO_BINDING = 1; // 材质 UBO
|
||||||
constexpr uint32_t INSTANCE_UBO_BINDING = 2; // 实例 UBO
|
|
||||||
|
|
||||||
} // namespace extra2d
|
} // namespace extra2d
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,13 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <array>
|
|
||||||
#include <event/events.h>
|
#include <event/events.h>
|
||||||
#include <module/module.h>
|
#include <module/module.h>
|
||||||
#include <module/module_registry.h>
|
#include <module/module_registry.h>
|
||||||
#include <renderer/material.h>
|
#include <renderer/command_queue.h>
|
||||||
#include <renderer/mesh.h>
|
#include <renderer/render_graph.h>
|
||||||
#include <renderer/render_types.h>
|
#include <renderer/render_types.h>
|
||||||
#include <renderer/shader.h>
|
#include <renderer/rhi/rhi.h>
|
||||||
#include <renderer/texture.h>
|
|
||||||
#include <renderer/uniform_buffer.h>
|
|
||||||
#include <renderer/viewport_adapter.h>
|
#include <renderer/viewport_adapter.h>
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
||||||
|
|
@ -21,15 +17,15 @@ class AssetsModule;
|
||||||
/**
|
/**
|
||||||
* @brief 渲染器模块
|
* @brief 渲染器模块
|
||||||
*
|
*
|
||||||
* 核心渲染系统模块,负责:
|
* 基于新 RHI 架构的核心渲染系统模块,负责:
|
||||||
* - 通过事件接收渲染命令
|
* - 通过事件接收渲染命令
|
||||||
* - 自动批处理和排序
|
* - 使用 RenderGraph 管理渲染流程
|
||||||
|
* - 使用 CommandQueue 进行命令排序和批处理
|
||||||
* - 执行实际渲染
|
* - 执行实际渲染
|
||||||
*
|
|
||||||
* 资源管理已迁移到 AssetsModule
|
|
||||||
*/
|
*/
|
||||||
class RendererModule : public Module {
|
class RendererModule : public Module {
|
||||||
E2D_REGISTER_MODULE(RendererModule, "Renderer", 3)
|
// 优先级为 4,在 AssetsModule (优先级 3) 之后初始化
|
||||||
|
E2D_REGISTER_MODULE(RendererModule, "Renderer", 4)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
|
|
@ -42,18 +38,16 @@ public:
|
||||||
*/
|
*/
|
||||||
~RendererModule() override;
|
~RendererModule() override;
|
||||||
|
|
||||||
// 禁止拷贝
|
// 禁止拷贝和移动
|
||||||
RendererModule(const RendererModule &) = delete;
|
RendererModule(const RendererModule &) = delete;
|
||||||
RendererModule &operator=(const RendererModule &) = delete;
|
RendererModule &operator=(const RendererModule &) = delete;
|
||||||
|
RendererModule(RendererModule &&) = delete;
|
||||||
// 允许移动
|
RendererModule &operator=(RendererModule &&) = delete;
|
||||||
RendererModule(RendererModule &&) noexcept;
|
|
||||||
RendererModule &operator=(RendererModule &&) noexcept;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 初始化模块
|
* @brief 初始化模块
|
||||||
*
|
*
|
||||||
* 绑定事件监听器,等待窗口显示事件进行 GL 初始化
|
* 绑定事件监听器,初始化 RenderGraph 和 CommandQueue
|
||||||
*
|
*
|
||||||
* @return 初始化是否成功
|
* @return 初始化是否成功
|
||||||
*/
|
*/
|
||||||
|
|
@ -67,29 +61,7 @@ public:
|
||||||
void shutdown() override;
|
void shutdown() override;
|
||||||
|
|
||||||
//===========================================================================
|
//===========================================================================
|
||||||
// 默认资源(通过 AssetsModule 获取)
|
// 渲染接口
|
||||||
//===========================================================================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取默认材质句柄
|
|
||||||
* @return 默认材质句柄
|
|
||||||
*/
|
|
||||||
MaterialHandle getDefaultMaterialHandle() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取默认四边形网格句柄
|
|
||||||
* @return 默认四边形网格句柄
|
|
||||||
*/
|
|
||||||
MeshHandle getDefaultQuadHandle() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取默认纹理句柄(1x1 白色纹理)
|
|
||||||
* @return 默认纹理句柄
|
|
||||||
*/
|
|
||||||
TextureHandle getDefaultTextureHandle() const;
|
|
||||||
|
|
||||||
//===========================================================================
|
|
||||||
// 渲染状态设置
|
|
||||||
//===========================================================================
|
//===========================================================================
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -121,6 +93,42 @@ public:
|
||||||
*/
|
*/
|
||||||
const ViewportAdapter &getViewportAdapter() const { return viewportAdapter_; }
|
const ViewportAdapter &getViewportAdapter() const { return viewportAdapter_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取渲染图
|
||||||
|
* @return 渲染图指针
|
||||||
|
*/
|
||||||
|
RenderGraph *getRenderGraph() { return &renderGraph_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取命令队列
|
||||||
|
* @return 命令队列指针
|
||||||
|
*/
|
||||||
|
CommandQueue *getCommandQueue() { return renderGraph_.getCommandQueue(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取 RHI 上下文
|
||||||
|
* @return RHI 上下文指针
|
||||||
|
*/
|
||||||
|
RHIContext *getRHIContext() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取默认材质句柄
|
||||||
|
* @return 默认材质句柄
|
||||||
|
*/
|
||||||
|
Handle<Material> getDefaultMaterialHandle() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取默认四边形网格句柄
|
||||||
|
* @return 默认四边形网格句柄
|
||||||
|
*/
|
||||||
|
Handle<Mesh> getDefaultQuadHandle() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取默认纹理句柄(1x1 白色纹理)
|
||||||
|
* @return 默认纹理句柄
|
||||||
|
*/
|
||||||
|
Handle<Texture> getDefaultTextureHandle() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
//===========================================================================
|
//===========================================================================
|
||||||
// 事件处理器
|
// 事件处理器
|
||||||
|
|
@ -129,7 +137,7 @@ private:
|
||||||
/**
|
/**
|
||||||
* @brief 渲染开始事件处理
|
* @brief 渲染开始事件处理
|
||||||
*
|
*
|
||||||
* 清空命令缓冲区,重置统计信息
|
* 开始渲染帧,初始化 RenderGraph
|
||||||
*/
|
*/
|
||||||
void onRenderBegin();
|
void onRenderBegin();
|
||||||
|
|
||||||
|
|
@ -148,7 +156,7 @@ private:
|
||||||
/**
|
/**
|
||||||
* @brief 渲染结束事件处理
|
* @brief 渲染结束事件处理
|
||||||
*
|
*
|
||||||
* 排序命令,批处理并执行绘制
|
* 执行 RenderGraph 渲染
|
||||||
*/
|
*/
|
||||||
void onRenderEnd();
|
void onRenderEnd();
|
||||||
|
|
||||||
|
|
@ -162,7 +170,7 @@ private:
|
||||||
/**
|
/**
|
||||||
* @brief 窗口显示事件处理
|
* @brief 窗口显示事件处理
|
||||||
*
|
*
|
||||||
* 延迟 GL 初始化到窗口显示时
|
* 延迟初始化到窗口显示时
|
||||||
*/
|
*/
|
||||||
void onWindowShow();
|
void onWindowShow();
|
||||||
|
|
||||||
|
|
@ -170,30 +178,6 @@ private:
|
||||||
// 渲染执行
|
// 渲染执行
|
||||||
//===========================================================================
|
//===========================================================================
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 排序渲染命令
|
|
||||||
*
|
|
||||||
* 根据 sortKey 对命令进行排序以优化绘制顺序
|
|
||||||
*/
|
|
||||||
void sortCommands();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 批处理并绘制
|
|
||||||
*
|
|
||||||
* 将相同材质和网格的命令合并批次绘制
|
|
||||||
*/
|
|
||||||
void batchAndDraw();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 绘制批次
|
|
||||||
* @param start 批次起始索引
|
|
||||||
* @param count 批次命令数量
|
|
||||||
* @param materialHandle 材质句柄
|
|
||||||
* @param meshHandle 网格句柄
|
|
||||||
*/
|
|
||||||
void drawBatch(uint32 start, uint32 count, MaterialHandle materialHandle,
|
|
||||||
MeshHandle meshHandle);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 执行单个渲染命令
|
* @brief 执行单个渲染命令
|
||||||
* @param cmd 渲染命令
|
* @param cmd 渲染命令
|
||||||
|
|
@ -201,18 +185,11 @@ private:
|
||||||
void executeCommand(const RenderCommand &cmd);
|
void executeCommand(const RenderCommand &cmd);
|
||||||
|
|
||||||
//===========================================================================
|
//===========================================================================
|
||||||
// 命令缓冲区
|
// 渲染图和命令队列
|
||||||
//===========================================================================
|
//===========================================================================
|
||||||
|
|
||||||
std::array<RenderCommand, MAX_RENDER_COMMANDS>
|
RenderGraph renderGraph_; // 渲染图
|
||||||
commandBuffer_; // 预分配命令缓冲区
|
CommandQueue *commandQueue_ = nullptr; // 命令队列(指向 renderGraph_ 内部)
|
||||||
uint32 commandCount_ = 0; // 当前命令数量
|
|
||||||
|
|
||||||
//===========================================================================
|
|
||||||
// UBO 管理器
|
|
||||||
//===========================================================================
|
|
||||||
|
|
||||||
UniformBufferManager uniformManager_;
|
|
||||||
|
|
||||||
//===========================================================================
|
//===========================================================================
|
||||||
// 事件监听器
|
// 事件监听器
|
||||||
|
|
@ -229,7 +206,7 @@ private:
|
||||||
// 状态标志
|
// 状态标志
|
||||||
//===========================================================================
|
//===========================================================================
|
||||||
|
|
||||||
bool glInitialized_ = false; // GL 是否已初始化
|
bool initialized_ = false; // 是否已初始化
|
||||||
|
|
||||||
//===========================================================================
|
//===========================================================================
|
||||||
// 渲染统计
|
// 渲染统计
|
||||||
|
|
@ -237,7 +214,6 @@ private:
|
||||||
|
|
||||||
struct Stats {
|
struct Stats {
|
||||||
uint32 commandsSubmitted = 0; // 提交的命令数
|
uint32 commandsSubmitted = 0; // 提交的命令数
|
||||||
uint32 commandsExecuted = 0; // 执行的命令数
|
|
||||||
uint32 drawCalls = 0; // 绘制调用次数
|
uint32 drawCalls = 0; // 绘制调用次数
|
||||||
uint32 batches = 0; // 批次数
|
uint32 batches = 0; // 批次数
|
||||||
} stats_;
|
} stats_;
|
||||||
|
|
@ -247,7 +223,7 @@ private:
|
||||||
//===========================================================================
|
//===========================================================================
|
||||||
|
|
||||||
int32 viewportX_ = 0, viewportY_ = 0;
|
int32 viewportX_ = 0, viewportY_ = 0;
|
||||||
int32 viewportWidth_ = 0, viewportHeight_ = 0;
|
int32 viewportWidth_ = 1280, viewportHeight_ = 720;
|
||||||
|
|
||||||
//===========================================================================
|
//===========================================================================
|
||||||
// 视口适配器
|
// 视口适配器
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <glad/glad.h>
|
||||||
|
#include <renderer/rhi/rhi.h>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief OpenGL 缓冲区实现
|
||||||
|
*/
|
||||||
|
class GLBuffer : public RHIBuffer {
|
||||||
|
public:
|
||||||
|
explicit GLBuffer(const BufferDesc &desc);
|
||||||
|
~GLBuffer() override;
|
||||||
|
|
||||||
|
bool create();
|
||||||
|
void destroy();
|
||||||
|
|
||||||
|
bool update(const void *data, uint32_t size, uint32_t offset) override;
|
||||||
|
void *map() override;
|
||||||
|
void unmap() override;
|
||||||
|
|
||||||
|
const BufferDesc &getDesc() const { return desc_; }
|
||||||
|
uint32_t getSize() const override { return desc_.size; }
|
||||||
|
BufferType getType() const override { return desc_.type; }
|
||||||
|
bool isValid() const override { return buffer_ != 0; }
|
||||||
|
|
||||||
|
GLuint getGLBuffer() const { return buffer_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
BufferDesc desc_;
|
||||||
|
GLuint buffer_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,176 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <glad/glad.h>
|
||||||
|
#include <renderer/rhi/rhi.h>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// 前向声明
|
||||||
|
class GLPipeline;
|
||||||
|
class GLBuffer;
|
||||||
|
class GLTexture;
|
||||||
|
class GLFramebuffer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief OpenGL 命令列表实现
|
||||||
|
*/
|
||||||
|
class GLCommandList : public RHICommandList {
|
||||||
|
public:
|
||||||
|
GLCommandList();
|
||||||
|
~GLCommandList() override;
|
||||||
|
|
||||||
|
//===========================================================================
|
||||||
|
// 命令列表生命周期
|
||||||
|
//===========================================================================
|
||||||
|
|
||||||
|
void begin() override;
|
||||||
|
void end() override;
|
||||||
|
void submit() override;
|
||||||
|
|
||||||
|
//===========================================================================
|
||||||
|
// 渲染通道
|
||||||
|
//===========================================================================
|
||||||
|
|
||||||
|
void beginRenderPass(RHIFramebuffer *framebuffer,
|
||||||
|
ClearFlags clearFlags = ClearFlags::Color,
|
||||||
|
const Color &clearColor = Color::Black,
|
||||||
|
float clearDepth = 1.0f,
|
||||||
|
uint8_t clearStencil = 0) override;
|
||||||
|
void endRenderPass() override;
|
||||||
|
|
||||||
|
//===========================================================================
|
||||||
|
// 状态设置
|
||||||
|
//===========================================================================
|
||||||
|
|
||||||
|
void setViewport(const Viewport &viewport) override;
|
||||||
|
void setScissor(const ScissorRect &scissor) override;
|
||||||
|
void setPipeline(RHIPipeline *pipeline) override;
|
||||||
|
|
||||||
|
//===========================================================================
|
||||||
|
// 资源绑定
|
||||||
|
//===========================================================================
|
||||||
|
|
||||||
|
void setVertexBuffer(uint32_t slot, RHIBuffer *buffer,
|
||||||
|
uint32_t offset = 0, uint32_t stride = 0) override;
|
||||||
|
void setIndexBuffer(RHIBuffer *buffer, IndexType type,
|
||||||
|
uint32_t offset = 0) override;
|
||||||
|
void setUniformBuffer(uint32_t slot, RHIBuffer *buffer) override;
|
||||||
|
void setUniformBuffer(uint32_t slot, RHIBuffer *buffer, uint32_t offset, uint32_t size = 0) override;
|
||||||
|
void setTexture(uint32_t slot, RHITexture *texture) override;
|
||||||
|
void setSampler(uint32_t slot, TextureFilter minFilter,
|
||||||
|
TextureFilter magFilter, TextureWrap wrapS,
|
||||||
|
TextureWrap wrapT) override;
|
||||||
|
|
||||||
|
//===========================================================================
|
||||||
|
// 绘制命令
|
||||||
|
//===========================================================================
|
||||||
|
|
||||||
|
void draw(uint32_t vertexCount, uint32_t firstVertex = 0,
|
||||||
|
uint32_t instanceCount = 1, uint32_t firstInstance = 0) override;
|
||||||
|
|
||||||
|
void drawIndexed(uint32_t indexCount, uint32_t firstIndex = 0,
|
||||||
|
int32_t vertexOffset = 0, uint32_t instanceCount = 1,
|
||||||
|
uint32_t firstInstance = 0) override;
|
||||||
|
|
||||||
|
//===========================================================================
|
||||||
|
// 工具方法
|
||||||
|
//===========================================================================
|
||||||
|
|
||||||
|
void clear(ClearFlags flags, const Color &color = Color::Black,
|
||||||
|
float depth = 1.0f, uint8_t stencil = 0) override;
|
||||||
|
|
||||||
|
bool isRecording() const override;
|
||||||
|
|
||||||
|
// 设置 uniform 变量
|
||||||
|
void setUniform(const char* name, float value) override;
|
||||||
|
void setUniform(const char* name, const Vec2& value) override;
|
||||||
|
void setUniform(const char* name, const Vec3& value) override;
|
||||||
|
void setUniform(const char* name, const Color& value) override;
|
||||||
|
void setUniform(const char* name, const Mat4& value) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
// 状态缓存机制 - 避免冗余的 OpenGL 状态切换
|
||||||
|
struct StateCache {
|
||||||
|
// 管线状态
|
||||||
|
GLPipeline* pipeline = nullptr;
|
||||||
|
GLuint shaderProgram = 0;
|
||||||
|
|
||||||
|
// 视口状态
|
||||||
|
Viewport viewport;
|
||||||
|
bool viewportValid = false;
|
||||||
|
|
||||||
|
// 裁剪状态
|
||||||
|
ScissorRect scissor{0, 0, 0, 0};
|
||||||
|
bool scissorValid = false;
|
||||||
|
bool scissorEnabled = false;
|
||||||
|
|
||||||
|
// 缓冲区状态
|
||||||
|
GLBuffer* vertexBuffers[4] = {nullptr, nullptr, nullptr, nullptr}; // 支持最多4个顶点缓冲区槽
|
||||||
|
uint32_t vertexBufferOffsets[4] = {0, 0, 0, 0};
|
||||||
|
GLBuffer* indexBuffer = nullptr;
|
||||||
|
|
||||||
|
// Uniform 缓冲区状态
|
||||||
|
struct UBOBinding {
|
||||||
|
GLBuffer* buffer = nullptr;
|
||||||
|
uint32_t offset = 0;
|
||||||
|
uint32_t size = 0;
|
||||||
|
bool valid = false;
|
||||||
|
};
|
||||||
|
UBOBinding uniformBuffers[4]; // 支持最多4个 UBO 槽
|
||||||
|
|
||||||
|
// 纹理状态
|
||||||
|
struct TextureBinding {
|
||||||
|
GLTexture* texture = nullptr;
|
||||||
|
bool valid = false;
|
||||||
|
};
|
||||||
|
TextureBinding textures[8]; // 支持最多8个纹理槽
|
||||||
|
|
||||||
|
// 帧缓冲状态
|
||||||
|
GLFramebuffer* framebuffer = nullptr;
|
||||||
|
|
||||||
|
// 重置所有状态缓存
|
||||||
|
void reset() {
|
||||||
|
pipeline = nullptr;
|
||||||
|
shaderProgram = 0;
|
||||||
|
viewportValid = false;
|
||||||
|
scissorValid = false;
|
||||||
|
scissorEnabled = false;
|
||||||
|
for (int i = 0; i < 4; ++i) {
|
||||||
|
vertexBuffers[i] = nullptr;
|
||||||
|
vertexBufferOffsets[i] = 0;
|
||||||
|
uniformBuffers[i] = {};
|
||||||
|
}
|
||||||
|
indexBuffer = nullptr;
|
||||||
|
for (int i = 0; i < 8; ++i) {
|
||||||
|
textures[i] = {};
|
||||||
|
}
|
||||||
|
framebuffer = nullptr;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
bool recording_ = false;
|
||||||
|
StateCache stateCache_;
|
||||||
|
|
||||||
|
// 统计信息(调试用)
|
||||||
|
struct Stats {
|
||||||
|
uint32_t pipelineChanges = 0;
|
||||||
|
uint32_t textureChanges = 0;
|
||||||
|
uint32_t bufferChanges = 0;
|
||||||
|
uint32_t viewportChanges = 0;
|
||||||
|
uint32_t redundantPipelineChanges = 0;
|
||||||
|
uint32_t redundantTextureChanges = 0;
|
||||||
|
uint32_t redundantBufferChanges = 0;
|
||||||
|
|
||||||
|
void reset() {
|
||||||
|
pipelineChanges = 0;
|
||||||
|
textureChanges = 0;
|
||||||
|
bufferChanges = 0;
|
||||||
|
viewportChanges = 0;
|
||||||
|
redundantPipelineChanges = 0;
|
||||||
|
redundantTextureChanges = 0;
|
||||||
|
redundantBufferChanges = 0;
|
||||||
|
}
|
||||||
|
} stats_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <renderer/rhi/rhi.h>
|
||||||
|
#include <glad/glad.h>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// 前向声明
|
||||||
|
class GLDevice;
|
||||||
|
class GLFramebuffer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief OpenGL 上下文实现
|
||||||
|
*/
|
||||||
|
class GLContext : public RHIContext {
|
||||||
|
public:
|
||||||
|
explicit GLContext(GLDevice* device);
|
||||||
|
~GLContext() override;
|
||||||
|
|
||||||
|
bool initialize() override;
|
||||||
|
void shutdown() override;
|
||||||
|
|
||||||
|
void beginFrame() override;
|
||||||
|
void endFrame() override;
|
||||||
|
|
||||||
|
void setViewport(int32_t x, int32_t y, uint32_t width, uint32_t height) override;
|
||||||
|
void bindDefaultFramebuffer() override;
|
||||||
|
RHIFramebuffer* getDefaultFramebuffer() override;
|
||||||
|
|
||||||
|
bool isFeatureSupported(const char* feature) const override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
GLDevice* device_;
|
||||||
|
std::unique_ptr<GLFramebuffer> defaultFramebuffer_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,53 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <renderer/rhi/rhi.h>
|
||||||
|
#include <glad/glad.h>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// 前向声明
|
||||||
|
class GLTexture;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief OpenGL 帧缓冲实现
|
||||||
|
*/
|
||||||
|
class GLFramebuffer : public RHIFramebuffer {
|
||||||
|
public:
|
||||||
|
GLFramebuffer();
|
||||||
|
explicit GLFramebuffer(const RenderPassDesc& desc);
|
||||||
|
~GLFramebuffer() override;
|
||||||
|
|
||||||
|
bool create();
|
||||||
|
void destroy();
|
||||||
|
|
||||||
|
void bind() override;
|
||||||
|
void unbind() override;
|
||||||
|
|
||||||
|
uint32_t getColorAttachmentCount() const override;
|
||||||
|
TextureHandle getColorAttachment(uint32_t index) const override;
|
||||||
|
TextureHandle getDepthStencilAttachment() const override;
|
||||||
|
|
||||||
|
uint32_t getWidth() const override { return width_; }
|
||||||
|
uint32_t getHeight() const override { return height_; }
|
||||||
|
|
||||||
|
bool hasDepthStencil() const override;
|
||||||
|
bool isValid() const override;
|
||||||
|
bool isDefault() const override;
|
||||||
|
|
||||||
|
void setSize(uint32_t width, uint32_t height);
|
||||||
|
GLuint getGLFramebuffer() const { return framebuffer_; }
|
||||||
|
|
||||||
|
const RenderPassDesc& getDesc() const { return desc_; }
|
||||||
|
void clear(ClearFlags flags, const Color& color, float depth, uint8_t stencil);
|
||||||
|
|
||||||
|
private:
|
||||||
|
RenderPassDesc desc_;
|
||||||
|
GLuint framebuffer_;
|
||||||
|
uint32_t width_;
|
||||||
|
uint32_t height_;
|
||||||
|
std::vector<std::unique_ptr<GLTexture>> colorAttachments_;
|
||||||
|
std::unique_ptr<GLTexture> depthStencilAttachment_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <renderer/rhi/rhi.h>
|
||||||
|
#include <glad/glad.h>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// 前向声明
|
||||||
|
class GLShader;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief OpenGL 管线实现
|
||||||
|
*/
|
||||||
|
class GLPipeline : public RHIPipeline {
|
||||||
|
public:
|
||||||
|
explicit GLPipeline(const PipelineDesc& desc);
|
||||||
|
~GLPipeline() override;
|
||||||
|
|
||||||
|
bool create();
|
||||||
|
void destroy();
|
||||||
|
|
||||||
|
void bind() override;
|
||||||
|
void unbind() override;
|
||||||
|
|
||||||
|
ShaderHandle getVertexShader() const override;
|
||||||
|
ShaderHandle getFragmentShader() const override;
|
||||||
|
const VertexLayout& getVertexLayout() const override;
|
||||||
|
bool isValid() const override;
|
||||||
|
|
||||||
|
const PipelineDesc& getDesc() const { return desc_; }
|
||||||
|
GLuint getGLVAO() const { return vao_; }
|
||||||
|
|
||||||
|
// 获取 shader program ID(用于设置 uniform)
|
||||||
|
GLuint getGLProgram() const { return shaderProgram_; }
|
||||||
|
|
||||||
|
// 设置 shader program ID(在创建时调用)
|
||||||
|
void setGLProgram(GLuint program) { shaderProgram_ = program; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
PipelineDesc desc_;
|
||||||
|
GLuint vao_;
|
||||||
|
GLuint shaderProgram_ = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <renderer/rhi/rhi.h>
|
||||||
|
#include <glad/glad.h>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief OpenGL 着色器实现
|
||||||
|
*/
|
||||||
|
class GLShader : public RHIShader {
|
||||||
|
public:
|
||||||
|
explicit GLShader(const ShaderDesc& desc);
|
||||||
|
~GLShader() override;
|
||||||
|
|
||||||
|
bool compile();
|
||||||
|
void destroy();
|
||||||
|
|
||||||
|
ShaderType getType() const override { return ShaderType::Vertex; } // 返回 Vertex 表示这是一个程序着色器
|
||||||
|
bool isCompiled() const override;
|
||||||
|
std::string getCompileLog() const override;
|
||||||
|
bool isValid() const override;
|
||||||
|
|
||||||
|
void bind() const;
|
||||||
|
void unbind() const;
|
||||||
|
|
||||||
|
void setUniform(const std::string& name, float value);
|
||||||
|
void setUniform(const std::string& name, const Vec2& value);
|
||||||
|
void setUniform(const std::string& name, const Vec3& value);
|
||||||
|
void setUniform(const std::string& name, const Color& value);
|
||||||
|
void setUniform(const std::string& name, const Mat4& value);
|
||||||
|
|
||||||
|
const ShaderDesc& getDesc() const { return desc_; }
|
||||||
|
GLuint getGLProgram() const { return program_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
ShaderDesc desc_;
|
||||||
|
GLuint shader_;
|
||||||
|
GLuint program_;
|
||||||
|
std::string compileLog_;
|
||||||
|
bool compiled_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <renderer/rhi/rhi.h>
|
||||||
|
#include <glad/glad.h>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief OpenGL 纹理实现
|
||||||
|
*/
|
||||||
|
class GLTexture : public RHITexture {
|
||||||
|
public:
|
||||||
|
explicit GLTexture(const TextureDesc& desc);
|
||||||
|
~GLTexture() override;
|
||||||
|
|
||||||
|
bool create();
|
||||||
|
void destroy();
|
||||||
|
|
||||||
|
TextureType getType() const override { return desc_.type; }
|
||||||
|
TextureFormat getFormat() const override { return desc_.format; }
|
||||||
|
uint32_t getWidth() const override { return desc_.width; }
|
||||||
|
uint32_t getHeight() const override { return desc_.height; }
|
||||||
|
uint32_t getMipLevels() const override { return desc_.mipLevels; }
|
||||||
|
|
||||||
|
bool update(const void* data, uint32_t mipLevel = 0) override;
|
||||||
|
void generateMipmap() override;
|
||||||
|
void bind(uint32_t slot) override;
|
||||||
|
void unbind() override;
|
||||||
|
|
||||||
|
bool isValid() const override;
|
||||||
|
bool isRenderTarget() const override;
|
||||||
|
|
||||||
|
GLuint getGLTexture() const { return texture_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
TextureDesc desc_;
|
||||||
|
GLuint texture_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,307 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <renderer/rhi/rhi.h>
|
||||||
|
#include <glad/glad.h>
|
||||||
|
#include <utils/logger.h>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief OpenGL 错误检查宏
|
||||||
|
*/
|
||||||
|
#define GL_CHECK(call) do { \
|
||||||
|
call; \
|
||||||
|
GLenum err = glGetError(); \
|
||||||
|
if (err != GL_NO_ERROR) { \
|
||||||
|
E2D_LOG_ERROR("OpenGL error in {}: {} ({})", #call, getGLErrorString(err), err); \
|
||||||
|
} \
|
||||||
|
} while(0)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取 OpenGL 错误字符串
|
||||||
|
* @param error OpenGL 错误码
|
||||||
|
* @return 错误描述字符串
|
||||||
|
*/
|
||||||
|
inline const char* getGLErrorString(GLenum error) {
|
||||||
|
switch (error) {
|
||||||
|
case GL_NO_ERROR: return "GL_NO_ERROR";
|
||||||
|
case GL_INVALID_ENUM: return "GL_INVALID_ENUM";
|
||||||
|
case GL_INVALID_VALUE: return "GL_INVALID_VALUE";
|
||||||
|
case GL_INVALID_OPERATION: return "GL_INVALID_OPERATION";
|
||||||
|
case GL_STACK_OVERFLOW: return "GL_STACK_OVERFLOW";
|
||||||
|
case GL_STACK_UNDERFLOW: return "GL_STACK_UNDERFLOW";
|
||||||
|
case GL_OUT_OF_MEMORY: return "GL_OUT_OF_MEMORY";
|
||||||
|
case GL_INVALID_FRAMEBUFFER_OPERATION: return "GL_INVALID_FRAMEBUFFER_OPERATION";
|
||||||
|
default: return "Unknown Error";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 将 BufferType 转换为 OpenGL 缓冲区目标
|
||||||
|
*/
|
||||||
|
inline GLenum bufferTypeToGL(BufferType type) {
|
||||||
|
switch (type) {
|
||||||
|
case BufferType::Vertex: return GL_ARRAY_BUFFER;
|
||||||
|
case BufferType::Index: return GL_ELEMENT_ARRAY_BUFFER;
|
||||||
|
case BufferType::Uniform: return GL_UNIFORM_BUFFER;
|
||||||
|
case BufferType::Storage: return GL_SHADER_STORAGE_BUFFER;
|
||||||
|
case BufferType::Staging: return GL_ARRAY_BUFFER;
|
||||||
|
default: return GL_ARRAY_BUFFER;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 将 BufferUsage 转换为 OpenGL 使用模式
|
||||||
|
*/
|
||||||
|
inline GLenum bufferUsageToGL(BufferUsage usage) {
|
||||||
|
switch (usage) {
|
||||||
|
case BufferUsage::Static: return GL_STATIC_DRAW;
|
||||||
|
case BufferUsage::Dynamic: return GL_DYNAMIC_DRAW;
|
||||||
|
case BufferUsage::Stream: return GL_STREAM_DRAW;
|
||||||
|
default: return GL_STATIC_DRAW;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 将 TextureFormat 转换为 OpenGL 内部格式
|
||||||
|
*/
|
||||||
|
inline GLenum textureFormatToGLInternal(TextureFormat format) {
|
||||||
|
switch (format) {
|
||||||
|
case TextureFormat::R8: return GL_R8;
|
||||||
|
case TextureFormat::RG8: return GL_RG8;
|
||||||
|
case TextureFormat::RGB8: return GL_RGB8;
|
||||||
|
case TextureFormat::RGBA8: return GL_RGBA8;
|
||||||
|
case TextureFormat::RGBA8_SRGB: return GL_SRGB8_ALPHA8;
|
||||||
|
case TextureFormat::Depth16: return GL_DEPTH_COMPONENT16;
|
||||||
|
case TextureFormat::Depth24: return GL_DEPTH_COMPONENT24;
|
||||||
|
case TextureFormat::Depth32F: return GL_DEPTH_COMPONENT32F;
|
||||||
|
case TextureFormat::Depth24Stencil8: return GL_DEPTH24_STENCIL8;
|
||||||
|
case TextureFormat::Depth32FStencil8:return GL_DEPTH32F_STENCIL8;
|
||||||
|
case TextureFormat::BC1: return GL_COMPRESSED_RGBA_S3TC_DXT1_EXT;
|
||||||
|
case TextureFormat::BC3: return GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
|
||||||
|
case TextureFormat::BC5: return 0x8DBD; // GL_COMPRESSED_RG_RGTC2
|
||||||
|
default: return GL_RGBA8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 将 TextureFormat 转换为 OpenGL 格式
|
||||||
|
*/
|
||||||
|
inline GLenum textureFormatToGLFormat(TextureFormat format) {
|
||||||
|
switch (format) {
|
||||||
|
case TextureFormat::R8: return GL_RED;
|
||||||
|
case TextureFormat::RG8: return GL_RG;
|
||||||
|
case TextureFormat::RGB8: return GL_RGB;
|
||||||
|
case TextureFormat::RGBA8:
|
||||||
|
case TextureFormat::RGBA8_SRGB: return GL_RGBA;
|
||||||
|
case TextureFormat::Depth16:
|
||||||
|
case TextureFormat::Depth24:
|
||||||
|
case TextureFormat::Depth32F: return GL_DEPTH_COMPONENT;
|
||||||
|
case TextureFormat::Depth24Stencil8:
|
||||||
|
case TextureFormat::Depth32FStencil8:return GL_DEPTH_STENCIL;
|
||||||
|
default: return GL_RGBA;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 将 TextureFormat 转换为 OpenGL 数据类型
|
||||||
|
*/
|
||||||
|
inline GLenum textureFormatToGLType(TextureFormat format) {
|
||||||
|
switch (format) {
|
||||||
|
case TextureFormat::R8:
|
||||||
|
case TextureFormat::RG8:
|
||||||
|
case TextureFormat::RGB8:
|
||||||
|
case TextureFormat::RGBA8:
|
||||||
|
case TextureFormat::RGBA8_SRGB: return GL_UNSIGNED_BYTE;
|
||||||
|
case TextureFormat::Depth16: return GL_UNSIGNED_SHORT;
|
||||||
|
case TextureFormat::Depth24: return GL_UNSIGNED_INT;
|
||||||
|
case TextureFormat::Depth32F: return GL_FLOAT;
|
||||||
|
case TextureFormat::Depth24Stencil8: return GL_UNSIGNED_INT_24_8;
|
||||||
|
case TextureFormat::Depth32FStencil8:return GL_FLOAT_32_UNSIGNED_INT_24_8_REV;
|
||||||
|
default: return GL_UNSIGNED_BYTE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 将 TextureFilter 转换为 OpenGL 过滤模式
|
||||||
|
*/
|
||||||
|
inline GLenum textureFilterToGLMin(TextureFilter filter) {
|
||||||
|
switch (filter) {
|
||||||
|
case TextureFilter::Nearest: return GL_NEAREST;
|
||||||
|
case TextureFilter::Linear: return GL_LINEAR;
|
||||||
|
case TextureFilter::NearestMipmapNearest: return GL_NEAREST_MIPMAP_NEAREST;
|
||||||
|
case TextureFilter::LinearMipmapNearest: return GL_LINEAR_MIPMAP_NEAREST;
|
||||||
|
case TextureFilter::NearestMipmapLinear: return GL_NEAREST_MIPMAP_LINEAR;
|
||||||
|
case TextureFilter::LinearMipmapLinear: return GL_LINEAR_MIPMAP_LINEAR;
|
||||||
|
default: return GL_LINEAR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline GLenum textureFilterToGLMag(TextureFilter filter) {
|
||||||
|
// Mag filter doesn't support mipmaps
|
||||||
|
switch (filter) {
|
||||||
|
case TextureFilter::Nearest:
|
||||||
|
case TextureFilter::NearestMipmapNearest:
|
||||||
|
case TextureFilter::NearestMipmapLinear:
|
||||||
|
return GL_NEAREST;
|
||||||
|
case TextureFilter::Linear:
|
||||||
|
case TextureFilter::LinearMipmapNearest:
|
||||||
|
case TextureFilter::LinearMipmapLinear:
|
||||||
|
default:
|
||||||
|
return GL_LINEAR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 将 TextureWrap 转换为 OpenGL 环绕模式
|
||||||
|
*/
|
||||||
|
inline GLenum textureWrapToGL(TextureWrap wrap) {
|
||||||
|
switch (wrap) {
|
||||||
|
case TextureWrap::Repeat: return GL_REPEAT;
|
||||||
|
case TextureWrap::ClampToEdge: return GL_CLAMP_TO_EDGE;
|
||||||
|
case TextureWrap::ClampToBorder: return GL_CLAMP_TO_BORDER;
|
||||||
|
case TextureWrap::MirroredRepeat: return GL_MIRRORED_REPEAT;
|
||||||
|
default: return GL_REPEAT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 将 VertexFormat 转换为 OpenGL 格式
|
||||||
|
*/
|
||||||
|
inline void vertexFormatToGL(VertexFormat format, GLint& components, GLenum& type, GLboolean& normalized) {
|
||||||
|
switch (format) {
|
||||||
|
case VertexFormat::Float1: components = 1; type = GL_FLOAT; normalized = GL_FALSE; break;
|
||||||
|
case VertexFormat::Float2: components = 2; type = GL_FLOAT; normalized = GL_FALSE; break;
|
||||||
|
case VertexFormat::Float3: components = 3; type = GL_FLOAT; normalized = GL_FALSE; break;
|
||||||
|
case VertexFormat::Float4: components = 4; type = GL_FLOAT; normalized = GL_FALSE; break;
|
||||||
|
case VertexFormat::Int1: components = 1; type = GL_INT; normalized = GL_FALSE; break;
|
||||||
|
case VertexFormat::Int2: components = 2; type = GL_INT; normalized = GL_FALSE; break;
|
||||||
|
case VertexFormat::Int3: components = 3; type = GL_INT; normalized = GL_FALSE; break;
|
||||||
|
case VertexFormat::Int4: components = 4; type = GL_INT; normalized = GL_FALSE; break;
|
||||||
|
case VertexFormat::UInt1: components = 1; type = GL_UNSIGNED_INT; normalized = GL_FALSE; break;
|
||||||
|
case VertexFormat::UInt2: components = 2; type = GL_UNSIGNED_INT; normalized = GL_FALSE; break;
|
||||||
|
case VertexFormat::UInt3: components = 3; type = GL_UNSIGNED_INT; normalized = GL_FALSE; break;
|
||||||
|
case VertexFormat::UInt4: components = 4; type = GL_UNSIGNED_INT; normalized = GL_FALSE; break;
|
||||||
|
case VertexFormat::Byte4: components = 4; type = GL_BYTE; normalized = GL_FALSE; break;
|
||||||
|
case VertexFormat::Byte4Normalized: components = 4; type = GL_BYTE; normalized = GL_TRUE; break;
|
||||||
|
case VertexFormat::UByte4: components = 4; type = GL_UNSIGNED_BYTE; normalized = GL_FALSE; break;
|
||||||
|
case VertexFormat::UByte4Normalized:components = 4; type = GL_UNSIGNED_BYTE; normalized = GL_TRUE; break;
|
||||||
|
default: components = 4; type = GL_FLOAT; normalized = GL_FALSE; break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 将 IndexType 转换为 OpenGL 类型
|
||||||
|
*/
|
||||||
|
inline GLenum indexTypeToGL(IndexType type) {
|
||||||
|
switch (type) {
|
||||||
|
case IndexType::UInt16: return GL_UNSIGNED_SHORT;
|
||||||
|
case IndexType::UInt32: return GL_UNSIGNED_INT;
|
||||||
|
default: return GL_UNSIGNED_SHORT;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 将 PrimitiveType 转换为 OpenGL 图元类型
|
||||||
|
*/
|
||||||
|
inline GLenum primitiveTypeToGL(PrimitiveType type) {
|
||||||
|
switch (type) {
|
||||||
|
case PrimitiveType::Points: return GL_POINTS;
|
||||||
|
case PrimitiveType::Lines: return GL_LINES;
|
||||||
|
case PrimitiveType::LineStrip: return GL_LINE_STRIP;
|
||||||
|
case PrimitiveType::Triangles: return GL_TRIANGLES;
|
||||||
|
case PrimitiveType::TriangleStrip: return GL_TRIANGLE_STRIP;
|
||||||
|
case PrimitiveType::TriangleFan: return GL_TRIANGLE_FAN;
|
||||||
|
default: return GL_TRIANGLES;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 将 BlendFactor 转换为 OpenGL 混合因子
|
||||||
|
*/
|
||||||
|
inline GLenum blendFactorToGL(BlendFactor factor) {
|
||||||
|
switch (factor) {
|
||||||
|
case BlendFactor::Zero: return GL_ZERO;
|
||||||
|
case BlendFactor::One: return GL_ONE;
|
||||||
|
case BlendFactor::SrcColor: return GL_SRC_COLOR;
|
||||||
|
case BlendFactor::OneMinusSrcColor: return GL_ONE_MINUS_SRC_COLOR;
|
||||||
|
case BlendFactor::DstColor: return GL_DST_COLOR;
|
||||||
|
case BlendFactor::OneMinusDstColor: return GL_ONE_MINUS_DST_COLOR;
|
||||||
|
case BlendFactor::SrcAlpha: return GL_SRC_ALPHA;
|
||||||
|
case BlendFactor::OneMinusSrcAlpha: return GL_ONE_MINUS_SRC_ALPHA;
|
||||||
|
case BlendFactor::DstAlpha: return GL_DST_ALPHA;
|
||||||
|
case BlendFactor::OneMinusDstAlpha: return GL_ONE_MINUS_DST_ALPHA;
|
||||||
|
default: return GL_ONE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 将 BlendOp 转换为 OpenGL 混合操作
|
||||||
|
*/
|
||||||
|
inline GLenum blendOpToGL(BlendOp op) {
|
||||||
|
switch (op) {
|
||||||
|
case BlendOp::Add: return GL_FUNC_ADD;
|
||||||
|
case BlendOp::Subtract: return GL_FUNC_SUBTRACT;
|
||||||
|
case BlendOp::ReverseSubtract: return GL_FUNC_REVERSE_SUBTRACT;
|
||||||
|
case BlendOp::Min: return GL_MIN;
|
||||||
|
case BlendOp::Max: return GL_MAX;
|
||||||
|
default: return GL_FUNC_ADD;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 将 CompareFunc 转换为 OpenGL 比较函数
|
||||||
|
*/
|
||||||
|
inline GLenum compareFuncToGL(CompareFunc func) {
|
||||||
|
switch (func) {
|
||||||
|
case CompareFunc::Never: return GL_NEVER;
|
||||||
|
case CompareFunc::Less: return GL_LESS;
|
||||||
|
case CompareFunc::Equal: return GL_EQUAL;
|
||||||
|
case CompareFunc::LessEqual: return GL_LEQUAL;
|
||||||
|
case CompareFunc::Greater: return GL_GREATER;
|
||||||
|
case CompareFunc::NotEqual: return GL_NOTEQUAL;
|
||||||
|
case CompareFunc::GreaterEqual: return GL_GEQUAL;
|
||||||
|
case CompareFunc::Always: return GL_ALWAYS;
|
||||||
|
default: return GL_LESS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 将 ShaderType 转换为 OpenGL 着色器类型
|
||||||
|
*/
|
||||||
|
inline GLenum shaderTypeToGL(ShaderType type) {
|
||||||
|
switch (type) {
|
||||||
|
case ShaderType::Vertex: return GL_VERTEX_SHADER;
|
||||||
|
case ShaderType::Fragment: return GL_FRAGMENT_SHADER;
|
||||||
|
case ShaderType::Geometry: return GL_GEOMETRY_SHADER;
|
||||||
|
case ShaderType::Compute: return GL_COMPUTE_SHADER;
|
||||||
|
default: return GL_VERTEX_SHADER;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取 VertexFormat 的大小(字节)
|
||||||
|
*/
|
||||||
|
inline uint32_t getVertexFormatSize(VertexFormat format) {
|
||||||
|
switch (format) {
|
||||||
|
case VertexFormat::Float1: return sizeof(float) * 1;
|
||||||
|
case VertexFormat::Float2: return sizeof(float) * 2;
|
||||||
|
case VertexFormat::Float3: return sizeof(float) * 3;
|
||||||
|
case VertexFormat::Float4: return sizeof(float) * 4;
|
||||||
|
case VertexFormat::Int1: return sizeof(int32_t) * 1;
|
||||||
|
case VertexFormat::Int2: return sizeof(int32_t) * 2;
|
||||||
|
case VertexFormat::Int3: return sizeof(int32_t) * 3;
|
||||||
|
case VertexFormat::Int4: return sizeof(int32_t) * 4;
|
||||||
|
case VertexFormat::UInt1: return sizeof(uint32_t) * 1;
|
||||||
|
case VertexFormat::UInt2: return sizeof(uint32_t) * 2;
|
||||||
|
case VertexFormat::UInt3: return sizeof(uint32_t) * 3;
|
||||||
|
case VertexFormat::UInt4: return sizeof(uint32_t) * 4;
|
||||||
|
case VertexFormat::Byte4:
|
||||||
|
case VertexFormat::Byte4Normalized: return sizeof(int8_t) * 4;
|
||||||
|
case VertexFormat::UByte4:
|
||||||
|
case VertexFormat::UByte4Normalized: return sizeof(uint8_t) * 4;
|
||||||
|
default: return sizeof(float) * 4;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief RHI (Render Hardware Interface) 头文件
|
||||||
|
*
|
||||||
|
* 包含所有 RHI 相关的接口和类型定义
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <renderer/rhi/rhi_types.h>
|
||||||
|
#include <renderer/rhi/rhi_device.h>
|
||||||
|
#include <renderer/rhi/rhi_buffer.h>
|
||||||
|
#include <renderer/rhi/rhi_shader.h>
|
||||||
|
#include <renderer/rhi/rhi_framebuffer.h>
|
||||||
|
#include <renderer/rhi/rhi_command_list.h>
|
||||||
|
|
@ -0,0 +1,131 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <renderer/rhi/rhi_types.h>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief RHI 缓冲区接口
|
||||||
|
*
|
||||||
|
* 抽象 GPU 缓冲区资源
|
||||||
|
*/
|
||||||
|
class RHIBuffer {
|
||||||
|
public:
|
||||||
|
virtual ~RHIBuffer() = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取缓冲区类型
|
||||||
|
* @return 缓冲区类型
|
||||||
|
*/
|
||||||
|
virtual BufferType getType() const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取缓冲区大小
|
||||||
|
* @return 缓冲区大小(字节)
|
||||||
|
*/
|
||||||
|
virtual uint32_t getSize() const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 更新缓冲区数据
|
||||||
|
* @param data 数据源指针
|
||||||
|
* @param size 数据大小
|
||||||
|
* @param offset 缓冲区偏移(字节)
|
||||||
|
* @return 更新是否成功
|
||||||
|
*/
|
||||||
|
virtual bool update(const void* data, uint32_t size, uint32_t offset = 0) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 映射缓冲区到 CPU 内存
|
||||||
|
* @return 映射的内存指针,失败返回 nullptr
|
||||||
|
*/
|
||||||
|
virtual void* map() = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 解除映射
|
||||||
|
*/
|
||||||
|
virtual void unmap() = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查是否有效
|
||||||
|
* @return 是否有效
|
||||||
|
*/
|
||||||
|
virtual bool isValid() const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief RHI 纹理接口
|
||||||
|
*
|
||||||
|
* 抽象 GPU 纹理资源
|
||||||
|
*/
|
||||||
|
class RHITexture {
|
||||||
|
public:
|
||||||
|
virtual ~RHITexture() = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取纹理类型
|
||||||
|
* @return 纹理类型
|
||||||
|
*/
|
||||||
|
virtual TextureType getType() const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取纹理格式
|
||||||
|
* @return 纹理格式
|
||||||
|
*/
|
||||||
|
virtual TextureFormat getFormat() const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取纹理宽度
|
||||||
|
* @return 宽度
|
||||||
|
*/
|
||||||
|
virtual uint32_t getWidth() const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取纹理高度
|
||||||
|
* @return 高度
|
||||||
|
*/
|
||||||
|
virtual uint32_t getHeight() const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取 Mipmap 层级数
|
||||||
|
* @return Mipmap 层级数
|
||||||
|
*/
|
||||||
|
virtual uint32_t getMipLevels() const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 更新纹理数据
|
||||||
|
* @param data 像素数据
|
||||||
|
* @param mipLevel Mipmap 层级
|
||||||
|
* @return 更新是否成功
|
||||||
|
*/
|
||||||
|
virtual bool update(const void* data, uint32_t mipLevel = 0) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 生成 Mipmap
|
||||||
|
*/
|
||||||
|
virtual void generateMipmap() = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 绑定到指定槽位
|
||||||
|
* @param slot 纹理槽位
|
||||||
|
*/
|
||||||
|
virtual void bind(uint32_t slot) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 解绑
|
||||||
|
*/
|
||||||
|
virtual void unbind() = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查是否有效
|
||||||
|
* @return 是否有效
|
||||||
|
*/
|
||||||
|
virtual bool isValid() const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查是否可作为渲染目标
|
||||||
|
* @return 是否可作为渲染目标
|
||||||
|
*/
|
||||||
|
virtual bool isRenderTarget() const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,229 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <renderer/rhi/rhi_types.h>
|
||||||
|
#include <renderer/rhi/rhi_buffer.h>
|
||||||
|
#include <renderer/rhi/rhi_shader.h>
|
||||||
|
#include <renderer/rhi/rhi_framebuffer.h>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief RHI 命令列表接口
|
||||||
|
*
|
||||||
|
* 抽象 GPU 命令录制和执行
|
||||||
|
*/
|
||||||
|
class RHICommandList {
|
||||||
|
public:
|
||||||
|
virtual ~RHICommandList() = default;
|
||||||
|
|
||||||
|
//===========================================================================
|
||||||
|
// 命令列表生命周期
|
||||||
|
//===========================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 开始录制命令
|
||||||
|
*/
|
||||||
|
virtual void begin() = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 结束录制命令
|
||||||
|
*/
|
||||||
|
virtual void end() = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 提交命令到 GPU
|
||||||
|
*/
|
||||||
|
virtual void submit() = 0;
|
||||||
|
|
||||||
|
//===========================================================================
|
||||||
|
// 渲染通道
|
||||||
|
//===========================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 开始渲染通道
|
||||||
|
* @param framebuffer 帧缓冲
|
||||||
|
* @param clearFlags 清除标志
|
||||||
|
* @param clearColor 清除颜色(当包含 Color 标志时)
|
||||||
|
* @param clearDepth 清除深度值(当包含 Depth 标志时)
|
||||||
|
* @param clearStencil 清除模板值(当包含 Stencil 标志时)
|
||||||
|
*/
|
||||||
|
virtual void beginRenderPass(RHIFramebuffer* framebuffer,
|
||||||
|
ClearFlags clearFlags = ClearFlags::Color,
|
||||||
|
const Color& clearColor = Color::Black,
|
||||||
|
float clearDepth = 1.0f,
|
||||||
|
uint8_t clearStencil = 0) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 结束渲染通道
|
||||||
|
*/
|
||||||
|
virtual void endRenderPass() = 0;
|
||||||
|
|
||||||
|
//===========================================================================
|
||||||
|
// 状态设置
|
||||||
|
//===========================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置视口
|
||||||
|
* @param viewport 视口
|
||||||
|
*/
|
||||||
|
virtual void setViewport(const Viewport& viewport) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置裁剪矩形
|
||||||
|
* @param scissor 裁剪矩形
|
||||||
|
*/
|
||||||
|
virtual void setScissor(const ScissorRect& scissor) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置图形管线
|
||||||
|
* @param pipeline 管线
|
||||||
|
*/
|
||||||
|
virtual void setPipeline(RHIPipeline* pipeline) = 0;
|
||||||
|
|
||||||
|
//===========================================================================
|
||||||
|
// 资源绑定
|
||||||
|
//===========================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置顶点缓冲区
|
||||||
|
* @param slot 槽位
|
||||||
|
* @param buffer 缓冲区
|
||||||
|
* @param offset 偏移(字节)
|
||||||
|
* @param stride 步长(字节,0表示使用布局中的步长)
|
||||||
|
*/
|
||||||
|
virtual void setVertexBuffer(uint32_t slot, RHIBuffer* buffer, uint32_t offset = 0, uint32_t stride = 0) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置索引缓冲区
|
||||||
|
* @param buffer 缓冲区
|
||||||
|
* @param type 索引类型
|
||||||
|
* @param offset 偏移(字节)
|
||||||
|
*/
|
||||||
|
virtual void setIndexBuffer(RHIBuffer* buffer, IndexType type, uint32_t offset = 0) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置 Uniform 缓冲区
|
||||||
|
* @param slot 槽位
|
||||||
|
* @param buffer 缓冲区
|
||||||
|
*/
|
||||||
|
virtual void setUniformBuffer(uint32_t slot, RHIBuffer* buffer) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置 Uniform 缓冲区(带偏移)
|
||||||
|
* @param slot 槽位
|
||||||
|
* @param buffer 缓冲区
|
||||||
|
* @param offset 缓冲区偏移(字节)
|
||||||
|
* @param size 绑定大小(字节,0 表示整个缓冲区)
|
||||||
|
*/
|
||||||
|
virtual void setUniformBuffer(uint32_t slot, RHIBuffer* buffer, uint32_t offset, uint32_t size = 0) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置纹理
|
||||||
|
* @param slot 槽位
|
||||||
|
* @param texture 纹理
|
||||||
|
*/
|
||||||
|
virtual void setTexture(uint32_t slot, RHITexture* texture) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置采样器
|
||||||
|
* @param slot 槽位
|
||||||
|
* @param minFilter 最小过滤
|
||||||
|
* @param magFilter 最大过滤
|
||||||
|
* @param wrapS S 轴环绕
|
||||||
|
* @param wrapT T 轴环绕
|
||||||
|
*/
|
||||||
|
virtual void setSampler(uint32_t slot,
|
||||||
|
TextureFilter minFilter,
|
||||||
|
TextureFilter magFilter,
|
||||||
|
TextureWrap wrapS,
|
||||||
|
TextureWrap wrapT) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置 float 类型的 uniform 变量
|
||||||
|
* @param name 变量名
|
||||||
|
* @param value 值
|
||||||
|
*/
|
||||||
|
virtual void setUniform(const char* name, float value) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置 vec2 类型的 uniform 变量
|
||||||
|
* @param name 变量名
|
||||||
|
* @param value 值
|
||||||
|
*/
|
||||||
|
virtual void setUniform(const char* name, const Vec2& value) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置 vec3 类型的 uniform 变量
|
||||||
|
* @param name 变量名
|
||||||
|
* @param value 值
|
||||||
|
*/
|
||||||
|
virtual void setUniform(const char* name, const Vec3& value) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置 vec4/Color 类型的 uniform 变量
|
||||||
|
* @param name 变量名
|
||||||
|
* @param value 值
|
||||||
|
*/
|
||||||
|
virtual void setUniform(const char* name, const Color& value) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置 mat4 类型的 uniform 变量
|
||||||
|
* @param name 变量名
|
||||||
|
* @param value 值
|
||||||
|
*/
|
||||||
|
virtual void setUniform(const char* name, const Mat4& value) = 0;
|
||||||
|
|
||||||
|
//===========================================================================
|
||||||
|
// 绘制命令
|
||||||
|
//===========================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 绘制顶点
|
||||||
|
* @param vertexCount 顶点数量
|
||||||
|
* @param firstVertex 起始顶点
|
||||||
|
* @param instanceCount 实例数量
|
||||||
|
* @param firstInstance 起始实例
|
||||||
|
*/
|
||||||
|
virtual void draw(uint32_t vertexCount,
|
||||||
|
uint32_t firstVertex = 0,
|
||||||
|
uint32_t instanceCount = 1,
|
||||||
|
uint32_t firstInstance = 0) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 绘制索引
|
||||||
|
* @param indexCount 索引数量
|
||||||
|
* @param firstIndex 起始索引
|
||||||
|
* @param vertexOffset 顶点偏移
|
||||||
|
* @param instanceCount 实例数量
|
||||||
|
* @param firstInstance 起始实例
|
||||||
|
*/
|
||||||
|
virtual void drawIndexed(uint32_t indexCount,
|
||||||
|
uint32_t firstIndex = 0,
|
||||||
|
int32_t vertexOffset = 0,
|
||||||
|
uint32_t instanceCount = 1,
|
||||||
|
uint32_t firstInstance = 0) = 0;
|
||||||
|
|
||||||
|
//===========================================================================
|
||||||
|
// 工具方法
|
||||||
|
//===========================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 清除当前帧缓冲
|
||||||
|
* @param flags 清除标志
|
||||||
|
* @param color 清除颜色
|
||||||
|
* @param depth 清除深度值
|
||||||
|
* @param stencil 清除模板值
|
||||||
|
*/
|
||||||
|
virtual void clear(ClearFlags flags,
|
||||||
|
const Color& color = Color::Black,
|
||||||
|
float depth = 1.0f,
|
||||||
|
uint8_t stencil = 0) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查是否在录制状态
|
||||||
|
* @return 是否在录制
|
||||||
|
*/
|
||||||
|
virtual bool isRecording() const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,169 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <renderer/rhi/rhi_types.h>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief RHI 设备接口
|
||||||
|
*
|
||||||
|
* 抽象图形设备,负责资源创建和上下文管理
|
||||||
|
*/
|
||||||
|
class RHIDevice {
|
||||||
|
public:
|
||||||
|
virtual ~RHIDevice() = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 初始化设备
|
||||||
|
* @param nativeWindow 原生窗口句柄(如 SDL_Window*)
|
||||||
|
* @return 初始化是否成功
|
||||||
|
*/
|
||||||
|
virtual bool initialize(void* nativeWindow) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 关闭设备
|
||||||
|
*/
|
||||||
|
virtual void shutdown() = 0;
|
||||||
|
|
||||||
|
//===========================================================================
|
||||||
|
// 资源创建
|
||||||
|
//===========================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 创建缓冲区
|
||||||
|
* @param desc 缓冲区描述
|
||||||
|
* @return 缓冲区对象
|
||||||
|
*/
|
||||||
|
virtual std::unique_ptr<RHIBuffer> createBuffer(const BufferDesc &desc) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 创建纹理
|
||||||
|
* @param desc 纹理描述
|
||||||
|
* @return 纹理对象
|
||||||
|
*/
|
||||||
|
virtual std::unique_ptr<RHITexture>
|
||||||
|
createTexture(const TextureDesc &desc) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 创建着色器
|
||||||
|
* @param desc 着色器描述
|
||||||
|
* @return 着色器对象
|
||||||
|
*/
|
||||||
|
virtual std::unique_ptr<RHIShader> createShader(const ShaderDesc &desc) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 创建图形管线
|
||||||
|
* @param desc 管线描述
|
||||||
|
* @return 管线对象
|
||||||
|
*/
|
||||||
|
virtual std::unique_ptr<RHIPipeline>
|
||||||
|
createPipeline(const PipelineDesc &desc) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 创建帧缓冲
|
||||||
|
* @param desc 帧缓冲描述
|
||||||
|
* @return 帧缓冲对象
|
||||||
|
*/
|
||||||
|
virtual std::unique_ptr<RHIFramebuffer>
|
||||||
|
createFramebuffer(const RenderPassDesc &desc) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 创建命令列表
|
||||||
|
* @return 命令列表对象
|
||||||
|
*/
|
||||||
|
virtual std::unique_ptr<RHICommandList> createCommandList() = 0;
|
||||||
|
|
||||||
|
//===========================================================================
|
||||||
|
// 上下文管理
|
||||||
|
//===========================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取渲染上下文
|
||||||
|
* @return 渲染上下文指针
|
||||||
|
*/
|
||||||
|
virtual RHIContext *getContext() = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取后端名称
|
||||||
|
* @return 后端名称(如 "OpenGL", "Vulkan")
|
||||||
|
*/
|
||||||
|
virtual const char *getBackendName() const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 等待 GPU 完成所有操作
|
||||||
|
*/
|
||||||
|
virtual void waitIdle() = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取渲染统计
|
||||||
|
* @return 渲染统计信息
|
||||||
|
*/
|
||||||
|
virtual const RenderStats &getStats() const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 重置渲染统计
|
||||||
|
*/
|
||||||
|
virtual void resetStats() = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief RHI 上下文接口
|
||||||
|
*
|
||||||
|
* 管理渲染上下文和呈现
|
||||||
|
*/
|
||||||
|
class RHIContext {
|
||||||
|
public:
|
||||||
|
virtual ~RHIContext() = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 初始化上下文
|
||||||
|
* @return 初始化是否成功
|
||||||
|
*/
|
||||||
|
virtual bool initialize() = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 关闭上下文
|
||||||
|
*/
|
||||||
|
virtual void shutdown() = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 开始一帧
|
||||||
|
*/
|
||||||
|
virtual void beginFrame() = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 结束一帧并呈现
|
||||||
|
*/
|
||||||
|
virtual void endFrame() = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置视口
|
||||||
|
* @param x 视口 X 坐标
|
||||||
|
* @param y 视口 Y 坐标
|
||||||
|
* @param width 视口宽度
|
||||||
|
* @param height 视口高度
|
||||||
|
*/
|
||||||
|
virtual void setViewport(int32_t x, int32_t y, uint32_t width,
|
||||||
|
uint32_t height) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置默认帧缓冲
|
||||||
|
*/
|
||||||
|
virtual void bindDefaultFramebuffer() = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取默认帧缓冲
|
||||||
|
* @return 默认帧缓冲
|
||||||
|
*/
|
||||||
|
virtual RHIFramebuffer *getDefaultFramebuffer() = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查是否支持特性
|
||||||
|
* @param feature 特性名称
|
||||||
|
* @return 是否支持
|
||||||
|
*/
|
||||||
|
virtual bool isFeatureSupported(const char *feature) const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,76 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <renderer/rhi/rhi_types.h>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief RHI 帧缓冲接口
|
||||||
|
*
|
||||||
|
* 抽象 GPU 帧缓冲对象 (FBO)
|
||||||
|
*/
|
||||||
|
class RHIFramebuffer {
|
||||||
|
public:
|
||||||
|
virtual ~RHIFramebuffer() = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 绑定帧缓冲
|
||||||
|
*/
|
||||||
|
virtual void bind() = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 解绑帧缓冲
|
||||||
|
*/
|
||||||
|
virtual void unbind() = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取颜色附件数量
|
||||||
|
* @return 颜色附件数量
|
||||||
|
*/
|
||||||
|
virtual uint32_t getColorAttachmentCount() const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取颜色附件纹理
|
||||||
|
* @param index 附件索引
|
||||||
|
* @return 纹理句柄
|
||||||
|
*/
|
||||||
|
virtual TextureHandle getColorAttachment(uint32_t index) const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取深度/模板附件纹理
|
||||||
|
* @return 纹理句柄,无则返回无效句柄
|
||||||
|
*/
|
||||||
|
virtual TextureHandle getDepthStencilAttachment() const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取帧缓冲宽度
|
||||||
|
* @return 宽度
|
||||||
|
*/
|
||||||
|
virtual uint32_t getWidth() const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取帧缓冲高度
|
||||||
|
* @return 高度
|
||||||
|
*/
|
||||||
|
virtual uint32_t getHeight() const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查是否有深度/模板附件
|
||||||
|
* @return 是否有深度/模板附件
|
||||||
|
*/
|
||||||
|
virtual bool hasDepthStencil() const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查是否有效
|
||||||
|
* @return 是否有效
|
||||||
|
*/
|
||||||
|
virtual bool isValid() const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查是否为默认帧缓冲(窗口帧缓冲)
|
||||||
|
* @return 是否为默认帧缓冲
|
||||||
|
*/
|
||||||
|
virtual bool isDefault() const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,86 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <renderer/rhi/rhi_types.h>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief RHI 着色器接口
|
||||||
|
*
|
||||||
|
* 抽象 GPU 着色器程序
|
||||||
|
*/
|
||||||
|
class RHIShader {
|
||||||
|
public:
|
||||||
|
virtual ~RHIShader() = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取着色器类型
|
||||||
|
* @return 着色器类型
|
||||||
|
*/
|
||||||
|
virtual ShaderType getType() const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查是否已编译
|
||||||
|
* @return 是否已编译
|
||||||
|
*/
|
||||||
|
virtual bool isCompiled() const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取编译日志
|
||||||
|
* @return 编译日志
|
||||||
|
*/
|
||||||
|
virtual std::string getCompileLog() const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查是否有效
|
||||||
|
* @return 是否有效
|
||||||
|
*/
|
||||||
|
virtual bool isValid() const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief RHI 图形管线接口
|
||||||
|
*
|
||||||
|
* 抽象 GPU 图形管线状态对象 (PSO)
|
||||||
|
*/
|
||||||
|
class RHIPipeline {
|
||||||
|
public:
|
||||||
|
virtual ~RHIPipeline() = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 绑定管线
|
||||||
|
*/
|
||||||
|
virtual void bind() = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 解绑管线
|
||||||
|
*/
|
||||||
|
virtual void unbind() = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取顶点着色器
|
||||||
|
* @return 顶点着色器
|
||||||
|
*/
|
||||||
|
virtual ShaderHandle getVertexShader() const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取片段着色器
|
||||||
|
* @return 片段着色器
|
||||||
|
*/
|
||||||
|
virtual ShaderHandle getFragmentShader() const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取顶点布局
|
||||||
|
* @return 顶点布局
|
||||||
|
*/
|
||||||
|
virtual const VertexLayout& getVertexLayout() const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查是否有效
|
||||||
|
* @return 是否有效
|
||||||
|
*/
|
||||||
|
virtual bool isValid() const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,501 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include <types/base/types.h>
|
||||||
|
#include <types/math/color.h>
|
||||||
|
#include <types/math/mat4.h>
|
||||||
|
#include <types/math/vec2.h>
|
||||||
|
#include <types/math/vec3.h>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// 前向声明
|
||||||
|
class RHIDevice;
|
||||||
|
class RHIContext;
|
||||||
|
class RHICommandList;
|
||||||
|
class RHIBuffer;
|
||||||
|
class RHITexture;
|
||||||
|
class RHIShader;
|
||||||
|
class RHIPipeline;
|
||||||
|
class RHIFramebuffer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief RHI 资源句柄基类
|
||||||
|
*/
|
||||||
|
template <typename T> class RHIHandle {
|
||||||
|
public:
|
||||||
|
RHIHandle() = default;
|
||||||
|
explicit RHIHandle(T *ptr) : ptr_(ptr) {}
|
||||||
|
|
||||||
|
T *get() const { return ptr_; }
|
||||||
|
bool isValid() const { return ptr_ != nullptr; }
|
||||||
|
explicit operator bool() const { return isValid(); }
|
||||||
|
|
||||||
|
bool operator==(const RHIHandle &other) const { return ptr_ == other.ptr_; }
|
||||||
|
bool operator!=(const RHIHandle &other) const { return ptr_ != other.ptr_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
T *ptr_ = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
using BufferHandle = RHIHandle<RHIBuffer>;
|
||||||
|
using TextureHandle = RHIHandle<RHITexture>;
|
||||||
|
using ShaderHandle = RHIHandle<RHIShader>;
|
||||||
|
using PipelineHandle = RHIHandle<RHIPipeline>;
|
||||||
|
using FramebufferHandle = RHIHandle<RHIFramebuffer>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 缓冲区类型
|
||||||
|
*/
|
||||||
|
enum class BufferType : uint8_t {
|
||||||
|
Vertex, // 顶点缓冲区
|
||||||
|
Index, // 索引缓冲区
|
||||||
|
Uniform, // Uniform 缓冲区
|
||||||
|
Storage, // 存储缓冲区 (SSBO)
|
||||||
|
Staging // 暂存缓冲区
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 缓冲区使用模式
|
||||||
|
*/
|
||||||
|
enum class BufferUsage : uint8_t {
|
||||||
|
Static, // 静态数据,很少修改
|
||||||
|
Dynamic, // 动态数据,每帧修改
|
||||||
|
Stream // 流数据,每帧多次修改
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 缓冲区描述
|
||||||
|
*/
|
||||||
|
struct BufferDesc {
|
||||||
|
BufferType type = BufferType::Vertex;
|
||||||
|
BufferUsage usage = BufferUsage::Static;
|
||||||
|
uint32_t size = 0; // 缓冲区大小(字节)
|
||||||
|
const void *initialData = nullptr; // 初始数据
|
||||||
|
|
||||||
|
static BufferDesc vertex(uint32_t size,
|
||||||
|
BufferUsage usage = BufferUsage::Static);
|
||||||
|
static BufferDesc index(uint32_t size,
|
||||||
|
BufferUsage usage = BufferUsage::Static);
|
||||||
|
static BufferDesc uniform(uint32_t size);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 纹理类型
|
||||||
|
*/
|
||||||
|
enum class TextureType : uint8_t {
|
||||||
|
Texture2D, // 2D 纹理
|
||||||
|
TextureCube, // 立方体贴图
|
||||||
|
TextureArray, // 纹理数组
|
||||||
|
RenderTarget // 渲染目标
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 纹理用途
|
||||||
|
*/
|
||||||
|
enum class TextureUsage : uint8_t {
|
||||||
|
Sampled, // 采样纹理(只读)
|
||||||
|
Storage, // 存储纹理(读写)
|
||||||
|
RenderTarget, // 渲染目标
|
||||||
|
DepthStencil // 深度/模板目标
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 纹理格式
|
||||||
|
*/
|
||||||
|
enum class TextureFormat : uint8_t {
|
||||||
|
// 颜色格式
|
||||||
|
R8, // 8位 红
|
||||||
|
RG8, // 8位 RG
|
||||||
|
RGB8, // 8位 RGB
|
||||||
|
RGBA8, // 8位 RGBA
|
||||||
|
RGBA8_SRGB, // sRGB 颜色空间
|
||||||
|
|
||||||
|
// 深度/模板格式
|
||||||
|
Depth16,
|
||||||
|
Depth24,
|
||||||
|
Depth32F,
|
||||||
|
Depth24Stencil8,
|
||||||
|
Depth32FStencil8,
|
||||||
|
|
||||||
|
// 压缩格式
|
||||||
|
BC1, // DXT1
|
||||||
|
BC3, // DXT5
|
||||||
|
BC5 // 3Dc
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 纹理过滤模式
|
||||||
|
*/
|
||||||
|
enum class TextureFilter : uint8_t {
|
||||||
|
Nearest, // 最近邻
|
||||||
|
Linear, // 线性
|
||||||
|
NearestMipmapNearest,
|
||||||
|
LinearMipmapNearest,
|
||||||
|
NearestMipmapLinear,
|
||||||
|
LinearMipmapLinear
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 纹理环绕模式
|
||||||
|
*/
|
||||||
|
enum class TextureWrap : uint8_t {
|
||||||
|
Repeat, // 重复
|
||||||
|
ClampToEdge, // 边缘钳制
|
||||||
|
ClampToBorder, // 边界钳制
|
||||||
|
MirroredRepeat // 镜像重复
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 纹理描述
|
||||||
|
*/
|
||||||
|
struct TextureDesc {
|
||||||
|
TextureType type = TextureType::Texture2D;
|
||||||
|
TextureFormat format = TextureFormat::RGBA8;
|
||||||
|
uint32_t width = 0;
|
||||||
|
uint32_t height = 0;
|
||||||
|
uint32_t depth = 1; // 对于 3D 纹理或数组层数
|
||||||
|
uint32_t mipLevels = 1;
|
||||||
|
TextureFilter minFilter = TextureFilter::Linear;
|
||||||
|
TextureFilter magFilter = TextureFilter::Linear;
|
||||||
|
TextureWrap wrapS = TextureWrap::Repeat;
|
||||||
|
TextureWrap wrapT = TextureWrap::Repeat;
|
||||||
|
bool renderTarget = false; // 是否可作为渲染目标
|
||||||
|
|
||||||
|
static TextureDesc texture2D(uint32_t width, uint32_t height,
|
||||||
|
TextureFormat format = TextureFormat::RGBA8);
|
||||||
|
static TextureDesc renderTarget2D(uint32_t width, uint32_t height,
|
||||||
|
TextureFormat format);
|
||||||
|
static TextureDesc depthStencil(uint32_t width, uint32_t height);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 着色器类型
|
||||||
|
*/
|
||||||
|
enum class ShaderType : uint8_t {
|
||||||
|
Vertex, // 顶点着色器
|
||||||
|
Fragment, // 片段着色器
|
||||||
|
Geometry, // 几何着色器
|
||||||
|
Compute // 计算着色器
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 着色器描述
|
||||||
|
*
|
||||||
|
* 同时包含顶点和片段着色器源码
|
||||||
|
*/
|
||||||
|
struct ShaderDesc {
|
||||||
|
std::string vertexSource; // 顶点着色器源码
|
||||||
|
std::string fragmentSource; // 片段着色器源码
|
||||||
|
std::string entryPoint = "main"; // 入口函数名
|
||||||
|
|
||||||
|
static ShaderDesc fromSource(const std::string &vertexSrc,
|
||||||
|
const std::string &fragmentSrc) {
|
||||||
|
ShaderDesc desc;
|
||||||
|
desc.vertexSource = vertexSrc;
|
||||||
|
desc.fragmentSource = fragmentSrc;
|
||||||
|
return desc;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 顶点属性格式
|
||||||
|
*/
|
||||||
|
enum class VertexFormat : uint8_t {
|
||||||
|
Float1,
|
||||||
|
Float2,
|
||||||
|
Float3,
|
||||||
|
Float4,
|
||||||
|
Int1,
|
||||||
|
Int2,
|
||||||
|
Int3,
|
||||||
|
Int4,
|
||||||
|
UInt1,
|
||||||
|
UInt2,
|
||||||
|
UInt3,
|
||||||
|
UInt4,
|
||||||
|
Byte4,
|
||||||
|
Byte4Normalized,
|
||||||
|
UByte4,
|
||||||
|
UByte4Normalized
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 顶点属性描述
|
||||||
|
*/
|
||||||
|
struct VertexAttribute {
|
||||||
|
uint32_t location = 0; // 属性位置
|
||||||
|
VertexFormat format = VertexFormat::Float3;
|
||||||
|
uint32_t offset = 0; // 在顶点结构中的偏移
|
||||||
|
uint32_t bufferIndex = 0; // 绑定的顶点缓冲区索引
|
||||||
|
uint32_t divisor = 0; // 实例化除数(0=每顶点,1=每实例,N=每N个实例)
|
||||||
|
|
||||||
|
static uint32_t getSize(VertexFormat format);
|
||||||
|
|
||||||
|
// 创建实例化属性
|
||||||
|
static VertexAttribute perInstance(uint32_t location, VertexFormat format,
|
||||||
|
uint32_t offset, uint32_t bufferIndex = 1) {
|
||||||
|
VertexAttribute attr;
|
||||||
|
attr.location = location;
|
||||||
|
attr.format = format;
|
||||||
|
attr.offset = offset;
|
||||||
|
attr.bufferIndex = bufferIndex;
|
||||||
|
attr.divisor = 1;
|
||||||
|
return attr;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 顶点布局描述
|
||||||
|
*/
|
||||||
|
struct VertexLayout {
|
||||||
|
std::vector<VertexAttribute> attributes;
|
||||||
|
uint32_t stride = 0; // 顶点大小(字节)
|
||||||
|
|
||||||
|
void addAttribute(uint32_t location, VertexFormat format, uint32_t offset,
|
||||||
|
uint32_t bufferIndex = 0);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 添加顶点属性(直接传入VertexAttribute)
|
||||||
|
* @param attr 顶点属性
|
||||||
|
*/
|
||||||
|
void addAttribute(const VertexAttribute& attr) {
|
||||||
|
attributes.push_back(attr);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 索引类型
|
||||||
|
*/
|
||||||
|
enum class IndexType : uint8_t { UInt16, UInt32 };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 图元类型
|
||||||
|
*/
|
||||||
|
enum class PrimitiveType : uint8_t {
|
||||||
|
Points,
|
||||||
|
Lines,
|
||||||
|
LineStrip,
|
||||||
|
Triangles,
|
||||||
|
TriangleStrip,
|
||||||
|
TriangleFan
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 混合因子
|
||||||
|
*/
|
||||||
|
enum class BlendFactor : uint8_t {
|
||||||
|
Zero,
|
||||||
|
One,
|
||||||
|
SrcColor,
|
||||||
|
OneMinusSrcColor,
|
||||||
|
DstColor,
|
||||||
|
OneMinusDstColor,
|
||||||
|
SrcAlpha,
|
||||||
|
OneMinusSrcAlpha,
|
||||||
|
DstAlpha,
|
||||||
|
OneMinusDstAlpha
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 混合操作
|
||||||
|
*/
|
||||||
|
enum class BlendOp : uint8_t { Add, Subtract, ReverseSubtract, Min, Max };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 混合状态
|
||||||
|
*/
|
||||||
|
struct BlendState {
|
||||||
|
bool enabled = false;
|
||||||
|
BlendFactor srcFactor = BlendFactor::One;
|
||||||
|
BlendFactor dstFactor = BlendFactor::Zero;
|
||||||
|
BlendOp op = BlendOp::Add;
|
||||||
|
BlendFactor srcAlphaFactor = BlendFactor::One;
|
||||||
|
BlendFactor dstAlphaFactor = BlendFactor::Zero;
|
||||||
|
BlendOp alphaOp = BlendOp::Add;
|
||||||
|
|
||||||
|
static BlendState opaque();
|
||||||
|
static BlendState alphaBlend();
|
||||||
|
static BlendState additive();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 深度测试比较函数
|
||||||
|
*/
|
||||||
|
enum class CompareFunc : uint8_t {
|
||||||
|
Never,
|
||||||
|
Less,
|
||||||
|
Equal,
|
||||||
|
LessEqual,
|
||||||
|
Greater,
|
||||||
|
NotEqual,
|
||||||
|
GreaterEqual,
|
||||||
|
Always
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 深度/模板状态
|
||||||
|
*/
|
||||||
|
struct DepthStencilState {
|
||||||
|
bool depthTestEnabled = true;
|
||||||
|
bool depthWriteEnabled = true;
|
||||||
|
CompareFunc depthCompare = CompareFunc::Less;
|
||||||
|
|
||||||
|
bool stencilEnabled = false;
|
||||||
|
// 模板测试配置(简化版)
|
||||||
|
uint8_t stencilReadMask = 0xFF;
|
||||||
|
uint8_t stencilWriteMask = 0xFF;
|
||||||
|
uint8_t stencilRef = 0;
|
||||||
|
|
||||||
|
static DepthStencilState depthTest();
|
||||||
|
static DepthStencilState depthTestWrite();
|
||||||
|
static DepthStencilState depthTestNoWrite();
|
||||||
|
static DepthStencilState noDepthTest();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 光栅化状态
|
||||||
|
*/
|
||||||
|
struct RasterizerState {
|
||||||
|
bool cullEnabled = false;
|
||||||
|
bool cullFrontFace = false; // true = 剔除正面, false = 剔除背面
|
||||||
|
bool frontCCW = false; // 正面是否为逆时针
|
||||||
|
bool scissorEnabled = false;
|
||||||
|
bool wireframe = false;
|
||||||
|
|
||||||
|
static RasterizerState cullBack();
|
||||||
|
static RasterizerState cullFront();
|
||||||
|
static RasterizerState noCull();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 管线描述
|
||||||
|
*/
|
||||||
|
struct PipelineDesc {
|
||||||
|
ShaderHandle vertexShader;
|
||||||
|
ShaderHandle fragmentShader;
|
||||||
|
VertexLayout vertexLayout;
|
||||||
|
PrimitiveType primitiveType = PrimitiveType::Triangles;
|
||||||
|
BlendState blendState;
|
||||||
|
DepthStencilState depthStencilState;
|
||||||
|
RasterizerState rasterizerState;
|
||||||
|
|
||||||
|
static PipelineDesc create(ShaderHandle vs, ShaderHandle fs,
|
||||||
|
const VertexLayout &layout);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 渲染通道加载/存储操作
|
||||||
|
*/
|
||||||
|
enum class LoadOp : uint8_t {
|
||||||
|
Load, // 加载现有内容
|
||||||
|
Clear, // 清除为指定值
|
||||||
|
DontCare // 不关心现有内容
|
||||||
|
};
|
||||||
|
|
||||||
|
enum class StoreOp : uint8_t {
|
||||||
|
Store, // 保存结果
|
||||||
|
DontCare // 不关心结果
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 渲染通道附件描述
|
||||||
|
*/
|
||||||
|
struct RenderPassAttachment {
|
||||||
|
TextureHandle texture;
|
||||||
|
LoadOp loadOp = LoadOp::Clear;
|
||||||
|
StoreOp storeOp = StoreOp::Store;
|
||||||
|
Color clearColor = Color::Black;
|
||||||
|
float clearDepth = 1.0f;
|
||||||
|
uint8_t clearStencil = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 渲染通道描述
|
||||||
|
*/
|
||||||
|
struct RenderPassDesc {
|
||||||
|
std::vector<RenderPassAttachment> colorAttachments;
|
||||||
|
RenderPassAttachment depthStencilAttachment;
|
||||||
|
bool hasDepthStencil = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 视口
|
||||||
|
*/
|
||||||
|
struct Viewport {
|
||||||
|
float x = 0.0f;
|
||||||
|
float y = 0.0f;
|
||||||
|
float width = 0.0f;
|
||||||
|
float height = 0.0f;
|
||||||
|
float minDepth = 0.0f;
|
||||||
|
float maxDepth = 1.0f;
|
||||||
|
|
||||||
|
Viewport() = default;
|
||||||
|
Viewport(float x, float y, float w, float h)
|
||||||
|
: x(x), y(y), width(w), height(h) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 裁剪矩形
|
||||||
|
*/
|
||||||
|
struct ScissorRect {
|
||||||
|
int32_t x = 0;
|
||||||
|
int32_t y = 0;
|
||||||
|
int32_t width = 0;
|
||||||
|
int32_t height = 0;
|
||||||
|
|
||||||
|
ScissorRect() = default;
|
||||||
|
ScissorRect(int32_t x, int32_t y, int32_t w, int32_t h)
|
||||||
|
: x(x), y(y), width(w), height(h) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 清除标志
|
||||||
|
*/
|
||||||
|
enum class ClearFlags : uint32_t {
|
||||||
|
None = 0,
|
||||||
|
Color = 1 << 0,
|
||||||
|
Depth = 1 << 1,
|
||||||
|
Stencil = 1 << 2,
|
||||||
|
All = Color | Depth | Stencil
|
||||||
|
};
|
||||||
|
|
||||||
|
inline ClearFlags operator|(ClearFlags a, ClearFlags b) {
|
||||||
|
return static_cast<ClearFlags>(static_cast<uint32_t>(a) |
|
||||||
|
static_cast<uint32_t>(b));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline ClearFlags operator&(ClearFlags a, ClearFlags b) {
|
||||||
|
return static_cast<ClearFlags>(static_cast<uint32_t>(a) &
|
||||||
|
static_cast<uint32_t>(b));
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool hasFlag(ClearFlags flags, ClearFlags flag) {
|
||||||
|
return (static_cast<uint32_t>(flags) & static_cast<uint32_t>(flag)) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 渲染统计
|
||||||
|
*/
|
||||||
|
struct RenderStats {
|
||||||
|
uint32_t drawCalls = 0;
|
||||||
|
uint32_t triangles = 0;
|
||||||
|
uint32_t vertices = 0;
|
||||||
|
uint32_t textureBinds = 0;
|
||||||
|
uint32_t bufferBinds = 0;
|
||||||
|
uint32_t pipelineBinds = 0;
|
||||||
|
uint32_t renderPassSwitches = 0;
|
||||||
|
|
||||||
|
void reset() {
|
||||||
|
drawCalls = 0;
|
||||||
|
triangles = 0;
|
||||||
|
vertices = 0;
|
||||||
|
textureBinds = 0;
|
||||||
|
bufferBinds = 0;
|
||||||
|
pipelineBinds = 0;
|
||||||
|
renderPassSwitches = 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,113 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <event/events.h>
|
||||||
|
#include <module/module.h>
|
||||||
|
#include <module/module_registry.h>
|
||||||
|
#include <renderer/rhi/rhi.h>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief RHI 模块
|
||||||
|
*
|
||||||
|
* 负责初始化和管理 RHI 后端,提供图形 API 抽象
|
||||||
|
* 优先级:1(在 WindowModule 之后初始化)
|
||||||
|
*/
|
||||||
|
class RHIModule : public Module {
|
||||||
|
E2D_REGISTER_MODULE(RHIModule, "RHI", 1)
|
||||||
|
|
||||||
|
public:
|
||||||
|
RHIModule();
|
||||||
|
~RHIModule() override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 初始化 RHI 模块
|
||||||
|
* @return 初始化是否成功
|
||||||
|
*/
|
||||||
|
bool init() override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 关闭 RHI 模块
|
||||||
|
*/
|
||||||
|
void shutdown() override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取 RHI 设备
|
||||||
|
* @return RHI 设备指针
|
||||||
|
*/
|
||||||
|
RHIDevice* getDevice() const { return device_.get(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取 RHI 上下文
|
||||||
|
* @return RHI 上下文指针
|
||||||
|
*/
|
||||||
|
RHIContext* getContext() const { return context_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取全局 RHIModule 实例
|
||||||
|
* @return RHIModule 指针
|
||||||
|
*/
|
||||||
|
static RHIModule* get();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查 RHI 是否已初始化
|
||||||
|
* @return 是否已初始化
|
||||||
|
*/
|
||||||
|
bool isInitialized() const { return initialized_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 创建缓冲区
|
||||||
|
* @param desc 缓冲区描述
|
||||||
|
* @return 缓冲区对象
|
||||||
|
*/
|
||||||
|
std::unique_ptr<RHIBuffer> createBuffer(const BufferDesc& desc);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 创建纹理
|
||||||
|
* @param desc 纹理描述
|
||||||
|
* @return 纹理对象
|
||||||
|
*/
|
||||||
|
std::unique_ptr<RHITexture> createTexture(const TextureDesc& desc);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 创建着色器
|
||||||
|
* @param desc 着色器描述
|
||||||
|
* @return 着色器对象
|
||||||
|
*/
|
||||||
|
std::unique_ptr<RHIShader> createShader(const ShaderDesc& desc);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 创建图形管线
|
||||||
|
* @param desc 管线描述
|
||||||
|
* @return 管线对象
|
||||||
|
*/
|
||||||
|
std::unique_ptr<RHIPipeline> createPipeline(const PipelineDesc& desc);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 创建帧缓冲
|
||||||
|
* @param desc 帧缓冲描述
|
||||||
|
* @return 帧缓冲对象
|
||||||
|
*/
|
||||||
|
std::unique_ptr<RHIFramebuffer> createFramebuffer(const RenderPassDesc& desc);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 创建命令列表
|
||||||
|
* @return 命令列表对象
|
||||||
|
*/
|
||||||
|
std::unique_ptr<RHICommandList> createCommandList();
|
||||||
|
|
||||||
|
private:
|
||||||
|
/**
|
||||||
|
* @brief 窗口显示事件回调 - 在窗口显示时创建 OpenGL 上下文
|
||||||
|
*/
|
||||||
|
void onWindowShow();
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unique_ptr<RHIDevice> device_;
|
||||||
|
RHIContext* context_;
|
||||||
|
bool initialized_ = false;
|
||||||
|
std::unique_ptr<events::OnShow::Listener> onShowListener_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -1,22 +1,18 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <types/ptr/ref_counted.h>
|
#include <renderer/rhi/rhi.h>
|
||||||
#include <types/ptr/intrusive_ptr.h>
|
|
||||||
#include <types/base/types.h>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <types/base/types.h>
|
||||||
|
#include <types/ptr/intrusive_ptr.h>
|
||||||
|
#include <types/ptr/ref_counted.h>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
|
||||||
// 前向声明 OpenGL 类型
|
|
||||||
typedef unsigned int GLuint;
|
|
||||||
typedef int GLint;
|
|
||||||
typedef unsigned int GLenum;
|
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 着色器类
|
* @brief 着色器类
|
||||||
*
|
*
|
||||||
* 管理 OpenGL 着色器程序的编译、链接和使用
|
* 基于 RHI 的着色器包装类,管理着色器资源的创建和编译
|
||||||
* 支持从文件或源码加载
|
* 支持从文件或源码加载
|
||||||
*/
|
*/
|
||||||
class Shader : public RefCounted {
|
class Shader : public RefCounted {
|
||||||
|
|
@ -31,125 +27,91 @@ public:
|
||||||
*/
|
*/
|
||||||
~Shader() override;
|
~Shader() override;
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 从文件加载着色器
|
|
||||||
* @param vsPath 顶点着色器文件路径
|
|
||||||
* @param fsPath 片段着色器文件路径
|
|
||||||
* @return 加载是否成功
|
|
||||||
*/
|
|
||||||
bool loadFromFile(const std::string& vsPath, const std::string& fsPath);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 从源码加载着色器
|
* @brief 从源码加载着色器
|
||||||
* @param vsSource 顶点着色器源码
|
* @param vsSource 顶点着色器源码
|
||||||
* @param fsSource 片段着色器源码
|
* @param fsSource 片段着色器源码
|
||||||
* @return 加载是否成功
|
* @return 加载是否成功
|
||||||
*/
|
*/
|
||||||
bool loadFromSource(const std::string& vsSource, const std::string& fsSource);
|
bool loadFromSource(const std::string &vsSource, const std::string &fsSource);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 绑定着色器程序
|
* @brief 获取 RHI 着色器句柄
|
||||||
|
* @return RHI 着色器句柄
|
||||||
*/
|
*/
|
||||||
void bind() const;
|
ShaderHandle getHandle() const { return handle_; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 解绑着色器程序
|
* @brief 获取 RHI 管线句柄
|
||||||
|
* @return RHI 管线句柄
|
||||||
*/
|
*/
|
||||||
void unbind() const;
|
PipelineHandle getPipeline() const { return pipeline_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查是否已加载
|
||||||
|
* @return 是否已加载
|
||||||
|
*/
|
||||||
|
bool isLoaded() const { return handle_.isValid() && pipeline_.isValid(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 使用自定义顶点布局从源码加载着色器
|
||||||
|
* @param vsSource 顶点着色器源码
|
||||||
|
* @param fsSource 片段着色器源码
|
||||||
|
* @param vertexLayout 顶点布局
|
||||||
|
* @return 加载是否成功
|
||||||
|
*/
|
||||||
|
bool loadFromSourceWithLayout(const std::string &vsSource,
|
||||||
|
const std::string &fsSource,
|
||||||
|
const VertexLayout &vertexLayout);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 设置 Uniform Block 绑定槽位
|
* @brief 设置 Uniform Block 绑定槽位
|
||||||
* @param name Uniform Block 名称
|
* @param name Uniform Block 名称
|
||||||
* @param binding 绑定槽位
|
* @param binding 绑定槽位
|
||||||
*/
|
*/
|
||||||
void setUniformBlock(const std::string& name, uint32_t binding);
|
void setUniformBlock(const std::string &name, uint32_t binding);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 设置 int 类型 uniform
|
* @brief 获取 Uniform Block 绑定槽位
|
||||||
* @param name uniform 名称
|
* @param name Uniform Block 名称
|
||||||
* @param value 值
|
* @return 绑定槽位,如果未找到返回 UINT32_MAX
|
||||||
*/
|
*/
|
||||||
void setInt(const std::string& name, int value);
|
uint32_t getUniformBlockBinding(const std::string& name) const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 设置 float 类型 uniform
|
* @brief 从源码重新加载着色器
|
||||||
* @param name uniform 名称
|
* @param vsSource 顶点着色器源码
|
||||||
* @param value 值
|
* @param fsSource 片段着色器源码
|
||||||
|
* @return 重新加载是否成功
|
||||||
*/
|
*/
|
||||||
void setFloat(const std::string& name, float value);
|
bool reloadFromSource(const std::string &vsSource, const std::string &fsSource);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 设置 vec2 类型 uniform
|
* @brief 使用自定义顶点布局从源码重新加载着色器
|
||||||
* @param name uniform 名称
|
* @param vsSource 顶点着色器源码
|
||||||
* @param x X 分量
|
* @param fsSource 片段着色器源码
|
||||||
* @param y Y 分量
|
* @param vertexLayout 顶点布局
|
||||||
|
* @return 重新加载是否成功
|
||||||
*/
|
*/
|
||||||
void setVec2(const std::string& name, float x, float y);
|
bool reloadFromSourceWithLayout(const std::string &vsSource,
|
||||||
|
const std::string &fsSource,
|
||||||
/**
|
const VertexLayout &vertexLayout);
|
||||||
* @brief 设置 vec4 类型 uniform
|
|
||||||
* @param name uniform 名称
|
|
||||||
* @param x X 分量
|
|
||||||
* @param y Y 分量
|
|
||||||
* @param z Z 分量
|
|
||||||
* @param w W 分量
|
|
||||||
*/
|
|
||||||
void setVec4(const std::string& name, float x, float y, float z, float w);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 设置 mat4 类型 uniform
|
|
||||||
* @param name uniform 名称
|
|
||||||
* @param value 矩阵数据指针(16个float)
|
|
||||||
*/
|
|
||||||
void setMat4(const std::string& name, const float* value);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取着色器程序 ID
|
|
||||||
* @return OpenGL 程序 ID
|
|
||||||
*/
|
|
||||||
GLuint getProgram() const { return program_; }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查是否已加载
|
|
||||||
* @return 是否已加载
|
|
||||||
*/
|
|
||||||
bool isLoaded() const { return program_ != 0; }
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/**
|
|
||||||
* @brief 编译着色器
|
|
||||||
* @param type 着色器类型
|
|
||||||
* @param source 源码
|
|
||||||
* @return 着色器对象,失败返回 0
|
|
||||||
*/
|
|
||||||
GLuint compileShader(GLenum type, const std::string& source);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 链接着色器程序
|
|
||||||
* @param vertexShader 顶点着色器
|
|
||||||
* @param fragmentShader 片段着色器
|
|
||||||
* @return 链接是否成功
|
|
||||||
*/
|
|
||||||
bool linkProgram(GLuint vertexShader, GLuint fragmentShader);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取 uniform 位置
|
|
||||||
* @param name uniform 名称
|
|
||||||
* @return uniform 位置,不存在返回 -1
|
|
||||||
*/
|
|
||||||
GLint getUniformLocation(const std::string& name);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 添加版本声明(如果不存在)
|
* @brief 添加版本声明(如果不存在)
|
||||||
* @param source 源码
|
* @param source 源码
|
||||||
* @param type 着色器类型
|
* @param isVertex 是否为顶点着色器
|
||||||
* @return 处理后的源码
|
* @return 处理后的源码
|
||||||
*/
|
*/
|
||||||
std::string addVersionIfNeeded(const std::string& source, GLenum type);
|
std::string addVersionIfNeeded(const std::string &source, bool isVertex);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
GLuint program_ = 0; // OpenGL 程序对象
|
ShaderHandle handle_; // RHI 着色器句柄
|
||||||
std::unordered_map<std::string, GLint> uniformCache_; // Uniform 位置缓存
|
PipelineHandle pipeline_; // RHI 管线句柄
|
||||||
|
|
||||||
|
// Uniform Block 绑定映射
|
||||||
|
std::unordered_map<std::string, uint32_t> uniformBlockBindings_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace extra2d
|
} // namespace extra2d
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,17 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <renderer/rhi/rhi.h>
|
||||||
#include <types/ptr/ref_counted.h>
|
#include <types/ptr/ref_counted.h>
|
||||||
#include <types/ptr/intrusive_ptr.h>
|
#include <types/ptr/intrusive_ptr.h>
|
||||||
#include <renderer/render_types.h>
|
|
||||||
#include <types/base/types.h>
|
#include <types/base/types.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
// 前向声明 OpenGL 类型
|
|
||||||
typedef unsigned int GLuint;
|
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 纹理类
|
* @brief 纹理类
|
||||||
*
|
*
|
||||||
* 管理 OpenGL 纹理的创建、加载和绑定
|
* 基于 RHI 的纹理包装类,管理纹理资源的创建和加载
|
||||||
* 支持从文件或内存加载
|
* 支持从文件或内存加载
|
||||||
*/
|
*/
|
||||||
class Texture : public RefCounted {
|
class Texture : public RefCounted {
|
||||||
|
|
@ -55,17 +52,6 @@ public:
|
||||||
*/
|
*/
|
||||||
bool create(int width, int height, TextureFormat format);
|
bool create(int width, int height, TextureFormat format);
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 绑定纹理到指定槽位
|
|
||||||
* @param slot 纹理槽位
|
|
||||||
*/
|
|
||||||
void bind(uint32_t slot = 0) const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 解绑纹理
|
|
||||||
*/
|
|
||||||
void unbind() const;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 获取纹理宽度
|
* @brief 获取纹理宽度
|
||||||
* @return 宽度
|
* @return 宽度
|
||||||
|
|
@ -85,19 +71,42 @@ public:
|
||||||
TextureFormat getFormat() const { return format_; }
|
TextureFormat getFormat() const { return format_; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 获取 OpenGL 纹理对象
|
* @brief 获取 RHI 纹理句柄
|
||||||
* @return OpenGL 纹理对象
|
* @return RHI 纹理句柄
|
||||||
*/
|
*/
|
||||||
GLuint getHandle() const { return texture_; }
|
TextureHandle getHandle() const { return handle_; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 检查是否已加载
|
* @brief 检查是否已加载
|
||||||
* @return 是否已加载
|
* @return 是否已加载
|
||||||
*/
|
*/
|
||||||
bool isLoaded() const { return texture_ != 0; }
|
bool isLoaded() const { return handle_.isValid(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 从文件重新加载纹理
|
||||||
|
* @param path 文件路径
|
||||||
|
* @return 重新加载是否成功
|
||||||
|
*/
|
||||||
|
bool reloadFromFile(const std::string& path);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 从内存重新加载纹理
|
||||||
|
* @param data 像素数据
|
||||||
|
* @param width 宽度
|
||||||
|
* @param height 高度
|
||||||
|
* @param format 像素格式
|
||||||
|
* @return 重新加载是否成功
|
||||||
|
*/
|
||||||
|
bool reloadFromMemory(const uint8_t* data, int width, int height, TextureFormat format);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
GLuint texture_ = 0; // OpenGL 纹理对象
|
/**
|
||||||
|
* @brief 获取每个像素的字节数
|
||||||
|
* @param format 纹理格式
|
||||||
|
* @return 字节数
|
||||||
|
*/
|
||||||
|
static uint32_t getBytesPerPixel(TextureFormat format);
|
||||||
|
TextureHandle handle_; // RHI 纹理句柄
|
||||||
int width_ = 0; // 纹理宽度
|
int width_ = 0; // 纹理宽度
|
||||||
int height_ = 0; // 纹理高度
|
int height_ = 0; // 纹理高度
|
||||||
TextureFormat format_ = TextureFormat::RGBA8; // 像素格式
|
TextureFormat format_ = TextureFormat::RGBA8; // 像素格式
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,247 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <renderer/texture.h>
|
||||||
|
#include <types/math/rect.h>
|
||||||
|
#include <types/ptr/intrusive_ptr.h>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
// 使用 stb_rect_pack 进行矩形打包
|
||||||
|
#define STB_RECT_PACK_IMPLEMENTATION
|
||||||
|
#include <stb/stb_rect_pack.h>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 图集区域
|
||||||
|
*
|
||||||
|
* 存储子图在图集中的位置和 UV 坐标
|
||||||
|
*/
|
||||||
|
struct AtlasRegion {
|
||||||
|
int x; // 在图集中的 X 坐标(像素)
|
||||||
|
int y; // 在图集中的 Y 坐标(像素)
|
||||||
|
int width; // 宽度(像素)
|
||||||
|
int height; // 高度(像素)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取归一化 UV 坐标
|
||||||
|
* @param atlasWidth 图集宽度
|
||||||
|
* @param atlasHeight 图集高度
|
||||||
|
* @return UV 矩形 (x, y, width, height)
|
||||||
|
*/
|
||||||
|
Rect getUVRect(int atlasWidth, int atlasHeight) const {
|
||||||
|
return Rect(
|
||||||
|
static_cast<float>(x) / atlasWidth,
|
||||||
|
static_cast<float>(y) / atlasHeight,
|
||||||
|
static_cast<float>(width) / atlasWidth,
|
||||||
|
static_cast<float>(height) / atlasHeight
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取归一化 UV 坐标(翻转 Y 轴)
|
||||||
|
* @param atlasWidth 图集宽度
|
||||||
|
* @param atlasHeight 图集高度
|
||||||
|
* @return UV 矩形 (x, y, width, height),Y 轴翻转
|
||||||
|
*/
|
||||||
|
Rect getUVRectFlipped(int atlasWidth, int atlasHeight) const {
|
||||||
|
float u = static_cast<float>(x) / atlasWidth;
|
||||||
|
float v = static_cast<float>(atlasHeight - y - height) / atlasHeight;
|
||||||
|
float w = static_cast<float>(width) / atlasWidth;
|
||||||
|
float h = static_cast<float>(height) / atlasHeight;
|
||||||
|
return Rect(u, v, w, h);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 纹理图集
|
||||||
|
*
|
||||||
|
* 使用 stb_rect_pack 将多个小纹理打包到一个大纹理中
|
||||||
|
* 减少纹理切换,提高批处理效率
|
||||||
|
*/
|
||||||
|
class TextureAtlas : public RefCounted {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief 默认构造函数
|
||||||
|
*/
|
||||||
|
TextureAtlas();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 析构函数
|
||||||
|
*/
|
||||||
|
~TextureAtlas();
|
||||||
|
|
||||||
|
// 禁止拷贝
|
||||||
|
TextureAtlas(const TextureAtlas&) = delete;
|
||||||
|
TextureAtlas& operator=(const TextureAtlas&) = delete;
|
||||||
|
|
||||||
|
// 允许移动
|
||||||
|
TextureAtlas(TextureAtlas&& other) noexcept;
|
||||||
|
TextureAtlas& operator=(TextureAtlas&& other) noexcept;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 初始化图集
|
||||||
|
* @param width 图集宽度(必须是 2 的幂)
|
||||||
|
* @param height 图集高度(必须是 2 的幂)
|
||||||
|
* @return 初始化是否成功
|
||||||
|
*/
|
||||||
|
bool initialize(int width, int height);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 关闭图集
|
||||||
|
*/
|
||||||
|
void shutdown();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 添加纹理到图集
|
||||||
|
* @param name 纹理名称(用于后续查询)
|
||||||
|
* @param texture 纹理指针
|
||||||
|
* @return 是否成功添加
|
||||||
|
*/
|
||||||
|
bool addTexture(const std::string& name, Ptr<Texture> texture);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 从内存添加纹理数据
|
||||||
|
* @param name 纹理名称
|
||||||
|
* @param data 像素数据
|
||||||
|
* @param width 纹理宽度
|
||||||
|
* @param height 纹理高度
|
||||||
|
* @param format 像素格式
|
||||||
|
* @return 是否成功添加
|
||||||
|
*/
|
||||||
|
bool addTextureData(const std::string& name, const uint8_t* data,
|
||||||
|
int width, int height, TextureFormat format);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 完成打包并生成图集纹理
|
||||||
|
* @return 生成是否成功
|
||||||
|
*/
|
||||||
|
bool finalize();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取图集纹理
|
||||||
|
* @return 图集纹理指针
|
||||||
|
*/
|
||||||
|
Ptr<Texture> getAtlasTexture() const { return atlasTexture_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取子图区域
|
||||||
|
* @param name 纹理名称
|
||||||
|
* @return 区域信息,不存在返回 nullptr
|
||||||
|
*/
|
||||||
|
const AtlasRegion* getRegion(const std::string& name) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取子图的 UV 坐标
|
||||||
|
* @param name 纹理名称
|
||||||
|
* @return UV 矩形,不存在返回 (0,0,1,1)
|
||||||
|
*/
|
||||||
|
Rect getUVRect(const std::string& name) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查是否包含指定纹理
|
||||||
|
* @param name 纹理名称
|
||||||
|
* @return 是否包含
|
||||||
|
*/
|
||||||
|
bool hasTexture(const std::string& name) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取图集宽度
|
||||||
|
*/
|
||||||
|
int getWidth() const { return width_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取图集高度
|
||||||
|
*/
|
||||||
|
int getHeight() const { return height_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取已用空间百分比
|
||||||
|
*/
|
||||||
|
float getUsageRatio() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取已添加纹理数量
|
||||||
|
*/
|
||||||
|
size_t getTextureCount() const { return regions_.size(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查是否已最终化
|
||||||
|
*/
|
||||||
|
bool isFinalized() const { return finalized_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
// 待打包的矩形信息
|
||||||
|
struct PendingTexture {
|
||||||
|
std::string name;
|
||||||
|
int width;
|
||||||
|
int height;
|
||||||
|
std::vector<uint8_t> data;
|
||||||
|
TextureFormat format;
|
||||||
|
};
|
||||||
|
|
||||||
|
int width_ = 0;
|
||||||
|
int height_ = 0;
|
||||||
|
Ptr<Texture> atlasTexture_;
|
||||||
|
std::unordered_map<std::string, AtlasRegion> regions_;
|
||||||
|
std::vector<PendingTexture> pendingTextures_;
|
||||||
|
|
||||||
|
// stb_rect_pack 上下文
|
||||||
|
std::unique_ptr<stbrp_context> packContext_;
|
||||||
|
std::vector<stbrp_node> packNodes_;
|
||||||
|
|
||||||
|
bool finalized_ = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 将像素数据复制到图集
|
||||||
|
*/
|
||||||
|
void copyTextureData(const PendingTexture& tex, const AtlasRegion& region);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 图集构建器
|
||||||
|
*
|
||||||
|
* 辅助构建纹理图集的工具类
|
||||||
|
*/
|
||||||
|
class AtlasBuilder {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief 设置目标图集大小
|
||||||
|
* @param width 宽度
|
||||||
|
* @param height 高度
|
||||||
|
*/
|
||||||
|
void setSize(int width, int height) {
|
||||||
|
width_ = width;
|
||||||
|
height_ = height;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 添加纹理
|
||||||
|
* @param name 纹理名称
|
||||||
|
* @param texture 纹理
|
||||||
|
*/
|
||||||
|
void addTexture(const std::string& name, Ptr<Texture> texture) {
|
||||||
|
textures_.push_back({name, texture});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 构建图集
|
||||||
|
* @return 图集指针,失败返回 nullptr
|
||||||
|
*/
|
||||||
|
Ptr<TextureAtlas> build();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 自动选择最佳图集大小并构建
|
||||||
|
* @return 图集指针,失败返回 nullptr
|
||||||
|
*/
|
||||||
|
Ptr<TextureAtlas> buildAuto();
|
||||||
|
|
||||||
|
private:
|
||||||
|
int width_ = 2048;
|
||||||
|
int height_ = 2048;
|
||||||
|
std::vector<std::pair<std::string, Ptr<Texture>>> textures_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <glad/glad.h>
|
#include <renderer/rhi/rhi.h>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <renderer/render_types.h>
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
@ -10,7 +9,7 @@ namespace extra2d {
|
||||||
/**
|
/**
|
||||||
* @brief 统一缓冲区对象 (UBO)
|
* @brief 统一缓冲区对象 (UBO)
|
||||||
*
|
*
|
||||||
* 用于高效地传递 uniform 数据到 GPU
|
* 基于 RHI 的 UBO 包装类,用于高效地传递 uniform 数据到 GPU
|
||||||
* 支持 std140 布局标准
|
* 支持 std140 布局标准
|
||||||
*/
|
*/
|
||||||
class UniformBuffer {
|
class UniformBuffer {
|
||||||
|
|
@ -44,7 +43,7 @@ public:
|
||||||
* @param size 数据大小
|
* @param size 数据大小
|
||||||
* @param offset 缓冲区偏移(字节)
|
* @param offset 缓冲区偏移(字节)
|
||||||
*/
|
*/
|
||||||
void update(const void *data, uint32_t size, uint32_t offset = 0);
|
void update(const void* data, uint32_t size, uint32_t offset = 0);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 绑定到指定槽位
|
* @brief 绑定到指定槽位
|
||||||
|
|
@ -59,19 +58,25 @@ public:
|
||||||
uint32_t getSize() const { return size_; }
|
uint32_t getSize() const { return size_; }
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 获取 OpenGL 缓冲区对象
|
* @brief 获取 RHI 缓冲区句柄
|
||||||
* @return OpenGL 缓冲区对象
|
* @return RHI 缓冲区句柄
|
||||||
*/
|
*/
|
||||||
GLuint getHandle() const { return ubo_; }
|
BufferHandle getHandle() const { return handle_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取底层 RHI 缓冲区指针
|
||||||
|
* @return RHI 缓冲区指针
|
||||||
|
*/
|
||||||
|
RHIBuffer* getRHIBuffer() const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 检查是否有效
|
* @brief 检查是否有效
|
||||||
* @return 是否有效
|
* @return 是否有效
|
||||||
*/
|
*/
|
||||||
bool isValid() const { return ubo_ != 0; }
|
bool isValid() const { return handle_.isValid(); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
GLuint ubo_ = 0; // OpenGL 缓冲区对象
|
BufferHandle handle_; // RHI 缓冲区句柄
|
||||||
uint32_t size_ = 0; // 缓冲区大小
|
uint32_t size_ = 0; // 缓冲区大小
|
||||||
uint32_t binding_ = 0; // 绑定槽位
|
uint32_t binding_ = 0; // 绑定槽位
|
||||||
};
|
};
|
||||||
|
|
@ -81,6 +86,7 @@ private:
|
||||||
*
|
*
|
||||||
* 管理多个 UBO 的分配和回收
|
* 管理多个 UBO 的分配和回收
|
||||||
* 支持每帧重置和对象池复用
|
* 支持每帧重置和对象池复用
|
||||||
|
* 支持双缓冲机制减少 CPU-GPU 等待
|
||||||
*/
|
*/
|
||||||
class UniformBufferManager {
|
class UniformBufferManager {
|
||||||
public:
|
public:
|
||||||
|
|
@ -95,12 +101,12 @@ public:
|
||||||
~UniformBufferManager();
|
~UniformBufferManager();
|
||||||
|
|
||||||
// 禁止拷贝
|
// 禁止拷贝
|
||||||
UniformBufferManager(const UniformBufferManager &) = delete;
|
UniformBufferManager(const UniformBufferManager&) = delete;
|
||||||
UniformBufferManager &operator=(const UniformBufferManager &) = delete;
|
UniformBufferManager& operator=(const UniformBufferManager&) = delete;
|
||||||
|
|
||||||
// 允许移动
|
// 允许移动
|
||||||
UniformBufferManager(UniformBufferManager &&) noexcept;
|
UniformBufferManager(UniformBufferManager&&) noexcept;
|
||||||
UniformBufferManager &operator=(UniformBufferManager &&) noexcept;
|
UniformBufferManager& operator=(UniformBufferManager&&) noexcept;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 初始化管理器
|
* @brief 初始化管理器
|
||||||
|
|
@ -114,17 +120,18 @@ public:
|
||||||
void shutdown();
|
void shutdown();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 获取全局 UBO
|
* @brief 获取当前帧的全局 UBO(双缓冲)
|
||||||
|
* @param frameIndex 当前帧索引
|
||||||
* @return 全局 UBO 指针
|
* @return 全局 UBO 指针
|
||||||
*/
|
*/
|
||||||
UniformBuffer *getGlobalUBO();
|
UniformBuffer* getGlobalUBO(uint32_t frameIndex);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 获取或创建材质 UBO
|
* @brief 获取或创建材质 UBO
|
||||||
* @param size 缓冲区大小
|
* @param size 缓冲区大小
|
||||||
* @return UBO 指针
|
* @return UBO 指针
|
||||||
*/
|
*/
|
||||||
UniformBuffer *acquireMaterialUBO(uint32_t size);
|
UniformBuffer* acquireMaterialUBO(uint32_t size);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 重置材质 UBO 池
|
* @brief 重置材质 UBO 池
|
||||||
|
|
@ -134,24 +141,57 @@ public:
|
||||||
void resetMaterialUBOs();
|
void resetMaterialUBOs();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 更新全局 UBO 数据
|
* @brief 更新全局 UBO 数据(双缓冲)
|
||||||
* @param data 数据指针
|
* @param data 数据指针
|
||||||
* @param size 数据大小
|
* @param size 数据大小
|
||||||
|
* @param frameIndex 当前帧索引
|
||||||
*/
|
*/
|
||||||
void updateGlobalUBO(const void *data, uint32_t size);
|
void updateGlobalUBO(const void* data, uint32_t size, uint32_t frameIndex);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 批量更新材质 UBO 数据
|
||||||
|
* @param data 材质数据指针
|
||||||
|
* @param size 数据大小
|
||||||
|
* @param offset 偏移量
|
||||||
|
*/
|
||||||
|
void batchUpdateMaterialUBO(const void* data, uint32_t size, uint32_t offset);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取材质 UBO 的 CPU 缓冲区指针(用于批量更新)
|
||||||
|
* @return CPU 缓冲区指针
|
||||||
|
*/
|
||||||
|
uint8_t* getMaterialUBOBuffer() { return materialUBOBuffer_.data(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取材质 UBO 缓冲区大小
|
||||||
|
* @return 缓冲区大小
|
||||||
|
*/
|
||||||
|
uint32_t getMaterialUBOBufferSize() const { return static_cast<uint32_t>(materialUBOBuffer_.size()); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 刷新材质 UBO 数据到 GPU
|
||||||
|
*/
|
||||||
|
void flushMaterialUBO();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// 全局 UBO(用于 viewProjection、time 等全局数据)
|
// 全局 UBO 双缓冲(用于 viewProjection、time 等全局数据)
|
||||||
std::unique_ptr<UniformBuffer> globalUBO_;
|
std::array<std::unique_ptr<UniformBuffer>, 2> globalUBOs_;
|
||||||
|
|
||||||
// 材质 UBO 池
|
// 材质 UBO 池
|
||||||
std::vector<std::unique_ptr<UniformBuffer>> materialUBOPool_;
|
std::vector<std::unique_ptr<UniformBuffer>> materialUBOPool_;
|
||||||
uint32_t currentUBOIndex_ = 0;
|
uint32_t currentUBOIndex_ = 0;
|
||||||
|
|
||||||
|
// 材质 UBO CPU 缓冲区(用于批量更新)
|
||||||
|
std::vector<uint8_t> materialUBOBuffer_;
|
||||||
|
uint32_t materialUBOBufferOffset_ = 0;
|
||||||
|
UniformBuffer* currentMaterialUBO_ = nullptr;
|
||||||
|
|
||||||
// 常量
|
// 常量
|
||||||
static constexpr uint32_t INITIAL_UBO_POOL_SIZE = 16;
|
static constexpr uint32_t INITIAL_UBO_POOL_SIZE = 16;
|
||||||
static constexpr uint32_t GLOBAL_UBO_SIZE =
|
static constexpr uint32_t GLOBAL_UBO_SIZE = 256; // 足够存储 viewProjection + time + screenSize
|
||||||
256; // 足够存储 viewProjection + time + screenSize
|
static constexpr uint32_t GLOBAL_UBO_BINDING = 0; // 全局 UBO 绑定槽位
|
||||||
|
static constexpr uint32_t MATERIAL_UBO_BINDING = 1; // 材质 UBO 绑定槽位
|
||||||
|
static constexpr uint32_t MATERIAL_UBO_BUFFER_SIZE = 1024 * 1024; // 1MB 材质 UBO 缓冲区
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace extra2d
|
} // namespace extra2d
|
||||||
|
|
|
||||||
|
|
@ -307,7 +307,7 @@ public:
|
||||||
/**
|
/**
|
||||||
* @brief 渲染(收集渲染命令)
|
* @brief 渲染(收集渲染命令)
|
||||||
*/
|
*/
|
||||||
void render();
|
virtual void render();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::string name_;
|
std::string name_;
|
||||||
|
|
|
||||||
|
|
@ -13,20 +13,18 @@ namespace extra2d {
|
||||||
* 将导演系统集成到引擎模块系统中
|
* 将导演系统集成到引擎模块系统中
|
||||||
*/
|
*/
|
||||||
class SceneModule : public Module {
|
class SceneModule : public Module {
|
||||||
// 自动注册到模块系统,优先级为 4(在 Renderer 之后)
|
// 优先级为 5,在 RendererModule (优先级 4) 之后初始化
|
||||||
E2D_REGISTER_MODULE(SceneModule, "Scene", 4)
|
E2D_REGISTER_MODULE(SceneModule, "Scene", 5)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
SceneModule();
|
SceneModule();
|
||||||
~SceneModule() override;
|
~SceneModule() override;
|
||||||
|
|
||||||
// 禁止拷贝
|
// 禁止拷贝和移动
|
||||||
SceneModule(const SceneModule &) = delete;
|
SceneModule(const SceneModule &) = delete;
|
||||||
SceneModule &operator=(const SceneModule &) = delete;
|
SceneModule &operator=(const SceneModule &) = delete;
|
||||||
|
SceneModule(SceneModule &&) = delete;
|
||||||
// 允许移动
|
SceneModule &operator=(SceneModule &&) = delete;
|
||||||
SceneModule(SceneModule &&) noexcept;
|
|
||||||
SceneModule &operator=(SceneModule &&) noexcept;
|
|
||||||
|
|
||||||
// Module 接口实现
|
// Module 接口实现
|
||||||
bool init() override;
|
bool init() override;
|
||||||
|
|
|
||||||
|
|
@ -44,20 +44,18 @@ struct TimerInfo {
|
||||||
* 通过事件总线接收更新事件,无需直接依赖
|
* 通过事件总线接收更新事件,无需直接依赖
|
||||||
*/
|
*/
|
||||||
class TimerModule : public Module {
|
class TimerModule : public Module {
|
||||||
// 自动注册到模块系统,优先级为 5(核心模块)
|
// 优先级为 6,在 SceneModule (优先级 5) 之后初始化
|
||||||
E2D_REGISTER_MODULE(TimerModule, "Timer", 5)
|
E2D_REGISTER_MODULE(TimerModule, "Timer", 6)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
TimerModule();
|
TimerModule();
|
||||||
~TimerModule() override;
|
~TimerModule() override;
|
||||||
|
|
||||||
// 禁止拷贝
|
// 禁止拷贝和移动
|
||||||
TimerModule(const TimerModule &) = delete;
|
TimerModule(const TimerModule &) = delete;
|
||||||
TimerModule &operator=(const TimerModule &) = delete;
|
TimerModule &operator=(const TimerModule &) = delete;
|
||||||
|
TimerModule(TimerModule &&) = delete;
|
||||||
// 允许移动
|
TimerModule &operator=(TimerModule &&) = delete;
|
||||||
TimerModule(TimerModule &&) noexcept;
|
|
||||||
TimerModule &operator=(TimerModule &&) noexcept;
|
|
||||||
|
|
||||||
// Module 接口实现
|
// Module 接口实现
|
||||||
bool init() override;
|
bool init() override;
|
||||||
|
|
|
||||||
|
|
@ -4,14 +4,12 @@ precision highp float;
|
||||||
// 从顶点着色器输入
|
// 从顶点着色器输入
|
||||||
in vec2 vTexCoord;
|
in vec2 vTexCoord;
|
||||||
in vec4 vColor;
|
in vec4 vColor;
|
||||||
|
in vec4 vTintColor;
|
||||||
|
in float vOpacity;
|
||||||
|
|
||||||
// 纹理采样器
|
// 纹理采样器
|
||||||
uniform sampler2D uTexture;
|
uniform sampler2D uTexture;
|
||||||
|
|
||||||
// 材质参数
|
|
||||||
uniform vec4 uTintColor;
|
|
||||||
uniform float uOpacity;
|
|
||||||
|
|
||||||
// 输出颜色
|
// 输出颜色
|
||||||
out vec4 fragColor;
|
out vec4 fragColor;
|
||||||
|
|
||||||
|
|
@ -24,11 +22,16 @@ void main() {
|
||||||
// 采样纹理
|
// 采样纹理
|
||||||
vec4 texColor = texture(uTexture, vTexCoord);
|
vec4 texColor = texture(uTexture, vTexCoord);
|
||||||
|
|
||||||
|
// 如果纹理采样结果是黑色或透明,使用白色作为默认值
|
||||||
|
if (texColor.rgb == vec3(0.0) || texColor.a < 0.01) {
|
||||||
|
texColor = vec4(1.0, 1.0, 1.0, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
// 混合:纹理 * 顶点颜色 * 色调
|
// 混合:纹理 * 顶点颜色 * 色调
|
||||||
fragColor = texColor * vColor * uTintColor;
|
fragColor = texColor * vColor * vTintColor;
|
||||||
|
|
||||||
// 应用透明度
|
// 应用透明度
|
||||||
fragColor.a *= uOpacity;
|
fragColor.a *= vOpacity;
|
||||||
|
|
||||||
// Alpha 测试:丢弃几乎透明的像素
|
// Alpha 测试:丢弃几乎透明的像素
|
||||||
if (fragColor.a < 0.01) {
|
if (fragColor.a < 0.01) {
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,26 @@
|
||||||
#version 320 es
|
#version 320 es
|
||||||
precision highp float;
|
precision highp float;
|
||||||
|
|
||||||
// 视图投影矩阵
|
// 全局 UBO (binding = 0) - 每帧更新一次
|
||||||
uniform mat4 uViewProjection;
|
layout(std140, binding = 0) uniform GlobalUBO {
|
||||||
|
mat4 uViewProjection;
|
||||||
|
vec4 uCameraPosition;
|
||||||
|
float uTime;
|
||||||
|
float uDeltaTime;
|
||||||
|
vec2 uScreenSize;
|
||||||
|
};
|
||||||
|
|
||||||
// 模型矩阵
|
// 材质 UBO (binding = 1) - 每物体更新
|
||||||
|
layout(std140, binding = 1) uniform MaterialUBO {
|
||||||
|
vec4 uColor;
|
||||||
|
vec4 uTintColor;
|
||||||
|
float uOpacity;
|
||||||
|
float uPadding[3]; // std140 对齐填充
|
||||||
|
};
|
||||||
|
|
||||||
|
// 模型矩阵作为单独的统一变量(每个物体设置)
|
||||||
uniform mat4 uModelMatrix;
|
uniform mat4 uModelMatrix;
|
||||||
|
|
||||||
// 顶点颜色(覆盖顶点属性中的颜色)
|
|
||||||
uniform vec4 uColor;
|
|
||||||
|
|
||||||
// 顶点属性
|
// 顶点属性
|
||||||
layout(location = 0) in vec2 aPosition;
|
layout(location = 0) in vec2 aPosition;
|
||||||
layout(location = 1) in vec2 aTexCoord;
|
layout(location = 1) in vec2 aTexCoord;
|
||||||
|
|
@ -18,16 +29,20 @@ layout(location = 2) in vec4 aColor;
|
||||||
// 输出到片段着色器
|
// 输出到片段着色器
|
||||||
out vec2 vTexCoord;
|
out vec2 vTexCoord;
|
||||||
out vec4 vColor;
|
out vec4 vColor;
|
||||||
|
out vec4 vTintColor;
|
||||||
|
out float vOpacity;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 顶点着色器入口
|
* @brief 顶点着色器入口
|
||||||
*
|
*
|
||||||
* 计算顶点在裁剪空间中的位置,
|
* 计算顶点在裁剪空间中的位置,
|
||||||
* 并传递纹理坐标和顶点颜色到片段着色器
|
* 并传递纹理坐标和颜色到片段着色器
|
||||||
*/
|
*/
|
||||||
void main() {
|
void main() {
|
||||||
gl_Position = uViewProjection * uModelMatrix * vec4(aPosition, 0.0, 1.0);
|
gl_Position = uViewProjection * uModelMatrix * vec4(aPosition, 0.0, 1.0);
|
||||||
vTexCoord = aTexCoord;
|
vTexCoord = aTexCoord;
|
||||||
// 使用 uniform 颜色覆盖顶点属性颜色
|
// 混合顶点颜色和材质 UBO 中的颜色
|
||||||
vColor = uColor;
|
vColor = aColor * uColor;
|
||||||
|
vTintColor = uTintColor;
|
||||||
|
vOpacity = uOpacity;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,73 @@
|
||||||
|
#version 320 es
|
||||||
|
precision highp float;
|
||||||
|
|
||||||
|
// 全局 UBO (binding = 0) - 每帧更新一次
|
||||||
|
layout(std140, binding = 0) uniform GlobalUBO {
|
||||||
|
mat4 uViewProjection;
|
||||||
|
vec4 uCameraPosition;
|
||||||
|
float uTime;
|
||||||
|
float uDeltaTime;
|
||||||
|
vec2 uScreenSize;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 材质 UBO (binding = 1) - 每批次更新
|
||||||
|
layout(std140, binding = 1) uniform MaterialUBO {
|
||||||
|
vec4 uColor;
|
||||||
|
vec4 uTintColor;
|
||||||
|
float uOpacity;
|
||||||
|
float uPadding[3]; // std140 对齐填充
|
||||||
|
};
|
||||||
|
|
||||||
|
// 顶点属性 (每个顶点)
|
||||||
|
layout(location = 0) in vec2 aPosition;
|
||||||
|
layout(location = 1) in vec2 aTexCoord;
|
||||||
|
layout(location = 2) in vec4 aColor;
|
||||||
|
|
||||||
|
// 实例属性 (每个实例) - 使用 location 3-6
|
||||||
|
layout(location = 3) in vec2 iPosition; // 实例位置
|
||||||
|
layout(location = 4) in float iRotation; // 实例旋转
|
||||||
|
layout(location = 5) in vec2 iScale; // 实例缩放
|
||||||
|
layout(location = 6) in vec4 iColor; // 实例颜色
|
||||||
|
|
||||||
|
// 输出到片段着色器
|
||||||
|
out vec2 vTexCoord;
|
||||||
|
out vec4 vColor;
|
||||||
|
out vec4 vTintColor;
|
||||||
|
out float vOpacity;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 从旋转角度构建2D变换矩阵
|
||||||
|
* @param angle 旋转角度(弧度)
|
||||||
|
* @return 2x2旋转矩阵
|
||||||
|
*/
|
||||||
|
mat2 rotate2D(float angle) {
|
||||||
|
float c = cos(angle);
|
||||||
|
float s = sin(angle);
|
||||||
|
return mat2(c, -s, s, c);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 顶点着色器入口
|
||||||
|
*
|
||||||
|
* 计算顶点在裁剪空间中的位置,
|
||||||
|
* 应用实例的变换(位置、旋转、缩放),
|
||||||
|
* 并传递纹理坐标和颜色到片段着色器
|
||||||
|
*/
|
||||||
|
void main() {
|
||||||
|
// 应用实例缩放和旋转
|
||||||
|
vec2 localPos = rotate2D(iRotation) * (aPosition * iScale);
|
||||||
|
|
||||||
|
// 应用实例位置偏移
|
||||||
|
vec2 worldPos = localPos + iPosition;
|
||||||
|
|
||||||
|
// 变换到裁剪空间
|
||||||
|
gl_Position = uViewProjection * vec4(worldPos, 0.0, 1.0);
|
||||||
|
|
||||||
|
vTexCoord = aTexCoord;
|
||||||
|
|
||||||
|
// 混合顶点颜色、实例颜色和材质颜色
|
||||||
|
vColor = aColor * iColor * uColor;
|
||||||
|
|
||||||
|
vTintColor = uTintColor;
|
||||||
|
vOpacity = uOpacity;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
#version 320 es
|
||||||
|
precision highp float;
|
||||||
|
|
||||||
|
// 从顶点着色器输入
|
||||||
|
in vec2 vTexCoord;
|
||||||
|
in vec4 vColor;
|
||||||
|
|
||||||
|
// 纹理采样器
|
||||||
|
uniform sampler2D uTexture;
|
||||||
|
|
||||||
|
// 输出颜色
|
||||||
|
out vec4 fragColor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 片段着色器入口(实例化版本)
|
||||||
|
*
|
||||||
|
* 采样纹理并与顶点颜色混合
|
||||||
|
*/
|
||||||
|
void main() {
|
||||||
|
// 采样纹理
|
||||||
|
vec4 texColor = texture(uTexture, vTexCoord);
|
||||||
|
|
||||||
|
// 如果纹理采样结果是黑色或透明,使用白色作为默认值
|
||||||
|
if (texColor.rgb == vec3(0.0) || texColor.a < 0.01) {
|
||||||
|
texColor = vec4(1.0, 1.0, 1.0, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 混合:纹理 * 顶点颜色
|
||||||
|
fragColor = texColor * vColor;
|
||||||
|
|
||||||
|
// Alpha 测试:丢弃几乎透明的像素
|
||||||
|
if (fragColor.a < 0.01) {
|
||||||
|
discard;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -63,6 +63,11 @@ void Application::initModules() {
|
||||||
// 从注册表自动创建所有模块
|
// 从注册表自动创建所有模块
|
||||||
modules_ = ModuleRegistry::instance().createModules();
|
modules_ = ModuleRegistry::instance().createModules();
|
||||||
|
|
||||||
|
// 存储模块实例到注册表,以便其他模块可以通过 getModule<T>() 获取
|
||||||
|
for (auto &module : modules_) {
|
||||||
|
ModuleRegistry::instance().storeModuleInstance(module.get());
|
||||||
|
}
|
||||||
|
|
||||||
// 初始化所有模块
|
// 初始化所有模块
|
||||||
for (auto &module : modules_) {
|
for (auto &module : modules_) {
|
||||||
if (!module->init()) {
|
if (!module->init()) {
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,69 @@
|
||||||
|
#include <algorithm>
|
||||||
#include <assets/assets_module.h>
|
#include <assets/assets_module.h>
|
||||||
#include <assets/loaders/shader_loader.h>
|
#include <assets/loaders/shader_loader.h>
|
||||||
#include <assets/loaders/texture_loader.h>
|
#include <assets/loaders/texture_loader.h>
|
||||||
#include <event/events.h>
|
#include <event/events.h>
|
||||||
#include <algorithm>
|
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
#include <module/module_registry.h>
|
||||||
|
#include <platform/file_module.h>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <utils/logger.h>
|
#include <utils/logger.h>
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取 FileModule 实例
|
||||||
|
*/
|
||||||
|
FileModule *getFileModule() { return getModule<FileModule>(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查路径是否为 RomFS 路径
|
||||||
|
*/
|
||||||
|
bool isRomfsPath(const std::string &path) {
|
||||||
|
FileModule *fileModule = getFileModule();
|
||||||
|
if (fileModule) {
|
||||||
|
return fileModule->isRomfsPath(path);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查文件是否存在(使用 FileModule)
|
||||||
|
*/
|
||||||
|
bool fileExists(const std::string &path) {
|
||||||
|
FileModule *fileModule = getFileModule();
|
||||||
|
if (fileModule) {
|
||||||
|
return fileModule->exists(path);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 读取文件内容为字符串(使用 FileModule,支持 RomFS)
|
||||||
|
*/
|
||||||
|
std::string readFileToString(const std::string &path) {
|
||||||
|
FileModule *fileModule = getFileModule();
|
||||||
|
if (fileModule) {
|
||||||
|
return fileModule->readString(path);
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取文件最后写入时间(支持 RomFS 路径)
|
||||||
|
*/
|
||||||
|
std::filesystem::file_time_type getLastWriteTime(const std::string &path) {
|
||||||
|
if (isRomfsPath(path)) {
|
||||||
|
// RomFS 文件是只读的,返回一个固定时间
|
||||||
|
return std::filesystem::file_time_type::min();
|
||||||
|
}
|
||||||
|
return std::filesystem::last_write_time(path);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // anonymous namespace
|
||||||
|
|
||||||
AssetsModule *g_assetsModule = nullptr;
|
AssetsModule *g_assetsModule = nullptr;
|
||||||
|
|
||||||
AssetsModule::AssetsModule() { g_assetsModule = this; }
|
AssetsModule::AssetsModule() { g_assetsModule = this; }
|
||||||
|
|
@ -177,8 +232,7 @@ Handle<Texture> AssetsModule::loadFromMemory<Texture>(const std::string &key,
|
||||||
// Shader 加载特化
|
// Shader 加载特化
|
||||||
//===========================================================================
|
//===========================================================================
|
||||||
|
|
||||||
template <>
|
template <> Handle<Shader> AssetsModule::load<Shader>(const std::string &path) {
|
||||||
Handle<Shader> AssetsModule::load<Shader>(const std::string &path) {
|
|
||||||
// 先检查缓存(读锁)
|
// 先检查缓存(读锁)
|
||||||
{
|
{
|
||||||
std::shared_lock<std::shared_mutex> lock(mutex_);
|
std::shared_lock<std::shared_mutex> lock(mutex_);
|
||||||
|
|
@ -293,7 +347,7 @@ AssetsModule::loadDir<Texture>(const std::string &directory,
|
||||||
|
|
||||||
try {
|
try {
|
||||||
std::filesystem::path dirPath(directory);
|
std::filesystem::path dirPath(directory);
|
||||||
if (!std::filesystem::exists(dirPath)) {
|
if (!fileExists(directory)) {
|
||||||
E2D_LOG_WARN("Directory not found: {}", directory);
|
E2D_LOG_WARN("Directory not found: {}", directory);
|
||||||
return handles;
|
return handles;
|
||||||
}
|
}
|
||||||
|
|
@ -342,12 +396,38 @@ AssetsModule::loadDir<Texture>(const std::string &directory,
|
||||||
template <>
|
template <>
|
||||||
void AssetsModule::loadAsync<Texture>(
|
void AssetsModule::loadAsync<Texture>(
|
||||||
const std::string &path, std::function<void(Handle<Texture>)> callback) {
|
const std::string &path, std::function<void(Handle<Texture>)> callback) {
|
||||||
std::thread([this, path, callback]() {
|
// 确保异步加载系统已初始化
|
||||||
Handle<Texture> handle = load<Texture>(path);
|
if (!asyncLoaderRunning_) {
|
||||||
if (callback) {
|
initAsyncLoader();
|
||||||
callback(handle);
|
|
||||||
}
|
}
|
||||||
}).detach();
|
|
||||||
|
// 创建并提交加载任务
|
||||||
|
LoadTask task;
|
||||||
|
task.type = LoadTask::Type::Texture;
|
||||||
|
task.priority = LoadTask::Priority::Normal;
|
||||||
|
task.path = path;
|
||||||
|
task.textureCallback = callback;
|
||||||
|
|
||||||
|
submitLoadTask(task);
|
||||||
|
}
|
||||||
|
|
||||||
|
template <>
|
||||||
|
void AssetsModule::loadAsync<Shader>(
|
||||||
|
const std::string &path, std::function<void(Handle<Shader>)> callback) {
|
||||||
|
// 确保异步加载系统已初始化
|
||||||
|
if (!asyncLoaderRunning_) {
|
||||||
|
initAsyncLoader();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建并提交加载任务
|
||||||
|
LoadTask task;
|
||||||
|
task.type = LoadTask::Type::Shader;
|
||||||
|
task.priority = LoadTask::Priority::Normal;
|
||||||
|
task.path = path;
|
||||||
|
task.secondaryPath = "";
|
||||||
|
task.shaderCallback = callback;
|
||||||
|
|
||||||
|
submitLoadTask(task);
|
||||||
}
|
}
|
||||||
|
|
||||||
//===========================================================================
|
//===========================================================================
|
||||||
|
|
@ -384,34 +464,70 @@ bool AssetsModule::createDefaultResources() {
|
||||||
|
|
||||||
{
|
{
|
||||||
// 从文件加载默认着色器
|
// 从文件加载默认着色器
|
||||||
std::filesystem::path vertPath = "shader/default.vert";
|
// 使用 FileModule 的 assetPath 来获取正确的资源路径(支持 RomFS)
|
||||||
std::filesystem::path fragPath = "shader/default.frag";
|
FileModule *fileModule = getFileModule();
|
||||||
|
std::string vertPath = fileModule
|
||||||
|
? fileModule->assetPath("shader/default.vert")
|
||||||
|
: "shader/default.vert";
|
||||||
|
std::string fragPath = fileModule
|
||||||
|
? fileModule->assetPath("shader/default.frag")
|
||||||
|
: "shader/default.frag";
|
||||||
|
|
||||||
Ptr<Shader> shader = makePtr<Shader>();
|
if (!fileExists(vertPath) || !fileExists(fragPath)) {
|
||||||
|
E2D_LOG_ERROR("Default shader files not found: {}, {}", vertPath,
|
||||||
if (!std::filesystem::exists(vertPath) ||
|
fragPath);
|
||||||
!std::filesystem::exists(fragPath)) {
|
|
||||||
E2D_LOG_ERROR("Default shader files not found: {}, {}", vertPath.string(),
|
|
||||||
fragPath.string());
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!shader->loadFromFile(vertPath.string(), fragPath.string())) {
|
// 读取着色器文件内容
|
||||||
E2D_LOG_ERROR("Failed to load default shader from files: {}, {}",
|
std::string vsSource = readFileToString(vertPath);
|
||||||
vertPath.string(), fragPath.string());
|
std::string fsSource = readFileToString(fragPath);
|
||||||
|
if (vsSource.empty() || fsSource.empty()) {
|
||||||
|
E2D_LOG_ERROR("Failed to read default shader files: {}, {}", vertPath,
|
||||||
|
fragPath);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从源码加载着色器
|
||||||
|
Ptr<Shader> shader = makePtr<Shader>();
|
||||||
|
if (!shader->loadFromSource(vsSource, fsSource)) {
|
||||||
|
E2D_LOG_ERROR("Failed to compile default shader: {}, {}", vertPath,
|
||||||
|
fragPath);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
defaultShader_ = shaders_.insert(shader);
|
defaultShader_ = shaders_.insert(shader);
|
||||||
E2D_LOG_DEBUG("Loaded default shader from files: {}, {}", vertPath.string(),
|
E2D_LOG_DEBUG("Loaded default shader from files: {}, {}", vertPath,
|
||||||
fragPath.string());
|
fragPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
|
// 创建材质布局(与着色器中的 MaterialUBO 匹配)
|
||||||
|
// layout(std140, binding = 1) uniform MaterialUBO {
|
||||||
|
// vec4 uColor; // 16 bytes
|
||||||
|
// vec4 uTintColor; // 16 bytes
|
||||||
|
// float uOpacity; // 4 bytes
|
||||||
|
// float uPadding[3]; // 12 bytes
|
||||||
|
// };
|
||||||
|
Ptr<MaterialLayout> layout = makePtr<MaterialLayout>();
|
||||||
|
layout->addParam("uColor", MaterialParamType::Color);
|
||||||
|
layout->addParam("uTintColor", MaterialParamType::Color);
|
||||||
|
layout->addParam("uOpacity", MaterialParamType::Float);
|
||||||
|
layout->finalize();
|
||||||
|
|
||||||
Ptr<Material> material = makePtr<Material>();
|
Ptr<Material> material = makePtr<Material>();
|
||||||
material->setShader(getPtr(defaultShader_));
|
material->setShader(getPtr(defaultShader_));
|
||||||
|
material->setLayout(layout);
|
||||||
|
|
||||||
|
// 设置默认材质参数
|
||||||
|
material->setColor("uColor", Color::White);
|
||||||
|
material->setColor("uTintColor", Color::White);
|
||||||
|
material->setFloat("uOpacity", 1.0f);
|
||||||
|
|
||||||
|
// 添加默认纹理到材质
|
||||||
|
material->setTexture("uTexture", getPtr(defaultTexture_), 0);
|
||||||
defaultMaterial_ = materials_.insert(material);
|
defaultMaterial_ = materials_.insert(material);
|
||||||
E2D_LOG_DEBUG("Created default material");
|
E2D_LOG_DEBUG("Created default material with default texture and layout");
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
|
|
@ -478,81 +594,81 @@ void AssetsModule::setHotReloadInterval(float interval) {
|
||||||
hotReloadInterval_ = interval;
|
hotReloadInterval_ = interval;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AssetsModule::addFileWatch(const std::string& path, Handle<Texture> handle) {
|
void AssetsModule::addFileWatchInternal(const std::string &path, FileWatchInfo &&info) {
|
||||||
try {
|
try {
|
||||||
if (!std::filesystem::exists(path)) {
|
if (!fileExists(path)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
FileWatchInfo info;
|
|
||||||
info.path = path;
|
info.path = path;
|
||||||
info.lastWriteTime = std::filesystem::last_write_time(path);
|
info.lastWriteTime = getLastWriteTime(path);
|
||||||
|
|
||||||
|
std::unique_lock<std::shared_mutex> lock(mutex_);
|
||||||
|
fileWatchList_.push_back(std::move(info));
|
||||||
|
const char *type = fileWatchList_.back().isTexture ? "texture" : "shader";
|
||||||
|
E2D_LOG_DEBUG("Watching {} file: {}", type, path);
|
||||||
|
} catch (const std::exception &e) {
|
||||||
|
E2D_LOG_ERROR("Failed to add file watch for {}: {}", path, e.what());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AssetsModule::addFileWatch(const std::string &path, Handle<Texture> handle) {
|
||||||
|
FileWatchInfo info;
|
||||||
info.textureHandle = handle;
|
info.textureHandle = handle;
|
||||||
info.isTexture = true;
|
info.isTexture = true;
|
||||||
|
addFileWatchInternal(path, std::move(info));
|
||||||
std::unique_lock<std::shared_mutex> lock(mutex_);
|
|
||||||
fileWatchList_.push_back(info);
|
|
||||||
E2D_LOG_DEBUG("Watching texture file: {}", path);
|
|
||||||
} catch (const std::exception& e) {
|
|
||||||
E2D_LOG_ERROR("Failed to add file watch for {}: {}", path, e.what());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AssetsModule::addFileWatch(const std::string& path, Handle<Shader> handle) {
|
void AssetsModule::addFileWatch(const std::string &path, Handle<Shader> handle) {
|
||||||
try {
|
|
||||||
if (!std::filesystem::exists(path)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
FileWatchInfo info;
|
FileWatchInfo info;
|
||||||
info.path = path;
|
|
||||||
info.lastWriteTime = std::filesystem::last_write_time(path);
|
|
||||||
info.shaderHandle = handle;
|
info.shaderHandle = handle;
|
||||||
info.isTexture = false;
|
info.isTexture = false;
|
||||||
|
addFileWatchInternal(path, std::move(info));
|
||||||
std::unique_lock<std::shared_mutex> lock(mutex_);
|
|
||||||
fileWatchList_.push_back(info);
|
|
||||||
E2D_LOG_DEBUG("Watching shader file: {}", path);
|
|
||||||
} catch (const std::exception& e) {
|
|
||||||
E2D_LOG_ERROR("Failed to add file watch for {}: {}", path, e.what());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AssetsModule::reloadTexture(const FileWatchInfo& info) {
|
void AssetsModule::reloadTexture(const FileWatchInfo &info) {
|
||||||
if (!textureLoader_ || !info.textureHandle.isValid()) {
|
if (!textureLoader_ || !info.textureHandle.isValid()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
E2D_LOG_INFO("Reloading texture: {}", info.path);
|
E2D_LOG_INFO("Reloading texture: {}", info.path);
|
||||||
|
|
||||||
Ptr<Texture> newTexture = textureLoader_->load(info.path);
|
// 获取旧纹理指针
|
||||||
if (!newTexture) {
|
Texture *oldTexture = textures_.get(info.textureHandle);
|
||||||
|
if (!oldTexture) {
|
||||||
|
E2D_LOG_ERROR("Old texture not found for reload: {}", info.path);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 直接在原有纹理对象上重新加载
|
||||||
|
if (!oldTexture->reloadFromFile(info.path)) {
|
||||||
E2D_LOG_ERROR("Failed to reload texture: {}", info.path);
|
E2D_LOG_ERROR("Failed to reload texture: {}", info.path);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 替换旧纹理的数据
|
E2D_LOG_INFO("Texture reloaded successfully: {}", info.path);
|
||||||
Texture* oldTexture = textures_.get(info.textureHandle);
|
|
||||||
if (oldTexture) {
|
|
||||||
// 这里假设 Texture 类有更新数据的方法
|
|
||||||
// 如果没有,可能需要重新设计
|
|
||||||
E2D_LOG_INFO("Texture reloaded: {}", info.path);
|
|
||||||
notifyTextureReloaded(info.textureHandle);
|
notifyTextureReloaded(info.textureHandle);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AssetsModule::reloadShader(const FileWatchInfo& info) {
|
void AssetsModule::reloadShader(const FileWatchInfo &info) {
|
||||||
if (!shaderLoader_ || !info.shaderHandle.isValid()) {
|
if (!shaderLoader_ || !info.shaderHandle.isValid()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
E2D_LOG_INFO("Reloading shader: {}", info.path);
|
E2D_LOG_INFO("Reloading shader: {}", info.path);
|
||||||
|
|
||||||
|
// 获取旧着色器指针
|
||||||
|
Shader *oldShader = shaders_.get(info.shaderHandle);
|
||||||
|
if (!oldShader) {
|
||||||
|
E2D_LOG_ERROR("Old shader not found for reload: {}", info.path);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// 查找缓存键
|
// 查找缓存键
|
||||||
std::string cacheKey;
|
std::string cacheKey;
|
||||||
{
|
{
|
||||||
std::shared_lock<std::shared_mutex> lock(mutex_);
|
std::shared_lock<std::shared_mutex> lock(mutex_);
|
||||||
for (const auto& pair : shaderPathCache_) {
|
for (const auto &pair : shaderPathCache_) {
|
||||||
if (pair.second == info.shaderHandle) {
|
if (pair.second == info.shaderHandle) {
|
||||||
cacheKey = pair.first;
|
cacheKey = pair.first;
|
||||||
break;
|
break;
|
||||||
|
|
@ -565,29 +681,32 @@ void AssetsModule::reloadShader(const FileWatchInfo& info) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 解析顶点/片段着色器路径
|
// 解析顶点/片段着色器路径并重新加载
|
||||||
size_t sepPos = cacheKey.find('|');
|
size_t sepPos = cacheKey.find('|');
|
||||||
if (sepPos == std::string::npos) {
|
if (sepPos == std::string::npos) {
|
||||||
// 单文件模式
|
// 单文件模式 - 这里暂不支持,因为 ShaderLoader 目前只支持双文件
|
||||||
Ptr<Shader> newShader = shaderLoader_->load(info.path);
|
E2D_LOG_WARN("Single-file shader reload not supported: {}", info.path);
|
||||||
if (!newShader) {
|
|
||||||
E2D_LOG_ERROR("Failed to reload shader: {}", info.path);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
// 双文件模式
|
// 双文件模式
|
||||||
std::string vertPath = cacheKey.substr(0, sepPos);
|
std::string vertPath = cacheKey.substr(0, sepPos);
|
||||||
std::string fragPath = cacheKey.substr(sepPos + 1);
|
std::string fragPath = cacheKey.substr(sepPos + 1);
|
||||||
|
|
||||||
ShaderLoader* loader = static_cast<ShaderLoader*>(shaderLoader_.get());
|
// 读取着色器文件内容
|
||||||
Ptr<Shader> newShader = loader->load(vertPath, fragPath);
|
std::string vsSource = readFileToString(vertPath);
|
||||||
if (!newShader) {
|
std::string fsSource = readFileToString(fragPath);
|
||||||
|
if (vsSource.empty() || fsSource.empty()) {
|
||||||
|
E2D_LOG_ERROR("Failed to read shader files for reload: {}, {}", vertPath, fragPath);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 直接在原有着色器对象上重新加载
|
||||||
|
if (!oldShader->reloadFromSource(vsSource, fsSource)) {
|
||||||
E2D_LOG_ERROR("Failed to reload shader: {} + {}", vertPath, fragPath);
|
E2D_LOG_ERROR("Failed to reload shader: {} + {}", vertPath, fragPath);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
E2D_LOG_INFO("Shader reloaded: {}", info.path);
|
E2D_LOG_INFO("Shader reloaded successfully: {}", info.path);
|
||||||
notifyShaderReloaded(info.shaderHandle);
|
notifyShaderReloaded(info.shaderHandle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -598,13 +717,18 @@ void AssetsModule::checkForChanges() {
|
||||||
|
|
||||||
std::unique_lock<std::shared_mutex> lock(mutex_);
|
std::unique_lock<std::shared_mutex> lock(mutex_);
|
||||||
|
|
||||||
for (auto& info : fileWatchList_) {
|
for (auto &info : fileWatchList_) {
|
||||||
try {
|
try {
|
||||||
if (!std::filesystem::exists(info.path)) {
|
// 跳过 RomFS 路径(只读文件系统)
|
||||||
|
if (isRomfsPath(info.path)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto currentTime = std::filesystem::last_write_time(info.path);
|
if (!fileExists(info.path)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto currentTime = getLastWriteTime(info.path);
|
||||||
if (currentTime != info.lastWriteTime) {
|
if (currentTime != info.lastWriteTime) {
|
||||||
info.lastWriteTime = currentTime;
|
info.lastWriteTime = currentTime;
|
||||||
|
|
||||||
|
|
@ -619,7 +743,7 @@ void AssetsModule::checkForChanges() {
|
||||||
|
|
||||||
lock.lock();
|
lock.lock();
|
||||||
}
|
}
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception &e) {
|
||||||
E2D_LOG_ERROR("Error checking file {}: {}", info.path, e.what());
|
E2D_LOG_ERROR("Error checking file {}: {}", info.path, e.what());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -655,7 +779,7 @@ void AssetsModule::shutdownAsyncLoader() {
|
||||||
asyncLoaderRunning_ = false;
|
asyncLoaderRunning_ = false;
|
||||||
queueCV_.notify_all();
|
queueCV_.notify_all();
|
||||||
|
|
||||||
for (auto& thread : workerThreads_) {
|
for (auto &thread : workerThreads_) {
|
||||||
if (thread.joinable()) {
|
if (thread.joinable()) {
|
||||||
thread.join();
|
thread.join();
|
||||||
}
|
}
|
||||||
|
|
@ -669,15 +793,16 @@ void AssetsModule::shutdownAsyncLoader() {
|
||||||
E2D_LOG_INFO("Async loader shutdown");
|
E2D_LOG_INFO("Async loader shutdown");
|
||||||
}
|
}
|
||||||
|
|
||||||
void AssetsModule::submitLoadTask(const LoadTask& task) {
|
void AssetsModule::submitLoadTask(const LoadTask &task) {
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(queueMutex_);
|
std::lock_guard<std::mutex> lock(queueMutex_);
|
||||||
loadQueue_.push_back(task);
|
loadQueue_.push_back(task);
|
||||||
|
|
||||||
// 按优先级排序
|
// 按优先级排序
|
||||||
std::sort(loadQueue_.begin(), loadQueue_.end(),
|
std::sort(loadQueue_.begin(), loadQueue_.end(),
|
||||||
[](const LoadTask& a, const LoadTask& b) {
|
[](const LoadTask &a, const LoadTask &b) {
|
||||||
return static_cast<int>(a.priority) > static_cast<int>(b.priority);
|
return static_cast<int>(a.priority) >
|
||||||
|
static_cast<int>(b.priority);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
queueCV_.notify_one();
|
queueCV_.notify_one();
|
||||||
|
|
@ -689,9 +814,8 @@ void AssetsModule::workerThreadLoop() {
|
||||||
|
|
||||||
{
|
{
|
||||||
std::unique_lock<std::mutex> lock(queueMutex_);
|
std::unique_lock<std::mutex> lock(queueMutex_);
|
||||||
queueCV_.wait(lock, [this] {
|
queueCV_.wait(
|
||||||
return !loadQueue_.empty() || !asyncLoaderRunning_;
|
lock, [this] { return !loadQueue_.empty() || !asyncLoaderRunning_; });
|
||||||
});
|
|
||||||
|
|
||||||
if (!asyncLoaderRunning_) {
|
if (!asyncLoaderRunning_) {
|
||||||
break;
|
break;
|
||||||
|
|
@ -711,9 +835,8 @@ void AssetsModule::workerThreadLoop() {
|
||||||
|
|
||||||
if (task.textureCallback) {
|
if (task.textureCallback) {
|
||||||
std::lock_guard<std::mutex> callbackLock(callbackMutex_);
|
std::lock_guard<std::mutex> callbackLock(callbackMutex_);
|
||||||
completedCallbacks_.push_back([handle, callback = task.textureCallback]() {
|
completedCallbacks_.push_back(
|
||||||
callback(handle);
|
[handle, callback = task.textureCallback]() { callback(handle); });
|
||||||
});
|
|
||||||
}
|
}
|
||||||
} else if (task.type == LoadTask::Type::Shader) {
|
} else if (task.type == LoadTask::Type::Shader) {
|
||||||
Handle<Shader> handle;
|
Handle<Shader> handle;
|
||||||
|
|
@ -725,9 +848,8 @@ void AssetsModule::workerThreadLoop() {
|
||||||
|
|
||||||
if (task.shaderCallback) {
|
if (task.shaderCallback) {
|
||||||
std::lock_guard<std::mutex> callbackLock(callbackMutex_);
|
std::lock_guard<std::mutex> callbackLock(callbackMutex_);
|
||||||
completedCallbacks_.push_back([handle, callback = task.shaderCallback]() {
|
completedCallbacks_.push_back(
|
||||||
callback(handle);
|
[handle, callback = task.shaderCallback]() { callback(handle); });
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -742,7 +864,7 @@ void AssetsModule::processAsyncCallbacks() {
|
||||||
completedCallbacks_.clear();
|
completedCallbacks_.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto& callback : callbacks) {
|
for (auto &callback : callbacks) {
|
||||||
callback();
|
callback();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -751,7 +873,8 @@ void AssetsModule::processAsyncCallbacks() {
|
||||||
// 资源依赖跟踪
|
// 资源依赖跟踪
|
||||||
//===========================================================================
|
//===========================================================================
|
||||||
|
|
||||||
void AssetsModule::registerMaterialDependency(Handle<Material> material, Handle<Texture> texture) {
|
void AssetsModule::registerMaterialDependency(Handle<Material> material,
|
||||||
|
Handle<Texture> texture) {
|
||||||
if (!material.isValid() || !texture.isValid()) {
|
if (!material.isValid() || !texture.isValid()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -759,11 +882,12 @@ void AssetsModule::registerMaterialDependency(Handle<Material> material, Handle<
|
||||||
std::unique_lock<std::shared_mutex> lock(dependencyMutex_);
|
std::unique_lock<std::shared_mutex> lock(dependencyMutex_);
|
||||||
|
|
||||||
uint32_t textureIndex = texture.index();
|
uint32_t textureIndex = texture.index();
|
||||||
auto& info = textureDependencies_[textureIndex];
|
auto &info = textureDependencies_[textureIndex];
|
||||||
info.texture = texture;
|
info.texture = texture;
|
||||||
|
|
||||||
// 检查是否已存在
|
// 检查是否已存在
|
||||||
auto it = std::find(info.dependentMaterials.begin(), info.dependentMaterials.end(), material);
|
auto it = std::find(info.dependentMaterials.begin(),
|
||||||
|
info.dependentMaterials.end(), material);
|
||||||
if (it == info.dependentMaterials.end()) {
|
if (it == info.dependentMaterials.end()) {
|
||||||
info.dependentMaterials.push_back(material);
|
info.dependentMaterials.push_back(material);
|
||||||
E2D_LOG_DEBUG("Registered material {} dependency on texture {}",
|
E2D_LOG_DEBUG("Registered material {} dependency on texture {}",
|
||||||
|
|
@ -771,7 +895,8 @@ void AssetsModule::registerMaterialDependency(Handle<Material> material, Handle<
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AssetsModule::registerMaterialDependency(Handle<Material> material, Handle<Shader> shader) {
|
void AssetsModule::registerMaterialDependency(Handle<Material> material,
|
||||||
|
Handle<Shader> shader) {
|
||||||
if (!material.isValid() || !shader.isValid()) {
|
if (!material.isValid() || !shader.isValid()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -779,11 +904,12 @@ void AssetsModule::registerMaterialDependency(Handle<Material> material, Handle<
|
||||||
std::unique_lock<std::shared_mutex> lock(dependencyMutex_);
|
std::unique_lock<std::shared_mutex> lock(dependencyMutex_);
|
||||||
|
|
||||||
uint32_t shaderIndex = shader.index();
|
uint32_t shaderIndex = shader.index();
|
||||||
auto& info = shaderDependencies_[shaderIndex];
|
auto &info = shaderDependencies_[shaderIndex];
|
||||||
info.shader = shader;
|
info.shader = shader;
|
||||||
|
|
||||||
// 检查是否已存在
|
// 检查是否已存在
|
||||||
auto it = std::find(info.dependentMaterials.begin(), info.dependentMaterials.end(), material);
|
auto it = std::find(info.dependentMaterials.begin(),
|
||||||
|
info.dependentMaterials.end(), material);
|
||||||
if (it == info.dependentMaterials.end()) {
|
if (it == info.dependentMaterials.end()) {
|
||||||
info.dependentMaterials.push_back(material);
|
info.dependentMaterials.push_back(material);
|
||||||
E2D_LOG_DEBUG("Registered material {} dependency on shader {}",
|
E2D_LOG_DEBUG("Registered material {} dependency on shader {}",
|
||||||
|
|
@ -806,8 +932,8 @@ void AssetsModule::notifyTextureReloaded(Handle<Texture> texture) {
|
||||||
|
|
||||||
// 材质需要更新纹理引用
|
// 材质需要更新纹理引用
|
||||||
// 这里可以触发材质更新事件
|
// 这里可以触发材质更新事件
|
||||||
for (const auto& materialHandle : it->second.dependentMaterials) {
|
for (const auto &materialHandle : it->second.dependentMaterials) {
|
||||||
Material* material = materials_.get(materialHandle);
|
Material *material = materials_.get(materialHandle);
|
||||||
if (material) {
|
if (material) {
|
||||||
// 材质自动使用新的纹理数据
|
// 材质自动使用新的纹理数据
|
||||||
E2D_LOG_DEBUG("Material {} updated with reloaded texture",
|
E2D_LOG_DEBUG("Material {} updated with reloaded texture",
|
||||||
|
|
@ -830,8 +956,8 @@ void AssetsModule::notifyShaderReloaded(Handle<Shader> shader) {
|
||||||
E2D_LOG_INFO("Notifying {} materials of shader reload",
|
E2D_LOG_INFO("Notifying {} materials of shader reload",
|
||||||
it->second.dependentMaterials.size());
|
it->second.dependentMaterials.size());
|
||||||
|
|
||||||
for (const auto& materialHandle : it->second.dependentMaterials) {
|
for (const auto &materialHandle : it->second.dependentMaterials) {
|
||||||
Material* material = materials_.get(materialHandle);
|
Material *material = materials_.get(materialHandle);
|
||||||
if (material) {
|
if (material) {
|
||||||
// 更新材质的着色器引用
|
// 更新材质的着色器引用
|
||||||
material->setShader(getPtr(shader));
|
material->setShader(getPtr(shader));
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,44 @@
|
||||||
#include <assets/loaders/shader_loader.h>
|
#include <assets/loaders/shader_loader.h>
|
||||||
#include <filesystem>
|
#include <module/module_registry.h>
|
||||||
|
#include <platform/file_module.h>
|
||||||
#include <renderer/shader.h>
|
#include <renderer/shader.h>
|
||||||
#include <utils/logger.h>
|
#include <utils/logger.h>
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取 FileModule 实例
|
||||||
|
*/
|
||||||
|
FileModule *getFileModule() {
|
||||||
|
return getModule<FileModule>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查文件是否存在(使用 FileModule)
|
||||||
|
*/
|
||||||
|
bool fileExists(const std::string &path) {
|
||||||
|
FileModule *fileModule = getFileModule();
|
||||||
|
if (fileModule) {
|
||||||
|
return fileModule->exists(path);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 读取文件内容为字符串(使用 FileModule,支持 RomFS)
|
||||||
|
*/
|
||||||
|
std::string readFileToString(const std::string &path) {
|
||||||
|
FileModule *fileModule = getFileModule();
|
||||||
|
if (fileModule) {
|
||||||
|
return fileModule->readString(path);
|
||||||
|
}
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
} // anonymous namespace
|
||||||
|
|
||||||
Ptr<Shader> ShaderLoader::load(const std::string &path) {
|
Ptr<Shader> ShaderLoader::load(const std::string &path) {
|
||||||
std::string basePath = path;
|
std::string basePath = path;
|
||||||
|
|
||||||
|
|
@ -16,10 +50,10 @@ Ptr<Shader> ShaderLoader::load(const std::string &path) {
|
||||||
std::string vertPath = basePath + ".vert";
|
std::string vertPath = basePath + ".vert";
|
||||||
std::string fragPath = basePath + ".frag";
|
std::string fragPath = basePath + ".frag";
|
||||||
|
|
||||||
if (!std::filesystem::exists(vertPath)) {
|
if (!fileExists(vertPath)) {
|
||||||
vertPath = basePath + ".vs";
|
vertPath = basePath + ".vs";
|
||||||
}
|
}
|
||||||
if (!std::filesystem::exists(fragPath)) {
|
if (!fileExists(fragPath)) {
|
||||||
fragPath = basePath + ".fs";
|
fragPath = basePath + ".fs";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -28,10 +62,24 @@ Ptr<Shader> ShaderLoader::load(const std::string &path) {
|
||||||
|
|
||||||
Ptr<Shader> ShaderLoader::load(const std::string &vertPath,
|
Ptr<Shader> ShaderLoader::load(const std::string &vertPath,
|
||||||
const std::string &fragPath) {
|
const std::string &fragPath) {
|
||||||
Ptr<Shader> shader = makePtr<Shader>();
|
// 读取顶点着色器文件
|
||||||
|
std::string vsSource = readFileToString(vertPath);
|
||||||
|
if (vsSource.empty()) {
|
||||||
|
E2D_LOG_ERROR("ShaderLoader: Failed to read vertex shader: {}", vertPath);
|
||||||
|
return Ptr<Shader>();
|
||||||
|
}
|
||||||
|
|
||||||
if (!shader->loadFromFile(vertPath, fragPath)) {
|
// 读取片段着色器文件
|
||||||
E2D_LOG_ERROR("ShaderLoader: Failed to load shader {} + {}", vertPath,
|
std::string fsSource = readFileToString(fragPath);
|
||||||
|
if (fsSource.empty()) {
|
||||||
|
E2D_LOG_ERROR("ShaderLoader: Failed to read fragment shader: {}", fragPath);
|
||||||
|
return Ptr<Shader>();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从源码加载着色器
|
||||||
|
Ptr<Shader> shader = makePtr<Shader>();
|
||||||
|
if (!shader->loadFromSource(vsSource, fsSource)) {
|
||||||
|
E2D_LOG_ERROR("ShaderLoader: Failed to compile shader {} + {}", vertPath,
|
||||||
fragPath);
|
fragPath);
|
||||||
return Ptr<Shader>();
|
return Ptr<Shader>();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,4 +26,21 @@ std::vector<std::unique_ptr<Module>> ModuleRegistry::createModules() {
|
||||||
return modules;
|
return modules;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ModuleRegistry::storeModuleInstance(Module* module) {
|
||||||
|
if (!module) return;
|
||||||
|
|
||||||
|
// 通过模块名称查找对应的注册信息
|
||||||
|
for (const auto& info : registrations_) {
|
||||||
|
if (info.name == module->getName()) {
|
||||||
|
// 使用注册时存储的 type 作为键
|
||||||
|
instances_[info.type] = module;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ModuleRegistry::clearInstances() {
|
||||||
|
instances_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace extra2d
|
} // namespace extra2d
|
||||||
|
|
|
||||||
|
|
@ -1,31 +1,18 @@
|
||||||
#include <SDL.h>
|
#include <SDL.h>
|
||||||
|
#include <filesystem>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <platform/file_module.h>
|
#include <platform/file_module.h>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <sys/stat.h>
|
|
||||||
#include <utils/logger.h>
|
#include <utils/logger.h>
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
#include <direct.h>
|
|
||||||
#include <windows.h>
|
|
||||||
#define mkdir_impl(path, mode) _mkdir(path)
|
|
||||||
#elif defined(__SWITCH__)
|
|
||||||
#include <dirent.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
// Switch 使用 ::mkdir 避免与类成员函数冲突
|
|
||||||
#define mkdir_impl(path, mode) ::mkdir(path, mode)
|
|
||||||
#else
|
|
||||||
#include <dirent.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#define mkdir_impl(path, mode) mkdir(path, mode)
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef __SWITCH__
|
#ifdef __SWITCH__
|
||||||
#include <switch.h>
|
#include <switch.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
FileModule::FileModule() = default;
|
FileModule::FileModule() = default;
|
||||||
|
|
||||||
FileModule::~FileModule() = default;
|
FileModule::~FileModule() = default;
|
||||||
|
|
@ -44,11 +31,19 @@ bool FileModule::init() {
|
||||||
Result rc = romfsInit();
|
Result rc = romfsInit();
|
||||||
if (R_SUCCEEDED(rc)) {
|
if (R_SUCCEEDED(rc)) {
|
||||||
E2D_LOG_INFO("RomFS initialized successfully");
|
E2D_LOG_INFO("RomFS initialized successfully");
|
||||||
|
// 在 Switch 上设置资源根目录为 RomFS
|
||||||
|
assetRoot_ = "romfs:/";
|
||||||
} else {
|
} else {
|
||||||
E2D_LOG_WARN("romfsInit failed: {:#08X}", rc);
|
E2D_LOG_WARN("romfsInit failed: {:#08X}", rc);
|
||||||
|
// RomFS 初始化失败,使用当前目录
|
||||||
|
assetRoot_ = "./";
|
||||||
}
|
}
|
||||||
|
#else
|
||||||
|
// 非 Switch 平台,使用当前目录作为资源根目录
|
||||||
|
assetRoot_ = "./";
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
E2D_LOG_INFO("Asset root set to: {}", assetRoot_);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -59,16 +54,16 @@ void FileModule::shutdown() {
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool FileModule::isRomfsPath(const std::string &path) const {
|
||||||
|
return path.find(romfsRoot_) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
bool FileModule::exists(const std::string &path) const {
|
bool FileModule::exists(const std::string &path) const {
|
||||||
struct stat st;
|
return fs::exists(path);
|
||||||
return stat(path.c_str(), &st) == 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FileModule::isDir(const std::string &path) const {
|
bool FileModule::isDir(const std::string &path) const {
|
||||||
struct stat st;
|
return fs::is_directory(path);
|
||||||
if (stat(path.c_str(), &st) != 0)
|
|
||||||
return false;
|
|
||||||
return (st.st_mode & S_IFDIR) != 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
FileData FileModule::read(const std::string &path) const {
|
FileData FileModule::read(const std::string &path) const {
|
||||||
|
|
@ -105,6 +100,12 @@ std::string FileModule::readString(const std::string &path) const {
|
||||||
|
|
||||||
bool FileModule::write(const std::string &path, const void *data,
|
bool FileModule::write(const std::string &path, const void *data,
|
||||||
size_t size) const {
|
size_t size) const {
|
||||||
|
// RomFS 是只读的,不允许写入
|
||||||
|
if (isRomfsPath(path)) {
|
||||||
|
E2D_LOG_WARN("Cannot write to RomFS path: {}", path);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
std::ofstream file(path, std::ios::binary);
|
std::ofstream file(path, std::ios::binary);
|
||||||
if (!file.is_open())
|
if (!file.is_open())
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -121,6 +122,12 @@ bool FileModule::writeString(const std::string &path,
|
||||||
|
|
||||||
bool FileModule::append(const std::string &path, const void *data,
|
bool FileModule::append(const std::string &path, const void *data,
|
||||||
size_t size) const {
|
size_t size) const {
|
||||||
|
// RomFS 是只读的,不允许写入
|
||||||
|
if (isRomfsPath(path)) {
|
||||||
|
E2D_LOG_WARN("Cannot append to RomFS path: {}", path);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
std::ofstream file(path, std::ios::binary | std::ios::app);
|
std::ofstream file(path, std::ios::binary | std::ios::app);
|
||||||
if (!file.is_open())
|
if (!file.is_open())
|
||||||
return false;
|
return false;
|
||||||
|
|
@ -131,105 +138,69 @@ bool FileModule::append(const std::string &path, const void *data,
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FileModule::remove(const std::string &path) const {
|
bool FileModule::remove(const std::string &path) const {
|
||||||
return std::remove(path.c_str()) == 0;
|
// RomFS 是只读的,不允许删除
|
||||||
|
if (isRomfsPath(path)) {
|
||||||
|
E2D_LOG_WARN("Cannot remove RomFS path: {}", path);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return fs::remove(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool FileModule::mkdir(const std::string &path) const {
|
bool FileModule::mkdir(const std::string &path) const {
|
||||||
#ifdef _WIN32
|
// RomFS 是只读的,不允许创建目录
|
||||||
return mkdir_impl(path.c_str(), 0755) == 0 || errno == EEXIST;
|
if (isRomfsPath(path)) {
|
||||||
#else
|
E2D_LOG_WARN("Cannot create directory in RomFS: {}", path);
|
||||||
return mkdir_impl(path.c_str(), 0755) == 0 || errno == EEXIST;
|
return false;
|
||||||
#endif
|
}
|
||||||
|
|
||||||
|
return fs::create_directories(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<FileInfo> FileModule::listDir(const std::string &path) const {
|
std::vector<FileInfo> FileModule::listDir(const std::string &path) const {
|
||||||
std::vector<FileInfo> result;
|
std::vector<FileInfo> result;
|
||||||
|
|
||||||
#ifdef _WIN32
|
if (!fs::exists(path) || !fs::is_directory(path)) {
|
||||||
WIN32_FIND_DATAA findData;
|
|
||||||
std::string searchPath = path + "\\*";
|
|
||||||
HANDLE hFind = FindFirstFileA(searchPath.c_str(), &findData);
|
|
||||||
|
|
||||||
if (hFind == INVALID_HANDLE_VALUE)
|
|
||||||
return result;
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
do {
|
for (const auto &entry : fs::directory_iterator(path)) {
|
||||||
std::string name = findData.cFileName;
|
std::string name = entry.path().filename().string();
|
||||||
if (name == "." || name == "..")
|
if (name == "." || name == "..")
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
FileInfo info;
|
FileInfo info;
|
||||||
info.name = name;
|
info.name = name;
|
||||||
info.path = join(path, name);
|
info.path = entry.path().string();
|
||||||
info.isDir = (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
|
info.isDir = entry.is_directory();
|
||||||
if (!info.isDir) {
|
if (!info.isDir && entry.is_regular_file()) {
|
||||||
info.size = static_cast<int64>(findData.nFileSizeLow) |
|
info.size = static_cast<int64>(entry.file_size());
|
||||||
(static_cast<int64>(findData.nFileSizeHigh) << 32);
|
|
||||||
}
|
|
||||||
result.push_back(info);
|
|
||||||
} while (FindNextFileA(hFind, &findData));
|
|
||||||
|
|
||||||
FindClose(hFind);
|
|
||||||
#else
|
|
||||||
DIR *dir = opendir(path.c_str());
|
|
||||||
if (!dir)
|
|
||||||
return result;
|
|
||||||
|
|
||||||
struct dirent *entry;
|
|
||||||
while ((entry = readdir(dir)) != nullptr) {
|
|
||||||
std::string name = entry->d_name;
|
|
||||||
if (name == "." || name == "..")
|
|
||||||
continue;
|
|
||||||
|
|
||||||
FileInfo info;
|
|
||||||
info.name = name;
|
|
||||||
info.path = join(path, name);
|
|
||||||
info.isDir = isDir(info.path);
|
|
||||||
if (!info.isDir) {
|
|
||||||
info.size = fileSize(info.path);
|
|
||||||
}
|
}
|
||||||
result.push_back(info);
|
result.push_back(info);
|
||||||
}
|
}
|
||||||
|
|
||||||
closedir(dir);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
int64 FileModule::fileSize(const std::string &path) const {
|
int64 FileModule::fileSize(const std::string &path) const {
|
||||||
struct stat st;
|
if (!fs::exists(path) || !fs::is_regular_file(path)) {
|
||||||
if (stat(path.c_str(), &st) != 0)
|
|
||||||
return -1;
|
return -1;
|
||||||
return static_cast<int64>(st.st_size);
|
}
|
||||||
|
return static_cast<int64>(fs::file_size(path));
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string FileModule::ext(const std::string &path) const {
|
std::string FileModule::ext(const std::string &path) const {
|
||||||
size_t pos = path.find_last_of('.');
|
fs::path p(path);
|
||||||
if (pos == std::string::npos || pos == 0)
|
return p.extension().string();
|
||||||
return "";
|
|
||||||
|
|
||||||
size_t lastSep = path.find_last_of("/\\");
|
|
||||||
if (lastSep != std::string::npos && pos < lastSep)
|
|
||||||
return "";
|
|
||||||
|
|
||||||
return path.substr(pos + 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string FileModule::fileName(const std::string &path) const {
|
std::string FileModule::fileName(const std::string &path) const {
|
||||||
size_t pos = path.find_last_of("/\\");
|
fs::path p(path);
|
||||||
if (pos == std::string::npos)
|
return p.filename().string();
|
||||||
return path;
|
|
||||||
return path.substr(pos + 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string FileModule::dirName(const std::string &path) const {
|
std::string FileModule::dirName(const std::string &path) const {
|
||||||
size_t pos = path.find_last_of("/\\");
|
fs::path p(path);
|
||||||
if (pos == std::string::npos)
|
return p.parent_path().string();
|
||||||
return ".";
|
|
||||||
if (pos == 0)
|
|
||||||
return "/";
|
|
||||||
return path.substr(0, pos);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string FileModule::join(const std::string &a, const std::string &b) const {
|
std::string FileModule::join(const std::string &a, const std::string &b) const {
|
||||||
|
|
@ -238,11 +209,9 @@ std::string FileModule::join(const std::string &a, const std::string &b) const {
|
||||||
if (b.empty())
|
if (b.empty())
|
||||||
return a;
|
return a;
|
||||||
|
|
||||||
char last = a.back();
|
fs::path base(a);
|
||||||
if (last == '/' || last == '\\') {
|
fs::path sub(b);
|
||||||
return a + b;
|
return (base / sub).string();
|
||||||
}
|
|
||||||
return a + "/" + b;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::string FileModule::writableDir() const { return writableDir_; }
|
std::string FileModule::writableDir() const { return writableDir_; }
|
||||||
|
|
|
||||||
|
|
@ -12,64 +12,6 @@ InputModule::~InputModule() {
|
||||||
shutdown();
|
shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
InputModule::InputModule(InputModule&& other) noexcept
|
|
||||||
: mouseX_(other.mouseX_)
|
|
||||||
, mouseY_(other.mouseY_)
|
|
||||||
, mouseWheel_(other.mouseWheel_)
|
|
||||||
, activeTouches_(std::move(other.activeTouches_))
|
|
||||||
, endedTouches_(std::move(other.endedTouches_))
|
|
||||||
, onKeyDown_(std::move(other.onKeyDown_))
|
|
||||||
, onKeyUp_(std::move(other.onKeyUp_))
|
|
||||||
, onMouseDown_(std::move(other.onMouseDown_))
|
|
||||||
, onMouseUp_(std::move(other.onMouseUp_))
|
|
||||||
, onTouchBegan_(std::move(other.onTouchBegan_))
|
|
||||||
, onTouchMoved_(std::move(other.onTouchMoved_))
|
|
||||||
, onTouchEnded_(std::move(other.onTouchEnded_)) {
|
|
||||||
std::memcpy(keyState_.data(), other.keyState_.data(), KEY_COUNT);
|
|
||||||
std::memcpy(keyPrev_.data(), other.keyPrev_.data(), KEY_COUNT);
|
|
||||||
std::memcpy(mouseState_.data(), other.mouseState_.data(), static_cast<size_t>(MouseBtn::Count));
|
|
||||||
std::memcpy(mousePrev_.data(), other.mousePrev_.data(), static_cast<size_t>(MouseBtn::Count));
|
|
||||||
std::memcpy(gamepads_, other.gamepads_, sizeof(gamepads_));
|
|
||||||
std::memcpy(padState_, other.padState_, sizeof(padState_));
|
|
||||||
std::memcpy(padPrev_, other.padPrev_, sizeof(padPrev_));
|
|
||||||
|
|
||||||
for (int32 i = 0; i < MAX_GAMEPADS; ++i) {
|
|
||||||
other.gamepads_[i] = nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
InputModule& InputModule::operator=(InputModule&& other) noexcept {
|
|
||||||
if (this != &other) {
|
|
||||||
shutdown();
|
|
||||||
|
|
||||||
mouseX_ = other.mouseX_;
|
|
||||||
mouseY_ = other.mouseY_;
|
|
||||||
mouseWheel_ = other.mouseWheel_;
|
|
||||||
activeTouches_ = std::move(other.activeTouches_);
|
|
||||||
endedTouches_ = std::move(other.endedTouches_);
|
|
||||||
onKeyDown_ = std::move(other.onKeyDown_);
|
|
||||||
onKeyUp_ = std::move(other.onKeyUp_);
|
|
||||||
onMouseDown_ = std::move(other.onMouseDown_);
|
|
||||||
onMouseUp_ = std::move(other.onMouseUp_);
|
|
||||||
onTouchBegan_ = std::move(other.onTouchBegan_);
|
|
||||||
onTouchMoved_ = std::move(other.onTouchMoved_);
|
|
||||||
onTouchEnded_ = std::move(other.onTouchEnded_);
|
|
||||||
|
|
||||||
std::memcpy(keyState_.data(), other.keyState_.data(), KEY_COUNT);
|
|
||||||
std::memcpy(keyPrev_.data(), other.keyPrev_.data(), KEY_COUNT);
|
|
||||||
std::memcpy(mouseState_.data(), other.mouseState_.data(), static_cast<size_t>(MouseBtn::Count));
|
|
||||||
std::memcpy(mousePrev_.data(), other.mousePrev_.data(), static_cast<size_t>(MouseBtn::Count));
|
|
||||||
std::memcpy(gamepads_, other.gamepads_, sizeof(gamepads_));
|
|
||||||
std::memcpy(padState_, other.padState_, sizeof(padState_));
|
|
||||||
std::memcpy(padPrev_, other.padPrev_, sizeof(padPrev_));
|
|
||||||
|
|
||||||
for (int32 i = 0; i < MAX_GAMEPADS; ++i) {
|
|
||||||
other.gamepads_[i] = nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool InputModule::init() {
|
bool InputModule::init() {
|
||||||
std::memset(keyState_.data(), 0, KEY_COUNT);
|
std::memset(keyState_.data(), 0, KEY_COUNT);
|
||||||
std::memset(keyPrev_.data(), 0, KEY_COUNT);
|
std::memset(keyPrev_.data(), 0, KEY_COUNT);
|
||||||
|
|
|
||||||
|
|
@ -4,39 +4,12 @@
|
||||||
#include <platform/window_module.h>
|
#include <platform/window_module.h>
|
||||||
#include <utils/logger.h>
|
#include <utils/logger.h>
|
||||||
|
|
||||||
// OpenGL ES 3.2 with GLAD
|
|
||||||
#include <glad/glad.h>
|
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
||||||
WindowModule::WindowModule() = default;
|
WindowModule::WindowModule() = default;
|
||||||
|
|
||||||
WindowModule::~WindowModule() { shutdown(); }
|
WindowModule::~WindowModule() { shutdown(); }
|
||||||
|
|
||||||
WindowModule::WindowModule(WindowModule &&other) noexcept
|
|
||||||
: window_(other.window_), glContext_(other.glContext_),
|
|
||||||
shouldClose_(other.shouldClose_), onClose_(std::move(other.onClose_)),
|
|
||||||
onResize_(std::move(other.onResize_)),
|
|
||||||
configListener_(std::move(other.configListener_)) {
|
|
||||||
other.window_ = nullptr;
|
|
||||||
other.glContext_ = nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
WindowModule &WindowModule::operator=(WindowModule &&other) noexcept {
|
|
||||||
if (this != &other) {
|
|
||||||
shutdown();
|
|
||||||
window_ = other.window_;
|
|
||||||
glContext_ = other.glContext_;
|
|
||||||
shouldClose_ = other.shouldClose_;
|
|
||||||
onClose_ = std::move(other.onClose_);
|
|
||||||
onResize_ = std::move(other.onResize_);
|
|
||||||
configListener_ = std::move(other.configListener_);
|
|
||||||
other.window_ = nullptr;
|
|
||||||
other.glContext_ = nullptr;
|
|
||||||
}
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool WindowModule::init() {
|
bool WindowModule::init() {
|
||||||
if (SDL_InitSubSystem(SDL_INIT_VIDEO) != 0) {
|
if (SDL_InitSubSystem(SDL_INIT_VIDEO) != 0) {
|
||||||
E2D_LOG_ERROR("Failed to initialize SDL video: {}", SDL_GetError());
|
E2D_LOG_ERROR("Failed to initialize SDL video: {}", SDL_GetError());
|
||||||
|
|
@ -58,7 +31,10 @@ bool WindowModule::init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void WindowModule::shutdown() {
|
void WindowModule::shutdown() {
|
||||||
destroyGLContext();
|
// 清理事件监听器
|
||||||
|
configListener_.reset();
|
||||||
|
renderPresentListener_.reset();
|
||||||
|
|
||||||
if (window_) {
|
if (window_) {
|
||||||
SDL_DestroyWindow(window_);
|
SDL_DestroyWindow(window_);
|
||||||
window_ = nullptr;
|
window_ = nullptr;
|
||||||
|
|
@ -163,14 +139,8 @@ void WindowModule::onModuleConfig(const AppConfig &config) {
|
||||||
cfg.resizable = config.resizable;
|
cfg.resizable = config.resizable;
|
||||||
|
|
||||||
if (create(cfg)) {
|
if (create(cfg)) {
|
||||||
// 创建 OpenGL 上下文
|
// 发送窗口显示事件
|
||||||
if (!createGLContext()) {
|
// RHI 模块会监听此事件并创建 OpenGL 上下文
|
||||||
E2D_LOG_ERROR("Failed to create OpenGL context");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// GL 上下文创建完成后,再发送窗口显示事件
|
|
||||||
// 这样渲染模块可以在收到事件时安全地使用 GL 函数
|
|
||||||
events::OnShow::emit();
|
events::OnShow::emit();
|
||||||
|
|
||||||
setVisible(true);
|
setVisible(true);
|
||||||
|
|
@ -240,56 +210,9 @@ bool WindowModule::isVisible() const {
|
||||||
return (flags & SDL_WINDOW_SHOWN) != 0;
|
return (flags & SDL_WINDOW_SHOWN) != 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool WindowModule::createGLContext() {
|
|
||||||
if (!window_) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 创建 OpenGL ES 3.2 上下文
|
|
||||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
|
|
||||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
|
|
||||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);
|
|
||||||
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
|
|
||||||
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
|
|
||||||
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
|
|
||||||
|
|
||||||
glContext_ = SDL_GL_CreateContext(window_);
|
|
||||||
if (!glContext_) {
|
|
||||||
E2D_LOG_ERROR("Failed to create OpenGL ES context: {}", SDL_GetError());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 初始化 GLAD
|
|
||||||
if (!gladLoadGLES2Loader((GLADloadproc)SDL_GL_GetProcAddress)) {
|
|
||||||
E2D_LOG_ERROR("Failed to initialize GLAD");
|
|
||||||
SDL_GL_DeleteContext(glContext_);
|
|
||||||
glContext_ = nullptr;
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
E2D_LOG_INFO("OpenGL ES context created: {}.{}", GLVersion.major,
|
|
||||||
GLVersion.minor);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void WindowModule::destroyGLContext() {
|
|
||||||
if (glContext_) {
|
|
||||||
SDL_GL_DeleteContext(glContext_);
|
|
||||||
glContext_ = nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void WindowModule::swapBuffers() {
|
|
||||||
if (window_) {
|
|
||||||
SDL_GL_SwapWindow(window_);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void WindowModule::onRenderPresent() {
|
void WindowModule::onRenderPresent() {
|
||||||
swapBuffers();
|
// 交换缓冲区由 RHI 模块处理
|
||||||
|
// 这里可以触发呈现事件
|
||||||
}
|
}
|
||||||
|
|
||||||
void *WindowModule::getGLContext() const { return glContext_; }
|
|
||||||
|
|
||||||
} // namespace extra2d
|
} // namespace extra2d
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,553 @@
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstring>
|
||||||
|
#include <vector>
|
||||||
|
#include <renderer/command_queue.h>
|
||||||
|
#include <renderer/material.h>
|
||||||
|
#include <renderer/mesh.h>
|
||||||
|
#include <renderer/rhi_module.h>
|
||||||
|
#include <renderer/rhi/opengl/gl_command_list.h>
|
||||||
|
#include <renderer/rhi/opengl/gl_texture.h>
|
||||||
|
#include <types/math/transform.h>
|
||||||
|
#include <types/ptr/intrusive_ptr.h>
|
||||||
|
#include <utils/logger.h>
|
||||||
|
|
||||||
|
#include <glm/gtc/matrix_transform.hpp>
|
||||||
|
#include <glm/gtc/type_ptr.hpp>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// CommandSorter 实现
|
||||||
|
// ========================================
|
||||||
|
|
||||||
|
CommandSorter::CommandSorter() {
|
||||||
|
// 预分配初始容量
|
||||||
|
commands_.reserve(INITIAL_CAPACITY);
|
||||||
|
sortedIndices_.reserve(INITIAL_CAPACITY);
|
||||||
|
}
|
||||||
|
|
||||||
|
CommandSorter::~CommandSorter() = default;
|
||||||
|
|
||||||
|
CommandSorter::CommandSorter(CommandSorter&& other) noexcept
|
||||||
|
: commands_(std::move(other.commands_)),
|
||||||
|
sortedIndices_(std::move(other.sortedIndices_)),
|
||||||
|
commandCount_(other.commandCount_) {
|
||||||
|
other.commandCount_ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
CommandSorter& CommandSorter::operator=(CommandSorter&& other) noexcept {
|
||||||
|
if (this != &other) {
|
||||||
|
commands_ = std::move(other.commands_);
|
||||||
|
sortedIndices_ = std::move(other.sortedIndices_);
|
||||||
|
commandCount_ = other.commandCount_;
|
||||||
|
other.commandCount_ = 0;
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t CommandSorter::addCommand(const DrawCommand &cmd) {
|
||||||
|
uint32_t index = commandCount_;
|
||||||
|
|
||||||
|
// 如果缓冲区不够大,扩展它
|
||||||
|
if (index >= commands_.size()) {
|
||||||
|
commands_.push_back(cmd);
|
||||||
|
sortedIndices_.push_back(index);
|
||||||
|
} else {
|
||||||
|
commands_[index] = cmd;
|
||||||
|
sortedIndices_[index] = index;
|
||||||
|
}
|
||||||
|
|
||||||
|
commandCount_++;
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CommandSorter::sort() {
|
||||||
|
if (commandCount_ == 0) return;
|
||||||
|
|
||||||
|
// 只排序有效范围的索引
|
||||||
|
auto indicesBegin = sortedIndices_.begin();
|
||||||
|
auto indicesEnd = sortedIndices_.begin() + commandCount_;
|
||||||
|
|
||||||
|
// 使用稳定排序保持相同键的命令顺序
|
||||||
|
std::stable_sort(indicesBegin, indicesEnd,
|
||||||
|
[this](uint32_t a, uint32_t b) {
|
||||||
|
return commands_[a].key < commands_[b].key;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void CommandSorter::clear() {
|
||||||
|
// 重置计数器,保留内存
|
||||||
|
commandCount_ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CommandSorter::reserve(uint32_t capacity) {
|
||||||
|
commands_.reserve(capacity);
|
||||||
|
sortedIndices_.reserve(capacity);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// CommandBatcher 实现
|
||||||
|
// ========================================
|
||||||
|
|
||||||
|
void CommandBatcher::process(const CommandSorter &sorter) {
|
||||||
|
clear();
|
||||||
|
sorter_ = &sorter;
|
||||||
|
|
||||||
|
if (sorter.getCount() == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
CommandBatch currentBatch;
|
||||||
|
currentBatch.startIndex = 0;
|
||||||
|
currentBatch.count = 1;
|
||||||
|
|
||||||
|
const auto &firstCmd = sorter.getCommand(0);
|
||||||
|
currentBatch.pipeline = firstCmd.pipeline;
|
||||||
|
currentBatch.textureCount = firstCmd.textureCount;
|
||||||
|
for (uint32_t i = 0; i < firstCmd.textureCount; ++i) {
|
||||||
|
currentBatch.textures[i] = firstCmd.textures[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 遍历所有命令,合并兼容的命令
|
||||||
|
for (uint32_t i = 1; i < sorter.getCount(); ++i) {
|
||||||
|
const auto &cmd = sorter.getCommand(i);
|
||||||
|
|
||||||
|
if (currentBatch.isCompatibleWith(cmd)) {
|
||||||
|
// 兼容,加入当前批次
|
||||||
|
currentBatch.count++;
|
||||||
|
} else {
|
||||||
|
// 不兼容,保存当前批次并创建新批次
|
||||||
|
batches_.push_back(currentBatch);
|
||||||
|
|
||||||
|
currentBatch.startIndex = i;
|
||||||
|
currentBatch.count = 1;
|
||||||
|
currentBatch.pipeline = cmd.pipeline;
|
||||||
|
currentBatch.textureCount = cmd.textureCount;
|
||||||
|
for (uint32_t j = 0; j < cmd.textureCount; ++j) {
|
||||||
|
currentBatch.textures[j] = cmd.textures[j];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存最后一个批次
|
||||||
|
batches_.push_back(currentBatch);
|
||||||
|
}
|
||||||
|
|
||||||
|
const DrawCommand &CommandBatcher::getCommand(uint32_t batchIndex,
|
||||||
|
uint32_t cmdIndex) const {
|
||||||
|
const auto &batch = batches_[batchIndex];
|
||||||
|
return sorter_->getCommand(batch.startIndex + cmdIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CommandBatcher::clear() {
|
||||||
|
batches_.clear();
|
||||||
|
sorter_ = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// CommandQueue 实现
|
||||||
|
// ========================================
|
||||||
|
|
||||||
|
CommandQueue::CommandQueue() = default;
|
||||||
|
|
||||||
|
CommandQueue::~CommandQueue() { shutdown(); }
|
||||||
|
|
||||||
|
CommandQueue::CommandQueue(CommandQueue &&other) noexcept
|
||||||
|
: sorter_(std::move(other.sorter_)), batcher_(std::move(other.batcher_)),
|
||||||
|
context_(other.context_), commandList_(std::move(other.commandList_)),
|
||||||
|
uboManager_(std::move(other.uboManager_)),
|
||||||
|
globalUBOData_(other.globalUBOData_),
|
||||||
|
materialUBOData_(std::move(other.materialUBOData_)),
|
||||||
|
nextMaterialId_(other.nextMaterialId_),
|
||||||
|
materialIds_(std::move(other.materialIds_)),
|
||||||
|
currentMaterialUBO_(other.currentMaterialUBO_),
|
||||||
|
materialUBOBufferOffset_(other.materialUBOBufferOffset_) {
|
||||||
|
other.context_ = nullptr;
|
||||||
|
other.globalUBOData_ = {};
|
||||||
|
other.nextMaterialId_ = 1;
|
||||||
|
other.currentMaterialUBO_ = nullptr;
|
||||||
|
other.materialUBOBufferOffset_ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
CommandQueue &CommandQueue::operator=(CommandQueue &&other) noexcept {
|
||||||
|
if (this != &other) {
|
||||||
|
shutdown();
|
||||||
|
|
||||||
|
sorter_ = std::move(other.sorter_);
|
||||||
|
batcher_ = std::move(other.batcher_);
|
||||||
|
context_ = other.context_;
|
||||||
|
commandList_ = std::move(other.commandList_);
|
||||||
|
uboManager_ = std::move(other.uboManager_);
|
||||||
|
globalUBOData_ = other.globalUBOData_;
|
||||||
|
materialUBOData_ = std::move(other.materialUBOData_);
|
||||||
|
nextMaterialId_ = other.nextMaterialId_;
|
||||||
|
materialIds_ = std::move(other.materialIds_);
|
||||||
|
currentMaterialUBO_ = other.currentMaterialUBO_;
|
||||||
|
materialUBOBufferOffset_ = other.materialUBOBufferOffset_;
|
||||||
|
|
||||||
|
other.context_ = nullptr;
|
||||||
|
other.globalUBOData_ = {};
|
||||||
|
other.nextMaterialId_ = 1;
|
||||||
|
other.currentMaterialUBO_ = nullptr;
|
||||||
|
other.materialUBOBufferOffset_ = 0;
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool CommandQueue::initialize() {
|
||||||
|
// 获取 RHI 模块
|
||||||
|
auto *rhiModule = RHIModule::get();
|
||||||
|
if (!rhiModule) {
|
||||||
|
E2D_LOG_ERROR("RHIModule not available");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto *device = rhiModule->getDevice();
|
||||||
|
if (!device) {
|
||||||
|
E2D_LOG_ERROR("RHIDevice not available");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
context_ = rhiModule->getContext();
|
||||||
|
if (!context_) {
|
||||||
|
E2D_LOG_ERROR("RHIContext not available");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建命令列表
|
||||||
|
commandList_ = device->createCommandList();
|
||||||
|
if (!commandList_) {
|
||||||
|
E2D_LOG_ERROR("Failed to create command list");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化 UBO 管理器
|
||||||
|
uboManager_ = std::make_unique<UniformBufferManager>();
|
||||||
|
if (!uboManager_->initialize()) {
|
||||||
|
E2D_LOG_ERROR("Failed to initialize UniformBufferManager");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 预分配材质 UBO 数据缓冲区
|
||||||
|
materialUBOData_.reserve(1024 * 1024); // 1MB
|
||||||
|
|
||||||
|
E2D_LOG_INFO("CommandQueue initialized");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CommandQueue::shutdown() {
|
||||||
|
uboManager_.reset();
|
||||||
|
commandList_.reset();
|
||||||
|
context_ = nullptr;
|
||||||
|
materialUBOData_.clear();
|
||||||
|
E2D_LOG_INFO("CommandQueue shutdown");
|
||||||
|
}
|
||||||
|
|
||||||
|
void CommandQueue::beginFrame() {
|
||||||
|
sorter_.clear();
|
||||||
|
batcher_.clear();
|
||||||
|
materialIds_.clear();
|
||||||
|
nextMaterialId_ = 1;
|
||||||
|
|
||||||
|
// 重置材质 UBO 缓冲区(保留容量,避免重新分配)
|
||||||
|
materialUBOBufferOffset_ = 0;
|
||||||
|
currentMaterialUBO_ = nullptr;
|
||||||
|
|
||||||
|
// 重置 UBO 管理器的材质 UBO 池
|
||||||
|
if (uboManager_) {
|
||||||
|
uboManager_->resetMaterialUBOs();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置统计
|
||||||
|
stats_ = {};
|
||||||
|
|
||||||
|
// 开始录制命令
|
||||||
|
if (commandList_) {
|
||||||
|
commandList_->begin();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CommandQueue::endFrame() {
|
||||||
|
// 结束录制
|
||||||
|
if (commandList_) {
|
||||||
|
commandList_->end();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t CommandQueue::getMaterialId(Material *material) {
|
||||||
|
auto it = materialIds_.find(material);
|
||||||
|
if (it != materialIds_.end()) {
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t id = nextMaterialId_++;
|
||||||
|
materialIds_[material] = id;
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<UniformBuffer*, uint32_t> CommandQueue::allocateMaterialUBO(uint32_t size) {
|
||||||
|
if (!uboManager_ || size == 0) {
|
||||||
|
return {nullptr, 0};
|
||||||
|
}
|
||||||
|
|
||||||
|
// OpenGL 要求 UBO 偏移对齐到 GL_UNIFORM_BUFFER_OFFSET_ALIGNMENT (通常是 256)
|
||||||
|
static constexpr uint32_t UBO_ALIGNMENT = 256;
|
||||||
|
|
||||||
|
// 使用批量更新机制:先分配到 CPU 缓冲区
|
||||||
|
uint32_t offset = materialUBOBufferOffset_;
|
||||||
|
// 对齐到 UBO_ALIGNMENT 字节
|
||||||
|
offset = (offset + UBO_ALIGNMENT - 1) & ~(UBO_ALIGNMENT - 1);
|
||||||
|
|
||||||
|
uint32_t alignedSize = (size + UBO_ALIGNMENT - 1) & ~(UBO_ALIGNMENT - 1);
|
||||||
|
|
||||||
|
// 检查 CPU 缓冲区是否有足够空间
|
||||||
|
if (offset + alignedSize > materialUBOData_.capacity()) {
|
||||||
|
// 如果缓冲区已满,先刷新到 GPU
|
||||||
|
flushMaterialUBOToGPU();
|
||||||
|
offset = 0; // 重置偏移量
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确保缓冲区足够大
|
||||||
|
if (offset + alignedSize > materialUBOData_.size()) {
|
||||||
|
materialUBOData_.resize(offset + alignedSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
materialUBOBufferOffset_ = offset + alignedSize;
|
||||||
|
|
||||||
|
return {nullptr, offset}; // 返回 nullptr 表示使用 CPU 缓冲区,offset 是 CPU 缓冲区偏移
|
||||||
|
}
|
||||||
|
|
||||||
|
void CommandQueue::flushMaterialUBOToGPU() {
|
||||||
|
if (materialUBOBufferOffset_ == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取一个足够大的 UBO
|
||||||
|
currentMaterialUBO_ = uboManager_->acquireMaterialUBO(materialUBOBufferOffset_);
|
||||||
|
if (!currentMaterialUBO_) {
|
||||||
|
E2D_LOG_ERROR("Failed to acquire material UBO for flush");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量更新到 GPU
|
||||||
|
currentMaterialUBO_->update(materialUBOData_.data(), materialUBOBufferOffset_, 0);
|
||||||
|
|
||||||
|
// 重置 CPU 缓冲区偏移
|
||||||
|
materialUBOBufferOffset_ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
UniformBuffer* CommandQueue::getCurrentMaterialUBO() const {
|
||||||
|
return currentMaterialUBO_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void CommandQueue::submitDraw(Ptr<Material> material, Ptr<Mesh> mesh,
|
||||||
|
const struct Transform &transform,
|
||||||
|
const Color &color) {
|
||||||
|
if (!material || !mesh || !material->getShader()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DrawCommand cmd;
|
||||||
|
|
||||||
|
// 构建排序键
|
||||||
|
uint32_t materialId = getMaterialId(material.get());
|
||||||
|
cmd.key = DrawKey::make(materialId, 0, 0);
|
||||||
|
|
||||||
|
// 设置管线
|
||||||
|
cmd.pipeline = material->getPipeline();
|
||||||
|
|
||||||
|
// 设置网格数据
|
||||||
|
cmd.vertexBuffer = mesh->getVertexBuffer();
|
||||||
|
cmd.indexBuffer = mesh->getIndexBuffer();
|
||||||
|
cmd.vertexCount = mesh->getVertexCount();
|
||||||
|
cmd.indexCount = mesh->getIndexCount();
|
||||||
|
|
||||||
|
// 设置变换和颜色
|
||||||
|
float matrixData[16];
|
||||||
|
transform.toMatrix(matrixData);
|
||||||
|
cmd.modelMatrix = glm::make_mat4(matrixData);
|
||||||
|
cmd.color = color;
|
||||||
|
|
||||||
|
// 设置纹理
|
||||||
|
const auto &textures = material->getTextures();
|
||||||
|
cmd.textureCount =
|
||||||
|
static_cast<uint32_t>(std::min(textures.size(), size_t(8)));
|
||||||
|
for (uint32_t i = 0; i < cmd.textureCount; ++i) {
|
||||||
|
if (textures[i].texture) {
|
||||||
|
cmd.textures[i] = textures[i].texture->getHandle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 分配材质 UBO 空间(使用 CPU 缓冲区)
|
||||||
|
uint32_t materialDataSize = material->getDataSize();
|
||||||
|
if (materialDataSize > 0) {
|
||||||
|
auto [ubo, offset] = allocateMaterialUBO(materialDataSize);
|
||||||
|
(void)ubo; // ubo 为 nullptr,使用 CPU 缓冲区
|
||||||
|
|
||||||
|
// 复制材质数据到 CPU 缓冲区,并修改颜色
|
||||||
|
if (offset + materialDataSize <= materialUBOData_.size()) {
|
||||||
|
std::memcpy(materialUBOData_.data() + offset, material->getData(), materialDataSize);
|
||||||
|
|
||||||
|
// 将实例颜色应用到 UBO 数据中的 uColor 参数
|
||||||
|
auto layout = material->getLayout();
|
||||||
|
if (layout) {
|
||||||
|
const auto* param = layout->getParam("uColor");
|
||||||
|
if (param && param->type == MaterialParamType::Color) {
|
||||||
|
std::memcpy(materialUBOData_.data() + offset + param->offset, &color.r, sizeof(float) * 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cmd.materialUBOOffset = offset;
|
||||||
|
cmd.materialUBOSize = materialDataSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
sorter_.addCommand(cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CommandQueue::submitClear(const Color &color, uint32_t flags) {
|
||||||
|
// 清除命令直接执行,不加入排序队列
|
||||||
|
if (!commandList_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
commandList_->clear(static_cast<ClearFlags>(flags), color, 1.0f, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CommandQueue::setViewport(int32_t x, int32_t y, int32_t width,
|
||||||
|
int32_t height) {
|
||||||
|
if (!commandList_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Viewport viewport;
|
||||||
|
viewport.x = static_cast<float>(x);
|
||||||
|
viewport.y = static_cast<float>(y);
|
||||||
|
viewport.width = static_cast<float>(width);
|
||||||
|
viewport.height = static_cast<float>(height);
|
||||||
|
viewport.minDepth = 0.0f;
|
||||||
|
viewport.maxDepth = 1.0f;
|
||||||
|
|
||||||
|
commandList_->setViewport(viewport);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CommandQueue::updateGlobalUBO(const Mat4& viewProjection, float deltaTime,
|
||||||
|
uint32_t screenWidth, uint32_t screenHeight, uint32_t frameIndex) {
|
||||||
|
if (!uboManager_) {
|
||||||
|
E2D_LOG_WARN("CommandQueue::updateGlobalUBO: uboManager is null");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 填充全局 UBO 数据
|
||||||
|
std::memcpy(globalUBOData_.viewProjection, glm::value_ptr(viewProjection), sizeof(float) * 16);
|
||||||
|
globalUBOData_.cameraPosition[0] = 0.0f;
|
||||||
|
globalUBOData_.cameraPosition[1] = 0.0f;
|
||||||
|
globalUBOData_.cameraPosition[2] = 0.0f;
|
||||||
|
globalUBOData_.cameraPosition[3] = 1.0f;
|
||||||
|
globalUBOData_.time = 0.0f; // TODO: 传递实际时间
|
||||||
|
globalUBOData_.deltaTime = deltaTime;
|
||||||
|
globalUBOData_.screenSize[0] = static_cast<float>(screenWidth);
|
||||||
|
globalUBOData_.screenSize[1] = static_cast<float>(screenHeight);
|
||||||
|
|
||||||
|
// 使用双缓冲更新 UBO
|
||||||
|
uboManager_->updateGlobalUBO(&globalUBOData_, sizeof(globalUBOData_), frameIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CommandQueue::execute(uint32_t frameIndex) {
|
||||||
|
if (!commandList_) {
|
||||||
|
E2D_LOG_ERROR("CommandQueue::execute: commandList is null");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 在排序前刷新材质 UBO 数据到 GPU
|
||||||
|
flushMaterialUBOToGPU();
|
||||||
|
|
||||||
|
// 排序命令
|
||||||
|
sorter_.sort();
|
||||||
|
|
||||||
|
// 批处理
|
||||||
|
batcher_.process(sorter_);
|
||||||
|
|
||||||
|
// 执行批次
|
||||||
|
for (uint32_t i = 0; i < batcher_.getBatchCount(); ++i) {
|
||||||
|
executeBatch(i, batcher_.getBatch(i), frameIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交命令到 GPU
|
||||||
|
commandList_->submit();
|
||||||
|
}
|
||||||
|
|
||||||
|
void CommandQueue::executeBatch(uint32_t batchIndex, const CommandBatch &batch, uint32_t frameIndex) {
|
||||||
|
if (!commandList_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 绑定管线
|
||||||
|
if (batch.pipeline.isValid()) {
|
||||||
|
commandList_->setPipeline(batch.pipeline.get());
|
||||||
|
stats_.pipelineBinds++;
|
||||||
|
} else {
|
||||||
|
E2D_LOG_WARN("Batch has no valid pipeline!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 绑定全局 UBO (binding = 0) - 使用双缓冲
|
||||||
|
if (uboManager_) {
|
||||||
|
UniformBuffer* globalUBO = uboManager_->getGlobalUBO(frameIndex);
|
||||||
|
if (globalUBO) {
|
||||||
|
commandList_->setUniformBuffer(0, globalUBO->getRHIBuffer());
|
||||||
|
stats_.bufferBinds++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 绑定纹理
|
||||||
|
if (batch.textureCount > 0) {
|
||||||
|
for (uint32_t i = 0; i < batch.textureCount; ++i) {
|
||||||
|
if (batch.textures[i].isValid()) {
|
||||||
|
commandList_->setTexture(i, batch.textures[i].get());
|
||||||
|
stats_.textureBinds++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行批次中的所有命令
|
||||||
|
for (uint32_t i = 0; i < batch.count; ++i) {
|
||||||
|
const auto &cmd = batcher_.getCommand(batchIndex, i);
|
||||||
|
|
||||||
|
// 绑定顶点缓冲区
|
||||||
|
if (cmd.vertexBuffer.isValid()) {
|
||||||
|
commandList_->setVertexBuffer(0, cmd.vertexBuffer.get(), 0);
|
||||||
|
stats_.bufferBinds++;
|
||||||
|
} else {
|
||||||
|
E2D_LOG_WARN("Draw command has no valid vertex buffer!");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 绑定索引缓冲区(如果有)
|
||||||
|
if (cmd.isIndexed() && cmd.indexBuffer.isValid()) {
|
||||||
|
commandList_->setIndexBuffer(cmd.indexBuffer.get(), IndexType::UInt16, 0);
|
||||||
|
stats_.bufferBinds++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 绑定材质 UBO (binding = 1)
|
||||||
|
if (cmd.materialUBOSize > 0 && currentMaterialUBO_) {
|
||||||
|
commandList_->setUniformBuffer(1, currentMaterialUBO_->getRHIBuffer(), cmd.materialUBOOffset, cmd.materialUBOSize);
|
||||||
|
stats_.bufferBinds++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置模型矩阵
|
||||||
|
commandList_->setUniform("uModelMatrix", cmd.modelMatrix);
|
||||||
|
|
||||||
|
// 绘制
|
||||||
|
if (cmd.isIndexed()) {
|
||||||
|
commandList_->drawIndexed(cmd.indexCount, 0, 0, 1, 0);
|
||||||
|
stats_.drawCalls++;
|
||||||
|
stats_.triangles += cmd.indexCount / 3;
|
||||||
|
} else {
|
||||||
|
commandList_->draw(cmd.vertexCount, 0, 1, 0);
|
||||||
|
stats_.drawCalls++;
|
||||||
|
stats_.triangles += cmd.vertexCount / 3;
|
||||||
|
}
|
||||||
|
stats_.vertices += cmd.vertexCount;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,358 @@
|
||||||
|
#include <renderer/instance_buffer.h>
|
||||||
|
#include <renderer/rhi_module.h>
|
||||||
|
#include <renderer/rhi/rhi_device.h>
|
||||||
|
#include <utils/logger.h>
|
||||||
|
#include <cstring>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// InstanceBuffer 实现
|
||||||
|
// ========================================
|
||||||
|
|
||||||
|
InstanceBuffer::InstanceBuffer() = default;
|
||||||
|
|
||||||
|
InstanceBuffer::~InstanceBuffer() {
|
||||||
|
shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
InstanceBuffer::InstanceBuffer(InstanceBuffer&& other) noexcept
|
||||||
|
: bufferHandle_(std::move(other.bufferHandle_))
|
||||||
|
, maxInstances_(other.maxInstances_)
|
||||||
|
, instanceCount_(other.instanceCount_)
|
||||||
|
, cpuBuffer_(std::move(other.cpuBuffer_))
|
||||||
|
, dirtyRanges_(std::move(other.dirtyRanges_)) {
|
||||||
|
other.maxInstances_ = 0;
|
||||||
|
other.instanceCount_ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
InstanceBuffer& InstanceBuffer::operator=(InstanceBuffer&& other) noexcept {
|
||||||
|
if (this != &other) {
|
||||||
|
shutdown();
|
||||||
|
|
||||||
|
bufferHandle_ = std::move(other.bufferHandle_);
|
||||||
|
maxInstances_ = other.maxInstances_;
|
||||||
|
instanceCount_ = other.instanceCount_;
|
||||||
|
cpuBuffer_ = std::move(other.cpuBuffer_);
|
||||||
|
dirtyRanges_ = std::move(other.dirtyRanges_);
|
||||||
|
|
||||||
|
other.maxInstances_ = 0;
|
||||||
|
other.instanceCount_ = 0;
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InstanceBuffer::initialize(uint32_t maxInstances) {
|
||||||
|
shutdown();
|
||||||
|
|
||||||
|
if (maxInstances == 0) {
|
||||||
|
E2D_LOG_ERROR("InstanceBuffer::initialize: maxInstances must be > 0");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* rhiModule = RHIModule::get();
|
||||||
|
if (!rhiModule || !rhiModule->getDevice()) {
|
||||||
|
E2D_LOG_ERROR("InstanceBuffer::initialize: RHIModule not available");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* device = rhiModule->getDevice();
|
||||||
|
|
||||||
|
// 创建实例数据缓冲区
|
||||||
|
BufferDesc desc;
|
||||||
|
desc.type = BufferType::Vertex;
|
||||||
|
desc.usage = BufferUsage::Dynamic;
|
||||||
|
desc.size = maxInstances * sizeof(InstanceData);
|
||||||
|
|
||||||
|
auto buffer = device->createBuffer(desc);
|
||||||
|
if (!buffer) {
|
||||||
|
E2D_LOG_ERROR("InstanceBuffer::initialize: Failed to create buffer");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bufferHandle_ = BufferHandle(buffer.release());
|
||||||
|
maxInstances_ = maxInstances;
|
||||||
|
instanceCount_ = 0;
|
||||||
|
cpuBuffer_.reserve(maxInstances);
|
||||||
|
dirtyRanges_.clear();
|
||||||
|
dirtyRanges_.reserve(MAX_DIRTY_RANGES);
|
||||||
|
|
||||||
|
E2D_LOG_DEBUG("InstanceBuffer initialized with capacity for {} instances", maxInstances);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void InstanceBuffer::shutdown() {
|
||||||
|
bufferHandle_ = BufferHandle();
|
||||||
|
maxInstances_ = 0;
|
||||||
|
instanceCount_ = 0;
|
||||||
|
cpuBuffer_.clear();
|
||||||
|
cpuBuffer_.shrink_to_fit();
|
||||||
|
dirtyRanges_.clear();
|
||||||
|
dirtyRanges_.shrink_to_fit();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InstanceBuffer::updateInstances(const InstanceData* instances, uint32_t count) {
|
||||||
|
if (!bufferHandle_.isValid() || !instances) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (count > maxInstances_) {
|
||||||
|
E2D_LOG_WARN("InstanceBuffer::updateInstances: count {} exceeds maxInstances {}",
|
||||||
|
count, maxInstances_);
|
||||||
|
count = maxInstances_;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 复制到CPU缓冲区
|
||||||
|
if (count > 0) {
|
||||||
|
if (cpuBuffer_.size() < count) {
|
||||||
|
cpuBuffer_.resize(count);
|
||||||
|
}
|
||||||
|
std::memcpy(cpuBuffer_.data(), instances, count * sizeof(InstanceData));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 标记整个范围为脏
|
||||||
|
markAllDirty();
|
||||||
|
|
||||||
|
// 立即上传到GPU
|
||||||
|
bool result = updateGPU();
|
||||||
|
|
||||||
|
instanceCount_ = count;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InstanceBuffer::updateInstancesRange(const InstanceData* instances, uint32_t start, uint32_t count) {
|
||||||
|
if (!bufferHandle_.isValid() || !instances || count == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (start >= maxInstances_) {
|
||||||
|
E2D_LOG_WARN("InstanceBuffer::updateInstancesRange: start {} exceeds maxInstances {}",
|
||||||
|
start, maxInstances_);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (start + count > maxInstances_) {
|
||||||
|
E2D_LOG_WARN("InstanceBuffer::updateInstancesRange: range {}-{} exceeds maxInstances {}",
|
||||||
|
start, start + count, maxInstances_);
|
||||||
|
count = maxInstances_ - start;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 确保CPU缓冲区足够大
|
||||||
|
if (cpuBuffer_.size() < start + count) {
|
||||||
|
cpuBuffer_.resize(start + count);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 复制到CPU缓冲区
|
||||||
|
std::memcpy(cpuBuffer_.data() + start, instances, count * sizeof(InstanceData));
|
||||||
|
|
||||||
|
// 标记为脏
|
||||||
|
markDirty(start, count);
|
||||||
|
|
||||||
|
// 更新实例数量
|
||||||
|
if (start + count > instanceCount_) {
|
||||||
|
instanceCount_ = start + count;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t InstanceBuffer::addInstance(const InstanceData& instance) {
|
||||||
|
if (instanceCount_ >= maxInstances_) {
|
||||||
|
E2D_LOG_WARN("InstanceBuffer::addInstance: buffer full (max {})", maxInstances_);
|
||||||
|
return UINT32_MAX;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t index = instanceCount_++;
|
||||||
|
|
||||||
|
// 添加到 CPU 缓冲区
|
||||||
|
if (index >= cpuBuffer_.size()) {
|
||||||
|
cpuBuffer_.push_back(instance);
|
||||||
|
} else {
|
||||||
|
cpuBuffer_[index] = instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 标记为脏(单个实例)
|
||||||
|
markDirty(index, 1);
|
||||||
|
|
||||||
|
return index;
|
||||||
|
}
|
||||||
|
|
||||||
|
void InstanceBuffer::markDirty(uint32_t start, uint32_t count) {
|
||||||
|
if (count == 0 || start >= maxInstances_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 限制范围
|
||||||
|
if (start + count > maxInstances_) {
|
||||||
|
count = maxInstances_ - start;
|
||||||
|
}
|
||||||
|
|
||||||
|
addDirtyRange(DirtyRange(start, count));
|
||||||
|
}
|
||||||
|
|
||||||
|
void InstanceBuffer::markAllDirty() {
|
||||||
|
dirtyRanges_.clear();
|
||||||
|
if (instanceCount_ > 0) {
|
||||||
|
dirtyRanges_.push_back(DirtyRange(0, instanceCount_));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InstanceBuffer::updateGPU() {
|
||||||
|
if (!bufferHandle_.isValid() || dirtyRanges_.empty()) {
|
||||||
|
return true; // 没有脏数据,无需更新
|
||||||
|
}
|
||||||
|
|
||||||
|
RHIBuffer* rhiBuffer = bufferHandle_.get();
|
||||||
|
if (!rhiBuffer) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果脏区域太多,合并为全量更新
|
||||||
|
if (dirtyRanges_.size() > MAX_DIRTY_RANGES) {
|
||||||
|
mergeAllDirtyRanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上传所有脏区域
|
||||||
|
for (const auto& range : dirtyRanges_) {
|
||||||
|
if (!range.isValid()) continue;
|
||||||
|
|
||||||
|
const uint8_t* data = reinterpret_cast<const uint8_t*>(cpuBuffer_.data());
|
||||||
|
uint32_t offset = range.start * sizeof(InstanceData);
|
||||||
|
uint32_t size = range.count * sizeof(InstanceData);
|
||||||
|
|
||||||
|
rhiBuffer->update(data + offset, size, offset);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清空脏区域列表
|
||||||
|
clearDirtyRanges();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void InstanceBuffer::clear() {
|
||||||
|
instanceCount_ = 0;
|
||||||
|
cpuBuffer_.clear();
|
||||||
|
clearDirtyRanges();
|
||||||
|
}
|
||||||
|
|
||||||
|
void InstanceBuffer::addDirtyRange(const DirtyRange& range) {
|
||||||
|
if (!range.isValid()) return;
|
||||||
|
|
||||||
|
// 检查是否可以与现有区域合并
|
||||||
|
for (auto& existing : dirtyRanges_) {
|
||||||
|
if (existing.canMerge(range)) {
|
||||||
|
existing.merge(range);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 添加新区域
|
||||||
|
dirtyRanges_.push_back(range);
|
||||||
|
|
||||||
|
// 如果脏区域太多,合并所有区域
|
||||||
|
if (dirtyRanges_.size() > MAX_DIRTY_RANGES) {
|
||||||
|
mergeAllDirtyRanges();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void InstanceBuffer::mergeAllDirtyRanges() {
|
||||||
|
if (dirtyRanges_.empty()) return;
|
||||||
|
|
||||||
|
// 找到最小起始和最大结束
|
||||||
|
uint32_t minStart = dirtyRanges_[0].start;
|
||||||
|
uint32_t maxEnd = dirtyRanges_[0].end();
|
||||||
|
|
||||||
|
for (const auto& range : dirtyRanges_) {
|
||||||
|
if (range.start < minStart) minStart = range.start;
|
||||||
|
if (range.end() > maxEnd) maxEnd = range.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
dirtyRanges_.clear();
|
||||||
|
dirtyRanges_.push_back(DirtyRange(minStart, maxEnd - minStart));
|
||||||
|
}
|
||||||
|
|
||||||
|
void InstanceBuffer::clearDirtyRanges() {
|
||||||
|
dirtyRanges_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// InstanceBufferManager 实现
|
||||||
|
// ========================================
|
||||||
|
|
||||||
|
InstanceBufferManager::InstanceBufferManager() = default;
|
||||||
|
|
||||||
|
InstanceBufferManager::~InstanceBufferManager() {
|
||||||
|
shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
InstanceBufferManager::InstanceBufferManager(InstanceBufferManager&& other) noexcept
|
||||||
|
: bufferPool_(std::move(other.bufferPool_))
|
||||||
|
, currentBufferIndex_(other.currentBufferIndex_) {
|
||||||
|
other.currentBufferIndex_ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
InstanceBufferManager& InstanceBufferManager::operator=(InstanceBufferManager&& other) noexcept {
|
||||||
|
if (this != &other) {
|
||||||
|
shutdown();
|
||||||
|
|
||||||
|
bufferPool_ = std::move(other.bufferPool_);
|
||||||
|
currentBufferIndex_ = other.currentBufferIndex_;
|
||||||
|
|
||||||
|
other.currentBufferIndex_ = 0;
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool InstanceBufferManager::initialize() {
|
||||||
|
// 预分配一些缓冲区
|
||||||
|
bufferPool_.reserve(4);
|
||||||
|
E2D_LOG_INFO("InstanceBufferManager initialized");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void InstanceBufferManager::shutdown() {
|
||||||
|
bufferPool_.clear();
|
||||||
|
currentBufferIndex_ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
InstanceBuffer* InstanceBufferManager::acquireBuffer(uint32_t minSize) {
|
||||||
|
// 查找现有缓冲区
|
||||||
|
for (uint32_t i = currentBufferIndex_; i < bufferPool_.size(); ++i) {
|
||||||
|
if (bufferPool_[i]->getMaxInstances() >= minSize) {
|
||||||
|
currentBufferIndex_ = i + 1;
|
||||||
|
bufferPool_[i]->clear();
|
||||||
|
return bufferPool_[i].get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建新缓冲区
|
||||||
|
auto buffer = std::make_unique<InstanceBuffer>();
|
||||||
|
uint32_t allocSize = std::max(minSize, DEFAULT_BUFFER_SIZE);
|
||||||
|
|
||||||
|
if (!buffer->initialize(allocSize)) {
|
||||||
|
E2D_LOG_ERROR("InstanceBufferManager::acquireBuffer: Failed to create buffer");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
InstanceBuffer* ptr = buffer.get();
|
||||||
|
bufferPool_.push_back(std::move(buffer));
|
||||||
|
currentBufferIndex_ = bufferPool_.size();
|
||||||
|
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void InstanceBufferManager::releaseBuffer(InstanceBuffer* buffer) {
|
||||||
|
// 简单实现:不清除缓冲区,只是重置索引
|
||||||
|
// 实际实现可能需要更复杂的池管理
|
||||||
|
(void)buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
void InstanceBufferManager::reset() {
|
||||||
|
currentBufferIndex_ = 0;
|
||||||
|
for (auto& buffer : bufferPool_) {
|
||||||
|
buffer->clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -21,12 +21,20 @@ void MaterialLayout::addParam(const std::string& name, MaterialParamType type) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 检查是否已存在
|
||||||
|
if (paramIndexMap_.find(name) != paramIndexMap_.end()) {
|
||||||
|
E2D_LOG_WARN("Param '{}' already exists in MaterialLayout", name);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
MaterialParamInfo info;
|
MaterialParamInfo info;
|
||||||
info.type = type;
|
info.type = type;
|
||||||
info.size = getMaterialParamSize(type);
|
info.size = getMaterialParamSize(type);
|
||||||
info.offset = 0; // 将在 finalize 时计算
|
info.offset = 0; // 将在 finalize 时计算
|
||||||
|
|
||||||
params_[name] = info;
|
size_t index = params_.size();
|
||||||
|
params_.emplace_back(name, info);
|
||||||
|
paramIndexMap_[name] = index;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -36,6 +44,7 @@ void MaterialLayout::addParam(const std::string& name, MaterialParamType type) {
|
||||||
* - 标量和向量:偏移必须是其大小的倍数
|
* - 标量和向量:偏移必须是其大小的倍数
|
||||||
* - 数组和结构体:偏移必须是 16 的倍数
|
* - 数组和结构体:偏移必须是 16 的倍数
|
||||||
* - vec3 后面需要填充到 16 字节边界
|
* - vec3 后面需要填充到 16 字节边界
|
||||||
|
* - 整个结构体大小必须是16的倍数
|
||||||
*/
|
*/
|
||||||
void MaterialLayout::finalize() {
|
void MaterialLayout::finalize() {
|
||||||
if (finalized_) return;
|
if (finalized_) return;
|
||||||
|
|
@ -44,7 +53,7 @@ void MaterialLayout::finalize() {
|
||||||
for (auto& pair : params_) {
|
for (auto& pair : params_) {
|
||||||
auto& info = pair.second;
|
auto& info = pair.second;
|
||||||
|
|
||||||
// 计算对齐要求
|
// 计算对齐要求 - std140规则
|
||||||
uint32_t alignment = 4; // 默认 4 字节对齐
|
uint32_t alignment = 4; // 默认 4 字节对齐
|
||||||
|
|
||||||
switch (info.type) {
|
switch (info.type) {
|
||||||
|
|
@ -60,21 +69,21 @@ void MaterialLayout::finalize() {
|
||||||
alignment = 16; // vec3/vec4/color: 16 字节对齐(std140 规则)
|
alignment = 16; // vec3/vec4/color: 16 字节对齐(std140 规则)
|
||||||
break;
|
break;
|
||||||
case MaterialParamType::Mat4:
|
case MaterialParamType::Mat4:
|
||||||
alignment = 16; // mat4: 16 字节对齐
|
alignment = 16; // mat4: 16 字节对齐,每列vec4对齐到16字节
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
alignment = 4;
|
alignment = 4;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 对齐偏移
|
// 对齐偏移到alignment的倍数
|
||||||
offset = (offset + alignment - 1) & ~(alignment - 1);
|
offset = (offset + alignment - 1) & ~(alignment - 1);
|
||||||
|
|
||||||
info.offset = offset;
|
info.offset = offset;
|
||||||
offset += info.size;
|
offset += info.size;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 最终大小对齐到 16 字节
|
// 最终大小对齐到 16 字节(std140要求结构体总大小是16的倍数)
|
||||||
bufferSize_ = (offset + 15) & ~15;
|
bufferSize_ = (offset + 15) & ~15;
|
||||||
finalized_ = true;
|
finalized_ = true;
|
||||||
}
|
}
|
||||||
|
|
@ -85,9 +94,9 @@ void MaterialLayout::finalize() {
|
||||||
* @return 参数信息指针,不存在返回 nullptr
|
* @return 参数信息指针,不存在返回 nullptr
|
||||||
*/
|
*/
|
||||||
const MaterialParamInfo* MaterialLayout::getParam(const std::string& name) const {
|
const MaterialParamInfo* MaterialLayout::getParam(const std::string& name) const {
|
||||||
auto it = params_.find(name);
|
auto mapIt = paramIndexMap_.find(name);
|
||||||
if (it != params_.end()) {
|
if (mapIt != paramIndexMap_.end()) {
|
||||||
return &it->second;
|
return ¶ms_[mapIt->second].second;
|
||||||
}
|
}
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
@ -117,6 +126,17 @@ void Material::setShader(Ptr<Shader> shader) {
|
||||||
shader_ = shader;
|
shader_ = shader;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取 RHI 管线句柄
|
||||||
|
* @return RHI 管线句柄
|
||||||
|
*/
|
||||||
|
PipelineHandle Material::getPipeline() const {
|
||||||
|
if (shader_) {
|
||||||
|
return shader_->getPipeline();
|
||||||
|
}
|
||||||
|
return PipelineHandle{};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 设置 float 参数
|
* @brief 设置 float 参数
|
||||||
* @param name 参数名称
|
* @param name 参数名称
|
||||||
|
|
@ -194,14 +214,14 @@ void Material::setMat4(const std::string& name, const float* value) {
|
||||||
/**
|
/**
|
||||||
* @brief 设置纹理
|
* @brief 设置纹理
|
||||||
* @param uniformName 着色器中的采样器 uniform 名称
|
* @param uniformName 着色器中的采样器 uniform 名称
|
||||||
* @param texture 纹理句柄
|
* @param texture 纹理
|
||||||
* @param slot 纹理槽位(0-15)
|
* @param slot 纹理槽位(0-15)
|
||||||
*/
|
*/
|
||||||
void Material::setTexture(const std::string& uniformName, Handle<Texture> texture, uint32_t slot) {
|
void Material::setTexture(const std::string& uniformName, Ptr<Texture> texture, uint32_t slot) {
|
||||||
// 查找是否已存在相同名称的纹理
|
// 查找是否已存在相同名称的纹理
|
||||||
for (auto& texSlot : textures_) {
|
for (auto& texSlot : textures_) {
|
||||||
if (texSlot.uniformName == uniformName) {
|
if (texSlot.uniformName == uniformName) {
|
||||||
texSlot.handle = texture;
|
texSlot.texture = texture;
|
||||||
texSlot.slot = slot;
|
texSlot.slot = slot;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -218,31 +238,4 @@ void Material::clearTextures() {
|
||||||
textures_.clear();
|
textures_.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 应用材质
|
|
||||||
*
|
|
||||||
* 绑定着色器、上传 UBO 数据
|
|
||||||
* @param uboManager UBO 管理器
|
|
||||||
*/
|
|
||||||
void Material::apply(UniformBufferManager& uboManager) {
|
|
||||||
if (!shader_) return;
|
|
||||||
|
|
||||||
// 绑定着色器
|
|
||||||
shader_->bind();
|
|
||||||
|
|
||||||
// 设置 Uniform Block 绑定
|
|
||||||
shader_->setUniformBlock("GlobalUBO", GLOBAL_UBO_BINDING);
|
|
||||||
shader_->setUniformBlock("MaterialUBO", MATERIAL_UBO_BINDING);
|
|
||||||
shader_->setUniformBlock("InstanceUBO", INSTANCE_UBO_BINDING);
|
|
||||||
|
|
||||||
// 上传材质数据到 UBO
|
|
||||||
if (!data_.empty()) {
|
|
||||||
auto* ubo = uboManager.acquireMaterialUBO(static_cast<uint32_t>(data_.size()));
|
|
||||||
if (ubo) {
|
|
||||||
ubo->update(data_.data(), static_cast<uint32_t>(data_.size()));
|
|
||||||
ubo->bind(MATERIAL_UBO_BINDING);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace extra2d
|
} // namespace extra2d
|
||||||
|
|
|
||||||
|
|
@ -1,150 +1,143 @@
|
||||||
#include <renderer/mesh.h>
|
#include <renderer/mesh.h>
|
||||||
|
#include <renderer/rhi_module.h>
|
||||||
#include <utils/logger.h>
|
#include <utils/logger.h>
|
||||||
#include <glad/glad.h>
|
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
||||||
Mesh::Mesh() = default;
|
Mesh::Mesh() = default;
|
||||||
|
|
||||||
Mesh::~Mesh() {
|
Mesh::~Mesh() {
|
||||||
if (vao_ != 0) {
|
// BufferHandle 是轻量级句柄,不需要显式释放
|
||||||
glDeleteVertexArrays(1, &vao_);
|
// 实际的缓冲区资源由 RHI 设备管理
|
||||||
vao_ = 0;
|
vertexBuffer_ = BufferHandle();
|
||||||
}
|
indexBuffer_ = BufferHandle();
|
||||||
if (vbo_ != 0) {
|
|
||||||
glDeleteBuffers(1, &vbo_);
|
|
||||||
vbo_ = 0;
|
|
||||||
}
|
|
||||||
if (ibo_ != 0) {
|
|
||||||
glDeleteBuffers(1, &ibo_);
|
|
||||||
ibo_ = 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Mesh::setVertices(const Vertex* vertices, uint32_t count) {
|
void Mesh::setVertices(const Vertex *vertices, uint32_t count) {
|
||||||
if (vao_ == 0) {
|
if (!vertices || count == 0)
|
||||||
glGenVertexArrays(1, &vao_);
|
return;
|
||||||
glGenBuffers(1, &vbo_);
|
|
||||||
|
|
||||||
E2D_LOG_INFO("Created VAO: {}, VBO: {}", vao_, vbo_);
|
// 获取 RHI 模块
|
||||||
|
auto *rhiModule = RHIModule::get();
|
||||||
|
if (!rhiModule) {
|
||||||
|
E2D_LOG_ERROR("RHIModule not available");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
glBindVertexArray(vao_);
|
auto *device = rhiModule->getDevice();
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, vbo_);
|
if (!device) {
|
||||||
|
E2D_LOG_ERROR("RHIDevice not available");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t dataSize = count * sizeof(Vertex);
|
||||||
|
|
||||||
|
// 如果缓冲区已存在且容量足够,更新数据
|
||||||
|
if (vertexBuffer_.isValid() && vertexCapacity_ >= count) {
|
||||||
|
// 通过句柄获取缓冲区指针并更新数据
|
||||||
|
// 注意:这里假设可以通过某种方式获取缓冲区指针
|
||||||
|
// 实际实现可能需要通过 RHI 命令列表来更新
|
||||||
|
E2D_LOG_WARN("Vertex buffer update not implemented yet");
|
||||||
|
} else {
|
||||||
|
// 需要创建新缓冲区
|
||||||
|
vertexBuffer_ = BufferHandle();
|
||||||
|
|
||||||
|
// 创建顶点缓冲区描述
|
||||||
|
BufferDesc desc = BufferDesc::vertex(dataSize, BufferUsage::Dynamic);
|
||||||
|
desc.initialData = vertices;
|
||||||
|
|
||||||
|
// 创建缓冲区
|
||||||
|
auto buffer = device->createBuffer(desc);
|
||||||
|
if (!buffer) {
|
||||||
|
E2D_LOG_ERROR("Failed to create vertex buffer");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取缓冲区的句柄
|
||||||
|
// 注意:createBuffer 返回的是 unique_ptr,我们需要存储句柄
|
||||||
|
// 这里直接使用缓冲区指针作为句柄,然后释放所有权让 RHI 内部管理
|
||||||
|
vertexBuffer_ = BufferHandle(buffer.release());
|
||||||
|
|
||||||
// 如果容量不足,重新分配
|
|
||||||
if (count > vertexCapacity_) {
|
|
||||||
glBufferData(GL_ARRAY_BUFFER, count * sizeof(Vertex), vertices, GL_DYNAMIC_DRAW);
|
|
||||||
vertexCapacity_ = count;
|
vertexCapacity_ = count;
|
||||||
} else {
|
|
||||||
glBufferSubData(GL_ARRAY_BUFFER, 0, count * sizeof(Vertex), vertices);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置顶点属性
|
|
||||||
// 位置 (location = 0)
|
|
||||||
glEnableVertexAttribArray(0);
|
|
||||||
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex),
|
|
||||||
reinterpret_cast<void*>(offsetof(Vertex, position)));
|
|
||||||
|
|
||||||
// 纹理坐标 (location = 1)
|
|
||||||
glEnableVertexAttribArray(1);
|
|
||||||
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex),
|
|
||||||
reinterpret_cast<void*>(offsetof(Vertex, texCoord)));
|
|
||||||
|
|
||||||
// 颜色 (location = 2)
|
|
||||||
glEnableVertexAttribArray(2);
|
|
||||||
glVertexAttribPointer(2, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex),
|
|
||||||
reinterpret_cast<void*>(offsetof(Vertex, color)));
|
|
||||||
|
|
||||||
glBindVertexArray(0);
|
|
||||||
vertexCount_ = count;
|
vertexCount_ = count;
|
||||||
|
E2D_LOG_INFO("Set {} vertices", count);
|
||||||
E2D_LOG_INFO("Set {} vertices for VAO {}", count, vao_);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Mesh::setIndices(const uint16_t* indices, uint32_t count) {
|
void Mesh::setIndices(const uint16_t *indices, uint32_t count) {
|
||||||
if (vao_ == 0) return;
|
if (!indices || count == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
if (ibo_ == 0) {
|
// 获取 RHI 模块
|
||||||
glGenBuffers(1, &ibo_);
|
auto *rhiModule = RHIModule::get();
|
||||||
|
if (!rhiModule) {
|
||||||
|
E2D_LOG_ERROR("RHIModule not available");
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
glBindVertexArray(vao_);
|
auto *device = rhiModule->getDevice();
|
||||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo_);
|
if (!device) {
|
||||||
|
E2D_LOG_ERROR("RHIDevice not available");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t dataSize = count * sizeof(uint16_t);
|
||||||
|
|
||||||
|
// 如果缓冲区已存在且容量足够,更新数据
|
||||||
|
if (indexBuffer_.isValid() && indexCapacity_ >= count) {
|
||||||
|
E2D_LOG_WARN("Index buffer update not implemented yet");
|
||||||
|
} else {
|
||||||
|
// 需要创建新缓冲区
|
||||||
|
indexBuffer_ = BufferHandle();
|
||||||
|
|
||||||
|
// 创建索引缓冲区描述
|
||||||
|
BufferDesc desc = BufferDesc::index(dataSize, BufferUsage::Static);
|
||||||
|
desc.initialData = indices;
|
||||||
|
|
||||||
|
// 创建缓冲区
|
||||||
|
auto buffer = device->createBuffer(desc);
|
||||||
|
if (!buffer) {
|
||||||
|
E2D_LOG_ERROR("Failed to create index buffer");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取缓冲区的句柄
|
||||||
|
indexBuffer_ = BufferHandle(buffer.release());
|
||||||
|
|
||||||
// 如果容量不足,重新分配
|
|
||||||
if (count > indexCapacity_) {
|
|
||||||
glBufferData(GL_ELEMENT_ARRAY_BUFFER, count * sizeof(uint16_t), indices, GL_STATIC_DRAW);
|
|
||||||
indexCapacity_ = count;
|
indexCapacity_ = count;
|
||||||
} else {
|
|
||||||
glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, count * sizeof(uint16_t), indices);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
glBindVertexArray(0);
|
|
||||||
indexCount_ = count;
|
indexCount_ = count;
|
||||||
|
E2D_LOG_INFO("Set {} indices", count);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Mesh::updateVertices(const Vertex* vertices, uint32_t count, uint32_t offset) {
|
void Mesh::updateVertices(const Vertex *vertices, uint32_t count,
|
||||||
if (vao_ == 0 || vbo_ == 0 || vertices == nullptr) return;
|
uint32_t offset) {
|
||||||
|
if (!vertices || count == 0 || !vertexBuffer_.isValid())
|
||||||
|
return;
|
||||||
|
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, vbo_);
|
if (offset + count > vertexCount_) {
|
||||||
glBufferSubData(GL_ARRAY_BUFFER, offset * sizeof(Vertex),
|
E2D_LOG_WARN("Vertex update out of bounds");
|
||||||
count * sizeof(Vertex), vertices);
|
return;
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
}
|
||||||
|
|
||||||
|
// 获取 RHI 模块
|
||||||
|
auto *rhiModule = RHIModule::get();
|
||||||
|
if (!rhiModule)
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto *device = rhiModule->getDevice();
|
||||||
|
if (!device)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// 通过句柄获取缓冲区并更新数据
|
||||||
|
// 注意:这里需要 RHI 提供更新缓冲区的方法
|
||||||
|
// 暂时记录为未实现
|
||||||
|
E2D_LOG_WARN("updateVertices not fully implemented yet");
|
||||||
}
|
}
|
||||||
|
|
||||||
void Mesh::bind() const {
|
Ptr<Mesh> Mesh::createQuad(const Vec2 &size, const Rect &uv) {
|
||||||
if (vao_ != 0) {
|
|
||||||
glBindVertexArray(vao_);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Mesh::unbind() const {
|
|
||||||
glBindVertexArray(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Mesh::draw() const {
|
|
||||||
if (vao_ == 0) return;
|
|
||||||
|
|
||||||
// 检查 OpenGL 错误
|
|
||||||
GLenum err = glGetError();
|
|
||||||
if (err != GL_NO_ERROR) {
|
|
||||||
static bool loggedOnce = false;
|
|
||||||
if (!loggedOnce) {
|
|
||||||
E2D_LOG_ERROR("OpenGL error before draw: {}", err);
|
|
||||||
loggedOnce = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (indexCount_ > 0) {
|
|
||||||
glDrawElements(GL_TRIANGLES, indexCount_, GL_UNSIGNED_SHORT, nullptr);
|
|
||||||
} else {
|
|
||||||
glDrawArrays(GL_TRIANGLES, 0, vertexCount_);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 检查绘制后的错误
|
|
||||||
err = glGetError();
|
|
||||||
if (err != GL_NO_ERROR) {
|
|
||||||
static bool loggedOnce2 = false;
|
|
||||||
if (!loggedOnce2) {
|
|
||||||
E2D_LOG_ERROR("OpenGL error after draw: {}", err);
|
|
||||||
loggedOnce2 = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Mesh::drawInstanced(uint32_t instanceCount) const {
|
|
||||||
if (vao_ == 0) return;
|
|
||||||
|
|
||||||
if (indexCount_ > 0) {
|
|
||||||
glDrawElementsInstanced(GL_TRIANGLES, indexCount_, GL_UNSIGNED_SHORT,
|
|
||||||
nullptr, instanceCount);
|
|
||||||
} else {
|
|
||||||
glDrawArraysInstanced(GL_TRIANGLES, 0, vertexCount_, instanceCount);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ptr<Mesh> Mesh::createQuad(const Vec2& size, const Rect& uv) {
|
|
||||||
Ptr<Mesh> mesh = makePtr<Mesh>();
|
Ptr<Mesh> mesh = makePtr<Mesh>();
|
||||||
|
|
||||||
float halfW = size.x * 0.5f;
|
float halfW = size.x * 0.5f;
|
||||||
|
|
@ -152,8 +145,9 @@ Ptr<Mesh> Mesh::createQuad(const Vec2& size, const Rect& uv) {
|
||||||
|
|
||||||
Vertex vertices[4] = {
|
Vertex vertices[4] = {
|
||||||
{Vec2(-halfW, -halfH), Vec2(uv.x, uv.y + uv.h), Color::White}, // 左下
|
{Vec2(-halfW, -halfH), Vec2(uv.x, uv.y + uv.h), Color::White}, // 左下
|
||||||
{Vec2( halfW, -halfH), Vec2(uv.x + uv.w, uv.y + uv.h), Color::White}, // 右下
|
{Vec2(halfW, -halfH), Vec2(uv.x + uv.w, uv.y + uv.h),
|
||||||
{Vec2( halfW, halfH), Vec2(uv.x + uv.w, uv.y), Color::White}, // 右上
|
Color::White}, // 右下
|
||||||
|
{Vec2(halfW, halfH), Vec2(uv.x + uv.w, uv.y), Color::White}, // 右上
|
||||||
{Vec2(-halfW, halfH), Vec2(uv.x, uv.y), Color::White} // 左上
|
{Vec2(-halfW, halfH), Vec2(uv.x, uv.y), Color::White} // 左上
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,333 @@
|
||||||
|
#include <renderer/render_graph.h>
|
||||||
|
#include <renderer/rhi_module.h>
|
||||||
|
#include <utils/logger.h>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// RenderPassContext 实现
|
||||||
|
// ========================================
|
||||||
|
|
||||||
|
TextureHandle
|
||||||
|
RenderPassContext::getTexture(RenderGraphResourceHandle handle) const {
|
||||||
|
if (graph) {
|
||||||
|
return graph->getTexture(handle);
|
||||||
|
}
|
||||||
|
return TextureHandle{};
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// RenderGraphBuilder 实现
|
||||||
|
// ========================================
|
||||||
|
|
||||||
|
RenderGraphResourceHandle
|
||||||
|
RenderGraphBuilder::createTexture(const RenderGraphTextureDesc &desc) {
|
||||||
|
// 实际的资源创建由 RenderGraph 管理
|
||||||
|
// 这里只记录描述,返回临时句柄
|
||||||
|
textureDescs_.push_back(desc);
|
||||||
|
RenderGraphResourceHandle handle;
|
||||||
|
handle.index = static_cast<uint32_t>(textureDescs_.size()) - 1;
|
||||||
|
handle.type = RenderGraphResourceType::Texture;
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderGraphBuilder::readResource(RenderGraphResourceHandle handle) {
|
||||||
|
// 记录资源读取依赖
|
||||||
|
// 用于后续的依赖分析和资源生命周期管理
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderGraphBuilder::writeResource(RenderGraphResourceHandle handle) {
|
||||||
|
// 记录资源写入依赖
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderGraphBuilder::setRenderTarget(
|
||||||
|
const RenderGraphRenderTargetDesc &desc) {
|
||||||
|
// 设置渲染目标
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// RenderGraph 实现
|
||||||
|
// ========================================
|
||||||
|
|
||||||
|
RenderGraph::RenderGraph() = default;
|
||||||
|
|
||||||
|
RenderGraph::~RenderGraph() { shutdown(); }
|
||||||
|
|
||||||
|
RenderGraph::RenderGraph(RenderGraph &&other) noexcept
|
||||||
|
: passes_(std::move(other.passes_)),
|
||||||
|
executionOrder_(std::move(other.executionOrder_)),
|
||||||
|
textures_(std::move(other.textures_)),
|
||||||
|
nextResourceId_(other.nextResourceId_),
|
||||||
|
builder_(std::move(other.builder_)),
|
||||||
|
commandQueue_(std::move(other.commandQueue_)),
|
||||||
|
outputWidth_(other.outputWidth_), outputHeight_(other.outputHeight_),
|
||||||
|
frameIndex_(other.frameIndex_), compiled_(other.compiled_) {
|
||||||
|
other.nextResourceId_ = 1;
|
||||||
|
other.outputWidth_ = 1280;
|
||||||
|
other.outputHeight_ = 720;
|
||||||
|
other.frameIndex_ = 0;
|
||||||
|
other.compiled_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderGraph &RenderGraph::operator=(RenderGraph &&other) noexcept {
|
||||||
|
if (this != &other) {
|
||||||
|
shutdown();
|
||||||
|
|
||||||
|
passes_ = std::move(other.passes_);
|
||||||
|
executionOrder_ = std::move(other.executionOrder_);
|
||||||
|
textures_ = std::move(other.textures_);
|
||||||
|
nextResourceId_ = other.nextResourceId_;
|
||||||
|
builder_ = std::move(other.builder_);
|
||||||
|
commandQueue_ = std::move(other.commandQueue_);
|
||||||
|
outputWidth_ = other.outputWidth_;
|
||||||
|
outputHeight_ = other.outputHeight_;
|
||||||
|
frameIndex_ = other.frameIndex_;
|
||||||
|
compiled_ = other.compiled_;
|
||||||
|
|
||||||
|
other.nextResourceId_ = 1;
|
||||||
|
other.outputWidth_ = 1280;
|
||||||
|
other.outputHeight_ = 720;
|
||||||
|
other.frameIndex_ = 0;
|
||||||
|
other.compiled_ = false;
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RenderGraph::initialize() {
|
||||||
|
if (!commandQueue_.initialize()) {
|
||||||
|
E2D_LOG_ERROR("Failed to initialize CommandQueue");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
E2D_LOG_INFO("RenderGraph initialized");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderGraph::shutdown() {
|
||||||
|
destroyResources();
|
||||||
|
passes_.clear();
|
||||||
|
executionOrder_.clear();
|
||||||
|
commandQueue_.shutdown();
|
||||||
|
compiled_ = false;
|
||||||
|
|
||||||
|
E2D_LOG_INFO("RenderGraph shutdown");
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderPass *RenderGraph::addPass(std::unique_ptr<RenderPass> pass) {
|
||||||
|
if (!pass)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
RenderPass *ptr = pass.get();
|
||||||
|
passes_.push_back(std::move(pass));
|
||||||
|
compiled_ = false; // 添加新通道需要重新编译
|
||||||
|
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderPass *RenderGraph::addLambdaPass(
|
||||||
|
const std::string &name,
|
||||||
|
std::function<void(const RenderPassContext &)> executeFunc) {
|
||||||
|
// 创建一个简单的 lambda 渲染通道
|
||||||
|
class LambdaRenderPass : public RenderPass {
|
||||||
|
public:
|
||||||
|
std::string name;
|
||||||
|
std::function<void(const RenderPassContext &)> func;
|
||||||
|
|
||||||
|
LambdaRenderPass(const std::string &n,
|
||||||
|
std::function<void(const RenderPassContext &)> f)
|
||||||
|
: name(n), func(f) {}
|
||||||
|
|
||||||
|
const char *getName() const override { return name.c_str(); }
|
||||||
|
|
||||||
|
void execute(const RenderPassContext &ctx) override {
|
||||||
|
if (func) {
|
||||||
|
func(ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return addPass(std::make_unique<LambdaRenderPass>(name, executeFunc));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RenderGraph::compile() {
|
||||||
|
if (compiled_)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
// 清空之前的编译结果
|
||||||
|
executionOrder_.clear();
|
||||||
|
|
||||||
|
// 简单的编译策略:按添加顺序执行
|
||||||
|
// 实际实现中应该进行依赖分析和拓扑排序
|
||||||
|
for (auto &pass : passes_) {
|
||||||
|
if (pass->isEnabled()) {
|
||||||
|
executionOrder_.push_back(pass.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 声明资源
|
||||||
|
for (auto *pass : executionOrder_) {
|
||||||
|
builder_.setCurrentPass(pass);
|
||||||
|
pass->declareResources(builder_);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建资源
|
||||||
|
createResources();
|
||||||
|
|
||||||
|
compiled_ = true;
|
||||||
|
E2D_LOG_INFO("RenderGraph compiled with {} passes", executionOrder_.size());
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderGraph::execute(float deltaTime, const Mat4& viewProjection) {
|
||||||
|
if (!compiled_) {
|
||||||
|
if (!compile()) {
|
||||||
|
E2D_LOG_ERROR("Failed to compile RenderGraph");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新全局 UBO(使用双缓冲)
|
||||||
|
commandQueue_.updateGlobalUBO(viewProjection, deltaTime, outputWidth_,
|
||||||
|
outputHeight_, frameIndex_);
|
||||||
|
|
||||||
|
// 获取 RHI 上下文
|
||||||
|
auto *rhiModule = RHIModule::get();
|
||||||
|
RHIContext *context = nullptr;
|
||||||
|
if (rhiModule && rhiModule->getDevice()) {
|
||||||
|
context = rhiModule->getDevice()->getContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建执行上下文
|
||||||
|
RenderPassContext ctx;
|
||||||
|
ctx.context = context;
|
||||||
|
ctx.commandQueue = &commandQueue_;
|
||||||
|
ctx.graph = this;
|
||||||
|
ctx.deltaTime = deltaTime;
|
||||||
|
ctx.frameIndex = frameIndex_;
|
||||||
|
|
||||||
|
// 执行所有通道
|
||||||
|
for (auto *pass : executionOrder_) {
|
||||||
|
if (pass->isEnabled()) {
|
||||||
|
pass->execute(ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行命令队列(传递帧索引用于双缓冲)
|
||||||
|
commandQueue_.execute(frameIndex_);
|
||||||
|
|
||||||
|
// 结束帧
|
||||||
|
commandQueue_.endFrame();
|
||||||
|
|
||||||
|
// 调用 RHI 上下文的 endFrame 进行缓冲区交换
|
||||||
|
if (context) {
|
||||||
|
context->endFrame();
|
||||||
|
}
|
||||||
|
|
||||||
|
frameIndex_++;
|
||||||
|
}
|
||||||
|
|
||||||
|
TextureHandle RenderGraph::getTexture(RenderGraphResourceHandle handle) const {
|
||||||
|
auto it = textures_.find(handle.index);
|
||||||
|
if (it != textures_.end()) {
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
return TextureHandle{};
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderGraph::setOutputSize(uint32_t width, uint32_t height) {
|
||||||
|
outputWidth_ = width;
|
||||||
|
outputHeight_ = height;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderGraph::createResources() {
|
||||||
|
auto *rhiModule = RHIModule::get();
|
||||||
|
if (!rhiModule || !rhiModule->getDevice())
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto *device = rhiModule->getDevice();
|
||||||
|
|
||||||
|
// 创建渲染图内部资源
|
||||||
|
// 这里可以根据需要创建默认的后缓冲等
|
||||||
|
}
|
||||||
|
|
||||||
|
void RenderGraph::destroyResources() {
|
||||||
|
// 释放所有渲染图管理的资源
|
||||||
|
textures_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// GeometryRenderPass 实现
|
||||||
|
// ========================================
|
||||||
|
|
||||||
|
GeometryRenderPass::GeometryRenderPass(const std::string &name) : name_(name) {}
|
||||||
|
|
||||||
|
void GeometryRenderPass::declareResources(RenderGraphBuilder &builder) {
|
||||||
|
// 声明几何渲染通道的资源依赖
|
||||||
|
if (colorTarget_.isValid()) {
|
||||||
|
builder.writeResource(colorTarget_);
|
||||||
|
}
|
||||||
|
if (depthTarget_.isValid()) {
|
||||||
|
builder.writeResource(depthTarget_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GeometryRenderPass::execute(const RenderPassContext &ctx) {
|
||||||
|
// 几何渲染通道的执行逻辑
|
||||||
|
// 通常由外部的渲染系统填充具体的绘制命令
|
||||||
|
|
||||||
|
if (!ctx.commandQueue)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// 清除颜色缓冲区
|
||||||
|
ctx.commandQueue->submitClear(Color::Black, CLEAR_COLOR_FLAG);
|
||||||
|
|
||||||
|
// 设置视口
|
||||||
|
if (ctx.graph) {
|
||||||
|
ctx.commandQueue->setViewport(
|
||||||
|
0, 0, static_cast<int32_t>(ctx.graph->getOutputWidth()),
|
||||||
|
static_cast<int32_t>(ctx.graph->getOutputHeight()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// PostProcessRenderPass 实现
|
||||||
|
// ========================================
|
||||||
|
|
||||||
|
PostProcessRenderPass::PostProcessRenderPass(const std::string &name)
|
||||||
|
: name_(name) {}
|
||||||
|
|
||||||
|
void PostProcessRenderPass::declareResources(RenderGraphBuilder &builder) {
|
||||||
|
// 声明后期处理通道的资源依赖
|
||||||
|
if (inputTexture_.isValid()) {
|
||||||
|
builder.readResource(inputTexture_);
|
||||||
|
}
|
||||||
|
if (outputTarget_.isValid()) {
|
||||||
|
builder.writeResource(outputTarget_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PostProcessRenderPass::execute(const RenderPassContext &ctx) {
|
||||||
|
// 后期处理通道的执行逻辑
|
||||||
|
// 这里可以实现全屏四边形绘制等后期处理效果
|
||||||
|
|
||||||
|
if (!ctx.context || !ctx.commandQueue)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// 获取命令列表
|
||||||
|
RHICommandList *cmdList = ctx.commandQueue->getCommandList();
|
||||||
|
if (!cmdList)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// 绑定输入纹理
|
||||||
|
if (inputTexture_.isValid()) {
|
||||||
|
TextureHandle tex = ctx.getTexture(inputTexture_);
|
||||||
|
if (tex.isValid()) {
|
||||||
|
cmdList->setTexture(0, tex.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 后期处理的具体实现由子类或外部提供
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -1,82 +1,20 @@
|
||||||
#include "glad/glad.h"
|
|
||||||
#include <algorithm>
|
|
||||||
#include <assets/assets_module.h>
|
#include <assets/assets_module.h>
|
||||||
#include <event/events.h>
|
#include <platform/window_module.h>
|
||||||
#include <glm/gtc/type_ptr.hpp>
|
#include <renderer/render_graph.h>
|
||||||
#include <renderer/renderer_module.h>
|
#include <renderer/renderer_module.h>
|
||||||
|
#include <renderer/rhi_module.h>
|
||||||
#include <utils/logger.h>
|
#include <utils/logger.h>
|
||||||
|
|
||||||
#include <SDL.h>
|
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
||||||
RendererModule::RendererModule() = default;
|
RendererModule::RendererModule() = default;
|
||||||
|
|
||||||
RendererModule::~RendererModule() = default;
|
RendererModule::~RendererModule() = default;
|
||||||
|
|
||||||
RendererModule::RendererModule(RendererModule &&other) noexcept
|
|
||||||
: commandBuffer_(std::move(other.commandBuffer_)),
|
|
||||||
commandCount_(other.commandCount_),
|
|
||||||
uniformManager_(std::move(other.uniformManager_)),
|
|
||||||
onRenderBeginListener_(std::move(other.onRenderBeginListener_)),
|
|
||||||
onRenderSubmitListener_(std::move(other.onRenderSubmitListener_)),
|
|
||||||
onRenderSetCameraListener_(std::move(other.onRenderSetCameraListener_)),
|
|
||||||
onRenderEndListener_(std::move(other.onRenderEndListener_)),
|
|
||||||
onResizeListener_(std::move(other.onResizeListener_)),
|
|
||||||
onShowListener_(std::move(other.onShowListener_)),
|
|
||||||
glInitialized_(other.glInitialized_), stats_(other.stats_),
|
|
||||||
viewportX_(other.viewportX_), viewportY_(other.viewportY_),
|
|
||||||
viewportWidth_(other.viewportWidth_),
|
|
||||||
viewportHeight_(other.viewportHeight_),
|
|
||||||
viewportAdapter_(std::move(other.viewportAdapter_)),
|
|
||||||
viewProjectionMatrix_(std::move(other.viewProjectionMatrix_)) {
|
|
||||||
other.commandCount_ = 0;
|
|
||||||
other.glInitialized_ = false;
|
|
||||||
other.stats_ = {};
|
|
||||||
other.viewportX_ = 0;
|
|
||||||
other.viewportY_ = 0;
|
|
||||||
other.viewportWidth_ = 0;
|
|
||||||
other.viewportHeight_ = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
RendererModule &RendererModule::operator=(RendererModule &&other) noexcept {
|
|
||||||
if (this != &other) {
|
|
||||||
if (glInitialized_) {
|
|
||||||
uniformManager_.shutdown();
|
|
||||||
}
|
|
||||||
|
|
||||||
commandBuffer_ = std::move(other.commandBuffer_);
|
|
||||||
commandCount_ = other.commandCount_;
|
|
||||||
uniformManager_ = std::move(other.uniformManager_);
|
|
||||||
onRenderBeginListener_ = std::move(other.onRenderBeginListener_);
|
|
||||||
onRenderSubmitListener_ = std::move(other.onRenderSubmitListener_);
|
|
||||||
onRenderSetCameraListener_ = std::move(other.onRenderSetCameraListener_);
|
|
||||||
onRenderEndListener_ = std::move(other.onRenderEndListener_);
|
|
||||||
onResizeListener_ = std::move(other.onResizeListener_);
|
|
||||||
onShowListener_ = std::move(other.onShowListener_);
|
|
||||||
glInitialized_ = other.glInitialized_;
|
|
||||||
stats_ = other.stats_;
|
|
||||||
viewportX_ = other.viewportX_;
|
|
||||||
viewportY_ = other.viewportY_;
|
|
||||||
viewportWidth_ = other.viewportWidth_;
|
|
||||||
viewportHeight_ = other.viewportHeight_;
|
|
||||||
viewportAdapter_ = std::move(other.viewportAdapter_);
|
|
||||||
viewProjectionMatrix_ = std::move(other.viewProjectionMatrix_);
|
|
||||||
|
|
||||||
other.commandCount_ = 0;
|
|
||||||
other.glInitialized_ = false;
|
|
||||||
other.stats_ = {};
|
|
||||||
other.viewportX_ = 0;
|
|
||||||
other.viewportY_ = 0;
|
|
||||||
other.viewportWidth_ = 0;
|
|
||||||
other.viewportHeight_ = 0;
|
|
||||||
}
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool RendererModule::init() {
|
bool RendererModule::init() {
|
||||||
E2D_LOG_INFO("Initializing RendererModule...");
|
E2D_LOG_INFO("Initializing RendererModule...");
|
||||||
|
|
||||||
|
// 绑定事件监听器
|
||||||
onRenderBeginListener_.bind([this]() { onRenderBegin(); });
|
onRenderBeginListener_.bind([this]() { onRenderBegin(); });
|
||||||
onRenderSubmitListener_.bind(
|
onRenderSubmitListener_.bind(
|
||||||
[this](const RenderCommand &cmd) { onRenderSubmit(cmd); });
|
[this](const RenderCommand &cmd) { onRenderSubmit(cmd); });
|
||||||
|
|
@ -84,69 +22,92 @@ bool RendererModule::init() {
|
||||||
[this](const Mat4 &viewProj) { onRenderSetCamera(viewProj); });
|
[this](const Mat4 &viewProj) { onRenderSetCamera(viewProj); });
|
||||||
onRenderEndListener_.bind([this]() { onRenderEnd(); });
|
onRenderEndListener_.bind([this]() { onRenderEnd(); });
|
||||||
onResizeListener_.bind([this](int32 w, int32 h) { onResize(w, h); });
|
onResizeListener_.bind([this](int32 w, int32 h) { onResize(w, h); });
|
||||||
|
|
||||||
onShowListener_.bind([this]() { onWindowShow(); });
|
onShowListener_.bind([this]() { onWindowShow(); });
|
||||||
|
|
||||||
E2D_LOG_INFO("RendererModule initialized (waiting for GL context)");
|
E2D_LOG_INFO("RendererModule initialized (waiting for window show)");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void RendererModule::onWindowShow() {
|
void RendererModule::onWindowShow() {
|
||||||
if (glInitialized_) {
|
if (initialized_) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
E2D_LOG_INFO("Initializing OpenGL context...");
|
E2D_LOG_INFO("Initializing RendererModule context...");
|
||||||
|
|
||||||
if (!uniformManager_.initialize()) {
|
// 初始化 RHI 模块
|
||||||
E2D_LOG_ERROR("Failed to initialize UniformBufferManager");
|
auto *rhiModule = RHIModule::get();
|
||||||
|
if (!rhiModule) {
|
||||||
|
E2D_LOG_ERROR("RHIModule not available");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int windowWidth = 800, windowHeight = 600;
|
if (!rhiModule->getDevice()) {
|
||||||
SDL_Window *sdlWindow = SDL_GL_GetCurrentWindow();
|
// RHI 设备尚未初始化,这是正常的时序问题
|
||||||
if (sdlWindow) {
|
// RHIModule 会在窗口显示后初始化设备,然后再次触发此函数
|
||||||
SDL_GetWindowSize(sdlWindow, &windowWidth, &windowHeight);
|
E2D_LOG_INFO("RHIDevice not ready yet, waiting for RHI initialization...");
|
||||||
E2D_LOG_INFO("Setting initial viewport to window size: {}x{}", windowWidth,
|
return;
|
||||||
windowHeight);
|
|
||||||
} else {
|
|
||||||
E2D_LOG_WARN("Could not get SDL window, using default viewport 800x600");
|
|
||||||
}
|
}
|
||||||
setViewport(0, 0, static_cast<int32>(windowWidth),
|
|
||||||
static_cast<int32>(windowHeight));
|
|
||||||
|
|
||||||
glEnable(GL_BLEND);
|
// 初始化渲染图
|
||||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
if (!renderGraph_.initialize()) {
|
||||||
|
E2D_LOG_ERROR("Failed to initialize RenderGraph");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
glInitialized_ = true;
|
commandQueue_ = renderGraph_.getCommandQueue();
|
||||||
E2D_LOG_INFO("OpenGL context initialized successfully");
|
|
||||||
|
// 添加默认的几何渲染通道
|
||||||
|
auto geometryPass = std::make_unique<GeometryRenderPass>("Geometry");
|
||||||
|
renderGraph_.addPass(std::move(geometryPass));
|
||||||
|
|
||||||
|
E2D_LOG_INFO("Added default geometry render pass");
|
||||||
|
|
||||||
|
auto windowModule = getModule<WindowModule>();
|
||||||
|
if (!windowModule) {
|
||||||
|
E2D_LOG_ERROR("WindowModule not available");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
setViewport(0, 0, static_cast<int32>(windowModule->getSize().w),
|
||||||
|
static_cast<int32>(windowModule->getSize().h));
|
||||||
|
|
||||||
|
initialized_ = true;
|
||||||
|
E2D_LOG_INFO("RendererModule context initialized successfully");
|
||||||
}
|
}
|
||||||
|
|
||||||
void RendererModule::shutdown() {
|
void RendererModule::shutdown() {
|
||||||
E2D_LOG_INFO("Shutting down RendererModule...");
|
E2D_LOG_INFO("Shutting down RendererModule...");
|
||||||
|
|
||||||
if (glInitialized_) {
|
renderGraph_.shutdown();
|
||||||
uniformManager_.shutdown();
|
commandQueue_ = nullptr;
|
||||||
}
|
initialized_ = false;
|
||||||
|
|
||||||
glInitialized_ = false;
|
// 注意:事件监听器是值类型,会在析构时自动清理
|
||||||
|
|
||||||
E2D_LOG_INFO("RendererModule shutdown complete");
|
E2D_LOG_INFO("RendererModule shutdown complete");
|
||||||
}
|
}
|
||||||
|
|
||||||
MaterialHandle RendererModule::getDefaultMaterialHandle() const {
|
RHIContext *RendererModule::getRHIContext() const {
|
||||||
auto* assets = getAssets();
|
auto *rhiModule = RHIModule::get();
|
||||||
return assets ? assets->getDefaultMaterial() : MaterialHandle::invalid();
|
if (rhiModule && rhiModule->getDevice()) {
|
||||||
|
return rhiModule->getDevice()->getContext();
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
MeshHandle RendererModule::getDefaultQuadHandle() const {
|
Handle<Material> RendererModule::getDefaultMaterialHandle() const {
|
||||||
auto* assets = getAssets();
|
auto *assets = getAssets();
|
||||||
return assets ? assets->getDefaultQuad() : MeshHandle::invalid();
|
return assets ? assets->getDefaultMaterial() : Handle<Material>::invalid();
|
||||||
}
|
}
|
||||||
|
|
||||||
TextureHandle RendererModule::getDefaultTextureHandle() const {
|
Handle<Mesh> RendererModule::getDefaultQuadHandle() const {
|
||||||
auto* assets = getAssets();
|
auto *assets = getAssets();
|
||||||
return assets ? assets->getDefaultTexture() : TextureHandle::invalid();
|
return assets ? assets->getDefaultQuad() : Handle<Mesh>::invalid();
|
||||||
|
}
|
||||||
|
|
||||||
|
Handle<Texture> RendererModule::getDefaultTextureHandle() const {
|
||||||
|
auto *assets = getAssets();
|
||||||
|
return assets ? assets->getDefaultTexture() : Handle<Texture>::invalid();
|
||||||
}
|
}
|
||||||
|
|
||||||
void RendererModule::setViewport(int32 x, int32 y, int32 width, int32 height) {
|
void RendererModule::setViewport(int32 x, int32 y, int32 width, int32 height) {
|
||||||
|
|
@ -157,216 +118,146 @@ void RendererModule::setViewport(int32 x, int32 y, int32 width, int32 height) {
|
||||||
|
|
||||||
viewportAdapter_.update(width, height);
|
viewportAdapter_.update(width, height);
|
||||||
|
|
||||||
|
// 更新渲染图输出尺寸
|
||||||
|
renderGraph_.setOutputSize(static_cast<uint32_t>(width),
|
||||||
|
static_cast<uint32_t>(height));
|
||||||
|
|
||||||
|
// 通过 RHI 设置视口
|
||||||
|
if (initialized_) {
|
||||||
|
auto *context = getRHIContext();
|
||||||
|
if (context) {
|
||||||
auto result = viewportAdapter_.getResult();
|
auto result = viewportAdapter_.getResult();
|
||||||
glViewport(static_cast<GLint>(result.viewport.x),
|
context->setViewport(static_cast<int32_t>(result.viewport.x),
|
||||||
static_cast<GLint>(result.viewport.y),
|
static_cast<int32_t>(result.viewport.y),
|
||||||
static_cast<GLsizei>(result.viewport.w),
|
static_cast<uint32_t>(result.viewport.w),
|
||||||
static_cast<GLsizei>(result.viewport.h));
|
static_cast<uint32_t>(result.viewport.h));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void RendererModule::clear(const Color &color, uint32 flags) {
|
void RendererModule::clear(const Color &color, uint32 flags) {
|
||||||
GLbitfield mask = 0;
|
if (!initialized_)
|
||||||
|
return;
|
||||||
|
|
||||||
if (flags & CLEAR_COLOR_FLAG) {
|
if (commandQueue_) {
|
||||||
glClearColor(color.r, color.g, color.b, color.a);
|
commandQueue_->submitClear(color, flags);
|
||||||
mask |= GL_COLOR_BUFFER_BIT;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (flags & CLEAR_DEPTH_FLAG) {
|
|
||||||
mask |= GL_DEPTH_BUFFER_BIT;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (flags & CLEAR_STENCIL_FLAG) {
|
|
||||||
mask |= GL_STENCIL_BUFFER_BIT;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (mask != 0) {
|
|
||||||
glClear(mask);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void RendererModule::onRenderBegin() {
|
void RendererModule::onRenderBegin() {
|
||||||
if (!glInitialized_) {
|
if (!initialized_) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
// 重置统计
|
||||||
glClear(GL_COLOR_BUFFER_BIT);
|
|
||||||
|
|
||||||
commandCount_ = 0;
|
|
||||||
|
|
||||||
stats_ = {};
|
stats_ = {};
|
||||||
|
|
||||||
uniformManager_.resetMaterialUBOs();
|
// 开始渲染帧 - 清空命令队列准备接收新的渲染命令
|
||||||
|
if (commandQueue_) {
|
||||||
|
commandQueue_->beginFrame();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 帧开始
|
||||||
}
|
}
|
||||||
|
|
||||||
void RendererModule::onRenderSubmit(const RenderCommand &cmd) {
|
void RendererModule::onRenderSubmit(const RenderCommand &cmd) {
|
||||||
if (!glInitialized_) {
|
if (!initialized_ || !commandQueue_) {
|
||||||
|
E2D_LOG_WARN(
|
||||||
|
"onRenderSubmit: RendererModule not initialized or no command queue");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (commandCount_ >= MAX_RENDER_COMMANDS) {
|
(void)cmd; // 避免未使用警告
|
||||||
E2D_LOG_WARN("Render command buffer full!");
|
|
||||||
|
// 将渲染命令转换为绘制命令提交到队列
|
||||||
|
switch (cmd.type) {
|
||||||
|
case RenderCommandType::DrawMesh: {
|
||||||
|
auto *assets = getAssets();
|
||||||
|
if (!assets)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
// 获取材质,如果没有指定则使用默认材质
|
||||||
|
Material *material = assets->get(cmd.drawMesh.material);
|
||||||
|
if (!material) {
|
||||||
|
material = assets->get(assets->getDefaultMaterial());
|
||||||
}
|
}
|
||||||
|
|
||||||
commandBuffer_[commandCount_++] = cmd;
|
// 获取网格,如果没有指定则使用默认四边形
|
||||||
|
Mesh *mesh = assets->get(cmd.drawMesh.mesh);
|
||||||
|
if (!mesh) {
|
||||||
|
mesh = assets->get(assets->getDefaultQuad());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (material && mesh) {
|
||||||
|
Transform transform(cmd.drawMesh.pos, cmd.drawMesh.scale,
|
||||||
|
cmd.drawMesh.rot);
|
||||||
|
commandQueue_->submitDraw(Ptr<Material>(material), Ptr<Mesh>(mesh),
|
||||||
|
transform, cmd.drawMesh.color);
|
||||||
stats_.commandsSubmitted++;
|
stats_.commandsSubmitted++;
|
||||||
|
} else {
|
||||||
|
E2D_LOG_WARN("Failed to submit draw command: material={}, mesh={}",
|
||||||
|
material ? "valid" : "null", mesh ? "valid" : "null");
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case RenderCommandType::SetViewport: {
|
||||||
|
setViewport(cmd.viewport.x, cmd.viewport.y, cmd.viewport.width,
|
||||||
|
cmd.viewport.height);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
case RenderCommandType::Clear: {
|
||||||
|
clear(cmd.clear.color, cmd.clear.flags);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void RendererModule::onRenderSetCamera(const Mat4 &viewProj) {
|
void RendererModule::onRenderSetCamera(const Mat4 &viewProj) {
|
||||||
if (!glInitialized_) {
|
if (!initialized_) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
viewProjectionMatrix_ = viewProj;
|
viewProjectionMatrix_ = viewProj;
|
||||||
|
|
||||||
uniformManager_.updateGlobalUBO(&viewProj, sizeof(Mat4));
|
// 更新全局 UBO(通过 RenderGraph 的命令队列)
|
||||||
|
// 实际的 UBO 更新会在 RenderGraph 执行时进行
|
||||||
}
|
}
|
||||||
|
|
||||||
void RendererModule::onRenderEnd() {
|
void RendererModule::onRenderEnd() {
|
||||||
if (!glInitialized_) {
|
if (!initialized_) {
|
||||||
|
E2D_LOG_WARN("onRenderEnd: RendererModule not initialized");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
sortCommands();
|
// 执行渲染图,传递视图投影矩阵
|
||||||
|
renderGraph_.execute(0.016f, viewProjectionMatrix_);
|
||||||
|
|
||||||
batchAndDraw();
|
// 获取统计信息
|
||||||
|
if (commandQueue_) {
|
||||||
|
const auto& stats = commandQueue_->getStats();
|
||||||
|
stats_.drawCalls = stats.drawCalls;
|
||||||
|
|
||||||
E2D_LOG_DEBUG("Render: {} commands, {} draw calls, {} batches",
|
// 每60帧输出一次统计信息
|
||||||
stats_.commandsExecuted, stats_.drawCalls, stats_.batches);
|
static uint32_t frameCount = 0;
|
||||||
|
if (++frameCount % 60 == 0) {
|
||||||
|
E2D_LOG_INFO("Render Stats: DrawCalls={}, Triangles={}, Vertices={}, PipelineBinds={}, TextureBinds={}, BufferBinds={}",
|
||||||
|
stats.drawCalls, stats.triangles, stats.vertices,
|
||||||
|
stats.pipelineBinds, stats.textureBinds, stats.bufferBinds);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 渲染完成
|
||||||
}
|
}
|
||||||
|
|
||||||
void RendererModule::onResize(int32 width, int32 height) {
|
void RendererModule::onResize(int32 width, int32 height) {
|
||||||
setViewport(0, 0, width, height);
|
setViewport(0, 0, width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
void RendererModule::sortCommands() {
|
|
||||||
std::sort(commandBuffer_.begin(), commandBuffer_.begin() + commandCount_,
|
|
||||||
[](const RenderCommand &a, const RenderCommand &b) {
|
|
||||||
return a.sortKey < b.sortKey;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void RendererModule::batchAndDraw() {
|
|
||||||
auto* assets = getAssets();
|
|
||||||
if (!assets) {
|
|
||||||
E2D_LOG_ERROR("AssetsModule not available");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
MaterialHandle lastMaterial = MaterialHandle::invalid();
|
|
||||||
MeshHandle lastMesh = MeshHandle::invalid();
|
|
||||||
uint32_t batchStart = 0;
|
|
||||||
uint32_t batchCount = 0;
|
|
||||||
|
|
||||||
for (uint32_t i = 0; i < commandCount_; ++i) {
|
|
||||||
const auto &cmd = commandBuffer_[i];
|
|
||||||
|
|
||||||
if (cmd.type != RenderCommandType::DrawMesh) {
|
|
||||||
if (batchCount > 0) {
|
|
||||||
drawBatch(batchStart, batchCount, lastMaterial, lastMesh);
|
|
||||||
stats_.batches++;
|
|
||||||
batchCount = 0;
|
|
||||||
}
|
|
||||||
executeCommand(cmd);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cmd.drawMesh.material != lastMaterial ||
|
|
||||||
cmd.drawMesh.mesh != lastMesh) {
|
|
||||||
|
|
||||||
if (batchCount > 0) {
|
|
||||||
drawBatch(batchStart, batchCount, lastMaterial, lastMesh);
|
|
||||||
stats_.batches++;
|
|
||||||
}
|
|
||||||
|
|
||||||
lastMaterial = cmd.drawMesh.material;
|
|
||||||
lastMesh = cmd.drawMesh.mesh;
|
|
||||||
batchStart = i;
|
|
||||||
batchCount = 1;
|
|
||||||
} else {
|
|
||||||
++batchCount;
|
|
||||||
}
|
|
||||||
|
|
||||||
stats_.commandsExecuted++;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (batchCount > 0) {
|
|
||||||
drawBatch(batchStart, batchCount, lastMaterial, lastMesh);
|
|
||||||
stats_.batches++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void RendererModule::drawBatch(uint32_t start, uint32_t count,
|
|
||||||
MaterialHandle materialHandle,
|
|
||||||
MeshHandle meshHandle) {
|
|
||||||
auto* assets = getAssets();
|
|
||||||
if (!assets) return;
|
|
||||||
|
|
||||||
Material* material = assets->get(materialHandle);
|
|
||||||
if (!material) {
|
|
||||||
material = assets->getDefaultMaterialPtr();
|
|
||||||
}
|
|
||||||
|
|
||||||
Mesh* mesh = assets->get(meshHandle);
|
|
||||||
if (!mesh) {
|
|
||||||
mesh = assets->getDefaultQuadPtr();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!material || !mesh)
|
|
||||||
return;
|
|
||||||
|
|
||||||
Shader* shader = material->getShader().get();
|
|
||||||
if (!shader)
|
|
||||||
return;
|
|
||||||
|
|
||||||
shader->bind();
|
|
||||||
|
|
||||||
shader->setMat4("uViewProjection", glm::value_ptr(viewProjectionMatrix_));
|
|
||||||
|
|
||||||
shader->setVec4("uTintColor", 1.0f, 1.0f, 1.0f, 1.0f);
|
|
||||||
shader->setFloat("uOpacity", 1.0f);
|
|
||||||
|
|
||||||
const auto &textureSlots = material->getTextures();
|
|
||||||
bool hasMaterialTexture = false;
|
|
||||||
for (const auto &slot : textureSlots) {
|
|
||||||
if (slot.handle.isValid()) {
|
|
||||||
Texture* texture = assets->get(slot.handle);
|
|
||||||
if (texture) {
|
|
||||||
texture->bind(slot.slot);
|
|
||||||
shader->setInt(slot.uniformName, static_cast<int>(slot.slot));
|
|
||||||
hasMaterialTexture = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!hasMaterialTexture) {
|
|
||||||
Texture* defaultTexture = assets->getDefaultTexturePtr();
|
|
||||||
if (defaultTexture) {
|
|
||||||
defaultTexture->bind(0);
|
|
||||||
shader->setInt("uTexture", 0);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mesh->bind();
|
|
||||||
|
|
||||||
for (uint32_t i = 0; i < count; ++i) {
|
|
||||||
Transform transform = commandBuffer_[start + i].getTransform();
|
|
||||||
float matrix[16];
|
|
||||||
transform.toMatrix(matrix);
|
|
||||||
|
|
||||||
shader->setMat4("uModelMatrix", matrix);
|
|
||||||
|
|
||||||
Color color = commandBuffer_[start + i].getColor();
|
|
||||||
shader->setVec4("uColor", color.r, color.g, color.b, color.a);
|
|
||||||
|
|
||||||
mesh->draw();
|
|
||||||
stats_.drawCalls++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void RendererModule::executeCommand(const RenderCommand &cmd) {
|
void RendererModule::executeCommand(const RenderCommand &cmd) {
|
||||||
|
// 此方法保留用于直接执行特定命令
|
||||||
|
// 大部分命令现在通过 RenderGraph 处理
|
||||||
switch (cmd.type) {
|
switch (cmd.type) {
|
||||||
case RenderCommandType::SetViewport:
|
case RenderCommandType::SetViewport:
|
||||||
setViewport(cmd.viewport.x, cmd.viewport.y, cmd.viewport.width,
|
setViewport(cmd.viewport.x, cmd.viewport.y, cmd.viewport.width,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,65 @@
|
||||||
|
#include <renderer/rhi/opengl/gl_buffer.h>
|
||||||
|
#include <renderer/rhi/opengl/gl_utils.h>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
GLBuffer::GLBuffer(const BufferDesc &desc) : desc_(desc), buffer_(0) {}
|
||||||
|
|
||||||
|
GLBuffer::~GLBuffer() { destroy(); }
|
||||||
|
|
||||||
|
bool GLBuffer::create() {
|
||||||
|
glGenBuffers(1, &buffer_);
|
||||||
|
if (buffer_ == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
GLenum target = bufferTypeToGL(desc_.type);
|
||||||
|
glBindBuffer(target, buffer_);
|
||||||
|
|
||||||
|
GLenum usage = bufferUsageToGL(desc_.usage);
|
||||||
|
glBufferData(target, desc_.size, desc_.initialData, usage);
|
||||||
|
|
||||||
|
glBindBuffer(target, 0);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLBuffer::destroy() {
|
||||||
|
if (buffer_ != 0) {
|
||||||
|
glDeleteBuffers(1, &buffer_);
|
||||||
|
buffer_ = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GLBuffer::update(const void *data, uint32_t size, uint32_t offset) {
|
||||||
|
if (buffer_ == 0 || data == nullptr)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
GLenum target = bufferTypeToGL(desc_.type);
|
||||||
|
glBindBuffer(target, buffer_);
|
||||||
|
glBufferSubData(target, offset, size, data);
|
||||||
|
glBindBuffer(target, 0);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void *GLBuffer::map() {
|
||||||
|
// OpenGL ES 3.2 不支持 glMapBuffer,使用 glMapBufferRange
|
||||||
|
if (buffer_ == 0)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
GLenum target = bufferTypeToGL(desc_.type);
|
||||||
|
glBindBuffer(target, buffer_);
|
||||||
|
void *ptr = glMapBufferRange(target, 0, desc_.size, GL_MAP_WRITE_BIT);
|
||||||
|
return ptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLBuffer::unmap() {
|
||||||
|
if (buffer_ == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
GLenum target = bufferTypeToGL(desc_.type);
|
||||||
|
glBindBuffer(target, buffer_);
|
||||||
|
glUnmapBuffer(target);
|
||||||
|
glBindBuffer(target, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,367 @@
|
||||||
|
#include <renderer/rhi/opengl/gl_command_list.h>
|
||||||
|
#include <renderer/rhi/opengl/gl_pipeline.h>
|
||||||
|
#include <renderer/rhi/opengl/gl_buffer.h>
|
||||||
|
#include <renderer/rhi/opengl/gl_texture.h>
|
||||||
|
#include <renderer/rhi/opengl/gl_framebuffer.h>
|
||||||
|
|
||||||
|
#include <glm/gtc/type_ptr.hpp>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
GLCommandList::GLCommandList() = default;
|
||||||
|
|
||||||
|
GLCommandList::~GLCommandList() = default;
|
||||||
|
|
||||||
|
void GLCommandList::begin() {
|
||||||
|
// 开始记录命令
|
||||||
|
recording_ = true;
|
||||||
|
|
||||||
|
// 重置状态缓存
|
||||||
|
stateCache_.reset();
|
||||||
|
|
||||||
|
// 重置统计
|
||||||
|
stats_.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLCommandList::end() {
|
||||||
|
// 结束记录命令
|
||||||
|
recording_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLCommandList::submit() {
|
||||||
|
// 提交命令列表(OpenGL 是立即模式,这里不需要特殊处理)
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLCommandList::beginRenderPass(RHIFramebuffer *framebuffer,
|
||||||
|
ClearFlags clearFlags,
|
||||||
|
const Color &clearColor,
|
||||||
|
float clearDepth,
|
||||||
|
uint8_t clearStencil) {
|
||||||
|
if (framebuffer) {
|
||||||
|
auto *glFramebuffer = static_cast<GLFramebuffer *>(framebuffer);
|
||||||
|
glFramebuffer->bind();
|
||||||
|
stateCache_.framebuffer = glFramebuffer;
|
||||||
|
} else {
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||||
|
stateCache_.framebuffer = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 禁用2D渲染不需要的OpenGL状态
|
||||||
|
glDisable(GL_DEPTH_TEST); // 禁用深度测试
|
||||||
|
glDisable(GL_CULL_FACE); // 禁用面剔除
|
||||||
|
glDisable(GL_STENCIL_TEST); // 禁用模板测试
|
||||||
|
|
||||||
|
// 启用混合(2D渲染通常需要透明度混合)
|
||||||
|
glEnable(GL_BLEND);
|
||||||
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||||
|
|
||||||
|
// 禁用裁剪测试(除非需要)
|
||||||
|
glDisable(GL_SCISSOR_TEST);
|
||||||
|
|
||||||
|
// 执行清除操作
|
||||||
|
if (clearFlags != ClearFlags::None) {
|
||||||
|
clear(clearFlags, clearColor, clearDepth, clearStencil);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLCommandList::endRenderPass() {
|
||||||
|
// 结束渲染通道
|
||||||
|
stateCache_.framebuffer = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLCommandList::setViewport(const Viewport &viewport) {
|
||||||
|
// 状态缓存检查
|
||||||
|
if (stateCache_.viewportValid &&
|
||||||
|
stateCache_.viewport.x == viewport.x &&
|
||||||
|
stateCache_.viewport.y == viewport.y &&
|
||||||
|
stateCache_.viewport.width == viewport.width &&
|
||||||
|
stateCache_.viewport.height == viewport.height) {
|
||||||
|
stats_.redundantPipelineChanges++;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
glViewport(static_cast<GLint>(viewport.x), static_cast<GLint>(viewport.y),
|
||||||
|
static_cast<GLsizei>(viewport.width),
|
||||||
|
static_cast<GLsizei>(viewport.height));
|
||||||
|
|
||||||
|
stateCache_.viewport = viewport;
|
||||||
|
stateCache_.viewportValid = true;
|
||||||
|
stats_.viewportChanges++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLCommandList::setScissor(const ScissorRect &scissor) {
|
||||||
|
glScissor(scissor.x, scissor.y, scissor.width, scissor.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLCommandList::setPipeline(RHIPipeline *pipeline) {
|
||||||
|
if (!pipeline) return;
|
||||||
|
|
||||||
|
auto *glPipeline = static_cast<GLPipeline *>(pipeline);
|
||||||
|
|
||||||
|
// 状态缓存检查
|
||||||
|
if (stateCache_.pipeline == glPipeline) {
|
||||||
|
stats_.redundantPipelineChanges++;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
glPipeline->bind();
|
||||||
|
stateCache_.pipeline = glPipeline;
|
||||||
|
stateCache_.shaderProgram = glPipeline->getGLProgram();
|
||||||
|
stats_.pipelineChanges++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLCommandList::setVertexBuffer(uint32_t slot, RHIBuffer *buffer,
|
||||||
|
uint32_t offset, uint32_t stride) {
|
||||||
|
if (!buffer || slot >= 4) return;
|
||||||
|
|
||||||
|
auto *glBuffer = static_cast<GLBuffer *>(buffer);
|
||||||
|
|
||||||
|
// 状态缓存检查
|
||||||
|
if (stateCache_.vertexBuffers[slot] == glBuffer &&
|
||||||
|
stateCache_.vertexBufferOffsets[slot] == offset) {
|
||||||
|
stats_.redundantBufferChanges++;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
glBindBuffer(GL_ARRAY_BUFFER, glBuffer->getGLBuffer());
|
||||||
|
|
||||||
|
// 配置属于当前 slot 的属性
|
||||||
|
// 注意:虽然 VAO 缓存了部分配置,但我们需要绑定具体的缓冲区
|
||||||
|
const auto &layout = stateCache_.pipeline ? stateCache_.pipeline->getVertexLayout() : VertexLayout{};
|
||||||
|
uint32_t actualStride = stride > 0 ? stride : layout.stride;
|
||||||
|
|
||||||
|
for (const auto &attr : layout.attributes) {
|
||||||
|
if (attr.bufferIndex != slot) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
glEnableVertexAttribArray(attr.location);
|
||||||
|
|
||||||
|
GLenum type = GL_FLOAT;
|
||||||
|
GLint size = 1;
|
||||||
|
GLboolean normalized = GL_FALSE;
|
||||||
|
|
||||||
|
switch (attr.format) {
|
||||||
|
case VertexFormat::Float1:
|
||||||
|
size = 1;
|
||||||
|
break;
|
||||||
|
case VertexFormat::Float2:
|
||||||
|
size = 2;
|
||||||
|
break;
|
||||||
|
case VertexFormat::Float3:
|
||||||
|
size = 3;
|
||||||
|
break;
|
||||||
|
case VertexFormat::Float4:
|
||||||
|
size = 4;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
glVertexAttribPointer(attr.location, size, type, normalized, actualStride,
|
||||||
|
reinterpret_cast<const void *>(attr.offset + offset));
|
||||||
|
|
||||||
|
// 设置实例化除数
|
||||||
|
glVertexAttribDivisor(attr.location, attr.divisor);
|
||||||
|
}
|
||||||
|
|
||||||
|
stateCache_.vertexBuffers[slot] = glBuffer;
|
||||||
|
stateCache_.vertexBufferOffsets[slot] = offset;
|
||||||
|
stats_.bufferChanges++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLCommandList::setIndexBuffer(RHIBuffer *buffer, IndexType type,
|
||||||
|
uint32_t offset) {
|
||||||
|
if (!buffer) return;
|
||||||
|
|
||||||
|
auto *glBuffer = static_cast<GLBuffer *>(buffer);
|
||||||
|
|
||||||
|
// 状态缓存检查
|
||||||
|
if (stateCache_.indexBuffer == glBuffer) {
|
||||||
|
stats_.redundantBufferChanges++;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, glBuffer->getGLBuffer());
|
||||||
|
stateCache_.indexBuffer = glBuffer;
|
||||||
|
stats_.bufferChanges++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLCommandList::setUniformBuffer(uint32_t slot, RHIBuffer *buffer) {
|
||||||
|
if (!buffer || slot >= 4) return;
|
||||||
|
|
||||||
|
auto *glBuffer = static_cast<GLBuffer *>(buffer);
|
||||||
|
|
||||||
|
// 状态缓存检查
|
||||||
|
if (stateCache_.uniformBuffers[slot].valid &&
|
||||||
|
stateCache_.uniformBuffers[slot].buffer == glBuffer &&
|
||||||
|
stateCache_.uniformBuffers[slot].offset == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
glBindBufferBase(GL_UNIFORM_BUFFER, slot, glBuffer->getGLBuffer());
|
||||||
|
|
||||||
|
stateCache_.uniformBuffers[slot].buffer = glBuffer;
|
||||||
|
stateCache_.uniformBuffers[slot].offset = 0;
|
||||||
|
stateCache_.uniformBuffers[slot].size = 0;
|
||||||
|
stateCache_.uniformBuffers[slot].valid = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLCommandList::setUniformBuffer(uint32_t slot, RHIBuffer *buffer, uint32_t offset, uint32_t size) {
|
||||||
|
if (!buffer || slot >= 4) return;
|
||||||
|
|
||||||
|
auto *glBuffer = static_cast<GLBuffer *>(buffer);
|
||||||
|
GLuint glBufferId = glBuffer->getGLBuffer();
|
||||||
|
|
||||||
|
// 状态缓存检查
|
||||||
|
if (stateCache_.uniformBuffers[slot].valid &&
|
||||||
|
stateCache_.uniformBuffers[slot].buffer == glBuffer &&
|
||||||
|
stateCache_.uniformBuffers[slot].offset == offset &&
|
||||||
|
stateCache_.uniformBuffers[slot].size == size) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (size == 0) {
|
||||||
|
// 绑定整个缓冲区
|
||||||
|
glBindBufferBase(GL_UNIFORM_BUFFER, slot, glBufferId);
|
||||||
|
} else {
|
||||||
|
// 绑定缓冲区范围
|
||||||
|
glBindBufferRange(GL_UNIFORM_BUFFER, slot, glBufferId, offset, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
stateCache_.uniformBuffers[slot].buffer = glBuffer;
|
||||||
|
stateCache_.uniformBuffers[slot].offset = offset;
|
||||||
|
stateCache_.uniformBuffers[slot].size = size;
|
||||||
|
stateCache_.uniformBuffers[slot].valid = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLCommandList::setTexture(uint32_t slot, RHITexture *texture) {
|
||||||
|
if (!texture || slot >= 8) return;
|
||||||
|
|
||||||
|
auto *glTexture = static_cast<GLTexture *>(texture);
|
||||||
|
|
||||||
|
// 状态缓存检查
|
||||||
|
if (stateCache_.textures[slot].valid &&
|
||||||
|
stateCache_.textures[slot].texture == glTexture) {
|
||||||
|
stats_.redundantTextureChanges++;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
glActiveTexture(GL_TEXTURE0 + slot);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, glTexture->getGLTexture());
|
||||||
|
|
||||||
|
stateCache_.textures[slot].texture = glTexture;
|
||||||
|
stateCache_.textures[slot].valid = true;
|
||||||
|
stats_.textureChanges++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLCommandList::setSampler(uint32_t slot, TextureFilter minFilter,
|
||||||
|
TextureFilter magFilter, TextureWrap wrapS,
|
||||||
|
TextureWrap wrapT) {
|
||||||
|
// OpenGL 的采样器状态是纹理对象的一部分
|
||||||
|
// 这里可以设置当前绑定纹理的采样参数
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLCommandList::draw(uint32_t vertexCount, uint32_t firstVertex,
|
||||||
|
uint32_t instanceCount, uint32_t firstInstance) {
|
||||||
|
if (instanceCount > 1) {
|
||||||
|
glDrawArraysInstanced(GL_TRIANGLES, firstVertex, vertexCount, instanceCount);
|
||||||
|
} else {
|
||||||
|
glDrawArrays(GL_TRIANGLES, firstVertex, vertexCount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLCommandList::drawIndexed(uint32_t indexCount, uint32_t firstIndex,
|
||||||
|
int32_t vertexOffset, uint32_t instanceCount,
|
||||||
|
uint32_t firstInstance) {
|
||||||
|
GLenum indexType = GL_UNSIGNED_SHORT;
|
||||||
|
if (stateCache_.indexBuffer) {
|
||||||
|
// 根据缓冲区类型确定索引类型
|
||||||
|
// 这里简化处理,使用 GL_UNSIGNED_SHORT
|
||||||
|
}
|
||||||
|
|
||||||
|
if (instanceCount > 1) {
|
||||||
|
glDrawElementsInstanced(
|
||||||
|
GL_TRIANGLES, indexCount, indexType,
|
||||||
|
reinterpret_cast<const void *>(firstIndex * sizeof(uint16_t)),
|
||||||
|
instanceCount);
|
||||||
|
} else {
|
||||||
|
glDrawElements(GL_TRIANGLES, indexCount, indexType,
|
||||||
|
reinterpret_cast<const void *>(firstIndex * sizeof(uint16_t)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLCommandList::clear(ClearFlags flags, const Color &color, float depth,
|
||||||
|
uint8_t stencil) {
|
||||||
|
GLbitfield mask = 0;
|
||||||
|
|
||||||
|
if (hasFlag(flags, ClearFlags::Color)) {
|
||||||
|
glClearColor(color.r, color.g, color.b, color.a);
|
||||||
|
mask |= GL_COLOR_BUFFER_BIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasFlag(flags, ClearFlags::Depth)) {
|
||||||
|
glClearDepthf(depth);
|
||||||
|
mask |= GL_DEPTH_BUFFER_BIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasFlag(flags, ClearFlags::Stencil)) {
|
||||||
|
glClearStencil(stencil);
|
||||||
|
mask |= GL_STENCIL_BUFFER_BIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mask != 0) {
|
||||||
|
glClear(mask);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GLCommandList::isRecording() const {
|
||||||
|
return recording_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLCommandList::setUniform(const char* name, float value) {
|
||||||
|
if (stateCache_.shaderProgram != 0) {
|
||||||
|
GLint location = glGetUniformLocation(stateCache_.shaderProgram, name);
|
||||||
|
if (location != -1) {
|
||||||
|
glUniform1f(location, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLCommandList::setUniform(const char* name, const Vec2& value) {
|
||||||
|
if (stateCache_.shaderProgram != 0) {
|
||||||
|
GLint location = glGetUniformLocation(stateCache_.shaderProgram, name);
|
||||||
|
if (location != -1) {
|
||||||
|
glUniform2f(location, value.x, value.y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLCommandList::setUniform(const char* name, const Vec3& value) {
|
||||||
|
if (stateCache_.shaderProgram != 0) {
|
||||||
|
GLint location = glGetUniformLocation(stateCache_.shaderProgram, name);
|
||||||
|
if (location != -1) {
|
||||||
|
glUniform3f(location, value.x, value.y, value.z);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLCommandList::setUniform(const char* name, const Color& value) {
|
||||||
|
if (stateCache_.shaderProgram != 0) {
|
||||||
|
GLint location = glGetUniformLocation(stateCache_.shaderProgram, name);
|
||||||
|
if (location != -1) {
|
||||||
|
glUniform4f(location, value.r, value.g, value.b, value.a);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLCommandList::setUniform(const char* name, const Mat4& value) {
|
||||||
|
if (stateCache_.shaderProgram != 0) {
|
||||||
|
GLint location = glGetUniformLocation(stateCache_.shaderProgram, name);
|
||||||
|
if (location != -1) {
|
||||||
|
glUniformMatrix4fv(location, 1, GL_FALSE, glm::value_ptr(value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,84 @@
|
||||||
|
#include <cstring>
|
||||||
|
#include <renderer/rhi/opengl/gl_context.h>
|
||||||
|
#include <renderer/rhi/opengl/gl_framebuffer.h>
|
||||||
|
#include <utils/logger.h>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
GLContext::GLContext(GLDevice *device)
|
||||||
|
: device_(device), defaultFramebuffer_(nullptr) {}
|
||||||
|
|
||||||
|
GLContext::~GLContext() = default;
|
||||||
|
|
||||||
|
bool GLContext::initialize() {
|
||||||
|
E2D_LOG_INFO("Initializing OpenGL context...");
|
||||||
|
|
||||||
|
// 创建默认帧缓冲(窗口帧缓冲)
|
||||||
|
defaultFramebuffer_ = std::make_unique<GLFramebuffer>();
|
||||||
|
defaultFramebuffer_->setSize(800, 600); // 默认大小,会被更新
|
||||||
|
|
||||||
|
E2D_LOG_INFO("OpenGL context initialized");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLContext::shutdown() {
|
||||||
|
E2D_LOG_INFO("Shutting down OpenGL context...");
|
||||||
|
|
||||||
|
defaultFramebuffer_.reset();
|
||||||
|
|
||||||
|
E2D_LOG_INFO("OpenGL context shutdown complete");
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLContext::beginFrame() {
|
||||||
|
// 重置每帧的统计
|
||||||
|
// device_->resetStats(); // 需要在 GLDevice 中添加此方法
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLContext::endFrame() {
|
||||||
|
// 交换缓冲区
|
||||||
|
// 通过 SDL 获取当前窗口并交换缓冲区
|
||||||
|
SDL_Window* window = SDL_GL_GetCurrentWindow();
|
||||||
|
if (window) {
|
||||||
|
SDL_GL_SwapWindow(window);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLContext::setViewport(int32_t x, int32_t y, uint32_t width,
|
||||||
|
uint32_t height) {
|
||||||
|
glViewport(x, y, static_cast<GLsizei>(width), static_cast<GLsizei>(height));
|
||||||
|
|
||||||
|
// 更新默认帧缓冲大小
|
||||||
|
if (defaultFramebuffer_) {
|
||||||
|
defaultFramebuffer_->setSize(width, height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLContext::bindDefaultFramebuffer() {
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
RHIFramebuffer *GLContext::getDefaultFramebuffer() {
|
||||||
|
return defaultFramebuffer_.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GLContext::isFeatureSupported(const char *feature) const {
|
||||||
|
// 检查 OpenGL 扩展或版本
|
||||||
|
if (std::strcmp(feature, "instancing") == 0) {
|
||||||
|
return glad_glDrawArraysInstanced != nullptr;
|
||||||
|
}
|
||||||
|
if (std::strcmp(feature, "compute") == 0) {
|
||||||
|
return glad_glDispatchCompute != nullptr;
|
||||||
|
}
|
||||||
|
if (std::strcmp(feature, "sRGB") == 0) {
|
||||||
|
return GLAD_GL_EXT_texture_sRGB_R8;
|
||||||
|
}
|
||||||
|
if (std::strcmp(feature, "anisotropic") == 0) {
|
||||||
|
return GLAD_GL_EXT_texture_filter_anisotropic;
|
||||||
|
}
|
||||||
|
if (std::strcmp(feature, "debug") == 0) {
|
||||||
|
return GLAD_GL_KHR_debug;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,200 @@
|
||||||
|
#include <renderer/rhi/opengl/gl_buffer.h>
|
||||||
|
#include <renderer/rhi/opengl/gl_command_list.h>
|
||||||
|
#include <renderer/rhi/opengl/gl_context.h>
|
||||||
|
#include <renderer/rhi/opengl/gl_framebuffer.h>
|
||||||
|
#include <renderer/rhi/opengl/gl_pipeline.h>
|
||||||
|
#include <renderer/rhi/opengl/gl_shader.h>
|
||||||
|
#include <renderer/rhi/opengl/gl_texture.h>
|
||||||
|
#include <renderer/rhi/opengl/gl_utils.h>
|
||||||
|
|
||||||
|
#include <glad/glad.h>
|
||||||
|
#include <SDL.h>
|
||||||
|
#include <utils/logger.h>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// 前向声明
|
||||||
|
class GLDevice;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief OpenGL 设备实现
|
||||||
|
*/
|
||||||
|
class GLDevice : public RHIDevice {
|
||||||
|
public:
|
||||||
|
GLDevice() : window_(nullptr), glContext_(nullptr), context_(nullptr), stats_() {}
|
||||||
|
|
||||||
|
~GLDevice() override { shutdown(); }
|
||||||
|
|
||||||
|
bool initialize(void* nativeWindow) override {
|
||||||
|
E2D_LOG_INFO("Initializing OpenGL device...");
|
||||||
|
|
||||||
|
if (!nativeWindow) {
|
||||||
|
E2D_LOG_ERROR("Native window handle is null");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
window_ = static_cast<SDL_Window*>(nativeWindow);
|
||||||
|
|
||||||
|
// 创建 OpenGL ES 3.2 上下文
|
||||||
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
|
||||||
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
|
||||||
|
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);
|
||||||
|
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
|
||||||
|
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
|
||||||
|
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
|
||||||
|
|
||||||
|
glContext_ = SDL_GL_CreateContext(window_);
|
||||||
|
if (!glContext_) {
|
||||||
|
E2D_LOG_ERROR("Failed to create OpenGL ES context: {}", SDL_GetError());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化 GLAD
|
||||||
|
if (!gladLoadGLES2Loader((GLADloadproc)SDL_GL_GetProcAddress)) {
|
||||||
|
E2D_LOG_ERROR("Failed to initialize GLAD");
|
||||||
|
SDL_GL_DeleteContext(glContext_);
|
||||||
|
glContext_ = nullptr;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
E2D_LOG_INFO("OpenGL ES context created: {}.{}", GLVersion.major, GLVersion.minor);
|
||||||
|
|
||||||
|
const char* version = reinterpret_cast<const char *>(glGetString(GL_VERSION));
|
||||||
|
if (!version) {
|
||||||
|
E2D_LOG_ERROR("Failed to get OpenGL version");
|
||||||
|
SDL_GL_DeleteContext(glContext_);
|
||||||
|
glContext_ = nullptr;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
E2D_LOG_INFO("OpenGL Version: {}", version);
|
||||||
|
E2D_LOG_INFO("OpenGL Vendor: {}",
|
||||||
|
reinterpret_cast<const char *>(glGetString(GL_VENDOR)));
|
||||||
|
E2D_LOG_INFO("OpenGL Renderer: {}",
|
||||||
|
reinterpret_cast<const char *>(glGetString(GL_RENDERER)));
|
||||||
|
|
||||||
|
// 创建上下文管理器
|
||||||
|
context_ = std::make_unique<GLContext>(this);
|
||||||
|
if (!context_->initialize()) {
|
||||||
|
E2D_LOG_ERROR("Failed to initialize GL context");
|
||||||
|
SDL_GL_DeleteContext(glContext_);
|
||||||
|
glContext_ = nullptr;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置默认状态
|
||||||
|
glEnable(GL_BLEND);
|
||||||
|
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||||
|
|
||||||
|
E2D_LOG_INFO("OpenGL device initialized successfully");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void shutdown() override {
|
||||||
|
E2D_LOG_INFO("Shutting down OpenGL device...");
|
||||||
|
|
||||||
|
if (context_) {
|
||||||
|
context_->shutdown();
|
||||||
|
context_.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (glContext_) {
|
||||||
|
SDL_GL_DeleteContext(glContext_);
|
||||||
|
glContext_ = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
window_ = nullptr;
|
||||||
|
|
||||||
|
E2D_LOG_INFO("OpenGL device shutdown complete");
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<RHIBuffer> createBuffer(const BufferDesc &desc) override {
|
||||||
|
auto buffer = std::make_unique<GLBuffer>(desc);
|
||||||
|
if (!buffer->create()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<RHITexture> createTexture(const TextureDesc &desc) override {
|
||||||
|
auto texture = std::make_unique<GLTexture>(desc);
|
||||||
|
if (!texture->create()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return texture;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<RHIShader> createShader(const ShaderDesc &desc) override {
|
||||||
|
auto shader = std::make_unique<GLShader>(desc);
|
||||||
|
if (!shader->compile()) {
|
||||||
|
E2D_LOG_ERROR("Shader compilation failed: {}", shader->getCompileLog());
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return shader;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<RHIPipeline>
|
||||||
|
createPipeline(const PipelineDesc &desc) override {
|
||||||
|
auto pipeline = std::make_unique<GLPipeline>(desc);
|
||||||
|
if (!pipeline->create()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取 vertex shader 的 program ID
|
||||||
|
if (desc.vertexShader.isValid()) {
|
||||||
|
RHIShader* shader = desc.vertexShader.get();
|
||||||
|
if (shader) {
|
||||||
|
auto* glShader = static_cast<GLShader*>(shader);
|
||||||
|
pipeline->setGLProgram(glShader->getGLProgram());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pipeline;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<RHIFramebuffer>
|
||||||
|
createFramebuffer(const RenderPassDesc &desc) override {
|
||||||
|
auto framebuffer = std::make_unique<GLFramebuffer>(desc);
|
||||||
|
if (!framebuffer->create()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return framebuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<RHICommandList> createCommandList() override {
|
||||||
|
return std::make_unique<GLCommandList>();
|
||||||
|
}
|
||||||
|
|
||||||
|
RHIContext *getContext() override { return context_.get(); }
|
||||||
|
|
||||||
|
const char *getBackendName() const override { return "OpenGL"; }
|
||||||
|
|
||||||
|
void *getNativeWindow() const { return window_; }
|
||||||
|
|
||||||
|
void waitIdle() override { glFinish(); }
|
||||||
|
|
||||||
|
const RenderStats &getStats() const override { return stats_; }
|
||||||
|
|
||||||
|
void resetStats() override { stats_.reset(); }
|
||||||
|
|
||||||
|
void incrementDrawCalls() { stats_.drawCalls++; }
|
||||||
|
void incrementTriangles(uint32_t count) { stats_.triangles += count; }
|
||||||
|
void incrementVertices(uint32_t count) { stats_.vertices += count; }
|
||||||
|
void incrementTextureBinds() { stats_.textureBinds++; }
|
||||||
|
void incrementBufferBinds() { stats_.bufferBinds++; }
|
||||||
|
void incrementPipelineBinds() { stats_.pipelineBinds++; }
|
||||||
|
void incrementRenderPassSwitches() { stats_.renderPassSwitches++; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
SDL_Window* window_;
|
||||||
|
SDL_GLContext glContext_;
|
||||||
|
std::unique_ptr<GLContext> context_;
|
||||||
|
RenderStats stats_;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 创建设备的工厂函数
|
||||||
|
std::unique_ptr<RHIDevice> CreateGLDevice() {
|
||||||
|
return std::make_unique<GLDevice>();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,151 @@
|
||||||
|
#include <renderer/rhi/opengl/gl_framebuffer.h>
|
||||||
|
#include <renderer/rhi/opengl/gl_texture.h>
|
||||||
|
#include <renderer/rhi/opengl/gl_utils.h>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查纹理格式是否为包含模板的深度格式
|
||||||
|
* @param format 纹理格式
|
||||||
|
* @return 是否为深度/模板格式
|
||||||
|
*/
|
||||||
|
static inline bool isStencilFormat(TextureFormat format) {
|
||||||
|
switch (format) {
|
||||||
|
case TextureFormat::Depth24Stencil8:
|
||||||
|
case TextureFormat::Depth32FStencil8:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GLFramebuffer::GLFramebuffer()
|
||||||
|
: desc_(), framebuffer_(0), width_(0), height_(0) {}
|
||||||
|
|
||||||
|
GLFramebuffer::GLFramebuffer(const RenderPassDesc &desc)
|
||||||
|
: desc_(desc), framebuffer_(0), width_(0), height_(0) {}
|
||||||
|
|
||||||
|
GLFramebuffer::~GLFramebuffer() { destroy(); }
|
||||||
|
|
||||||
|
bool GLFramebuffer::create() {
|
||||||
|
glGenFramebuffers(1, &framebuffer_);
|
||||||
|
if (framebuffer_ == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer_);
|
||||||
|
|
||||||
|
// 附加颜色附件
|
||||||
|
std::vector<GLenum> drawBuffers;
|
||||||
|
for (size_t i = 0; i < desc_.colorAttachments.size(); ++i) {
|
||||||
|
const auto &attachment = desc_.colorAttachments[i];
|
||||||
|
if (attachment.texture.isValid()) {
|
||||||
|
auto *texture = static_cast<GLTexture *>(attachment.texture.get());
|
||||||
|
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0 + i,
|
||||||
|
GL_TEXTURE_2D, texture->getGLTexture(), 0);
|
||||||
|
drawBuffers.push_back(GL_COLOR_ATTACHMENT0 + i);
|
||||||
|
|
||||||
|
if (i == 0) {
|
||||||
|
width_ = texture->getWidth();
|
||||||
|
height_ = texture->getHeight();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 附加深度/模板附件
|
||||||
|
if (desc_.hasDepthStencil && desc_.depthStencilAttachment.texture.isValid()) {
|
||||||
|
auto *texture =
|
||||||
|
static_cast<GLTexture *>(desc_.depthStencilAttachment.texture.get());
|
||||||
|
GLenum attachment = isStencilFormat(texture->getFormat())
|
||||||
|
? GL_DEPTH_STENCIL_ATTACHMENT
|
||||||
|
: GL_DEPTH_ATTACHMENT;
|
||||||
|
glFramebufferTexture2D(GL_FRAMEBUFFER, attachment, GL_TEXTURE_2D,
|
||||||
|
texture->getGLTexture(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!drawBuffers.empty()) {
|
||||||
|
glDrawBuffers(static_cast<GLsizei>(drawBuffers.size()), drawBuffers.data());
|
||||||
|
}
|
||||||
|
|
||||||
|
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||||
|
|
||||||
|
return status == GL_FRAMEBUFFER_COMPLETE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLFramebuffer::destroy() {
|
||||||
|
if (framebuffer_ != 0) {
|
||||||
|
glDeleteFramebuffers(1, &framebuffer_);
|
||||||
|
framebuffer_ = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLFramebuffer::bind() {
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, framebuffer_);
|
||||||
|
glViewport(0, 0, width_, height_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLFramebuffer::unbind() { glBindFramebuffer(GL_FRAMEBUFFER, 0); }
|
||||||
|
|
||||||
|
void GLFramebuffer::clear(ClearFlags flags, const Color &color, float depth,
|
||||||
|
uint8_t stencil) {
|
||||||
|
bind();
|
||||||
|
|
||||||
|
GLbitfield mask = 0;
|
||||||
|
|
||||||
|
if (hasFlag(flags, ClearFlags::Color)) {
|
||||||
|
glClearColor(color.r, color.g, color.b, color.a);
|
||||||
|
mask |= GL_COLOR_BUFFER_BIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasFlag(flags, ClearFlags::Depth)) {
|
||||||
|
glClearDepthf(depth);
|
||||||
|
mask |= GL_DEPTH_BUFFER_BIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasFlag(flags, ClearFlags::Stencil)) {
|
||||||
|
glClearStencil(stencil);
|
||||||
|
mask |= GL_STENCIL_BUFFER_BIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mask != 0) {
|
||||||
|
glClear(mask);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLFramebuffer::setSize(uint32_t width, uint32_t height) {
|
||||||
|
width_ = width;
|
||||||
|
height_ = height;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t GLFramebuffer::getColorAttachmentCount() const {
|
||||||
|
return static_cast<uint32_t>(desc_.colorAttachments.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
TextureHandle GLFramebuffer::getColorAttachment(uint32_t index) const {
|
||||||
|
if (index < desc_.colorAttachments.size()) {
|
||||||
|
return desc_.colorAttachments[index].texture;
|
||||||
|
}
|
||||||
|
return TextureHandle();
|
||||||
|
}
|
||||||
|
|
||||||
|
TextureHandle GLFramebuffer::getDepthStencilAttachment() const {
|
||||||
|
if (desc_.hasDepthStencil) {
|
||||||
|
return desc_.depthStencilAttachment.texture;
|
||||||
|
}
|
||||||
|
return TextureHandle();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GLFramebuffer::hasDepthStencil() const {
|
||||||
|
return desc_.hasDepthStencil;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GLFramebuffer::isValid() const {
|
||||||
|
return framebuffer_ != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GLFramebuffer::isDefault() const {
|
||||||
|
return framebuffer_ == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,116 @@
|
||||||
|
#include <renderer/rhi/opengl/gl_pipeline.h>
|
||||||
|
#include <renderer/rhi/opengl/gl_utils.h>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
GLPipeline::GLPipeline(const PipelineDesc &desc) : desc_(desc), vao_(0) {}
|
||||||
|
|
||||||
|
GLPipeline::~GLPipeline() { destroy(); }
|
||||||
|
|
||||||
|
bool GLPipeline::create() {
|
||||||
|
// 创建 VAO
|
||||||
|
glGenVertexArrays(1, &vao_);
|
||||||
|
if (vao_ == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
glBindVertexArray(vao_);
|
||||||
|
|
||||||
|
// 配置顶点属性
|
||||||
|
const auto &layout = desc_.vertexLayout;
|
||||||
|
for (const auto &attr : layout.attributes) {
|
||||||
|
glEnableVertexAttribArray(attr.location);
|
||||||
|
|
||||||
|
GLenum type = GL_FLOAT;
|
||||||
|
GLint size = 1;
|
||||||
|
GLboolean normalized = GL_FALSE;
|
||||||
|
|
||||||
|
switch (attr.format) {
|
||||||
|
case VertexFormat::Float1:
|
||||||
|
size = 1;
|
||||||
|
break;
|
||||||
|
case VertexFormat::Float2:
|
||||||
|
size = 2;
|
||||||
|
break;
|
||||||
|
case VertexFormat::Float3:
|
||||||
|
size = 3;
|
||||||
|
break;
|
||||||
|
case VertexFormat::Float4:
|
||||||
|
size = 4;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
glVertexAttribPointer(attr.location, size, type, normalized, layout.stride,
|
||||||
|
reinterpret_cast<const void *>(attr.offset));
|
||||||
|
}
|
||||||
|
|
||||||
|
glBindVertexArray(0);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLPipeline::destroy() {
|
||||||
|
if (vao_ != 0) {
|
||||||
|
glDeleteVertexArrays(1, &vao_);
|
||||||
|
vao_ = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLPipeline::bind() {
|
||||||
|
// 绑定着色器程序
|
||||||
|
if (shaderProgram_ != 0) {
|
||||||
|
glUseProgram(shaderProgram_);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 注意:VAO 需要与具体的顶点缓冲区绑定才能工作
|
||||||
|
// 所以我们不在此处绑定 VAO,而是在渲染时动态配置顶点属性
|
||||||
|
// 这样可以支持不同的 Mesh 使用相同的 Pipeline
|
||||||
|
|
||||||
|
// 应用混合状态
|
||||||
|
if (desc_.blendState.enabled) {
|
||||||
|
glEnable(GL_BLEND);
|
||||||
|
glBlendFunc(blendFactorToGL(desc_.blendState.srcFactor),
|
||||||
|
blendFactorToGL(desc_.blendState.dstFactor));
|
||||||
|
} else {
|
||||||
|
glDisable(GL_BLEND);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 应用深度状态
|
||||||
|
if (desc_.depthStencilState.depthTestEnabled) {
|
||||||
|
glEnable(GL_DEPTH_TEST);
|
||||||
|
glDepthFunc(compareFuncToGL(desc_.depthStencilState.depthCompare));
|
||||||
|
glDepthMask(desc_.depthStencilState.depthWriteEnabled ? GL_TRUE : GL_FALSE);
|
||||||
|
} else {
|
||||||
|
glDisable(GL_DEPTH_TEST);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 应用光栅化状态
|
||||||
|
if (desc_.rasterizerState.cullEnabled) {
|
||||||
|
glEnable(GL_CULL_FACE);
|
||||||
|
glCullFace(desc_.rasterizerState.cullFrontFace ? GL_FRONT : GL_BACK);
|
||||||
|
glFrontFace(desc_.rasterizerState.frontCCW ? GL_CCW : GL_CW);
|
||||||
|
} else {
|
||||||
|
glDisable(GL_CULL_FACE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLPipeline::unbind() { glBindVertexArray(0); }
|
||||||
|
|
||||||
|
ShaderHandle GLPipeline::getVertexShader() const {
|
||||||
|
return desc_.vertexShader;
|
||||||
|
}
|
||||||
|
|
||||||
|
ShaderHandle GLPipeline::getFragmentShader() const {
|
||||||
|
return desc_.fragmentShader;
|
||||||
|
}
|
||||||
|
|
||||||
|
const VertexLayout& GLPipeline::getVertexLayout() const {
|
||||||
|
return desc_.vertexLayout;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GLPipeline::isValid() const {
|
||||||
|
return vao_ != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,137 @@
|
||||||
|
#include <glm/gtc/type_ptr.hpp>
|
||||||
|
#include <renderer/rhi/opengl/gl_shader.h>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
GLShader::GLShader(const ShaderDesc &desc)
|
||||||
|
: desc_(desc), shader_(0), program_(0) {}
|
||||||
|
|
||||||
|
GLShader::~GLShader() { destroy(); }
|
||||||
|
|
||||||
|
bool GLShader::compile() {
|
||||||
|
// 创建程序
|
||||||
|
program_ = glCreateProgram();
|
||||||
|
if (program_ == 0) {
|
||||||
|
compileLog_ = "Failed to create shader program";
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 编译顶点着色器
|
||||||
|
GLuint vs = glCreateShader(GL_VERTEX_SHADER);
|
||||||
|
const char *vsSource = desc_.vertexSource.c_str();
|
||||||
|
glShaderSource(vs, 1, &vsSource, nullptr);
|
||||||
|
glCompileShader(vs);
|
||||||
|
|
||||||
|
GLint success;
|
||||||
|
glGetShaderiv(vs, GL_COMPILE_STATUS, &success);
|
||||||
|
if (!success) {
|
||||||
|
char infoLog[512];
|
||||||
|
glGetShaderInfoLog(vs, 512, nullptr, infoLog);
|
||||||
|
compileLog_ = std::string("Vertex shader compilation failed: ") + infoLog;
|
||||||
|
glDeleteShader(vs);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 编译片段着色器
|
||||||
|
GLuint fs = glCreateShader(GL_FRAGMENT_SHADER);
|
||||||
|
const char *fsSource = desc_.fragmentSource.c_str();
|
||||||
|
glShaderSource(fs, 1, &fsSource, nullptr);
|
||||||
|
glCompileShader(fs);
|
||||||
|
|
||||||
|
glGetShaderiv(fs, GL_COMPILE_STATUS, &success);
|
||||||
|
if (!success) {
|
||||||
|
char infoLog[512];
|
||||||
|
glGetShaderInfoLog(fs, 512, nullptr, infoLog);
|
||||||
|
compileLog_ = std::string("Fragment shader compilation failed: ") + infoLog;
|
||||||
|
glDeleteShader(vs);
|
||||||
|
glDeleteShader(fs);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 链接着色器程序
|
||||||
|
glAttachShader(program_, vs);
|
||||||
|
glAttachShader(program_, fs);
|
||||||
|
glLinkProgram(program_);
|
||||||
|
|
||||||
|
glGetProgramiv(program_, GL_LINK_STATUS, &success);
|
||||||
|
if (!success) {
|
||||||
|
char infoLog[512];
|
||||||
|
glGetProgramInfoLog(program_, 512, nullptr, infoLog);
|
||||||
|
compileLog_ = std::string("Shader program linking failed: ") + infoLog;
|
||||||
|
glDeleteShader(vs);
|
||||||
|
glDeleteShader(fs);
|
||||||
|
glDeleteProgram(program_);
|
||||||
|
program_ = 0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清理着色器对象
|
||||||
|
glDeleteShader(vs);
|
||||||
|
glDeleteShader(fs);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLShader::destroy() {
|
||||||
|
if (program_ != 0) {
|
||||||
|
glDeleteProgram(program_);
|
||||||
|
program_ = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLShader::bind() const {
|
||||||
|
if (program_ != 0) {
|
||||||
|
glUseProgram(program_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLShader::unbind() const { glUseProgram(0); }
|
||||||
|
|
||||||
|
void GLShader::setUniform(const std::string &name, float value) {
|
||||||
|
GLint location = glGetUniformLocation(program_, name.c_str());
|
||||||
|
if (location != -1) {
|
||||||
|
glUniform1f(location, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLShader::setUniform(const std::string &name, const Vec2 &value) {
|
||||||
|
GLint location = glGetUniformLocation(program_, name.c_str());
|
||||||
|
if (location != -1) {
|
||||||
|
glUniform2f(location, value.x, value.y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLShader::setUniform(const std::string &name, const Vec3 &value) {
|
||||||
|
GLint location = glGetUniformLocation(program_, name.c_str());
|
||||||
|
if (location != -1) {
|
||||||
|
glUniform3f(location, value.x, value.y, value.z);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLShader::setUniform(const std::string &name, const Color &value) {
|
||||||
|
GLint location = glGetUniformLocation(program_, name.c_str());
|
||||||
|
if (location != -1) {
|
||||||
|
glUniform4f(location, value.r, value.g, value.b, value.a);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLShader::setUniform(const std::string &name, const Mat4 &value) {
|
||||||
|
GLint location = glGetUniformLocation(program_, name.c_str());
|
||||||
|
if (location != -1) {
|
||||||
|
glUniformMatrix4fv(location, 1, GL_FALSE, glm::value_ptr(value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GLShader::isCompiled() const {
|
||||||
|
return program_ != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string GLShader::getCompileLog() const {
|
||||||
|
return compileLog_;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GLShader::isValid() const {
|
||||||
|
return program_ != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,120 @@
|
||||||
|
#include <renderer/rhi/opengl/gl_texture.h>
|
||||||
|
#include <renderer/rhi/opengl/gl_utils.h>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查纹理格式是否为深度格式
|
||||||
|
* @param format 纹理格式
|
||||||
|
* @return 是否为深度格式
|
||||||
|
*/
|
||||||
|
static inline bool isDepthFormat(TextureFormat format) {
|
||||||
|
switch (format) {
|
||||||
|
case TextureFormat::Depth16:
|
||||||
|
case TextureFormat::Depth24:
|
||||||
|
case TextureFormat::Depth32F:
|
||||||
|
case TextureFormat::Depth24Stencil8:
|
||||||
|
case TextureFormat::Depth32FStencil8:
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GLTexture::GLTexture(const TextureDesc &desc) : desc_(desc), texture_(0) {}
|
||||||
|
|
||||||
|
GLTexture::~GLTexture() { destroy(); }
|
||||||
|
|
||||||
|
bool GLTexture::create() {
|
||||||
|
glGenTextures(1, &texture_);
|
||||||
|
if (texture_ == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
GLenum target = GL_TEXTURE_2D;
|
||||||
|
glBindTexture(target, texture_);
|
||||||
|
|
||||||
|
// 设置纹理参数
|
||||||
|
glTexParameteri(target, GL_TEXTURE_MIN_FILTER,
|
||||||
|
textureFilterToGLMin(desc_.minFilter));
|
||||||
|
glTexParameteri(target, GL_TEXTURE_MAG_FILTER,
|
||||||
|
textureFilterToGLMag(desc_.magFilter));
|
||||||
|
glTexParameteri(target, GL_TEXTURE_WRAP_S, textureWrapToGL(desc_.wrapS));
|
||||||
|
glTexParameteri(target, GL_TEXTURE_WRAP_T, textureWrapToGL(desc_.wrapT));
|
||||||
|
|
||||||
|
// 分配纹理存储
|
||||||
|
GLenum format = textureFormatToGLFormat(desc_.format);
|
||||||
|
GLenum internalFormat = textureFormatToGLInternal(desc_.format);
|
||||||
|
GLenum type = GL_UNSIGNED_BYTE;
|
||||||
|
|
||||||
|
if (isDepthFormat(desc_.format)) {
|
||||||
|
type = GL_FLOAT;
|
||||||
|
}
|
||||||
|
|
||||||
|
glTexImage2D(target, 0, internalFormat, desc_.width, desc_.height, 0, format,
|
||||||
|
type, nullptr);
|
||||||
|
|
||||||
|
// 生成 mipmap(如果需要)
|
||||||
|
if (desc_.mipLevels > 1) {
|
||||||
|
glGenerateMipmap(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
glBindTexture(target, 0);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLTexture::destroy() {
|
||||||
|
if (texture_ != 0) {
|
||||||
|
glDeleteTextures(1, &texture_);
|
||||||
|
texture_ = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GLTexture::update(const void *data, uint32_t mipLevel) {
|
||||||
|
if (texture_ == 0 || data == nullptr)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
glBindTexture(GL_TEXTURE_2D, texture_);
|
||||||
|
GLenum format = textureFormatToGLFormat(desc_.format);
|
||||||
|
GLenum type = isDepthFormat(desc_.format) ? GL_FLOAT : GL_UNSIGNED_BYTE;
|
||||||
|
// 计算当前 mipmap 层级的尺寸
|
||||||
|
uint32_t width = desc_.width >> mipLevel;
|
||||||
|
uint32_t height = desc_.height >> mipLevel;
|
||||||
|
if (width == 0)
|
||||||
|
width = 1;
|
||||||
|
if (height == 0)
|
||||||
|
height = 1;
|
||||||
|
glTexSubImage2D(GL_TEXTURE_2D, mipLevel, 0, 0, width, height, format, type,
|
||||||
|
data);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLTexture::generateMipmap() {
|
||||||
|
if (texture_ == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
glBindTexture(GL_TEXTURE_2D, texture_);
|
||||||
|
glGenerateMipmap(GL_TEXTURE_2D);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLTexture::bind(uint32_t slot) {
|
||||||
|
if (texture_ == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
glActiveTexture(GL_TEXTURE0 + slot);
|
||||||
|
glBindTexture(GL_TEXTURE_2D, texture_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLTexture::unbind() { glBindTexture(GL_TEXTURE_2D, 0); }
|
||||||
|
|
||||||
|
bool GLTexture::isValid() const {
|
||||||
|
return texture_ != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GLTexture::isRenderTarget() const {
|
||||||
|
return desc_.renderTarget;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,352 @@
|
||||||
|
#include <module/module_registry.h>
|
||||||
|
#include <platform/window_module.h>
|
||||||
|
#include <renderer/rhi_module.h>
|
||||||
|
#include <utils/logger.h>
|
||||||
|
|
||||||
|
// 包含 OpenGL 设备实现
|
||||||
|
// 注意:GLDevice 定义在 gl_device.cpp 中,需要确保链接时包含
|
||||||
|
namespace extra2d {
|
||||||
|
// 前向声明 GLDevice 类
|
||||||
|
class GLDevice;
|
||||||
|
} // namespace extra2d
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// 全局实例指针
|
||||||
|
static RHIModule *g_rhiModule = nullptr;
|
||||||
|
|
||||||
|
// GLDevice 创建函数声明 - 在 gl_device.cpp 中定义
|
||||||
|
std::unique_ptr<RHIDevice> CreateGLDevice();
|
||||||
|
|
||||||
|
RHIModule::RHIModule() : context_(nullptr) { g_rhiModule = this; }
|
||||||
|
|
||||||
|
RHIModule::~RHIModule() {
|
||||||
|
shutdown();
|
||||||
|
g_rhiModule = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RHIModule::init() {
|
||||||
|
E2D_LOG_INFO("Initializing RHIModule...");
|
||||||
|
|
||||||
|
// 注册窗口显示事件监听,在窗口显示时创建 OpenGL 上下文
|
||||||
|
onShowListener_ = std::make_unique<events::OnShow::Listener>();
|
||||||
|
onShowListener_->bind([this]() { this->onWindowShow(); });
|
||||||
|
|
||||||
|
E2D_LOG_INFO("RHIModule initialized (waiting for window show)");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RHIModule::onWindowShow() {
|
||||||
|
if (initialized_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
E2D_LOG_INFO("RHIModule: Creating OpenGL device...");
|
||||||
|
|
||||||
|
// 获取窗口模块
|
||||||
|
auto *windowModule = getModule<WindowModule>();
|
||||||
|
if (!windowModule) {
|
||||||
|
E2D_LOG_ERROR("WindowModule not available");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 等待窗口创建完成
|
||||||
|
if (!windowModule->handle()) {
|
||||||
|
E2D_LOG_ERROR("Window not created yet");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建 OpenGL 设备并传入窗口句柄
|
||||||
|
device_ = CreateGLDevice();
|
||||||
|
if (!device_) {
|
||||||
|
E2D_LOG_ERROR("Failed to create RHI device");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!device_->initialize(windowModule->handle())) {
|
||||||
|
E2D_LOG_ERROR("Failed to initialize RHI device");
|
||||||
|
device_.reset();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
context_ = device_->getContext();
|
||||||
|
if (!context_) {
|
||||||
|
E2D_LOG_ERROR("Failed to get RHI context");
|
||||||
|
device_->shutdown();
|
||||||
|
device_.reset();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化上下文
|
||||||
|
if (!context_->initialize()) {
|
||||||
|
E2D_LOG_ERROR("Failed to initialize RHI context");
|
||||||
|
device_->shutdown();
|
||||||
|
device_.reset();
|
||||||
|
context_ = nullptr;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
initialized_ = true;
|
||||||
|
E2D_LOG_INFO("RHIModule initialized successfully (backend: {})",
|
||||||
|
device_->getBackendName());
|
||||||
|
|
||||||
|
// RHI 初始化完成,再次触发 OnShow 事件通知其他模块
|
||||||
|
// 这样 RendererModule 等依赖 RHI 的模块可以完成初始化
|
||||||
|
events::OnShow::emit();
|
||||||
|
}
|
||||||
|
|
||||||
|
void RHIModule::shutdown() {
|
||||||
|
E2D_LOG_INFO("Shutting down RHIModule...");
|
||||||
|
|
||||||
|
// 清理事件监听器
|
||||||
|
onShowListener_.reset();
|
||||||
|
|
||||||
|
if (context_) {
|
||||||
|
context_->shutdown();
|
||||||
|
context_ = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (device_) {
|
||||||
|
device_->shutdown();
|
||||||
|
device_.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
initialized_ = false;
|
||||||
|
|
||||||
|
E2D_LOG_INFO("RHIModule shutdown complete");
|
||||||
|
}
|
||||||
|
|
||||||
|
RHIModule *RHIModule::get() { return g_rhiModule; }
|
||||||
|
|
||||||
|
std::unique_ptr<RHIBuffer> RHIModule::createBuffer(const BufferDesc &desc) {
|
||||||
|
if (!device_) {
|
||||||
|
E2D_LOG_ERROR("Cannot create buffer: RHI device not initialized");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return device_->createBuffer(desc);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<RHITexture> RHIModule::createTexture(const TextureDesc &desc) {
|
||||||
|
if (!device_) {
|
||||||
|
E2D_LOG_ERROR("Cannot create texture: RHI device not initialized");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return device_->createTexture(desc);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<RHIShader> RHIModule::createShader(const ShaderDesc &desc) {
|
||||||
|
if (!device_) {
|
||||||
|
E2D_LOG_ERROR("Cannot create shader: RHI device not initialized");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return device_->createShader(desc);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<RHIPipeline>
|
||||||
|
RHIModule::createPipeline(const PipelineDesc &desc) {
|
||||||
|
if (!device_) {
|
||||||
|
E2D_LOG_ERROR("Cannot create pipeline: RHI device not initialized");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return device_->createPipeline(desc);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<RHIFramebuffer>
|
||||||
|
RHIModule::createFramebuffer(const RenderPassDesc &desc) {
|
||||||
|
if (!device_) {
|
||||||
|
E2D_LOG_ERROR("Cannot create framebuffer: RHI device not initialized");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return device_->createFramebuffer(desc);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<RHICommandList> RHIModule::createCommandList() {
|
||||||
|
if (!device_) {
|
||||||
|
E2D_LOG_ERROR("Cannot create command list: RHI device not initialized");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return device_->createCommandList();
|
||||||
|
}
|
||||||
|
|
||||||
|
// BufferDesc 静态方法实现
|
||||||
|
BufferDesc BufferDesc::vertex(uint32_t size, BufferUsage usage) {
|
||||||
|
BufferDesc desc;
|
||||||
|
desc.type = BufferType::Vertex;
|
||||||
|
desc.usage = usage;
|
||||||
|
desc.size = size;
|
||||||
|
return desc;
|
||||||
|
}
|
||||||
|
|
||||||
|
BufferDesc BufferDesc::index(uint32_t size, BufferUsage usage) {
|
||||||
|
BufferDesc desc;
|
||||||
|
desc.type = BufferType::Index;
|
||||||
|
desc.usage = usage;
|
||||||
|
desc.size = size;
|
||||||
|
return desc;
|
||||||
|
}
|
||||||
|
|
||||||
|
BufferDesc BufferDesc::uniform(uint32_t size) {
|
||||||
|
BufferDesc desc;
|
||||||
|
desc.type = BufferType::Uniform;
|
||||||
|
desc.usage = BufferUsage::Dynamic;
|
||||||
|
desc.size = size;
|
||||||
|
return desc;
|
||||||
|
}
|
||||||
|
|
||||||
|
// BlendState 静态方法实现
|
||||||
|
BlendState BlendState::opaque() {
|
||||||
|
BlendState state;
|
||||||
|
state.enabled = false;
|
||||||
|
state.srcFactor = BlendFactor::One;
|
||||||
|
state.dstFactor = BlendFactor::Zero;
|
||||||
|
state.op = BlendOp::Add;
|
||||||
|
state.srcAlphaFactor = BlendFactor::One;
|
||||||
|
state.dstAlphaFactor = BlendFactor::Zero;
|
||||||
|
state.alphaOp = BlendOp::Add;
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
BlendState BlendState::alphaBlend() {
|
||||||
|
BlendState state;
|
||||||
|
state.enabled = true;
|
||||||
|
state.srcFactor = BlendFactor::SrcAlpha;
|
||||||
|
state.dstFactor = BlendFactor::OneMinusSrcAlpha;
|
||||||
|
state.op = BlendOp::Add;
|
||||||
|
state.srcAlphaFactor = BlendFactor::One;
|
||||||
|
state.dstAlphaFactor = BlendFactor::OneMinusSrcAlpha;
|
||||||
|
state.alphaOp = BlendOp::Add;
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
BlendState BlendState::additive() {
|
||||||
|
BlendState state;
|
||||||
|
state.enabled = true;
|
||||||
|
state.srcFactor = BlendFactor::SrcAlpha;
|
||||||
|
state.dstFactor = BlendFactor::One;
|
||||||
|
state.op = BlendOp::Add;
|
||||||
|
state.srcAlphaFactor = BlendFactor::SrcAlpha;
|
||||||
|
state.dstAlphaFactor = BlendFactor::One;
|
||||||
|
state.alphaOp = BlendOp::Add;
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
// DepthStencilState 静态方法实现
|
||||||
|
DepthStencilState DepthStencilState::depthTest() {
|
||||||
|
DepthStencilState state;
|
||||||
|
state.depthTestEnabled = true;
|
||||||
|
state.depthWriteEnabled = true;
|
||||||
|
state.depthCompare = CompareFunc::Less;
|
||||||
|
state.stencilEnabled = false;
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
DepthStencilState DepthStencilState::depthTestWrite() {
|
||||||
|
DepthStencilState state;
|
||||||
|
state.depthTestEnabled = true;
|
||||||
|
state.depthWriteEnabled = true;
|
||||||
|
state.depthCompare = CompareFunc::Less;
|
||||||
|
state.stencilEnabled = false;
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
DepthStencilState DepthStencilState::depthTestNoWrite() {
|
||||||
|
DepthStencilState state;
|
||||||
|
state.depthTestEnabled = true;
|
||||||
|
state.depthWriteEnabled = false;
|
||||||
|
state.depthCompare = CompareFunc::Less;
|
||||||
|
state.stencilEnabled = false;
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
DepthStencilState DepthStencilState::noDepthTest() {
|
||||||
|
DepthStencilState state;
|
||||||
|
state.depthTestEnabled = false;
|
||||||
|
state.depthWriteEnabled = false;
|
||||||
|
state.depthCompare = CompareFunc::Always;
|
||||||
|
state.stencilEnabled = false;
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
// RasterizerState 静态方法实现
|
||||||
|
RasterizerState RasterizerState::cullBack() {
|
||||||
|
RasterizerState state;
|
||||||
|
state.cullEnabled = true;
|
||||||
|
state.cullFrontFace = false;
|
||||||
|
state.frontCCW = false;
|
||||||
|
state.scissorEnabled = false;
|
||||||
|
state.wireframe = false;
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
RasterizerState RasterizerState::cullFront() {
|
||||||
|
RasterizerState state;
|
||||||
|
state.cullEnabled = true;
|
||||||
|
state.cullFrontFace = true;
|
||||||
|
state.frontCCW = false;
|
||||||
|
state.scissorEnabled = false;
|
||||||
|
state.wireframe = false;
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
RasterizerState RasterizerState::noCull() {
|
||||||
|
RasterizerState state;
|
||||||
|
state.cullEnabled = false;
|
||||||
|
state.cullFrontFace = false;
|
||||||
|
state.frontCCW = false;
|
||||||
|
state.scissorEnabled = false;
|
||||||
|
state.wireframe = false;
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
// VertexLayout 方法实现
|
||||||
|
void VertexLayout::addAttribute(uint32_t location, VertexFormat format,
|
||||||
|
uint32_t offset, uint32_t bufferIndex) {
|
||||||
|
VertexAttribute attr;
|
||||||
|
attr.location = location;
|
||||||
|
attr.format = format;
|
||||||
|
attr.offset = offset;
|
||||||
|
attr.bufferIndex = bufferIndex;
|
||||||
|
attributes.push_back(attr);
|
||||||
|
}
|
||||||
|
|
||||||
|
// VertexAttribute 方法实现
|
||||||
|
uint32_t VertexAttribute::getSize(VertexFormat format) {
|
||||||
|
switch (format) {
|
||||||
|
case VertexFormat::Float1:
|
||||||
|
return 4;
|
||||||
|
case VertexFormat::Float2:
|
||||||
|
return 8;
|
||||||
|
case VertexFormat::Float3:
|
||||||
|
return 12;
|
||||||
|
case VertexFormat::Float4:
|
||||||
|
return 16;
|
||||||
|
case VertexFormat::Int1:
|
||||||
|
return 4;
|
||||||
|
case VertexFormat::Int2:
|
||||||
|
return 8;
|
||||||
|
case VertexFormat::Int3:
|
||||||
|
return 12;
|
||||||
|
case VertexFormat::Int4:
|
||||||
|
return 16;
|
||||||
|
case VertexFormat::UInt1:
|
||||||
|
return 4;
|
||||||
|
case VertexFormat::UInt2:
|
||||||
|
return 8;
|
||||||
|
case VertexFormat::UInt3:
|
||||||
|
return 12;
|
||||||
|
case VertexFormat::UInt4:
|
||||||
|
return 16;
|
||||||
|
case VertexFormat::Byte4:
|
||||||
|
return 4;
|
||||||
|
case VertexFormat::Byte4Normalized:
|
||||||
|
return 4;
|
||||||
|
case VertexFormat::UByte4:
|
||||||
|
return 4;
|
||||||
|
case VertexFormat::UByte4Normalized:
|
||||||
|
return 4;
|
||||||
|
default:
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -1,7 +1,5 @@
|
||||||
#include <fstream>
|
#include <renderer/rhi_module.h>
|
||||||
#include <glad/glad.h>
|
|
||||||
#include <renderer/shader.h>
|
#include <renderer/shader.h>
|
||||||
#include <sstream>
|
|
||||||
#include <utils/logger.h>
|
#include <utils/logger.h>
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
@ -9,203 +7,181 @@ namespace extra2d {
|
||||||
Shader::Shader() = default;
|
Shader::Shader() = default;
|
||||||
|
|
||||||
Shader::~Shader() {
|
Shader::~Shader() {
|
||||||
if (program_ != 0) {
|
// RHIHandle 是轻量级句柄,不需要显式释放
|
||||||
glDeleteProgram(program_);
|
// 实际的资源由 RHI 设备管理
|
||||||
program_ = 0;
|
pipeline_ = PipelineHandle();
|
||||||
}
|
handle_ = ShaderHandle();
|
||||||
}
|
|
||||||
|
|
||||||
bool Shader::loadFromFile(const std::string &vsPath,
|
|
||||||
const std::string &fsPath) {
|
|
||||||
// 读取顶点着色器
|
|
||||||
std::ifstream vsFile(vsPath);
|
|
||||||
if (!vsFile.is_open()) {
|
|
||||||
E2D_LOG_ERROR("Failed to open vertex shader: {}", vsPath);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
std::stringstream vsStream;
|
|
||||||
vsStream << vsFile.rdbuf();
|
|
||||||
std::string vsSource = vsStream.str();
|
|
||||||
|
|
||||||
// 读取片段着色器
|
|
||||||
std::ifstream fsFile(fsPath);
|
|
||||||
if (!fsFile.is_open()) {
|
|
||||||
E2D_LOG_ERROR("Failed to open fragment shader: {}", fsPath);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
std::stringstream fsStream;
|
|
||||||
fsStream << fsFile.rdbuf();
|
|
||||||
std::string fsSource = fsStream.str();
|
|
||||||
|
|
||||||
return loadFromSource(vsSource, fsSource);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Shader::loadFromSource(const std::string &vsSource,
|
bool Shader::loadFromSource(const std::string &vsSource,
|
||||||
const std::string &fsSource) {
|
const std::string &fsSource) {
|
||||||
// 删除旧程序
|
// 创建标准2D顶点布局(位置 + 纹理坐标 + 颜色)
|
||||||
if (program_ != 0) {
|
VertexLayout vertexLayout;
|
||||||
glDeleteProgram(program_);
|
vertexLayout.stride = sizeof(float) * 8; // 2 (pos) + 2 (uv) + 4 (color)
|
||||||
program_ = 0;
|
vertexLayout.addAttribute(0, VertexFormat::Float2, 0); // 位置
|
||||||
|
vertexLayout.addAttribute(1, VertexFormat::Float2, 8); // 纹理坐标
|
||||||
|
vertexLayout.addAttribute(2, VertexFormat::Float4, 16); // 颜色
|
||||||
|
|
||||||
|
return loadFromSourceWithLayout(vsSource, fsSource, vertexLayout);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Shader::loadFromSourceWithLayout(const std::string &vsSource,
|
||||||
|
const std::string &fsSource,
|
||||||
|
const VertexLayout &vertexLayout) {
|
||||||
|
// 释放旧资源
|
||||||
|
pipeline_ = PipelineHandle();
|
||||||
|
handle_ = ShaderHandle();
|
||||||
|
|
||||||
|
// 获取 RHI 设备
|
||||||
|
auto *rhiModule = RHIModule::get();
|
||||||
|
if (!rhiModule) {
|
||||||
|
E2D_LOG_ERROR("RHIModule not available");
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
uniformCache_.clear();
|
auto *device = rhiModule->getDevice();
|
||||||
|
if (!device) {
|
||||||
|
E2D_LOG_ERROR("RHIDevice not available");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// 处理源码(添加版本声明)
|
// 处理源码(添加版本声明)
|
||||||
std::string processedVS = addVersionIfNeeded(vsSource, GL_VERTEX_SHADER);
|
std::string processedVS = addVersionIfNeeded(vsSource, true);
|
||||||
std::string processedFS = addVersionIfNeeded(fsSource, GL_FRAGMENT_SHADER);
|
std::string processedFS = addVersionIfNeeded(fsSource, false);
|
||||||
|
|
||||||
E2D_LOG_INFO("Compiling vertex shader...");
|
// 创建着色器描述
|
||||||
// 编译顶点着色器
|
ShaderDesc shaderDesc;
|
||||||
GLuint vs = compileShader(GL_VERTEX_SHADER, processedVS);
|
shaderDesc.vertexSource = processedVS;
|
||||||
if (vs == 0) {
|
shaderDesc.fragmentSource = processedFS;
|
||||||
E2D_LOG_ERROR("Vertex shader compilation failed");
|
|
||||||
|
// 创建着色器
|
||||||
|
auto shader = device->createShader(shaderDesc);
|
||||||
|
if (!shader) {
|
||||||
|
E2D_LOG_ERROR("Failed to create shader");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
E2D_LOG_INFO("Compiling fragment shader...");
|
// 获取着色器句柄
|
||||||
// 编译片段着色器
|
handle_ = ShaderHandle(shader.release());
|
||||||
GLuint fs = compileShader(GL_FRAGMENT_SHADER, processedFS);
|
|
||||||
if (fs == 0) {
|
// 创建管线描述
|
||||||
E2D_LOG_ERROR("Fragment shader compilation failed");
|
PipelineDesc pipelineDesc;
|
||||||
glDeleteShader(vs);
|
pipelineDesc.vertexShader = handle_;
|
||||||
|
pipelineDesc.fragmentShader = handle_;
|
||||||
|
pipelineDesc.vertexLayout = vertexLayout;
|
||||||
|
pipelineDesc.blendState = BlendState::alphaBlend();
|
||||||
|
pipelineDesc.depthStencilState = DepthStencilState::noDepthTest();
|
||||||
|
pipelineDesc.rasterizerState = RasterizerState::noCull();
|
||||||
|
|
||||||
|
// 创建管线
|
||||||
|
auto pipeline = device->createPipeline(pipelineDesc);
|
||||||
|
if (!pipeline) {
|
||||||
|
E2D_LOG_ERROR("Failed to create pipeline");
|
||||||
|
handle_ = ShaderHandle();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
E2D_LOG_INFO("Linking shader program...");
|
// 获取管线句柄
|
||||||
// 链接程序
|
pipeline_ = PipelineHandle(pipeline.release());
|
||||||
if (!linkProgram(vs, fs)) {
|
|
||||||
E2D_LOG_ERROR("Shader program linking failed");
|
|
||||||
glDeleteShader(vs);
|
|
||||||
glDeleteShader(fs);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 清理着色器对象
|
E2D_LOG_INFO("Shader created successfully");
|
||||||
glDeleteShader(vs);
|
|
||||||
glDeleteShader(fs);
|
|
||||||
|
|
||||||
E2D_LOG_INFO("Shader program created successfully, program ID: {}", program_);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Shader::bind() const {
|
|
||||||
if (program_ != 0) {
|
|
||||||
glUseProgram(program_);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Shader::unbind() const { glUseProgram(0); }
|
|
||||||
|
|
||||||
void Shader::setUniformBlock(const std::string &name, uint32_t binding) {
|
void Shader::setUniformBlock(const std::string &name, uint32_t binding) {
|
||||||
if (program_ == 0)
|
// 存储 uniform block 绑定信息
|
||||||
return;
|
uniformBlockBindings_[name] = binding;
|
||||||
|
|
||||||
GLuint index = glGetUniformBlockIndex(program_, name.c_str());
|
// 注意:实际的 uniform block 绑定需要在渲染时通过 RHI 命令列表设置
|
||||||
if (index != GL_INVALID_INDEX) {
|
// 这里仅存储绑定信息,供后续渲染使用
|
||||||
glUniformBlockBinding(program_, index, binding);
|
// 例如:commandList->setUniformBlock(binding, buffer);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Shader::setInt(const std::string &name, int value) {
|
uint32_t Shader::getUniformBlockBinding(const std::string &name) const {
|
||||||
GLint location = getUniformLocation(name);
|
auto it = uniformBlockBindings_.find(name);
|
||||||
if (location != -1) {
|
if (it != uniformBlockBindings_.end()) {
|
||||||
glUniform1i(location, value);
|
return it->second;
|
||||||
}
|
}
|
||||||
|
return UINT32_MAX; // 未找到
|
||||||
}
|
}
|
||||||
|
|
||||||
void Shader::setFloat(const std::string &name, float value) {
|
bool Shader::reloadFromSource(const std::string &vsSource,
|
||||||
GLint location = getUniformLocation(name);
|
const std::string &fsSource) {
|
||||||
if (location != -1) {
|
// 创建标准2D顶点布局(位置 + 纹理坐标 + 颜色)
|
||||||
glUniform1f(location, value);
|
VertexLayout vertexLayout;
|
||||||
}
|
vertexLayout.stride = sizeof(float) * 8; // 2 (pos) + 2 (uv) + 4 (color)
|
||||||
|
vertexLayout.addAttribute(0, VertexFormat::Float2, 0); // 位置
|
||||||
|
vertexLayout.addAttribute(1, VertexFormat::Float2, 8); // 纹理坐标
|
||||||
|
vertexLayout.addAttribute(2, VertexFormat::Float4, 16); // 颜色
|
||||||
|
|
||||||
|
return reloadFromSourceWithLayout(vsSource, fsSource, vertexLayout);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Shader::setVec2(const std::string &name, float x, float y) {
|
bool Shader::reloadFromSourceWithLayout(const std::string &vsSource,
|
||||||
GLint location = getUniformLocation(name);
|
const std::string &fsSource,
|
||||||
if (location != -1) {
|
const VertexLayout &vertexLayout) {
|
||||||
glUniform2f(location, x, y);
|
// 释放旧资源
|
||||||
}
|
pipeline_ = PipelineHandle();
|
||||||
}
|
handle_ = ShaderHandle();
|
||||||
|
|
||||||
void Shader::setVec4(const std::string &name, float x, float y, float z,
|
// 获取 RHI 设备
|
||||||
float w) {
|
auto *rhiModule = RHIModule::get();
|
||||||
GLint location = getUniformLocation(name);
|
if (!rhiModule) {
|
||||||
if (location != -1) {
|
E2D_LOG_ERROR("RHIModule not available");
|
||||||
glUniform4f(location, x, y, z, w);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Shader::setMat4(const std::string &name, const float *value) {
|
|
||||||
GLint location = getUniformLocation(name);
|
|
||||||
if (location != -1) {
|
|
||||||
glUniformMatrix4fv(location, 1, GL_FALSE, value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
GLuint Shader::compileShader(GLenum type, const std::string &source) {
|
|
||||||
GLuint shader = glCreateShader(type);
|
|
||||||
|
|
||||||
const char *src = source.c_str();
|
|
||||||
glShaderSource(shader, 1, &src, nullptr);
|
|
||||||
glCompileShader(shader);
|
|
||||||
|
|
||||||
// 检查编译状态
|
|
||||||
GLint success;
|
|
||||||
glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
|
|
||||||
if (!success) {
|
|
||||||
char infoLog[512];
|
|
||||||
glGetShaderInfoLog(shader, 512, nullptr, infoLog);
|
|
||||||
|
|
||||||
const char *typeStr = (type == GL_VERTEX_SHADER) ? "vertex" : "fragment";
|
|
||||||
E2D_LOG_ERROR("{} shader compilation failed: {}", typeStr, infoLog);
|
|
||||||
|
|
||||||
glDeleteShader(shader);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
return shader;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Shader::linkProgram(GLuint vertexShader, GLuint fragmentShader) {
|
|
||||||
program_ = glCreateProgram();
|
|
||||||
glAttachShader(program_, vertexShader);
|
|
||||||
glAttachShader(program_, fragmentShader);
|
|
||||||
glLinkProgram(program_);
|
|
||||||
|
|
||||||
// 检查链接状态
|
|
||||||
GLint success;
|
|
||||||
glGetProgramiv(program_, GL_LINK_STATUS, &success);
|
|
||||||
if (!success) {
|
|
||||||
char infoLog[512];
|
|
||||||
glGetProgramInfoLog(program_, 512, nullptr, infoLog);
|
|
||||||
E2D_LOG_ERROR("Shader program linking failed: {}", infoLog);
|
|
||||||
|
|
||||||
glDeleteProgram(program_);
|
|
||||||
program_ = 0;
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto *device = rhiModule->getDevice();
|
||||||
|
if (!device) {
|
||||||
|
E2D_LOG_ERROR("RHIDevice not available");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理源码(添加版本声明)
|
||||||
|
std::string processedVS = addVersionIfNeeded(vsSource, true);
|
||||||
|
std::string processedFS = addVersionIfNeeded(fsSource, false);
|
||||||
|
|
||||||
|
// 创建着色器描述
|
||||||
|
ShaderDesc shaderDesc;
|
||||||
|
shaderDesc.vertexSource = processedVS;
|
||||||
|
shaderDesc.fragmentSource = processedFS;
|
||||||
|
|
||||||
|
// 创建着色器
|
||||||
|
auto shader = device->createShader(shaderDesc);
|
||||||
|
if (!shader) {
|
||||||
|
E2D_LOG_ERROR("Failed to create shader during reload");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取着色器句柄
|
||||||
|
handle_ = ShaderHandle(shader.release());
|
||||||
|
|
||||||
|
// 创建管线描述
|
||||||
|
PipelineDesc pipelineDesc;
|
||||||
|
pipelineDesc.vertexShader = handle_;
|
||||||
|
pipelineDesc.fragmentShader = handle_;
|
||||||
|
pipelineDesc.vertexLayout = vertexLayout;
|
||||||
|
pipelineDesc.blendState = BlendState::alphaBlend();
|
||||||
|
pipelineDesc.depthStencilState = DepthStencilState::noDepthTest();
|
||||||
|
pipelineDesc.rasterizerState = RasterizerState::noCull();
|
||||||
|
|
||||||
|
// 创建管线
|
||||||
|
auto pipeline = device->createPipeline(pipelineDesc);
|
||||||
|
if (!pipeline) {
|
||||||
|
E2D_LOG_ERROR("Failed to create pipeline during reload");
|
||||||
|
handle_ = ShaderHandle();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取管线句柄
|
||||||
|
pipeline_ = PipelineHandle(pipeline.release());
|
||||||
|
|
||||||
|
E2D_LOG_INFO("Shader reloaded successfully");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
GLint Shader::getUniformLocation(const std::string &name) {
|
std::string Shader::addVersionIfNeeded(const std::string &source,
|
||||||
if (program_ == 0)
|
bool isVertex) {
|
||||||
return -1;
|
|
||||||
|
|
||||||
// 检查缓存
|
|
||||||
auto it = uniformCache_.find(name);
|
|
||||||
if (it != uniformCache_.end()) {
|
|
||||||
return it->second;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 查询 uniform 位置
|
|
||||||
GLint location = glGetUniformLocation(program_, name.c_str());
|
|
||||||
uniformCache_[name] = location;
|
|
||||||
|
|
||||||
return location;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string Shader::addVersionIfNeeded(const std::string &source, GLenum type) {
|
|
||||||
// 如果已经包含版本声明,直接返回
|
// 如果已经包含版本声明,直接返回
|
||||||
if (source.find("#version") != std::string::npos) {
|
if (source.find("#version") != std::string::npos) {
|
||||||
return source;
|
return source;
|
||||||
|
|
@ -215,7 +191,7 @@ std::string Shader::addVersionIfNeeded(const std::string &source, GLenum type) {
|
||||||
std::string result = "#version 320 es\n";
|
std::string result = "#version 320 es\n";
|
||||||
|
|
||||||
// 片段着色器需要添加精度声明
|
// 片段着色器需要添加精度声明
|
||||||
if (type == GL_FRAGMENT_SHADER) {
|
if (!isVertex) {
|
||||||
result += "precision mediump float;\n";
|
result += "precision mediump float;\n";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,46 +1,17 @@
|
||||||
#include <renderer/texture.h>
|
#include <renderer/texture.h>
|
||||||
|
#include <renderer/rhi_module.h>
|
||||||
#include <utils/logger.h>
|
#include <utils/logger.h>
|
||||||
#include <glad/glad.h>
|
|
||||||
#define STB_IMAGE_IMPLEMENTATION
|
#define STB_IMAGE_IMPLEMENTATION
|
||||||
#include <stb/stb_image.h>
|
#include <stb/stb_image.h>
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
||||||
// OpenGL 格式转换函数
|
|
||||||
static GLint getTextureInternalFormat(TextureFormat format) {
|
|
||||||
switch (format) {
|
|
||||||
case TextureFormat::RGBA8: return GL_RGBA8;
|
|
||||||
case TextureFormat::RGB8: return GL_RGB8;
|
|
||||||
case TextureFormat::RGBA4: return GL_RGBA4;
|
|
||||||
case TextureFormat::R8: return GL_R8;
|
|
||||||
case TextureFormat::RG8: return GL_RG8;
|
|
||||||
default: return GL_RGBA8;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static GLenum getTextureFormat(TextureFormat format) {
|
|
||||||
switch (format) {
|
|
||||||
case TextureFormat::RGBA8:
|
|
||||||
case TextureFormat::RGBA4:
|
|
||||||
return GL_RGBA;
|
|
||||||
case TextureFormat::RGB8:
|
|
||||||
return GL_RGB;
|
|
||||||
case TextureFormat::R8:
|
|
||||||
return GL_RED;
|
|
||||||
case TextureFormat::RG8:
|
|
||||||
return GL_RG;
|
|
||||||
default:
|
|
||||||
return GL_RGBA;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Texture::Texture() = default;
|
Texture::Texture() = default;
|
||||||
|
|
||||||
Texture::~Texture() {
|
Texture::~Texture() {
|
||||||
if (texture_ != 0) {
|
// RHI 纹理句柄是轻量级句柄,不需要显式释放
|
||||||
glDeleteTextures(1, &texture_);
|
// 实际的纹理资源由 RHI 设备管理
|
||||||
texture_ = 0;
|
handle_ = TextureHandle();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Texture::loadFromFile(const std::string& path) {
|
bool Texture::loadFromFile(const std::string& path) {
|
||||||
|
|
@ -75,81 +46,186 @@ bool Texture::loadFromFile(const std::string& path) {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Texture::loadFromMemory(const uint8_t* data, int width, int height, TextureFormat format) {
|
bool Texture::loadFromMemory(const uint8_t* data, int width, int height, TextureFormat format) {
|
||||||
if (texture_ != 0) {
|
// 释放旧纹理
|
||||||
glDeleteTextures(1, &texture_);
|
handle_ = TextureHandle();
|
||||||
texture_ = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
width_ = width;
|
width_ = width;
|
||||||
height_ = height;
|
height_ = height;
|
||||||
format_ = format;
|
format_ = format;
|
||||||
|
|
||||||
// 创建纹理
|
// 获取 RHI 设备
|
||||||
glGenTextures(1, &texture_);
|
auto* rhiModule = RHIModule::get();
|
||||||
glBindTexture(GL_TEXTURE_2D, texture_);
|
if (!rhiModule) {
|
||||||
|
E2D_LOG_ERROR("RHIModule not available");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// 设置纹理参数
|
auto* device = rhiModule->getDevice();
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
if (!device) {
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
E2D_LOG_ERROR("RHIDevice not available");
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
|
return false;
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
}
|
||||||
|
|
||||||
|
// 创建纹理描述
|
||||||
|
TextureDesc desc;
|
||||||
|
desc.width = static_cast<uint32_t>(width);
|
||||||
|
desc.height = static_cast<uint32_t>(height);
|
||||||
|
desc.format = format;
|
||||||
|
desc.mipLevels = 1; // 初始创建 1 层,后续生成 mipmap
|
||||||
|
|
||||||
|
// 创建 RHI 纹理
|
||||||
|
auto texture = device->createTexture(desc);
|
||||||
|
if (!texture) {
|
||||||
|
E2D_LOG_ERROR("Failed to create RHI texture");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// 上传纹理数据
|
// 上传纹理数据
|
||||||
glTexImage2D(GL_TEXTURE_2D, 0,
|
if (data) {
|
||||||
getTextureInternalFormat(format_),
|
// 注意:update 方法的参数需要根据实际 RHI 接口调整
|
||||||
width_, height_, 0,
|
// 这里假设 update 接受数据指针
|
||||||
getTextureFormat(format_),
|
texture->update(data, static_cast<size_t>(width * height * getBytesPerPixel(format)));
|
||||||
GL_UNSIGNED_BYTE, data);
|
|
||||||
|
|
||||||
// 生成 mipmap
|
// 生成 mipmap
|
||||||
glGenerateMipmap(GL_TEXTURE_2D);
|
// 注意:generateMipmap 方法需要在 RHI 接口中实现
|
||||||
|
}
|
||||||
|
|
||||||
glBindTexture(GL_TEXTURE_2D, 0);
|
// 获取纹理句柄
|
||||||
|
handle_ = TextureHandle(texture.release());
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Texture::create(int width, int height, TextureFormat format) {
|
bool Texture::create(int width, int height, TextureFormat format) {
|
||||||
if (texture_ != 0) {
|
// 释放旧纹理
|
||||||
glDeleteTextures(1, &texture_);
|
handle_ = TextureHandle();
|
||||||
texture_ = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
width_ = width;
|
width_ = width;
|
||||||
height_ = height;
|
height_ = height;
|
||||||
format_ = format;
|
format_ = format;
|
||||||
|
|
||||||
// 创建纹理
|
// 获取 RHI 设备
|
||||||
glGenTextures(1, &texture_);
|
auto* rhiModule = RHIModule::get();
|
||||||
glBindTexture(GL_TEXTURE_2D, texture_);
|
if (!rhiModule) {
|
||||||
|
E2D_LOG_ERROR("RHIModule not available");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// 设置纹理参数
|
auto* device = rhiModule->getDevice();
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
if (!device) {
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
E2D_LOG_ERROR("RHIDevice not available");
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
return false;
|
||||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
}
|
||||||
|
|
||||||
// 分配纹理存储(无数据)
|
// 创建纹理描述
|
||||||
glTexImage2D(GL_TEXTURE_2D, 0,
|
TextureDesc desc;
|
||||||
getTextureInternalFormat(format_),
|
desc.width = static_cast<uint32_t>(width);
|
||||||
width_, height_, 0,
|
desc.height = static_cast<uint32_t>(height);
|
||||||
getTextureFormat(format_),
|
desc.format = format;
|
||||||
GL_UNSIGNED_BYTE, nullptr);
|
desc.mipLevels = 1; // 不生成 mipmap
|
||||||
|
|
||||||
glBindTexture(GL_TEXTURE_2D, 0);
|
// 创建 RHI 纹理
|
||||||
|
auto texture = device->createTexture(desc);
|
||||||
|
if (!texture) {
|
||||||
|
E2D_LOG_ERROR("Failed to create RHI texture");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取纹理句柄
|
||||||
|
handle_ = TextureHandle(texture.release());
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Texture::bind(uint32_t slot) const {
|
bool Texture::reloadFromFile(const std::string& path) {
|
||||||
if (texture_ != 0) {
|
// 加载图片
|
||||||
glActiveTexture(GL_TEXTURE0 + slot);
|
int channels;
|
||||||
glBindTexture(GL_TEXTURE_2D, texture_);
|
stbi_set_flip_vertically_on_load(true);
|
||||||
|
unsigned char* data = stbi_load(path.c_str(), &width_, &height_, &channels, 0);
|
||||||
|
|
||||||
|
if (!data) {
|
||||||
|
E2D_LOG_ERROR("Failed to reload texture: {}", path);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据通道数确定格式
|
||||||
|
TextureFormat format;
|
||||||
|
switch (channels) {
|
||||||
|
case 1: format = TextureFormat::R8; break;
|
||||||
|
case 2: format = TextureFormat::RG8; break;
|
||||||
|
case 3: format = TextureFormat::RGB8; break;
|
||||||
|
case 4: format = TextureFormat::RGBA8; break;
|
||||||
|
default: format = TextureFormat::RGBA8; break;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool result = reloadFromMemory(data, width_, height_, format);
|
||||||
|
stbi_image_free(data);
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
E2D_LOG_INFO("Texture reloaded: {} ({}x{})", path, width_, height_);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool Texture::reloadFromMemory(const uint8_t* data, int width, int height, TextureFormat format) {
|
||||||
|
// 更新尺寸和格式
|
||||||
|
width_ = width;
|
||||||
|
height_ = height;
|
||||||
|
format_ = format;
|
||||||
|
|
||||||
|
// 获取 RHI 设备
|
||||||
|
auto* rhiModule = RHIModule::get();
|
||||||
|
if (!rhiModule) {
|
||||||
|
E2D_LOG_ERROR("RHIModule not available");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto* device = rhiModule->getDevice();
|
||||||
|
if (!device) {
|
||||||
|
E2D_LOG_ERROR("RHIDevice not available");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建新的纹理描述
|
||||||
|
TextureDesc desc;
|
||||||
|
desc.width = static_cast<uint32_t>(width);
|
||||||
|
desc.height = static_cast<uint32_t>(height);
|
||||||
|
desc.format = format;
|
||||||
|
desc.mipLevels = 1;
|
||||||
|
|
||||||
|
// 创建新的 RHI 纹理
|
||||||
|
auto texture = device->createTexture(desc);
|
||||||
|
if (!texture) {
|
||||||
|
E2D_LOG_ERROR("Failed to create RHI texture during reload");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上传纹理数据
|
||||||
|
if (data) {
|
||||||
|
texture->update(data, static_cast<size_t>(width * height * getBytesPerPixel(format)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 替换旧的纹理句柄
|
||||||
|
handle_ = TextureHandle(texture.release());
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 辅助函数:获取每个像素的字节数
|
||||||
|
uint32_t Texture::getBytesPerPixel(TextureFormat format) {
|
||||||
|
switch (format) {
|
||||||
|
case TextureFormat::R8: return 1;
|
||||||
|
case TextureFormat::RG8: return 2;
|
||||||
|
case TextureFormat::RGB8: return 3;
|
||||||
|
case TextureFormat::RGBA8:
|
||||||
|
case TextureFormat::RGBA8_SRGB: return 4;
|
||||||
|
case TextureFormat::Depth16: return 2;
|
||||||
|
case TextureFormat::Depth24: return 3;
|
||||||
|
case TextureFormat::Depth32F: return 4;
|
||||||
|
case TextureFormat::Depth24Stencil8: return 4;
|
||||||
|
case TextureFormat::Depth32FStencil8: return 5;
|
||||||
|
default: return 4;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Texture::unbind() const {
|
|
||||||
glBindTexture(GL_TEXTURE_2D, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace extra2d
|
} // namespace extra2d
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,367 @@
|
||||||
|
#include <renderer/texture_atlas.h>
|
||||||
|
#include <utils/logger.h>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// TextureAtlas 实现
|
||||||
|
// ========================================
|
||||||
|
|
||||||
|
TextureAtlas::TextureAtlas() = default;
|
||||||
|
|
||||||
|
TextureAtlas::~TextureAtlas() {
|
||||||
|
shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
TextureAtlas::TextureAtlas(TextureAtlas&& other) noexcept
|
||||||
|
: width_(other.width_)
|
||||||
|
, height_(other.height_)
|
||||||
|
, atlasTexture_(std::move(other.atlasTexture_))
|
||||||
|
, regions_(std::move(other.regions_))
|
||||||
|
, pendingTextures_(std::move(other.pendingTextures_))
|
||||||
|
, packContext_(std::move(other.packContext_))
|
||||||
|
, packNodes_(std::move(other.packNodes_))
|
||||||
|
, finalized_(other.finalized_) {
|
||||||
|
other.width_ = 0;
|
||||||
|
other.height_ = 0;
|
||||||
|
other.finalized_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
TextureAtlas& TextureAtlas::operator=(TextureAtlas&& other) noexcept {
|
||||||
|
if (this != &other) {
|
||||||
|
shutdown();
|
||||||
|
|
||||||
|
width_ = other.width_;
|
||||||
|
height_ = other.height_;
|
||||||
|
atlasTexture_ = std::move(other.atlasTexture_);
|
||||||
|
regions_ = std::move(other.regions_);
|
||||||
|
pendingTextures_ = std::move(other.pendingTextures_);
|
||||||
|
packContext_ = std::move(other.packContext_);
|
||||||
|
packNodes_ = std::move(other.packNodes_);
|
||||||
|
finalized_ = other.finalized_;
|
||||||
|
|
||||||
|
other.width_ = 0;
|
||||||
|
other.height_ = 0;
|
||||||
|
other.finalized_ = false;
|
||||||
|
}
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TextureAtlas::initialize(int width, int height) {
|
||||||
|
shutdown();
|
||||||
|
|
||||||
|
if (width <= 0 || height <= 0) {
|
||||||
|
E2D_LOG_ERROR("TextureAtlas::initialize: Invalid size {}x{}", width, height);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
width_ = width;
|
||||||
|
height_ = height;
|
||||||
|
|
||||||
|
// 初始化 stb_rect_pack
|
||||||
|
packContext_ = std::make_unique<stbrp_context>();
|
||||||
|
packNodes_.resize(width);
|
||||||
|
stbrp_init_target(packContext_.get(), width, height, packNodes_.data(), width);
|
||||||
|
|
||||||
|
E2D_LOG_DEBUG("TextureAtlas initialized: {}x{}", width, height);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void TextureAtlas::shutdown() {
|
||||||
|
atlasTexture_.reset();
|
||||||
|
regions_.clear();
|
||||||
|
pendingTextures_.clear();
|
||||||
|
packContext_.reset();
|
||||||
|
packNodes_.clear();
|
||||||
|
width_ = 0;
|
||||||
|
height_ = 0;
|
||||||
|
finalized_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TextureAtlas::addTexture(const std::string& name, Ptr<Texture> texture) {
|
||||||
|
if (finalized_) {
|
||||||
|
E2D_LOG_WARN("TextureAtlas::addTexture: Cannot add texture to finalized atlas");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!texture) {
|
||||||
|
E2D_LOG_WARN("TextureAtlas::addTexture: Texture is null");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (regions_.find(name) != regions_.end()) {
|
||||||
|
E2D_LOG_WARN("TextureAtlas::addTexture: Texture '{}' already exists", name);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取纹理尺寸
|
||||||
|
int texWidth = static_cast<int>(texture->getWidth());
|
||||||
|
int texHeight = static_cast<int>(texture->getHeight());
|
||||||
|
|
||||||
|
if (texWidth <= 0 || texHeight <= 0) {
|
||||||
|
E2D_LOG_WARN("TextureAtlas::addTexture: Invalid texture size {}x{}", texWidth, texHeight);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查纹理是否适合图集
|
||||||
|
if (texWidth > width_ || texHeight > height_) {
|
||||||
|
E2D_LOG_WARN("TextureAtlas::addTexture: Texture {}x{} too large for atlas {}x{}",
|
||||||
|
texWidth, texHeight, width_, height_);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建待处理纹理信息
|
||||||
|
PendingTexture pending;
|
||||||
|
pending.name = name;
|
||||||
|
pending.width = texWidth;
|
||||||
|
pending.height = texHeight;
|
||||||
|
pending.format = texture->getFormat();
|
||||||
|
|
||||||
|
// 获取纹理数据
|
||||||
|
size_t dataSize = texWidth * texHeight * 4; // 假设 RGBA
|
||||||
|
pending.data.resize(dataSize);
|
||||||
|
|
||||||
|
// TODO: 实现 Texture::getData() 方法来读取像素数据
|
||||||
|
// 暂时使用占位数据
|
||||||
|
std::fill(pending.data.begin(), pending.data.end(), 255);
|
||||||
|
|
||||||
|
pendingTextures_.push_back(std::move(pending));
|
||||||
|
|
||||||
|
E2D_LOG_DEBUG("TextureAtlas: Added texture '{}' ({}x{})", name, texWidth, texHeight);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TextureAtlas::addTextureData(const std::string& name, const uint8_t* data,
|
||||||
|
int width, int height, TextureFormat format) {
|
||||||
|
if (finalized_) {
|
||||||
|
E2D_LOG_WARN("TextureAtlas::addTextureData: Cannot add texture to finalized atlas");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!data || width <= 0 || height <= 0) {
|
||||||
|
E2D_LOG_WARN("TextureAtlas::addTextureData: Invalid parameters");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (regions_.find(name) != regions_.end()) {
|
||||||
|
E2D_LOG_WARN("TextureAtlas::addTextureData: Texture '{}' already exists", name);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (width > width_ || height > height_) {
|
||||||
|
E2D_LOG_WARN("TextureAtlas::addTextureData: Texture {}x{} too large for atlas {}x{}",
|
||||||
|
width, height, width_, height_);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
PendingTexture pending;
|
||||||
|
pending.name = name;
|
||||||
|
pending.width = width;
|
||||||
|
pending.height = height;
|
||||||
|
pending.format = format;
|
||||||
|
|
||||||
|
// 复制像素数据
|
||||||
|
int channels = (format == TextureFormat::RGBA8) ? 4 : 3;
|
||||||
|
size_t dataSize = width * height * channels;
|
||||||
|
pending.data.resize(dataSize);
|
||||||
|
std::memcpy(pending.data.data(), data, dataSize);
|
||||||
|
|
||||||
|
pendingTextures_.push_back(std::move(pending));
|
||||||
|
|
||||||
|
E2D_LOG_DEBUG("TextureAtlas: Added texture data '{}' ({}x{})", name, width, height);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TextureAtlas::finalize() {
|
||||||
|
if (finalized_) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pendingTextures_.empty()) {
|
||||||
|
E2D_LOG_WARN("TextureAtlas::finalize: No textures to pack");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 准备矩形数组
|
||||||
|
std::vector<stbrp_rect> rects;
|
||||||
|
rects.reserve(pendingTextures_.size());
|
||||||
|
|
||||||
|
for (size_t i = 0; i < pendingTextures_.size(); ++i) {
|
||||||
|
stbrp_rect rect;
|
||||||
|
rect.id = static_cast<int>(i);
|
||||||
|
rect.w = pendingTextures_[i].width;
|
||||||
|
rect.h = pendingTextures_[i].height;
|
||||||
|
rect.x = 0;
|
||||||
|
rect.y = 0;
|
||||||
|
rect.was_packed = 0;
|
||||||
|
rects.push_back(rect);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 执行打包
|
||||||
|
int result = stbrp_pack_rects(packContext_.get(), rects.data(), static_cast<int>(rects.size()));
|
||||||
|
|
||||||
|
if (!result) {
|
||||||
|
E2D_LOG_ERROR("TextureAtlas::finalize: Failed to pack all textures");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建图集纹理数据
|
||||||
|
std::vector<uint8_t> atlasData(width_ * height_ * 4, 0); // RGBA 黑色背景
|
||||||
|
|
||||||
|
// 处理打包结果
|
||||||
|
for (const auto& rect : rects) {
|
||||||
|
if (!rect.was_packed) {
|
||||||
|
E2D_LOG_WARN("TextureAtlas::finalize: Texture {} not packed", rect.id);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& pending = pendingTextures_[rect.id];
|
||||||
|
|
||||||
|
// 创建区域信息
|
||||||
|
AtlasRegion region;
|
||||||
|
region.x = rect.x;
|
||||||
|
region.y = rect.y;
|
||||||
|
region.width = rect.w;
|
||||||
|
region.height = rect.h;
|
||||||
|
|
||||||
|
regions_[pending.name] = region;
|
||||||
|
|
||||||
|
// 复制像素数据到图集
|
||||||
|
// TODO: 实现正确的像素格式转换
|
||||||
|
// 目前假设所有纹理都是 RGBA8
|
||||||
|
for (int y = 0; y < pending.height; ++y) {
|
||||||
|
for (int x = 0; x < pending.width; ++x) {
|
||||||
|
int srcIdx = (y * pending.width + x) * 4;
|
||||||
|
int dstIdx = ((rect.y + y) * width_ + (rect.x + x)) * 4;
|
||||||
|
|
||||||
|
if (srcIdx + 3 < static_cast<int>(pending.data.size()) &&
|
||||||
|
dstIdx + 3 < static_cast<int>(atlasData.size())) {
|
||||||
|
atlasData[dstIdx + 0] = pending.data[srcIdx + 0];
|
||||||
|
atlasData[dstIdx + 1] = pending.data[srcIdx + 1];
|
||||||
|
atlasData[dstIdx + 2] = pending.data[srcIdx + 2];
|
||||||
|
atlasData[dstIdx + 3] = pending.data[srcIdx + 3];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
E2D_LOG_DEBUG("TextureAtlas: Packed '{}' at ({}, {}) size {}x{}",
|
||||||
|
pending.name, rect.x, rect.y, rect.w, rect.h);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建图集纹理
|
||||||
|
atlasTexture_ = makePtr<Texture>();
|
||||||
|
if (!atlasTexture_->loadFromMemory(atlasData.data(), width_, height_, TextureFormat::RGBA8)) {
|
||||||
|
E2D_LOG_ERROR("TextureAtlas::finalize: Failed to create atlas texture");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清理待处理列表
|
||||||
|
pendingTextures_.clear();
|
||||||
|
finalized_ = true;
|
||||||
|
|
||||||
|
E2D_LOG_INFO("TextureAtlas finalized: {} textures packed into {}x{} atlas ({}% usage)",
|
||||||
|
regions_.size(), width_, height_,
|
||||||
|
static_cast<int>(getUsageRatio() * 100));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const AtlasRegion* TextureAtlas::getRegion(const std::string& name) const {
|
||||||
|
auto it = regions_.find(name);
|
||||||
|
if (it != regions_.end()) {
|
||||||
|
return &it->second;
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
Rect TextureAtlas::getUVRect(const std::string& name) const {
|
||||||
|
const AtlasRegion* region = getRegion(name);
|
||||||
|
if (region) {
|
||||||
|
return region->getUVRect(width_, height_);
|
||||||
|
}
|
||||||
|
return Rect(0.0f, 0.0f, 1.0f, 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TextureAtlas::hasTexture(const std::string& name) const {
|
||||||
|
return regions_.find(name) != regions_.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
float TextureAtlas::getUsageRatio() const {
|
||||||
|
if (!finalized_ || regions_.empty()) {
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
int totalArea = 0;
|
||||||
|
for (const auto& pair : regions_) {
|
||||||
|
totalArea += pair.second.width * pair.second.height;
|
||||||
|
}
|
||||||
|
|
||||||
|
return static_cast<float>(totalArea) / (width_ * height_);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// AtlasBuilder 实现
|
||||||
|
// ========================================
|
||||||
|
|
||||||
|
Ptr<TextureAtlas> AtlasBuilder::build() {
|
||||||
|
if (textures_.empty()) {
|
||||||
|
E2D_LOG_WARN("AtlasBuilder::build: No textures to build");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto atlas = makePtr<TextureAtlas>();
|
||||||
|
if (!atlas->initialize(width_, height_)) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& pair : textures_) {
|
||||||
|
atlas->addTexture(pair.first, pair.second);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!atlas->finalize()) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
return atlas;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ptr<TextureAtlas> AtlasBuilder::buildAuto() {
|
||||||
|
if (textures_.empty()) {
|
||||||
|
E2D_LOG_WARN("AtlasBuilder::buildAuto: No textures to build");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算总面积
|
||||||
|
int totalArea = 0;
|
||||||
|
int maxWidth = 0;
|
||||||
|
int maxHeight = 0;
|
||||||
|
|
||||||
|
for (const auto& pair : textures_) {
|
||||||
|
if (pair.second) {
|
||||||
|
int w = static_cast<int>(pair.second->getWidth());
|
||||||
|
int h = static_cast<int>(pair.second->getHeight());
|
||||||
|
totalArea += w * h;
|
||||||
|
maxWidth = std::max(maxWidth, w);
|
||||||
|
maxHeight = std::max(maxHeight, h);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 选择合适的大小(2 的幂)
|
||||||
|
int atlasSize = 64;
|
||||||
|
while (atlasSize < maxWidth || atlasSize < maxHeight || atlasSize * atlasSize < totalArea * 1.5f) {
|
||||||
|
atlasSize *= 2;
|
||||||
|
if (atlasSize > 8192) {
|
||||||
|
E2D_LOG_ERROR("AtlasBuilder::buildAuto: Textures too large for atlas");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
width_ = atlasSize;
|
||||||
|
height_ = atlasSize;
|
||||||
|
|
||||||
|
return build();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -1,3 +1,5 @@
|
||||||
|
#include <renderer/rhi/rhi_buffer.h>
|
||||||
|
#include <renderer/rhi_module.h>
|
||||||
#include <renderer/uniform_buffer.h>
|
#include <renderer/uniform_buffer.h>
|
||||||
#include <utils/logger.h>
|
#include <utils/logger.h>
|
||||||
|
|
||||||
|
|
@ -5,43 +7,54 @@ namespace extra2d {
|
||||||
|
|
||||||
UniformBuffer::UniformBuffer() = default;
|
UniformBuffer::UniformBuffer() = default;
|
||||||
|
|
||||||
UniformBuffer::~UniformBuffer() {
|
UniformBuffer::~UniformBuffer() { destroy(); }
|
||||||
destroy();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool UniformBuffer::create(uint32_t size, uint32_t binding) {
|
bool UniformBuffer::create(uint32_t size, uint32_t binding) {
|
||||||
if (ubo_ != 0) {
|
|
||||||
destroy();
|
destroy();
|
||||||
|
|
||||||
|
// 获取 RHI 设备
|
||||||
|
auto *rhiModule = RHIModule::get();
|
||||||
|
if (!rhiModule) {
|
||||||
|
E2D_LOG_ERROR("RHIModule not available");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto *device = rhiModule->getDevice();
|
||||||
|
if (!device) {
|
||||||
|
E2D_LOG_ERROR("RHIDevice not available");
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
size_ = size;
|
size_ = size;
|
||||||
binding_ = binding;
|
binding_ = binding;
|
||||||
|
|
||||||
glGenBuffers(1, &ubo_);
|
// 创建 uniform 缓冲区描述
|
||||||
if (ubo_ == 0) {
|
BufferDesc desc = BufferDesc::uniform(size);
|
||||||
E2D_LOG_ERROR("Failed to generate UBO");
|
|
||||||
|
// 创建缓冲区
|
||||||
|
auto buffer = device->createBuffer(desc);
|
||||||
|
if (!buffer) {
|
||||||
|
E2D_LOG_ERROR("Failed to create uniform buffer");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
glBindBuffer(GL_UNIFORM_BUFFER, ubo_);
|
// 获取缓冲区句柄
|
||||||
glBufferData(GL_UNIFORM_BUFFER, size, nullptr, GL_DYNAMIC_DRAW);
|
handle_ = BufferHandle(buffer.release());
|
||||||
glBindBufferBase(GL_UNIFORM_BUFFER, binding, ubo_);
|
|
||||||
glBindBuffer(GL_UNIFORM_BUFFER, 0);
|
|
||||||
|
|
||||||
E2D_LOG_DEBUG("UBO created: size={}, binding={}", size, binding);
|
E2D_LOG_DEBUG("UBO created: size={}, binding={}", size, binding);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void UniformBuffer::destroy() {
|
void UniformBuffer::destroy() {
|
||||||
if (ubo_ != 0) {
|
// RHIHandle 是轻量级句柄,不需要显式释放
|
||||||
glDeleteBuffers(1, &ubo_);
|
// 实际的缓冲区资源由 RHI 设备管理
|
||||||
ubo_ = 0;
|
handle_ = BufferHandle();
|
||||||
size_ = 0;
|
size_ = 0;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void UniformBuffer::update(const void* data, uint32_t size, uint32_t offset) {
|
void UniformBuffer::update(const void *data, uint32_t size, uint32_t offset) {
|
||||||
if (ubo_ == 0 || data == nullptr) return;
|
if (!handle_.isValid() || data == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
if (offset + size > size_) {
|
if (offset + size > size_) {
|
||||||
E2D_LOG_WARN("UBO update out of bounds: offset={}, size={}, bufferSize={}",
|
E2D_LOG_WARN("UBO update out of bounds: offset={}, size={}, bufferSize={}",
|
||||||
|
|
@ -49,71 +62,102 @@ void UniformBuffer::update(const void* data, uint32_t size, uint32_t offset) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
glBindBuffer(GL_UNIFORM_BUFFER, ubo_);
|
// 通过 RHI 缓冲区接口直接更新数据
|
||||||
glBufferSubData(GL_UNIFORM_BUFFER, offset, size, data);
|
RHIBuffer *buffer = handle_.get();
|
||||||
glBindBuffer(GL_UNIFORM_BUFFER, 0);
|
if (buffer) {
|
||||||
|
buffer->update(data, size, offset);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void UniformBuffer::bind(uint32_t binding) {
|
void UniformBuffer::bind(uint32_t binding) {
|
||||||
if (ubo_ == 0) return;
|
if (!handle_.isValid())
|
||||||
|
return;
|
||||||
|
|
||||||
glBindBufferBase(GL_UNIFORM_BUFFER, binding, ubo_);
|
// 记录绑定槽位
|
||||||
|
// 注意:实际的 GPU 绑定操作需要在渲染时通过 RHICommandList::setUniformBuffer
|
||||||
|
// 进行 示例:commandList->setUniformBuffer(binding, handle_.get());
|
||||||
|
binding_ = binding;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RHIBuffer *UniformBuffer::getRHIBuffer() const { return handle_.get(); }
|
||||||
|
|
||||||
UniformBufferManager::UniformBufferManager() = default;
|
UniformBufferManager::UniformBufferManager() = default;
|
||||||
|
|
||||||
UniformBufferManager::~UniformBufferManager() {
|
UniformBufferManager::~UniformBufferManager() { shutdown(); }
|
||||||
shutdown();
|
|
||||||
}
|
|
||||||
|
|
||||||
UniformBufferManager::UniformBufferManager(UniformBufferManager&& other) noexcept
|
UniformBufferManager::UniformBufferManager(
|
||||||
: globalUBO_(std::move(other.globalUBO_)),
|
UniformBufferManager &&other) noexcept
|
||||||
|
: globalUBOs_(std::move(other.globalUBOs_)),
|
||||||
materialUBOPool_(std::move(other.materialUBOPool_)),
|
materialUBOPool_(std::move(other.materialUBOPool_)),
|
||||||
currentUBOIndex_(other.currentUBOIndex_) {
|
currentUBOIndex_(other.currentUBOIndex_),
|
||||||
|
materialUBOBuffer_(std::move(other.materialUBOBuffer_)),
|
||||||
|
materialUBOBufferOffset_(other.materialUBOBufferOffset_),
|
||||||
|
currentMaterialUBO_(other.currentMaterialUBO_) {
|
||||||
other.currentUBOIndex_ = 0;
|
other.currentUBOIndex_ = 0;
|
||||||
|
other.materialUBOBufferOffset_ = 0;
|
||||||
|
other.currentMaterialUBO_ = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
UniformBufferManager& UniformBufferManager::operator=(UniformBufferManager&& other) noexcept {
|
UniformBufferManager &
|
||||||
|
UniformBufferManager::operator=(UniformBufferManager &&other) noexcept {
|
||||||
if (this != &other) {
|
if (this != &other) {
|
||||||
shutdown();
|
shutdown();
|
||||||
|
|
||||||
globalUBO_ = std::move(other.globalUBO_);
|
globalUBOs_ = std::move(other.globalUBOs_);
|
||||||
materialUBOPool_ = std::move(other.materialUBOPool_);
|
materialUBOPool_ = std::move(other.materialUBOPool_);
|
||||||
currentUBOIndex_ = other.currentUBOIndex_;
|
currentUBOIndex_ = other.currentUBOIndex_;
|
||||||
|
materialUBOBuffer_ = std::move(other.materialUBOBuffer_);
|
||||||
|
materialUBOBufferOffset_ = other.materialUBOBufferOffset_;
|
||||||
|
currentMaterialUBO_ = other.currentMaterialUBO_;
|
||||||
|
|
||||||
other.currentUBOIndex_ = 0;
|
other.currentUBOIndex_ = 0;
|
||||||
|
other.materialUBOBufferOffset_ = 0;
|
||||||
|
other.currentMaterialUBO_ = nullptr;
|
||||||
}
|
}
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool UniformBufferManager::initialize() {
|
bool UniformBufferManager::initialize() {
|
||||||
// 创建全局 UBO
|
// 创建全局 UBO 双缓冲
|
||||||
globalUBO_ = std::make_unique<UniformBuffer>();
|
for (size_t i = 0; i < globalUBOs_.size(); ++i) {
|
||||||
if (!globalUBO_->create(GLOBAL_UBO_SIZE, GLOBAL_UBO_BINDING)) {
|
globalUBOs_[i] = std::make_unique<UniformBuffer>();
|
||||||
E2D_LOG_ERROR("Failed to create global UBO");
|
if (!globalUBOs_[i]->create(UniformBufferManager::GLOBAL_UBO_SIZE,
|
||||||
|
UniformBufferManager::GLOBAL_UBO_BINDING)) {
|
||||||
|
E2D_LOG_ERROR("Failed to create global UBO {}", i);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 预分配材质 UBO 池
|
// 预分配材质 UBO 池
|
||||||
materialUBOPool_.reserve(INITIAL_UBO_POOL_SIZE);
|
materialUBOPool_.reserve(INITIAL_UBO_POOL_SIZE);
|
||||||
|
|
||||||
E2D_LOG_INFO("UniformBufferManager initialized");
|
// 预分配材质 UBO CPU 缓冲区
|
||||||
|
materialUBOBuffer_.resize(MATERIAL_UBO_BUFFER_SIZE);
|
||||||
|
|
||||||
|
E2D_LOG_INFO("UniformBufferManager initialized with double buffering");
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void UniformBufferManager::shutdown() {
|
void UniformBufferManager::shutdown() {
|
||||||
materialUBOPool_.clear();
|
materialUBOPool_.clear();
|
||||||
globalUBO_.reset();
|
for (auto& ubo : globalUBOs_) {
|
||||||
|
ubo.reset();
|
||||||
|
}
|
||||||
currentUBOIndex_ = 0;
|
currentUBOIndex_ = 0;
|
||||||
|
materialUBOBuffer_.clear();
|
||||||
|
materialUBOBufferOffset_ = 0;
|
||||||
|
currentMaterialUBO_ = nullptr;
|
||||||
|
|
||||||
E2D_LOG_INFO("UniformBufferManager shutdown");
|
E2D_LOG_INFO("UniformBufferManager shutdown");
|
||||||
}
|
}
|
||||||
|
|
||||||
UniformBuffer* UniformBufferManager::getGlobalUBO() {
|
UniformBuffer *UniformBufferManager::getGlobalUBO(uint32_t frameIndex) {
|
||||||
return globalUBO_.get();
|
// 使用双缓冲,根据帧索引选择 UBO
|
||||||
|
size_t index = frameIndex % 2;
|
||||||
|
return globalUBOs_[index].get();
|
||||||
}
|
}
|
||||||
|
|
||||||
UniformBuffer* UniformBufferManager::acquireMaterialUBO(uint32_t size) {
|
UniformBuffer *UniformBufferManager::acquireMaterialUBO(uint32_t size) {
|
||||||
// 查找或创建合适大小的 UBO
|
// 查找或创建合适大小的 UBO
|
||||||
for (uint32_t i = currentUBOIndex_; i < materialUBOPool_.size(); ++i) {
|
for (uint32_t i = currentUBOIndex_; i < materialUBOPool_.size(); ++i) {
|
||||||
if (materialUBOPool_[i]->getSize() >= size) {
|
if (materialUBOPool_[i]->getSize() >= size) {
|
||||||
|
|
@ -124,12 +168,12 @@ UniformBuffer* UniformBufferManager::acquireMaterialUBO(uint32_t size) {
|
||||||
|
|
||||||
// 需要创建新的 UBO
|
// 需要创建新的 UBO
|
||||||
auto ubo = std::make_unique<UniformBuffer>();
|
auto ubo = std::make_unique<UniformBuffer>();
|
||||||
if (!ubo->create(size, MATERIAL_UBO_BINDING)) {
|
if (!ubo->create(size, UniformBufferManager::MATERIAL_UBO_BINDING)) {
|
||||||
E2D_LOG_ERROR("Failed to create material UBO");
|
E2D_LOG_ERROR("Failed to create material UBO");
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
UniformBuffer* result = ubo.get();
|
UniformBuffer *result = ubo.get();
|
||||||
materialUBOPool_.push_back(std::move(ubo));
|
materialUBOPool_.push_back(std::move(ubo));
|
||||||
currentUBOIndex_ = materialUBOPool_.size();
|
currentUBOIndex_ = materialUBOPool_.size();
|
||||||
|
|
||||||
|
|
@ -138,13 +182,37 @@ UniformBuffer* UniformBufferManager::acquireMaterialUBO(uint32_t size) {
|
||||||
|
|
||||||
void UniformBufferManager::resetMaterialUBOs() {
|
void UniformBufferManager::resetMaterialUBOs() {
|
||||||
currentUBOIndex_ = 0;
|
currentUBOIndex_ = 0;
|
||||||
|
materialUBOBufferOffset_ = 0;
|
||||||
|
currentMaterialUBO_ = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void UniformBufferManager::updateGlobalUBO(const void* data, uint32_t size) {
|
void UniformBufferManager::updateGlobalUBO(const void *data, uint32_t size, uint32_t frameIndex) {
|
||||||
if (globalUBO_) {
|
// 使用双缓冲更新全局 UBO
|
||||||
globalUBO_->update(data, size);
|
size_t index = frameIndex % 2;
|
||||||
globalUBO_->bind(GLOBAL_UBO_BINDING);
|
if (globalUBOs_[index]) {
|
||||||
|
globalUBOs_[index]->update(data, size);
|
||||||
|
globalUBOs_[index]->bind(UniformBufferManager::GLOBAL_UBO_BINDING);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void UniformBufferManager::batchUpdateMaterialUBO(const void *data, uint32_t size, uint32_t offset) {
|
||||||
|
if (!data || size == 0 || offset + size > materialUBOBuffer_.size()) {
|
||||||
|
E2D_LOG_WARN("Invalid batch update parameters");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 复制数据到 CPU 缓冲区
|
||||||
|
std::memcpy(materialUBOBuffer_.data() + offset, data, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
void UniformBufferManager::flushMaterialUBO() {
|
||||||
|
if (materialUBOBufferOffset_ == 0 || !currentMaterialUBO_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 一次性将 CPU 缓冲区数据更新到 GPU
|
||||||
|
currentMaterialUBO_->update(materialUBOBuffer_.data(), materialUBOBufferOffset_, 0);
|
||||||
|
materialUBOBufferOffset_ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace extra2d
|
} // namespace extra2d
|
||||||
|
|
|
||||||
|
|
@ -33,8 +33,9 @@ void SpriteRenderer::render() {
|
||||||
uint32_t textureId = texture_.index();
|
uint32_t textureId = texture_.index();
|
||||||
cmd.sortKey = (materialId << 16) | (textureId & 0xFFFF);
|
cmd.sortKey = (materialId << 16) | (textureId & 0xFFFF);
|
||||||
|
|
||||||
cmd.drawMesh.mesh = MeshHandle::invalid();
|
// 如果没有指定网格,使用默认的四边形网格
|
||||||
cmd.drawMesh.material = material_.isValid() ? material_ : MaterialHandle::invalid();
|
cmd.drawMesh.mesh = Handle<Mesh>::invalid(); // RendererModule 会使用默认网格
|
||||||
|
cmd.drawMesh.material = material_.isValid() ? material_ : Handle<Material>::invalid();
|
||||||
cmd.setTransform(worldTransform);
|
cmd.setTransform(worldTransform);
|
||||||
|
|
||||||
cmd.setColor(color_);
|
cmd.setColor(color_);
|
||||||
|
|
|
||||||
|
|
@ -130,8 +130,9 @@ void Director::render() {
|
||||||
CameraComponent *camera = runningScene_->getMainCamera();
|
CameraComponent *camera = runningScene_->getMainCamera();
|
||||||
if (camera) {
|
if (camera) {
|
||||||
Mat4 viewProj = camera->getViewProjectionMatrix();
|
Mat4 viewProj = camera->getViewProjectionMatrix();
|
||||||
|
|
||||||
events::OnRenderSetCamera::emit(viewProj);
|
events::OnRenderSetCamera::emit(viewProj);
|
||||||
|
} else {
|
||||||
|
E2D_LOG_WARN("Director::render: No main camera set!");
|
||||||
}
|
}
|
||||||
|
|
||||||
runningScene_->render();
|
runningScene_->render();
|
||||||
|
|
|
||||||
|
|
@ -8,10 +8,6 @@ SceneModule::~SceneModule() {
|
||||||
shutdown();
|
shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
SceneModule::SceneModule(SceneModule &&) noexcept = default;
|
|
||||||
|
|
||||||
SceneModule &SceneModule::operator=(SceneModule &&) noexcept = default;
|
|
||||||
|
|
||||||
bool SceneModule::init() {
|
bool SceneModule::init() {
|
||||||
// 创建导演
|
// 创建导演
|
||||||
director_ = makePtr<Director>();
|
director_ = makePtr<Director>();
|
||||||
|
|
|
||||||
|
|
@ -7,24 +7,6 @@ TimerModule::TimerModule() = default;
|
||||||
|
|
||||||
TimerModule::~TimerModule() { shutdown(); }
|
TimerModule::~TimerModule() { shutdown(); }
|
||||||
|
|
||||||
TimerModule::TimerModule(TimerModule &&other) noexcept
|
|
||||||
: timers_(std::move(other.timers_)),
|
|
||||||
pendingRemove_(std::move(other.pendingRemove_)), nextId_(other.nextId_),
|
|
||||||
timeScale_(other.timeScale_), inUpdate_(other.inUpdate_) {}
|
|
||||||
|
|
||||||
TimerModule &TimerModule::operator=(TimerModule &&other) noexcept {
|
|
||||||
if (this != &other) {
|
|
||||||
shutdown();
|
|
||||||
|
|
||||||
timers_ = std::move(other.timers_);
|
|
||||||
pendingRemove_ = std::move(other.pendingRemove_);
|
|
||||||
nextId_ = other.nextId_;
|
|
||||||
timeScale_ = other.timeScale_;
|
|
||||||
inUpdate_ = other.inUpdate_;
|
|
||||||
}
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool TimerModule::init() {
|
bool TimerModule::init() {
|
||||||
// 初始化完成,等待 OnUpdate 事件
|
// 初始化完成,等待 OnUpdate 事件
|
||||||
return true;
|
return true;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue