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:*)"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
@ -6,34 +6,34 @@
|
|||
// ========================================
|
||||
|
||||
PlayerNode::PlayerNode() {
|
||||
setName("Player");
|
||||
setTag(1);
|
||||
setName("Player");
|
||||
setTag(1);
|
||||
|
||||
// 设置玩家尺寸
|
||||
setSize(64.0f, 64.0f);
|
||||
// 设置玩家尺寸
|
||||
setSize(64.0f, 64.0f);
|
||||
|
||||
// 设置锚点为中心点
|
||||
setAnchor(0.5f, 0.5f);
|
||||
// 设置锚点为中心点
|
||||
setAnchor(0.5f, 0.5f);
|
||||
|
||||
// 添加精灵渲染组件
|
||||
auto sprite = makePtr<SpriteRenderer>();
|
||||
sprite->setColor(Color::Blue);
|
||||
addComponent(sprite);
|
||||
// 添加精灵渲染组件
|
||||
auto sprite = makePtr<SpriteRenderer>();
|
||||
sprite->setColor(Color::Blue);
|
||||
addComponent(sprite);
|
||||
}
|
||||
|
||||
void PlayerNode::onUpdate(float dt) {
|
||||
time_ += dt;
|
||||
time_ += dt;
|
||||
|
||||
// 简单的圆周运动
|
||||
float radius = 200.0f;
|
||||
float x = 640.0f + radius * std::cos(time_ * 0.5f);
|
||||
float y = 360.0f + radius * std::sin(time_ * 0.5f);
|
||||
// 简单的圆周运动
|
||||
float radius = 200.0f;
|
||||
float x = 640.0f + radius * std::cos(time_ * 0.5f);
|
||||
float y = 360.0f + radius * std::sin(time_ * 0.5f);
|
||||
|
||||
setPosition(x, y);
|
||||
setPosition(x, y);
|
||||
|
||||
// 根据移动方向旋转
|
||||
float angle = time_ * 0.5f * 57.2958f + 90.0f;
|
||||
setRotation(angle);
|
||||
// 根据移动方向旋转(顺时针,使用负角度)
|
||||
float angle = -time_ * 0.5f * 57.2958f + 90.0f;
|
||||
setRotation(angle);
|
||||
}
|
||||
|
||||
// ========================================
|
||||
|
|
@ -41,23 +41,23 @@ void PlayerNode::onUpdate(float dt) {
|
|||
// ========================================
|
||||
|
||||
RotatingDecoration::RotatingDecoration() {
|
||||
setName("Decoration");
|
||||
setName("Decoration");
|
||||
|
||||
// 设置尺寸
|
||||
setSize(32.0f, 32.0f);
|
||||
// 设置尺寸
|
||||
setSize(32.0f, 32.0f);
|
||||
|
||||
// 设置锚点为中心
|
||||
setAnchor(0.5f, 0.5f);
|
||||
// 设置锚点为中心
|
||||
setAnchor(0.5f, 0.5f);
|
||||
|
||||
// 添加精灵渲染组件
|
||||
auto sprite = makePtr<SpriteRenderer>();
|
||||
sprite->setColor(Color::Yellow);
|
||||
addComponent(sprite);
|
||||
// 添加精灵渲染组件
|
||||
auto sprite = makePtr<SpriteRenderer>();
|
||||
sprite->setColor(Color::Yellow);
|
||||
addComponent(sprite);
|
||||
}
|
||||
|
||||
void RotatingDecoration::onUpdate(float dt) {
|
||||
// 自转
|
||||
setRotation(getRotation() + rotationSpeed_ * dt);
|
||||
// 自转(顺时针,使用负角度)
|
||||
setRotation(getRotation() - rotationSpeed_ * dt);
|
||||
}
|
||||
|
||||
// ========================================
|
||||
|
|
@ -65,106 +65,104 @@ void RotatingDecoration::onUpdate(float dt) {
|
|||
// ========================================
|
||||
|
||||
GameScene::GameScene() {
|
||||
// 场景初始化
|
||||
// 场景初始化
|
||||
}
|
||||
|
||||
void GameScene::onEnter() {
|
||||
Scene::onEnter();
|
||||
Scene::onEnter();
|
||||
|
||||
// 创建相机
|
||||
createCamera();
|
||||
// 创建相机
|
||||
createCamera();
|
||||
|
||||
// 创建玩家
|
||||
createPlayer();
|
||||
// 创建玩家
|
||||
createPlayer();
|
||||
|
||||
// 创建装饰物
|
||||
createDecorations();
|
||||
// 创建装饰物
|
||||
createDecorations();
|
||||
}
|
||||
|
||||
void GameScene::onExit() {
|
||||
// 清理资源
|
||||
player_.reset();
|
||||
decorations_.clear();
|
||||
// 清理资源
|
||||
player_.reset();
|
||||
decorations_.clear();
|
||||
|
||||
Scene::onExit();
|
||||
Scene::onExit();
|
||||
}
|
||||
|
||||
|
||||
void GameScene::update(float dt) {
|
||||
Scene::update(dt);
|
||||
Scene::update(dt);
|
||||
|
||||
sceneTime_ += dt;
|
||||
sceneTime_ += dt;
|
||||
|
||||
// 更新玩家
|
||||
if (player_) {
|
||||
player_->onUpdate(dt);
|
||||
}
|
||||
// 更新玩家
|
||||
if (player_) {
|
||||
player_->onUpdate(dt);
|
||||
}
|
||||
|
||||
// 更新装饰物
|
||||
for (auto& decoration : decorations_) {
|
||||
decoration->onUpdate(dt);
|
||||
}
|
||||
// 更新装饰物
|
||||
for (auto &decoration : decorations_) {
|
||||
decoration->onUpdate(dt);
|
||||
}
|
||||
}
|
||||
|
||||
void GameScene::createPlayer() {
|
||||
player_ = makePtr<PlayerNode>();
|
||||
player_->setPosition(640.0f, 360.0f);
|
||||
addChild(player_);
|
||||
player_ = makePtr<PlayerNode>();
|
||||
player_->setPosition(640.0f, 360.0f);
|
||||
addChild(player_);
|
||||
}
|
||||
|
||||
void GameScene::createDecorations() {
|
||||
// 创建围绕玩家旋转的装饰物
|
||||
int count = 8;
|
||||
float radius = 150.0f;
|
||||
// 创建围绕玩家旋转的装饰物
|
||||
int count = 8;
|
||||
float radius = 150.0f;
|
||||
|
||||
for (int i = 0; i < count; ++i) {
|
||||
auto decoration = makePtr<RotatingDecoration>();
|
||||
for (int i = 0; i < count; ++i) {
|
||||
auto decoration = makePtr<RotatingDecoration>();
|
||||
|
||||
// 计算位置(圆形分布)
|
||||
float angle = (2.0f * 3.14159f * i) / count;
|
||||
float x = 640.0f + radius * std::cos(angle);
|
||||
float y = 360.0f + radius * std::sin(angle);
|
||||
// 计算位置(圆形分布)
|
||||
float angle = (2.0f * 3.14159f * i) / count;
|
||||
float x = 640.0f + radius * std::cos(angle);
|
||||
float y = 360.0f + radius * std::sin(angle);
|
||||
|
||||
decoration->setPosition(x, y);
|
||||
decoration->setRotationSpeed(45.0f + i * 10.0f);
|
||||
decoration->setPosition(x, y);
|
||||
decoration->setRotationSpeed(45.0f + i * 10.0f);
|
||||
|
||||
// 设置不同颜色
|
||||
auto sprite = decoration->getComponent<SpriteRenderer>();
|
||||
if (sprite) {
|
||||
float hue = static_cast<float>(i) / count;
|
||||
// 简单的HSV到RGB转换
|
||||
float r = std::abs(hue * 6.0f - 3.0f) - 1.0f;
|
||||
float g = 2.0f - std::abs(hue * 6.0f - 2.0f);
|
||||
float b = 2.0f - std::abs(hue * 6.0f - 4.0f);
|
||||
sprite->setColor(Color(
|
||||
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, b)),
|
||||
1.0f
|
||||
));
|
||||
}
|
||||
|
||||
decorations_.push_back(decoration);
|
||||
addChild(decoration);
|
||||
// 设置不同颜色
|
||||
auto sprite = decoration->getComponent<SpriteRenderer>();
|
||||
if (sprite) {
|
||||
float hue = static_cast<float>(i) / count;
|
||||
// 简单的HSV到RGB转换
|
||||
float r = std::abs(hue * 6.0f - 3.0f) - 1.0f;
|
||||
float g = 2.0f - std::abs(hue * 6.0f - 2.0f);
|
||||
float b = 2.0f - std::abs(hue * 6.0f - 4.0f);
|
||||
sprite->setColor(Color(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, b)), 1.0f));
|
||||
}
|
||||
|
||||
decorations_.push_back(decoration);
|
||||
addChild(decoration);
|
||||
}
|
||||
}
|
||||
|
||||
void GameScene::createCamera() {
|
||||
// 创建相机节点
|
||||
auto cameraNode = makePtr<Node>();
|
||||
cameraNode->setName("MainCamera");
|
||||
// 相机位置在(0, 0),这样世界坐标直接映射到屏幕
|
||||
cameraNode->setPosition(0.0f, 0.0f);
|
||||
// 创建相机节点
|
||||
auto cameraNode = makePtr<Node>();
|
||||
cameraNode->setName("MainCamera");
|
||||
// 相机位置在(0, 0),这样世界坐标直接映射到屏幕
|
||||
cameraNode->setPosition(0.0f, 0.0f);
|
||||
|
||||
// 添加相机组件
|
||||
auto camera = makePtr<CameraComponent>();
|
||||
// 使用标准的2D投影:左上角为(0, 0),右下角为(1280, 720)
|
||||
// Y轴向下:bottom=720, top=0
|
||||
camera->setOrtho(0.0f, 1280.0f, 720.0f, 0.0f, -1.0f, 1.0f);
|
||||
cameraNode->addComponent(camera);
|
||||
// 添加相机组件
|
||||
auto camera = makePtr<CameraComponent>();
|
||||
// 使用标准的2D投影:左上角为(0, 0),右下角为(1280, 720)
|
||||
// Y轴向下:bottom=720, top=0
|
||||
camera->setOrtho(0.0f, 1280.0f, 720.0f, 0.0f, -1.0f, 1.0f);
|
||||
cameraNode->addComponent(camera);
|
||||
|
||||
// 设置为主相机
|
||||
setMainCamera(camera);
|
||||
// 设置为主相机
|
||||
setMainCamera(camera);
|
||||
|
||||
// 添加相机节点到场景
|
||||
addChild(cameraNode);
|
||||
// 添加相机节点到场景
|
||||
addChild(cameraNode);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 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
|
||||
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
|
||||
os.vrunv(elf2nro, {elf_file, nro_file, "--nacp=" .. nacp_file, "--romfsdir=" .. romfs})
|
||||
else
|
||||
|
|
|
|||
|
|
@ -4,24 +4,42 @@
|
|||
#include <types/ptr/intrusive_ptr.h>
|
||||
#include <vector>
|
||||
#include <cstdint>
|
||||
#include <chrono>
|
||||
#include <algorithm>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
/**
|
||||
* @brief 资源加载状态
|
||||
*/
|
||||
enum class AssetLoadState {
|
||||
Unloaded, ///< 未加载
|
||||
Loading, ///< 加载中
|
||||
Loaded, ///< 已加载
|
||||
Failed ///< 加载失败
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 密集存储的资源仓库
|
||||
*
|
||||
* 使用 Dense Vec 存储,类似 ECS 的组件存储。
|
||||
* 提供 O(1) 的插入、删除、访问。
|
||||
* 支持引用统计和 LRU(最近最少使用)缓存策略。
|
||||
*
|
||||
* @tparam T 资源类型
|
||||
*/
|
||||
template<typename T>
|
||||
class AssetStorage {
|
||||
public:
|
||||
using TimePoint = std::chrono::steady_clock::time_point;
|
||||
|
||||
struct Slot {
|
||||
Ptr<T> asset;
|
||||
typename Handle<T>::Generation generation = 0;
|
||||
bool active = false;
|
||||
AssetLoadState loadState = AssetLoadState::Unloaded;
|
||||
TimePoint lastAccessTime; ///< 最后访问时间(用于 LRU)
|
||||
};
|
||||
|
||||
AssetStorage() = default;
|
||||
|
|
@ -37,18 +55,23 @@ public:
|
|||
*/
|
||||
Handle<T> insert(Ptr<T> asset) {
|
||||
uint32_t index;
|
||||
auto now = std::chrono::steady_clock::now();
|
||||
|
||||
if (!freeIndices_.empty()) {
|
||||
index = freeIndices_.back();
|
||||
freeIndices_.pop_back();
|
||||
slots_[index].asset = std::move(asset);
|
||||
slots_[index].active = true;
|
||||
slots_[index].loadState = AssetLoadState::Loaded;
|
||||
slots_[index].lastAccessTime = now;
|
||||
} else {
|
||||
index = static_cast<uint32_t>(slots_.size());
|
||||
Slot slot;
|
||||
slot.asset = std::move(asset);
|
||||
slot.generation = nextGeneration_++;
|
||||
slot.active = true;
|
||||
slot.loadState = AssetLoadState::Loaded;
|
||||
slot.lastAccessTime = now;
|
||||
slots_.push_back(std::move(slot));
|
||||
}
|
||||
|
||||
|
|
@ -76,7 +99,18 @@ public:
|
|||
* @param handle 资源句柄
|
||||
* @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;
|
||||
return slots_[handle.index()].asset.get();
|
||||
}
|
||||
|
|
@ -86,6 +120,17 @@ public:
|
|||
* @param handle 资源句柄
|
||||
* @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 {
|
||||
if (!isValid(handle)) return Ptr<T>();
|
||||
return slots_[handle.index()].asset;
|
||||
|
|
@ -104,6 +149,44 @@ public:
|
|||
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 遍历所有资源
|
||||
* @param func 回调函数,签名 void(Handle<T>, T*)
|
||||
|
|
@ -137,6 +220,111 @@ public:
|
|||
*/
|
||||
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:
|
||||
std::vector<Slot> slots_;
|
||||
std::vector<uint32_t> freeIndices_;
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
#pragma once
|
||||
|
||||
#include <assets/asset_loader.h>
|
||||
#include <assets/asset_storage.h>
|
||||
#include <assets/asset_loader.h>
|
||||
#include <assets/handle.h>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <condition_variable>
|
||||
#include <event/events.h>
|
||||
#include <filesystem>
|
||||
|
|
@ -34,7 +33,8 @@ namespace extra2d {
|
|||
* - Handle<T>: 轻量级句柄
|
||||
*/
|
||||
class AssetsModule : public Module {
|
||||
E2D_REGISTER_MODULE(AssetsModule, "Assets", 2)
|
||||
// 优先级为 3,在 FileModule (优先级 2) 之后初始化
|
||||
E2D_REGISTER_MODULE(AssetsModule, "Assets", 3)
|
||||
|
||||
public:
|
||||
AssetsModule();
|
||||
|
|
@ -103,11 +103,52 @@ public:
|
|||
*/
|
||||
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 移除资源
|
||||
*/
|
||||
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<Shader> handle);
|
||||
|
||||
private:
|
||||
/**
|
||||
* @brief 内部实现:添加文件监控(减少重复代码)
|
||||
*/
|
||||
void addFileWatchInternal(const std::string &path, FileWatchInfo &&info);
|
||||
|
||||
/**
|
||||
* @brief 重新加载纹理
|
||||
*/
|
||||
|
|
@ -470,6 +517,75 @@ template <> inline bool AssetsModule::isValid<Mesh>(Handle<Mesh> handle) const {
|
|||
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 <> inline void AssetsModule::remove<Texture>(Handle<Texture> handle) {
|
||||
|
|
@ -489,6 +605,108 @@ template <> inline void AssetsModule::remove<Mesh>(Handle<Mesh> 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>
|
||||
std::vector<Handle<T>> AssetsModule::loadDir(const std::string &directory,
|
||||
const std::string &pattern,
|
||||
|
|
@ -509,6 +727,10 @@ template <>
|
|||
void AssetsModule::loadAsync<Texture>(
|
||||
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>
|
||||
void AssetsModule::registerLoader(std::unique_ptr<AssetLoader<T>> loader) {}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
#include <module/module.h>
|
||||
#include <string>
|
||||
#include <typeindex>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace extra2d {
|
||||
|
|
@ -67,9 +68,35 @@ public:
|
|||
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:
|
||||
ModuleRegistry() = default;
|
||||
std::vector<ModuleInfo> registrations_;
|
||||
std::unordered_map<std::type_index, Module*> instances_;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -108,4 +135,14 @@ public: \
|
|||
#define E2D_REGISTER_MODULE_SIMPLE(ClassName) \
|
||||
E2D_REGISTER_MODULE(ClassName, #ClassName, 100)
|
||||
|
||||
/**
|
||||
* @brief 获取模块实例的辅助函数
|
||||
* @tparam T 模块类型
|
||||
* @return 模块实例指针
|
||||
*/
|
||||
template <typename T>
|
||||
T* getModule() {
|
||||
return ModuleRegistry::instance().getModule<T>();
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
|
|||
|
|
@ -2,8 +2,8 @@
|
|||
|
||||
#include <module/module.h>
|
||||
#include <module/module_registry.h>
|
||||
#include <types/base/types.h>
|
||||
#include <string>
|
||||
#include <types/base/types.h>
|
||||
#include <vector>
|
||||
|
||||
namespace extra2d {
|
||||
|
|
@ -12,23 +12,23 @@ namespace extra2d {
|
|||
* @brief 文件信息
|
||||
*/
|
||||
struct FileInfo {
|
||||
std::string path;
|
||||
std::string name;
|
||||
bool isDir = false;
|
||||
int64 size = 0;
|
||||
std::string path;
|
||||
std::string name;
|
||||
bool isDir = false;
|
||||
int64 size = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 文件读取结果
|
||||
*/
|
||||
struct FileData {
|
||||
bool ok = false;
|
||||
std::vector<uint8> data;
|
||||
std::string error;
|
||||
bool ok = false;
|
||||
std::vector<uint8> data;
|
||||
std::string error;
|
||||
|
||||
operator bool() const { return ok; }
|
||||
const uint8* ptr() const { return data.data(); }
|
||||
size_t size() const { return data.size(); }
|
||||
operator bool() const { return ok; }
|
||||
const uint8 *ptr() const { return data.data(); }
|
||||
size_t size() const { return data.size(); }
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -38,123 +38,142 @@ struct FileData {
|
|||
* 非单例设计,通过 Context 管理生命周期
|
||||
*/
|
||||
class FileModule : public Module {
|
||||
// 自动注册到模块系统,优先级为 20(系统模块)
|
||||
E2D_REGISTER_MODULE(FileModule, "File", 20)
|
||||
// 优先级为 2,系统级模块,在 TimerModule (优先级 4) 之前初始化
|
||||
E2D_REGISTER_MODULE(FileModule, "File", 2)
|
||||
|
||||
public:
|
||||
FileModule();
|
||||
~FileModule() override;
|
||||
FileModule();
|
||||
~FileModule() override;
|
||||
|
||||
// 禁止拷贝
|
||||
FileModule(const FileModule&) = delete;
|
||||
FileModule& operator=(const FileModule&) = delete;
|
||||
// 禁止拷贝
|
||||
FileModule(const FileModule &) = delete;
|
||||
FileModule &operator=(const FileModule &) = delete;
|
||||
|
||||
// 允许移动
|
||||
FileModule(FileModule&&) noexcept;
|
||||
FileModule& operator=(FileModule&&) noexcept;
|
||||
// 允许移动
|
||||
FileModule(FileModule &&) noexcept;
|
||||
FileModule &operator=(FileModule &&) noexcept;
|
||||
|
||||
// Module 接口实现
|
||||
bool init() override;
|
||||
void shutdown() override;
|
||||
// Module 接口实现
|
||||
bool init() override;
|
||||
void shutdown() override;
|
||||
|
||||
/**
|
||||
* @brief 检查文件是否存在
|
||||
*/
|
||||
bool exists(const std::string& path) const;
|
||||
/**
|
||||
* @brief 检查文件是否存在
|
||||
*/
|
||||
bool exists(const std::string &path) const;
|
||||
|
||||
/**
|
||||
* @brief 检查是否为目录
|
||||
*/
|
||||
bool isDir(const std::string& path) const;
|
||||
/**
|
||||
* @brief 检查是否为目录
|
||||
*/
|
||||
bool isDir(const std::string &path) const;
|
||||
|
||||
/**
|
||||
* @brief 读取整个文件到内存
|
||||
*/
|
||||
FileData read(const std::string& path) const;
|
||||
/**
|
||||
* @brief 读取整个文件到内存
|
||||
*/
|
||||
FileData read(const std::string &path) const;
|
||||
|
||||
/**
|
||||
* @brief 读取文件为字符串
|
||||
*/
|
||||
std::string readString(const std::string& path) const;
|
||||
/**
|
||||
* @brief 读取文件为字符串
|
||||
*/
|
||||
std::string readString(const std::string &path) const;
|
||||
|
||||
/**
|
||||
* @brief 写入数据到文件
|
||||
*/
|
||||
bool write(const std::string& path, const void* data, size_t size) const;
|
||||
/**
|
||||
* @brief 写入数据到文件
|
||||
*/
|
||||
bool write(const std::string &path, const void *data, size_t size) const;
|
||||
|
||||
/**
|
||||
* @brief 写入字符串到文件
|
||||
*/
|
||||
bool writeString(const std::string& path, const std::string& content) const;
|
||||
/**
|
||||
* @brief 写入字符串到文件
|
||||
*/
|
||||
bool writeString(const std::string &path, const std::string &content) const;
|
||||
|
||||
/**
|
||||
* @brief 追加数据到文件
|
||||
*/
|
||||
bool append(const std::string& path, const void* data, size_t size) const;
|
||||
/**
|
||||
* @brief 追加数据到文件
|
||||
*/
|
||||
bool append(const std::string &path, const void *data, size_t size) const;
|
||||
|
||||
/**
|
||||
* @brief 删除文件
|
||||
*/
|
||||
bool remove(const std::string& path) const;
|
||||
/**
|
||||
* @brief 删除文件
|
||||
*/
|
||||
bool remove(const std::string &path) const;
|
||||
|
||||
/**
|
||||
* @brief 创建目录
|
||||
*/
|
||||
bool mkdir(const std::string& path) const;
|
||||
/**
|
||||
* @brief 创建目录
|
||||
*/
|
||||
bool mkdir(const std::string &path) const;
|
||||
|
||||
/**
|
||||
* @brief 列出目录内容
|
||||
*/
|
||||
std::vector<FileInfo> listDir(const std::string& path) const;
|
||||
/**
|
||||
* @brief 列出目录内容
|
||||
*/
|
||||
std::vector<FileInfo> listDir(const std::string &path) const;
|
||||
|
||||
/**
|
||||
* @brief 获取文件大小
|
||||
*/
|
||||
int64 fileSize(const std::string& path) const;
|
||||
/**
|
||||
* @brief 获取文件大小
|
||||
*/
|
||||
int64 fileSize(const std::string &path) const;
|
||||
|
||||
/**
|
||||
* @brief 获取文件扩展名
|
||||
*/
|
||||
std::string ext(const std::string& path) const;
|
||||
/**
|
||||
* @brief 获取文件扩展名
|
||||
*/
|
||||
std::string ext(const std::string &path) const;
|
||||
|
||||
/**
|
||||
* @brief 获取文件名(不含路径)
|
||||
*/
|
||||
std::string fileName(const std::string& path) const;
|
||||
/**
|
||||
* @brief 获取文件名(不含路径)
|
||||
*/
|
||||
std::string fileName(const std::string &path) const;
|
||||
|
||||
/**
|
||||
* @brief 获取文件所在目录
|
||||
*/
|
||||
std::string dirName(const std::string& path) const;
|
||||
/**
|
||||
* @brief 获取文件所在目录
|
||||
*/
|
||||
std::string dirName(const std::string &path) const;
|
||||
|
||||
/**
|
||||
* @brief 连接路径
|
||||
*/
|
||||
std::string join(const std::string& a, const std::string& b) const;
|
||||
/**
|
||||
* @brief 连接路径
|
||||
*/
|
||||
std::string join(const std::string &a, const std::string &b) const;
|
||||
|
||||
/**
|
||||
* @brief 获取可写目录(用于存档等)
|
||||
*/
|
||||
std::string writableDir() const;
|
||||
/**
|
||||
* @brief 获取可写目录(用于存档等)
|
||||
*/
|
||||
std::string writableDir() const;
|
||||
|
||||
/**
|
||||
* @brief 设置资源根目录
|
||||
*/
|
||||
void setAssetRoot(const std::string& root) { assetRoot_ = root; }
|
||||
/**
|
||||
* @brief 设置资源根目录
|
||||
*/
|
||||
void setAssetRoot(const std::string &root) { assetRoot_ = root; }
|
||||
|
||||
/**
|
||||
* @brief 获取资源根目录
|
||||
*/
|
||||
const std::string& assetRoot() const { return assetRoot_; }
|
||||
/**
|
||||
* @brief 获取资源根目录
|
||||
*/
|
||||
const std::string &assetRoot() const { return assetRoot_; }
|
||||
|
||||
/**
|
||||
* @brief 获取资源完整路径
|
||||
*/
|
||||
std::string assetPath(const std::string& relPath) const;
|
||||
/**
|
||||
* @brief 获取资源完整路径
|
||||
*/
|
||||
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:
|
||||
std::string assetRoot_;
|
||||
std::string writableDir_;
|
||||
std::string assetRoot_;
|
||||
std::string writableDir_;
|
||||
std::string romfsRoot_ = "romfs:/";
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
|
|||
|
|
@ -172,20 +172,18 @@ using TouchCb = std::function<void(const TouchPoint &)>;
|
|||
* 使用新的 Module 基类,支持自动注册
|
||||
*/
|
||||
class InputModule : public Module {
|
||||
// 自动注册到模块系统,优先级为 10
|
||||
E2D_REGISTER_MODULE(InputModule, "Input", 10)
|
||||
// 优先级为 7,在 TimerModule (优先级 6) 之后初始化
|
||||
E2D_REGISTER_MODULE(InputModule, "Input", 7)
|
||||
|
||||
public:
|
||||
InputModule();
|
||||
~InputModule() override;
|
||||
|
||||
// 禁止拷贝
|
||||
// 禁止拷贝和移动
|
||||
InputModule(const InputModule &) = delete;
|
||||
InputModule &operator=(const InputModule &) = delete;
|
||||
|
||||
// 允许移动
|
||||
InputModule(InputModule &&) noexcept;
|
||||
InputModule &operator=(InputModule &&) noexcept;
|
||||
InputModule(InputModule &&) = delete;
|
||||
InputModule &operator=(InputModule &&) = delete;
|
||||
|
||||
// Module 接口实现
|
||||
bool init() override;
|
||||
|
|
|
|||
|
|
@ -33,13 +33,11 @@ public:
|
|||
WindowModule();
|
||||
~WindowModule() override;
|
||||
|
||||
// 禁止拷贝
|
||||
// 禁止拷贝和移动
|
||||
WindowModule(const WindowModule &) = delete;
|
||||
WindowModule &operator=(const WindowModule &) = delete;
|
||||
|
||||
// 允许移动
|
||||
WindowModule(WindowModule &&) noexcept;
|
||||
WindowModule &operator=(WindowModule &&) noexcept;
|
||||
WindowModule(WindowModule &&) = delete;
|
||||
WindowModule &operator=(WindowModule &&) = delete;
|
||||
|
||||
// Module 接口实现
|
||||
bool init() override;
|
||||
|
|
@ -121,28 +119,6 @@ public:
|
|||
*/
|
||||
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:
|
||||
void handleWindowEvent(const SDL_WindowEvent &evt);
|
||||
|
||||
|
|
@ -162,7 +138,6 @@ private:
|
|||
void onRenderPresent();
|
||||
|
||||
SDL_Window *window_ = nullptr;
|
||||
SDL_GLContext glContext_ = nullptr;
|
||||
bool shouldClose_ = false;
|
||||
|
||||
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
|
||||
|
||||
#include <renderer/rhi/rhi.h>
|
||||
#include <renderer/shader.h>
|
||||
#include <renderer/texture.h>
|
||||
#include <types/ptr/ref_counted.h>
|
||||
#include <types/ptr/intrusive_ptr.h>
|
||||
#include <types/math/vec2.h>
|
||||
#include <types/math/color.h>
|
||||
#include <renderer/render_types.h>
|
||||
#include <renderer/shader.h>
|
||||
#include <renderer/uniform_buffer.h>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
|
@ -15,7 +15,37 @@ namespace extra2d {
|
|||
|
||||
// 前向声明
|
||||
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 材质参数信息
|
||||
|
|
@ -30,7 +60,7 @@ struct MaterialParamInfo {
|
|||
* @brief 纹理槽位信息
|
||||
*/
|
||||
struct TextureSlot {
|
||||
Handle<Texture> handle;
|
||||
Ptr<Texture> texture;
|
||||
uint32_t slot;
|
||||
std::string uniformName;
|
||||
};
|
||||
|
|
@ -79,7 +109,9 @@ public:
|
|||
bool isFinalized() const { return finalized_; }
|
||||
|
||||
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;
|
||||
bool finalized_ = false;
|
||||
};
|
||||
|
|
@ -87,7 +119,7 @@ private:
|
|||
/**
|
||||
* @brief 材质类
|
||||
*
|
||||
* 作为着色器和纹理的中间层,管理:
|
||||
* 基于 RHI 的材质包装类,管理:
|
||||
* - 着色器程序
|
||||
* - 材质参数(通过 UBO 上传)
|
||||
* - 纹理绑定
|
||||
|
|
@ -112,10 +144,7 @@ private:
|
|||
* material->setFloat("uOpacity", 1.0f);
|
||||
*
|
||||
* // 设置纹理
|
||||
* material->setTexture("uTexture", textureHandle, 0);
|
||||
*
|
||||
* // 注册到渲染器
|
||||
* MaterialHandle handle = renderer->registerMaterial(material);
|
||||
* material->setTexture("uTexture", texture, 0);
|
||||
* @endcode
|
||||
*/
|
||||
class Material : public RefCounted {
|
||||
|
|
@ -147,6 +176,12 @@ public:
|
|||
*/
|
||||
Ptr<Shader> getShader() const { return shader_; }
|
||||
|
||||
/**
|
||||
* @brief 获取 RHI 管线句柄
|
||||
* @return RHI 管线句柄
|
||||
*/
|
||||
PipelineHandle getPipeline() const;
|
||||
|
||||
// ========================================
|
||||
// 参数设置
|
||||
// ========================================
|
||||
|
|
@ -168,7 +203,10 @@ public:
|
|||
/**
|
||||
* @brief 设置 vec4 参数
|
||||
* @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);
|
||||
|
||||
|
|
@ -193,10 +231,10 @@ public:
|
|||
/**
|
||||
* @brief 设置纹理
|
||||
* @param uniformName 着色器中的采样器 uniform 名称
|
||||
* @param texture 纹理句柄
|
||||
* @param texture 纹理
|
||||
* @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 获取所有纹理槽位
|
||||
|
|
@ -225,17 +263,11 @@ public:
|
|||
*/
|
||||
uint32_t getDataSize() const { return static_cast<uint32_t>(data_.size()); }
|
||||
|
||||
// ========================================
|
||||
// 应用材质(渲染时调用)
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* @brief 应用材质
|
||||
*
|
||||
* 绑定着色器、上传 UBO 数据
|
||||
* @param uboManager UBO 管理器
|
||||
* @brief 获取材质布局
|
||||
* @return 材质布局
|
||||
*/
|
||||
void apply(UniformBufferManager& uboManager);
|
||||
Ptr<MaterialLayout> getLayout() const { return layout_; }
|
||||
|
||||
private:
|
||||
Ptr<MaterialLayout> layout_; // 材质布局
|
||||
|
|
|
|||
|
|
@ -1,119 +1,113 @@
|
|||
#pragma once
|
||||
|
||||
#include <renderer/rhi/rhi.h>
|
||||
#include <types/math/color.h>
|
||||
#include <types/math/rect.h>
|
||||
#include <types/math/vec2.h>
|
||||
#include <types/ptr/intrusive_ptr.h>
|
||||
#include <types/ptr/ref_counted.h>
|
||||
|
||||
// 前向声明 OpenGL 类型
|
||||
typedef unsigned int GLuint;
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
/**
|
||||
* @brief 顶点结构
|
||||
*/
|
||||
struct Vertex {
|
||||
Vec2 position; // 位置
|
||||
Vec2 texCoord; // 纹理坐标
|
||||
Color color; // 颜色
|
||||
Vec2 position; // 位置
|
||||
Vec2 texCoord; // 纹理坐标
|
||||
Color color; // 颜色
|
||||
|
||||
Vertex() = default;
|
||||
Vertex(const Vec2 &pos, const Vec2 &uv, const Color &col)
|
||||
: position(pos), texCoord(uv), color(col) {}
|
||||
Vertex() = default;
|
||||
Vertex(const Vec2& pos, const Vec2& uv, const Color& col)
|
||||
: position(pos), texCoord(uv), color(col) {}
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief 网格类
|
||||
*
|
||||
* 管理 OpenGL 顶点数组对象和缓冲区
|
||||
* 基于 RHI 的网格包装类,管理顶点缓冲区和索引缓冲区
|
||||
* 支持静态和动态顶点数据
|
||||
*/
|
||||
class Mesh : public RefCounted {
|
||||
public:
|
||||
/**
|
||||
* @brief 默认构造函数
|
||||
*/
|
||||
Mesh();
|
||||
/**
|
||||
* @brief 默认构造函数
|
||||
*/
|
||||
Mesh();
|
||||
|
||||
/**
|
||||
* @brief 析构函数
|
||||
*/
|
||||
~Mesh() override;
|
||||
/**
|
||||
* @brief 析构函数
|
||||
*/
|
||||
~Mesh() override;
|
||||
|
||||
/**
|
||||
* @brief 设置顶点数据
|
||||
* @param vertices 顶点数组
|
||||
* @param count 顶点数量
|
||||
*/
|
||||
void setVertices(const Vertex *vertices, uint32_t count);
|
||||
/**
|
||||
* @brief 设置顶点数据
|
||||
* @param vertices 顶点数组
|
||||
* @param count 顶点数量
|
||||
*/
|
||||
void setVertices(const Vertex* vertices, uint32_t count);
|
||||
|
||||
/**
|
||||
* @brief 设置索引数据
|
||||
* @param indices 索引数组
|
||||
* @param count 索引数量
|
||||
*/
|
||||
void setIndices(const uint16_t *indices, uint32_t count);
|
||||
/**
|
||||
* @brief 设置索引数据
|
||||
* @param indices 索引数组
|
||||
* @param count 索引数量
|
||||
*/
|
||||
void setIndices(const uint16_t* indices, uint32_t count);
|
||||
|
||||
/**
|
||||
* @brief 更新部分顶点数据
|
||||
* @param vertices 顶点数据
|
||||
* @param count 顶点数量
|
||||
* @param offset 偏移量
|
||||
*/
|
||||
void updateVertices(const Vertex *vertices, uint32_t count, uint32_t offset);
|
||||
/**
|
||||
* @brief 更新部分顶点数据
|
||||
* @param vertices 顶点数据
|
||||
* @param count 顶点数量
|
||||
* @param offset 偏移量
|
||||
*/
|
||||
void updateVertices(const Vertex* vertices, uint32_t count, uint32_t offset);
|
||||
|
||||
/**
|
||||
* @brief 绑定网格
|
||||
*/
|
||||
void bind() const;
|
||||
/**
|
||||
* @brief 获取顶点数量
|
||||
* @return 顶点数量
|
||||
*/
|
||||
uint32_t getVertexCount() const { return vertexCount_; }
|
||||
|
||||
/**
|
||||
* @brief 解绑网格
|
||||
*/
|
||||
void unbind() const;
|
||||
/**
|
||||
* @brief 获取索引数量
|
||||
* @return 索引数量
|
||||
*/
|
||||
uint32_t getIndexCount() const { return indexCount_; }
|
||||
|
||||
/**
|
||||
* @brief 绘制网格
|
||||
*/
|
||||
void draw() const;
|
||||
/**
|
||||
* @brief 获取顶点缓冲区句柄
|
||||
* @return 顶点缓冲区句柄
|
||||
*/
|
||||
BufferHandle getVertexBuffer() const { return vertexBuffer_; }
|
||||
|
||||
/**
|
||||
* @brief 实例化绘制
|
||||
* @param instanceCount 实例数量
|
||||
*/
|
||||
void drawInstanced(uint32_t instanceCount) const;
|
||||
/**
|
||||
* @brief 获取索引缓冲区句柄
|
||||
* @return 索引缓冲区句柄
|
||||
*/
|
||||
BufferHandle getIndexBuffer() const { return indexBuffer_; }
|
||||
|
||||
/**
|
||||
* @brief 获取顶点数量
|
||||
* @return 顶点数量
|
||||
*/
|
||||
uint32_t getVertexCount() const { return vertexCount_; }
|
||||
/**
|
||||
* @brief 检查是否有索引数据
|
||||
* @return 是否有索引数据
|
||||
*/
|
||||
bool hasIndices() const { return indexCount_ > 0; }
|
||||
|
||||
/**
|
||||
* @brief 获取索引数量
|
||||
* @return 索引数量
|
||||
*/
|
||||
uint32_t getIndexCount() const { return indexCount_; }
|
||||
|
||||
/**
|
||||
* @brief 创建标准四边形
|
||||
* @param size 四边形大小
|
||||
* @param uv 纹理坐标范围
|
||||
* @return 网格对象
|
||||
*/
|
||||
static Ptr<Mesh> createQuad(const Vec2 &size,
|
||||
const Rect &uv = Rect(0, 0, 1, 1));
|
||||
/**
|
||||
* @brief 创建标准四边形
|
||||
* @param size 四边形大小
|
||||
* @param uv 纹理坐标范围
|
||||
* @return 网格对象
|
||||
*/
|
||||
static Ptr<Mesh> createQuad(const Vec2& size,
|
||||
const Rect& uv = Rect(0, 0, 1, 1));
|
||||
|
||||
private:
|
||||
GLuint vao_ = 0; // 顶点数组对象
|
||||
GLuint vbo_ = 0; // 顶点缓冲区
|
||||
GLuint ibo_ = 0; // 索引缓冲区
|
||||
uint32_t vertexCount_ = 0; // 顶点数量
|
||||
uint32_t indexCount_ = 0; // 索引数量
|
||||
uint32_t vertexCapacity_ = 0; // 顶点缓冲区容量
|
||||
uint32_t indexCapacity_ = 0; // 索引缓冲区容量
|
||||
BufferHandle vertexBuffer_; // 顶点缓冲区句柄
|
||||
BufferHandle indexBuffer_; // 索引缓冲区句柄
|
||||
uint32_t vertexCount_ = 0; // 顶点数量
|
||||
uint32_t indexCount_ = 0; // 索引数量
|
||||
uint32_t vertexCapacity_ = 0; // 顶点缓冲区容量
|
||||
uint32_t indexCapacity_ = 0; // 索引缓冲区容量
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
#include <assets/handle.h>
|
||||
#include <cstdint>
|
||||
#include <types/base/types.h>
|
||||
#include <types/math/vec2.h>
|
||||
#include <types/math/color.h>
|
||||
#include <types/math/transform.h>
|
||||
#include <assets/handle.h>
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <types/math/vec2.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
|
|
@ -15,79 +14,17 @@ class Material;
|
|||
class Mesh;
|
||||
class Texture;
|
||||
|
||||
// 资源句柄类型(使用新的 Handle<T>)
|
||||
using MaterialHandle = Handle<Material>;
|
||||
using MeshHandle = Handle<Mesh>;
|
||||
using TextureHandle = Handle<Texture>;
|
||||
// 注意:资源句柄直接使用 Handle<T>,与 RHI 的 TextureHandle 等区分
|
||||
|
||||
/**
|
||||
* @brief 渲染命令类型
|
||||
*/
|
||||
enum class RenderCommandType : uint8_t {
|
||||
DrawMesh, // 绘制网格
|
||||
DrawMeshInstanced, // 实例化绘制
|
||||
SetViewport, // 设置视口
|
||||
Clear // 清除缓冲区
|
||||
DrawMesh, // 绘制网格
|
||||
SetViewport, // 设置视口
|
||||
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 渲染命令结构
|
||||
*
|
||||
|
|
@ -95,104 +32,88 @@ inline uint32_t getMaterialParamSize(MaterialParamType type) {
|
|||
* 通过资源句柄引用实际资源
|
||||
*/
|
||||
struct RenderCommand {
|
||||
RenderCommandType type; // 命令类型
|
||||
uint32_t sortKey; // 排序键(材质ID + 层)
|
||||
RenderCommandType type; // 命令类型
|
||||
uint32_t sortKey; // 排序键(材质ID + 层)
|
||||
|
||||
// 绘制网格命令数据
|
||||
struct DrawMeshData {
|
||||
MeshHandle mesh; // 网格句柄
|
||||
MaterialHandle material; // 材质句柄
|
||||
Vec2 pos; // 位置
|
||||
Vec2 scale; // 缩放
|
||||
float rot; // 旋转角度
|
||||
Color color; // 顶点颜色
|
||||
};
|
||||
// 绘制网格命令数据
|
||||
struct DrawMeshData {
|
||||
Handle<Mesh> mesh; // 网格句柄
|
||||
Handle<Material> material; // 材质句柄
|
||||
Vec2 pos; // 位置
|
||||
Vec2 scale; // 缩放
|
||||
float rot; // 旋转角度
|
||||
Color color; // 顶点颜色
|
||||
};
|
||||
|
||||
// 实例化绘制命令数据
|
||||
struct DrawInstancedData {
|
||||
MeshHandle mesh; // 网格句柄
|
||||
MaterialHandle material; // 材质句柄
|
||||
uint32_t instanceCount; // 实例数量
|
||||
uint32_t instanceOffset; // 实例数据偏移
|
||||
};
|
||||
// 设置视口命令数据
|
||||
struct ViewportData {
|
||||
int32_t x, y; // 视口位置
|
||||
int32_t width, height; // 视口大小
|
||||
};
|
||||
|
||||
// 设置视口命令数据
|
||||
struct ViewportData {
|
||||
int32_t x, y; // 视口位置
|
||||
int32_t width, height; // 视口大小
|
||||
};
|
||||
// 清除缓冲区命令数据
|
||||
struct ClearData {
|
||||
Color color; // 清除颜色
|
||||
uint32_t flags; // 清除标志(颜色/深度/模板)
|
||||
};
|
||||
|
||||
// 清除缓冲区命令数据
|
||||
struct ClearData {
|
||||
Color color; // 清除颜色
|
||||
uint32_t flags; // 清除标志(颜色/深度/模板)
|
||||
};
|
||||
union {
|
||||
DrawMeshData drawMesh;
|
||||
ViewportData viewport;
|
||||
ClearData clear;
|
||||
};
|
||||
|
||||
union {
|
||||
DrawMeshData drawMesh;
|
||||
DrawInstancedData drawInstanced;
|
||||
ViewportData viewport;
|
||||
ClearData clear;
|
||||
};
|
||||
/**
|
||||
* @brief 默认构造函数
|
||||
*/
|
||||
RenderCommand() : type(RenderCommandType::DrawMesh), sortKey(0) {
|
||||
drawMesh.mesh = Handle<Mesh>::invalid();
|
||||
drawMesh.material = Handle<Material>::invalid();
|
||||
drawMesh.pos = Vec2(0.0f, 0.0f);
|
||||
drawMesh.scale = Vec2(1.0f, 1.0f);
|
||||
drawMesh.rot = 0.0f;
|
||||
drawMesh.color = Color::White;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 默认构造函数
|
||||
*/
|
||||
RenderCommand() : type(RenderCommandType::DrawMesh), sortKey(0) {
|
||||
drawMesh.mesh = MeshHandle::invalid();
|
||||
drawMesh.material = MaterialHandle::invalid();
|
||||
drawMesh.pos = Vec2(0.0f, 0.0f);
|
||||
drawMesh.scale = Vec2(1.0f, 1.0f);
|
||||
drawMesh.rot = 0.0f;
|
||||
drawMesh.color = Color::White;
|
||||
}
|
||||
/**
|
||||
* @brief 从 Transform 设置绘制数据
|
||||
*/
|
||||
void setTransform(const Transform &t) {
|
||||
drawMesh.pos = t.pos;
|
||||
drawMesh.scale = t.scale;
|
||||
drawMesh.rot = t.rot;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 从 Transform 设置绘制数据
|
||||
*/
|
||||
void setTransform(const Transform& t) {
|
||||
drawMesh.pos = t.pos;
|
||||
drawMesh.scale = t.scale;
|
||||
drawMesh.rot = t.rot;
|
||||
}
|
||||
/**
|
||||
* @brief 设置顶点颜色
|
||||
*/
|
||||
void setColor(const Color &c) { drawMesh.color = c; }
|
||||
|
||||
/**
|
||||
* @brief 设置顶点颜色
|
||||
*/
|
||||
void setColor(const Color& c) {
|
||||
drawMesh.color = c;
|
||||
}
|
||||
/**
|
||||
* @brief 获取 Transform
|
||||
*/
|
||||
Transform getTransform() const {
|
||||
return Transform(drawMesh.pos, drawMesh.scale, drawMesh.rot);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取 Transform
|
||||
*/
|
||||
Transform getTransform() const {
|
||||
return Transform(drawMesh.pos, drawMesh.scale, drawMesh.rot);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取顶点颜色
|
||||
*/
|
||||
Color getColor() const {
|
||||
return drawMesh.color;
|
||||
}
|
||||
/**
|
||||
* @brief 获取顶点颜色
|
||||
*/
|
||||
Color getColor() const { return drawMesh.color; }
|
||||
};
|
||||
|
||||
// 清除标志
|
||||
constexpr uint32_t CLEAR_COLOR_FLAG = 1 << 0;
|
||||
constexpr uint32_t CLEAR_DEPTH_FLAG = 1 << 1;
|
||||
constexpr uint32_t CLEAR_COLOR_FLAG = 1 << 0;
|
||||
constexpr uint32_t CLEAR_DEPTH_FLAG = 1 << 1;
|
||||
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_INSTANCES = 256;
|
||||
|
||||
// UBO 绑定槽位
|
||||
constexpr uint32_t GLOBAL_UBO_BINDING = 0; // 全局 UBO
|
||||
constexpr uint32_t MATERIAL_UBO_BINDING = 1; // 材质 UBO
|
||||
constexpr uint32_t INSTANCE_UBO_BINDING = 2; // 实例 UBO
|
||||
// UBO 绑定槽位(已移至 RHI)
|
||||
constexpr uint32_t GLOBAL_UBO_BINDING = 0; // 全局 UBO
|
||||
constexpr uint32_t MATERIAL_UBO_BINDING = 1; // 材质 UBO
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
|
|||
|
|
@ -1,17 +1,13 @@
|
|||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <event/events.h>
|
||||
#include <module/module.h>
|
||||
#include <module/module_registry.h>
|
||||
#include <renderer/material.h>
|
||||
#include <renderer/mesh.h>
|
||||
#include <renderer/command_queue.h>
|
||||
#include <renderer/render_graph.h>
|
||||
#include <renderer/render_types.h>
|
||||
#include <renderer/shader.h>
|
||||
#include <renderer/texture.h>
|
||||
#include <renderer/uniform_buffer.h>
|
||||
#include <renderer/rhi/rhi.h>
|
||||
#include <renderer/viewport_adapter.h>
|
||||
#include <vector>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
|
|
@ -21,15 +17,15 @@ class AssetsModule;
|
|||
/**
|
||||
* @brief 渲染器模块
|
||||
*
|
||||
* 核心渲染系统模块,负责:
|
||||
* 基于新 RHI 架构的核心渲染系统模块,负责:
|
||||
* - 通过事件接收渲染命令
|
||||
* - 自动批处理和排序
|
||||
* - 使用 RenderGraph 管理渲染流程
|
||||
* - 使用 CommandQueue 进行命令排序和批处理
|
||||
* - 执行实际渲染
|
||||
*
|
||||
* 资源管理已迁移到 AssetsModule
|
||||
*/
|
||||
class RendererModule : public Module {
|
||||
E2D_REGISTER_MODULE(RendererModule, "Renderer", 3)
|
||||
// 优先级为 4,在 AssetsModule (优先级 3) 之后初始化
|
||||
E2D_REGISTER_MODULE(RendererModule, "Renderer", 4)
|
||||
|
||||
public:
|
||||
/**
|
||||
|
|
@ -42,18 +38,16 @@ public:
|
|||
*/
|
||||
~RendererModule() override;
|
||||
|
||||
// 禁止拷贝
|
||||
// 禁止拷贝和移动
|
||||
RendererModule(const RendererModule &) = delete;
|
||||
RendererModule &operator=(const RendererModule &) = delete;
|
||||
|
||||
// 允许移动
|
||||
RendererModule(RendererModule &&) noexcept;
|
||||
RendererModule &operator=(RendererModule &&) noexcept;
|
||||
RendererModule(RendererModule &&) = delete;
|
||||
RendererModule &operator=(RendererModule &&) = delete;
|
||||
|
||||
/**
|
||||
* @brief 初始化模块
|
||||
*
|
||||
* 绑定事件监听器,等待窗口显示事件进行 GL 初始化
|
||||
* 绑定事件监听器,初始化 RenderGraph 和 CommandQueue
|
||||
*
|
||||
* @return 初始化是否成功
|
||||
*/
|
||||
|
|
@ -67,29 +61,7 @@ public:
|
|||
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_; }
|
||||
|
||||
/**
|
||||
* @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:
|
||||
//===========================================================================
|
||||
// 事件处理器
|
||||
|
|
@ -129,7 +137,7 @@ private:
|
|||
/**
|
||||
* @brief 渲染开始事件处理
|
||||
*
|
||||
* 清空命令缓冲区,重置统计信息
|
||||
* 开始渲染帧,初始化 RenderGraph
|
||||
*/
|
||||
void onRenderBegin();
|
||||
|
||||
|
|
@ -148,7 +156,7 @@ private:
|
|||
/**
|
||||
* @brief 渲染结束事件处理
|
||||
*
|
||||
* 排序命令,批处理并执行绘制
|
||||
* 执行 RenderGraph 渲染
|
||||
*/
|
||||
void onRenderEnd();
|
||||
|
||||
|
|
@ -162,7 +170,7 @@ private:
|
|||
/**
|
||||
* @brief 窗口显示事件处理
|
||||
*
|
||||
* 延迟 GL 初始化到窗口显示时
|
||||
* 延迟初始化到窗口显示时
|
||||
*/
|
||||
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 执行单个渲染命令
|
||||
* @param cmd 渲染命令
|
||||
|
|
@ -201,18 +185,11 @@ private:
|
|||
void executeCommand(const RenderCommand &cmd);
|
||||
|
||||
//===========================================================================
|
||||
// 命令缓冲区
|
||||
// 渲染图和命令队列
|
||||
//===========================================================================
|
||||
|
||||
std::array<RenderCommand, MAX_RENDER_COMMANDS>
|
||||
commandBuffer_; // 预分配命令缓冲区
|
||||
uint32 commandCount_ = 0; // 当前命令数量
|
||||
|
||||
//===========================================================================
|
||||
// UBO 管理器
|
||||
//===========================================================================
|
||||
|
||||
UniformBufferManager uniformManager_;
|
||||
RenderGraph renderGraph_; // 渲染图
|
||||
CommandQueue *commandQueue_ = nullptr; // 命令队列(指向 renderGraph_ 内部)
|
||||
|
||||
//===========================================================================
|
||||
// 事件监听器
|
||||
|
|
@ -229,7 +206,7 @@ private:
|
|||
// 状态标志
|
||||
//===========================================================================
|
||||
|
||||
bool glInitialized_ = false; // GL 是否已初始化
|
||||
bool initialized_ = false; // 是否已初始化
|
||||
|
||||
//===========================================================================
|
||||
// 渲染统计
|
||||
|
|
@ -237,7 +214,6 @@ private:
|
|||
|
||||
struct Stats {
|
||||
uint32 commandsSubmitted = 0; // 提交的命令数
|
||||
uint32 commandsExecuted = 0; // 执行的命令数
|
||||
uint32 drawCalls = 0; // 绘制调用次数
|
||||
uint32 batches = 0; // 批次数
|
||||
} stats_;
|
||||
|
|
@ -247,7 +223,7 @@ private:
|
|||
//===========================================================================
|
||||
|
||||
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,155 +1,117 @@
|
|||
#pragma once
|
||||
|
||||
#include <types/ptr/ref_counted.h>
|
||||
#include <types/ptr/intrusive_ptr.h>
|
||||
#include <types/base/types.h>
|
||||
#include <renderer/rhi/rhi.h>
|
||||
#include <string>
|
||||
#include <types/base/types.h>
|
||||
#include <types/ptr/intrusive_ptr.h>
|
||||
#include <types/ptr/ref_counted.h>
|
||||
#include <unordered_map>
|
||||
|
||||
// 前向声明 OpenGL 类型
|
||||
typedef unsigned int GLuint;
|
||||
typedef int GLint;
|
||||
typedef unsigned int GLenum;
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
/**
|
||||
* @brief 着色器类
|
||||
*
|
||||
* 管理 OpenGL 着色器程序的编译、链接和使用
|
||||
* 基于 RHI 的着色器包装类,管理着色器资源的创建和编译
|
||||
* 支持从文件或源码加载
|
||||
*/
|
||||
class Shader : public RefCounted {
|
||||
public:
|
||||
/**
|
||||
* @brief 默认构造函数
|
||||
*/
|
||||
Shader();
|
||||
/**
|
||||
* @brief 默认构造函数
|
||||
*/
|
||||
Shader();
|
||||
|
||||
/**
|
||||
* @brief 析构函数
|
||||
*/
|
||||
~Shader() override;
|
||||
/**
|
||||
* @brief 析构函数
|
||||
*/
|
||||
~Shader() override;
|
||||
|
||||
/**
|
||||
* @brief 从文件加载着色器
|
||||
* @param vsPath 顶点着色器文件路径
|
||||
* @param fsPath 片段着色器文件路径
|
||||
* @return 加载是否成功
|
||||
*/
|
||||
bool loadFromFile(const std::string& vsPath, const std::string& fsPath);
|
||||
/**
|
||||
* @brief 从源码加载着色器
|
||||
* @param vsSource 顶点着色器源码
|
||||
* @param fsSource 片段着色器源码
|
||||
* @return 加载是否成功
|
||||
*/
|
||||
bool loadFromSource(const std::string &vsSource, const std::string &fsSource);
|
||||
|
||||
/**
|
||||
* @brief 从源码加载着色器
|
||||
* @param vsSource 顶点着色器源码
|
||||
* @param fsSource 片段着色器源码
|
||||
* @return 加载是否成功
|
||||
*/
|
||||
bool loadFromSource(const std::string& vsSource, const std::string& fsSource);
|
||||
/**
|
||||
* @brief 获取 RHI 着色器句柄
|
||||
* @return RHI 着色器句柄
|
||||
*/
|
||||
ShaderHandle getHandle() const { return handle_; }
|
||||
|
||||
/**
|
||||
* @brief 绑定着色器程序
|
||||
*/
|
||||
void bind() const;
|
||||
/**
|
||||
* @brief 获取 RHI 管线句柄
|
||||
* @return RHI 管线句柄
|
||||
*/
|
||||
PipelineHandle getPipeline() const { return pipeline_; }
|
||||
|
||||
/**
|
||||
* @brief 解绑着色器程序
|
||||
*/
|
||||
void unbind() const;
|
||||
/**
|
||||
* @brief 检查是否已加载
|
||||
* @return 是否已加载
|
||||
*/
|
||||
bool isLoaded() const { return handle_.isValid() && pipeline_.isValid(); }
|
||||
|
||||
/**
|
||||
* @brief 设置 Uniform Block 绑定槽位
|
||||
* @param name Uniform Block 名称
|
||||
* @param binding 绑定槽位
|
||||
*/
|
||||
void setUniformBlock(const std::string& name, uint32_t binding);
|
||||
/**
|
||||
* @brief 使用自定义顶点布局从源码加载着色器
|
||||
* @param vsSource 顶点着色器源码
|
||||
* @param fsSource 片段着色器源码
|
||||
* @param vertexLayout 顶点布局
|
||||
* @return 加载是否成功
|
||||
*/
|
||||
bool loadFromSourceWithLayout(const std::string &vsSource,
|
||||
const std::string &fsSource,
|
||||
const VertexLayout &vertexLayout);
|
||||
|
||||
/**
|
||||
* @brief 设置 int 类型 uniform
|
||||
* @param name uniform 名称
|
||||
* @param value 值
|
||||
*/
|
||||
void setInt(const std::string& name, int value);
|
||||
/**
|
||||
* @brief 设置 Uniform Block 绑定槽位
|
||||
* @param name Uniform Block 名称
|
||||
* @param binding 绑定槽位
|
||||
*/
|
||||
void setUniformBlock(const std::string &name, uint32_t binding);
|
||||
|
||||
/**
|
||||
* @brief 设置 float 类型 uniform
|
||||
* @param name uniform 名称
|
||||
* @param value 值
|
||||
*/
|
||||
void setFloat(const std::string& name, float value);
|
||||
/**
|
||||
* @brief 获取 Uniform Block 绑定槽位
|
||||
* @param name Uniform Block 名称
|
||||
* @return 绑定槽位,如果未找到返回 UINT32_MAX
|
||||
*/
|
||||
uint32_t getUniformBlockBinding(const std::string& name) const;
|
||||
|
||||
/**
|
||||
* @brief 设置 vec2 类型 uniform
|
||||
* @param name uniform 名称
|
||||
* @param x X 分量
|
||||
* @param y Y 分量
|
||||
*/
|
||||
void setVec2(const std::string& name, float x, float y);
|
||||
/**
|
||||
* @brief 从源码重新加载着色器
|
||||
* @param vsSource 顶点着色器源码
|
||||
* @param fsSource 片段着色器源码
|
||||
* @return 重新加载是否成功
|
||||
*/
|
||||
bool reloadFromSource(const std::string &vsSource, const std::string &fsSource);
|
||||
|
||||
/**
|
||||
* @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; }
|
||||
/**
|
||||
* @brief 使用自定义顶点布局从源码重新加载着色器
|
||||
* @param vsSource 顶点着色器源码
|
||||
* @param fsSource 片段着色器源码
|
||||
* @param vertexLayout 顶点布局
|
||||
* @return 重新加载是否成功
|
||||
*/
|
||||
bool reloadFromSourceWithLayout(const std::string &vsSource,
|
||||
const std::string &fsSource,
|
||||
const VertexLayout &vertexLayout);
|
||||
|
||||
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 添加版本声明(如果不存在)
|
||||
* @param source 源码
|
||||
* @param type 着色器类型
|
||||
* @return 处理后的源码
|
||||
*/
|
||||
std::string addVersionIfNeeded(const std::string& source, GLenum type);
|
||||
/**
|
||||
* @brief 添加版本声明(如果不存在)
|
||||
* @param source 源码
|
||||
* @param isVertex 是否为顶点着色器
|
||||
* @return 处理后的源码
|
||||
*/
|
||||
std::string addVersionIfNeeded(const std::string &source, bool isVertex);
|
||||
|
||||
private:
|
||||
GLuint program_ = 0; // OpenGL 程序对象
|
||||
std::unordered_map<std::string, GLint> uniformCache_; // Uniform 位置缓存
|
||||
ShaderHandle handle_; // RHI 着色器句柄
|
||||
PipelineHandle pipeline_; // RHI 管线句柄
|
||||
|
||||
// Uniform Block 绑定映射
|
||||
std::unordered_map<std::string, uint32_t> uniformBlockBindings_;
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
|
|||
|
|
@ -1,20 +1,17 @@
|
|||
#pragma once
|
||||
|
||||
#include <renderer/rhi/rhi.h>
|
||||
#include <types/ptr/ref_counted.h>
|
||||
#include <types/ptr/intrusive_ptr.h>
|
||||
#include <renderer/render_types.h>
|
||||
#include <types/base/types.h>
|
||||
#include <string>
|
||||
|
||||
// 前向声明 OpenGL 类型
|
||||
typedef unsigned int GLuint;
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
/**
|
||||
* @brief 纹理类
|
||||
*
|
||||
* 管理 OpenGL 纹理的创建、加载和绑定
|
||||
* 基于 RHI 的纹理包装类,管理纹理资源的创建和加载
|
||||
* 支持从文件或内存加载
|
||||
*/
|
||||
class Texture : public RefCounted {
|
||||
|
|
@ -55,17 +52,6 @@ public:
|
|||
*/
|
||||
bool create(int width, int height, TextureFormat format);
|
||||
|
||||
/**
|
||||
* @brief 绑定纹理到指定槽位
|
||||
* @param slot 纹理槽位
|
||||
*/
|
||||
void bind(uint32_t slot = 0) const;
|
||||
|
||||
/**
|
||||
* @brief 解绑纹理
|
||||
*/
|
||||
void unbind() const;
|
||||
|
||||
/**
|
||||
* @brief 获取纹理宽度
|
||||
* @return 宽度
|
||||
|
|
@ -85,19 +71,42 @@ public:
|
|||
TextureFormat getFormat() const { return format_; }
|
||||
|
||||
/**
|
||||
* @brief 获取 OpenGL 纹理对象
|
||||
* @return OpenGL 纹理对象
|
||||
* @brief 获取 RHI 纹理句柄
|
||||
* @return RHI 纹理句柄
|
||||
*/
|
||||
GLuint getHandle() const { return texture_; }
|
||||
TextureHandle getHandle() const { return handle_; }
|
||||
|
||||
/**
|
||||
* @brief 检查是否已加载
|
||||
* @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:
|
||||
GLuint texture_ = 0; // OpenGL 纹理对象
|
||||
/**
|
||||
* @brief 获取每个像素的字节数
|
||||
* @param format 纹理格式
|
||||
* @return 字节数
|
||||
*/
|
||||
static uint32_t getBytesPerPixel(TextureFormat format);
|
||||
TextureHandle handle_; // RHI 纹理句柄
|
||||
int width_ = 0; // 纹理宽度
|
||||
int height_ = 0; // 纹理高度
|
||||
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
|
||||
|
||||
#include <glad/glad.h>
|
||||
#include <renderer/rhi/rhi.h>
|
||||
#include <memory>
|
||||
#include <renderer/render_types.h>
|
||||
#include <vector>
|
||||
|
||||
namespace extra2d {
|
||||
|
|
@ -10,70 +9,76 @@ namespace extra2d {
|
|||
/**
|
||||
* @brief 统一缓冲区对象 (UBO)
|
||||
*
|
||||
* 用于高效地传递 uniform 数据到 GPU
|
||||
* 基于 RHI 的 UBO 包装类,用于高效地传递 uniform 数据到 GPU
|
||||
* 支持 std140 布局标准
|
||||
*/
|
||||
class UniformBuffer {
|
||||
public:
|
||||
/**
|
||||
* @brief 默认构造函数
|
||||
*/
|
||||
UniformBuffer();
|
||||
/**
|
||||
* @brief 默认构造函数
|
||||
*/
|
||||
UniformBuffer();
|
||||
|
||||
/**
|
||||
* @brief 析构函数
|
||||
*/
|
||||
~UniformBuffer();
|
||||
/**
|
||||
* @brief 析构函数
|
||||
*/
|
||||
~UniformBuffer();
|
||||
|
||||
/**
|
||||
* @brief 创建 UBO
|
||||
* @param size 缓冲区大小(字节)
|
||||
* @param binding 绑定槽位
|
||||
* @return 创建是否成功
|
||||
*/
|
||||
bool create(uint32_t size, uint32_t binding);
|
||||
/**
|
||||
* @brief 创建 UBO
|
||||
* @param size 缓冲区大小(字节)
|
||||
* @param binding 绑定槽位
|
||||
* @return 创建是否成功
|
||||
*/
|
||||
bool create(uint32_t size, uint32_t binding);
|
||||
|
||||
/**
|
||||
* @brief 销毁 UBO
|
||||
*/
|
||||
void destroy();
|
||||
/**
|
||||
* @brief 销毁 UBO
|
||||
*/
|
||||
void destroy();
|
||||
|
||||
/**
|
||||
* @brief 更新缓冲区数据
|
||||
* @param data 数据源指针
|
||||
* @param size 数据大小
|
||||
* @param offset 缓冲区偏移(字节)
|
||||
*/
|
||||
void update(const void *data, uint32_t size, uint32_t offset = 0);
|
||||
/**
|
||||
* @brief 更新缓冲区数据
|
||||
* @param data 数据源指针
|
||||
* @param size 数据大小
|
||||
* @param offset 缓冲区偏移(字节)
|
||||
*/
|
||||
void update(const void* data, uint32_t size, uint32_t offset = 0);
|
||||
|
||||
/**
|
||||
* @brief 绑定到指定槽位
|
||||
* @param binding 绑定槽位
|
||||
*/
|
||||
void bind(uint32_t binding);
|
||||
/**
|
||||
* @brief 绑定到指定槽位
|
||||
* @param binding 绑定槽位
|
||||
*/
|
||||
void bind(uint32_t binding);
|
||||
|
||||
/**
|
||||
* @brief 获取缓冲区大小
|
||||
* @return 缓冲区大小
|
||||
*/
|
||||
uint32_t getSize() const { return size_; }
|
||||
/**
|
||||
* @brief 获取缓冲区大小
|
||||
* @return 缓冲区大小
|
||||
*/
|
||||
uint32_t getSize() const { return size_; }
|
||||
|
||||
/**
|
||||
* @brief 获取 OpenGL 缓冲区对象
|
||||
* @return OpenGL 缓冲区对象
|
||||
*/
|
||||
GLuint getHandle() const { return ubo_; }
|
||||
/**
|
||||
* @brief 获取 RHI 缓冲区句柄
|
||||
* @return RHI 缓冲区句柄
|
||||
*/
|
||||
BufferHandle getHandle() const { return handle_; }
|
||||
|
||||
/**
|
||||
* @brief 检查是否有效
|
||||
* @return 是否有效
|
||||
*/
|
||||
bool isValid() const { return ubo_ != 0; }
|
||||
/**
|
||||
* @brief 获取底层 RHI 缓冲区指针
|
||||
* @return RHI 缓冲区指针
|
||||
*/
|
||||
RHIBuffer* getRHIBuffer() const;
|
||||
|
||||
/**
|
||||
* @brief 检查是否有效
|
||||
* @return 是否有效
|
||||
*/
|
||||
bool isValid() const { return handle_.isValid(); }
|
||||
|
||||
private:
|
||||
GLuint ubo_ = 0; // OpenGL 缓冲区对象
|
||||
uint32_t size_ = 0; // 缓冲区大小
|
||||
uint32_t binding_ = 0; // 绑定槽位
|
||||
BufferHandle handle_; // RHI 缓冲区句柄
|
||||
uint32_t size_ = 0; // 缓冲区大小
|
||||
uint32_t binding_ = 0; // 绑定槽位
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -81,77 +86,112 @@ private:
|
|||
*
|
||||
* 管理多个 UBO 的分配和回收
|
||||
* 支持每帧重置和对象池复用
|
||||
* 支持双缓冲机制减少 CPU-GPU 等待
|
||||
*/
|
||||
class UniformBufferManager {
|
||||
public:
|
||||
/**
|
||||
* @brief 默认构造函数
|
||||
*/
|
||||
UniformBufferManager();
|
||||
/**
|
||||
* @brief 默认构造函数
|
||||
*/
|
||||
UniformBufferManager();
|
||||
|
||||
/**
|
||||
* @brief 析构函数
|
||||
*/
|
||||
~UniformBufferManager();
|
||||
/**
|
||||
* @brief 析构函数
|
||||
*/
|
||||
~UniformBufferManager();
|
||||
|
||||
// 禁止拷贝
|
||||
UniformBufferManager(const UniformBufferManager &) = delete;
|
||||
UniformBufferManager &operator=(const UniformBufferManager &) = delete;
|
||||
// 禁止拷贝
|
||||
UniformBufferManager(const UniformBufferManager&) = delete;
|
||||
UniformBufferManager& operator=(const UniformBufferManager&) = delete;
|
||||
|
||||
// 允许移动
|
||||
UniformBufferManager(UniformBufferManager &&) noexcept;
|
||||
UniformBufferManager &operator=(UniformBufferManager &&) noexcept;
|
||||
// 允许移动
|
||||
UniformBufferManager(UniformBufferManager&&) noexcept;
|
||||
UniformBufferManager& operator=(UniformBufferManager&&) noexcept;
|
||||
|
||||
/**
|
||||
* @brief 初始化管理器
|
||||
* @return 初始化是否成功
|
||||
*/
|
||||
bool initialize();
|
||||
/**
|
||||
* @brief 初始化管理器
|
||||
* @return 初始化是否成功
|
||||
*/
|
||||
bool initialize();
|
||||
|
||||
/**
|
||||
* @brief 关闭管理器
|
||||
*/
|
||||
void shutdown();
|
||||
/**
|
||||
* @brief 关闭管理器
|
||||
*/
|
||||
void shutdown();
|
||||
|
||||
/**
|
||||
* @brief 获取全局 UBO
|
||||
* @return 全局 UBO 指针
|
||||
*/
|
||||
UniformBuffer *getGlobalUBO();
|
||||
/**
|
||||
* @brief 获取当前帧的全局 UBO(双缓冲)
|
||||
* @param frameIndex 当前帧索引
|
||||
* @return 全局 UBO 指针
|
||||
*/
|
||||
UniformBuffer* getGlobalUBO(uint32_t frameIndex);
|
||||
|
||||
/**
|
||||
* @brief 获取或创建材质 UBO
|
||||
* @param size 缓冲区大小
|
||||
* @return UBO 指针
|
||||
*/
|
||||
UniformBuffer *acquireMaterialUBO(uint32_t size);
|
||||
/**
|
||||
* @brief 获取或创建材质 UBO
|
||||
* @param size 缓冲区大小
|
||||
* @return UBO 指针
|
||||
*/
|
||||
UniformBuffer* acquireMaterialUBO(uint32_t size);
|
||||
|
||||
/**
|
||||
* @brief 重置材质 UBO 池
|
||||
*
|
||||
* 每帧调用,重置当前索引以便复用 UBO
|
||||
*/
|
||||
void resetMaterialUBOs();
|
||||
/**
|
||||
* @brief 重置材质 UBO 池
|
||||
*
|
||||
* 每帧调用,重置当前索引以便复用 UBO
|
||||
*/
|
||||
void resetMaterialUBOs();
|
||||
|
||||
/**
|
||||
* @brief 更新全局 UBO 数据
|
||||
* @param data 数据指针
|
||||
* @param size 数据大小
|
||||
*/
|
||||
void updateGlobalUBO(const void *data, uint32_t size);
|
||||
/**
|
||||
* @brief 更新全局 UBO 数据(双缓冲)
|
||||
* @param data 数据指针
|
||||
* @param size 数据大小
|
||||
* @param frameIndex 当前帧索引
|
||||
*/
|
||||
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:
|
||||
// 全局 UBO(用于 viewProjection、time 等全局数据)
|
||||
std::unique_ptr<UniformBuffer> globalUBO_;
|
||||
// 全局 UBO 双缓冲(用于 viewProjection、time 等全局数据)
|
||||
std::array<std::unique_ptr<UniformBuffer>, 2> globalUBOs_;
|
||||
|
||||
// 材质 UBO 池
|
||||
std::vector<std::unique_ptr<UniformBuffer>> materialUBOPool_;
|
||||
uint32_t currentUBOIndex_ = 0;
|
||||
// 材质 UBO 池
|
||||
std::vector<std::unique_ptr<UniformBuffer>> materialUBOPool_;
|
||||
uint32_t currentUBOIndex_ = 0;
|
||||
|
||||
// 常量
|
||||
static constexpr uint32_t INITIAL_UBO_POOL_SIZE = 16;
|
||||
static constexpr uint32_t GLOBAL_UBO_SIZE =
|
||||
256; // 足够存储 viewProjection + time + screenSize
|
||||
// 材质 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 GLOBAL_UBO_SIZE = 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
|
||||
|
|
|
|||
|
|
@ -307,7 +307,7 @@ public:
|
|||
/**
|
||||
* @brief 渲染(收集渲染命令)
|
||||
*/
|
||||
void render();
|
||||
virtual void render();
|
||||
|
||||
private:
|
||||
std::string name_;
|
||||
|
|
|
|||
|
|
@ -13,20 +13,18 @@ namespace extra2d {
|
|||
* 将导演系统集成到引擎模块系统中
|
||||
*/
|
||||
class SceneModule : public Module {
|
||||
// 自动注册到模块系统,优先级为 4(在 Renderer 之后)
|
||||
E2D_REGISTER_MODULE(SceneModule, "Scene", 4)
|
||||
// 优先级为 5,在 RendererModule (优先级 4) 之后初始化
|
||||
E2D_REGISTER_MODULE(SceneModule, "Scene", 5)
|
||||
|
||||
public:
|
||||
SceneModule();
|
||||
~SceneModule() override;
|
||||
|
||||
// 禁止拷贝
|
||||
// 禁止拷贝和移动
|
||||
SceneModule(const SceneModule &) = delete;
|
||||
SceneModule &operator=(const SceneModule &) = delete;
|
||||
|
||||
// 允许移动
|
||||
SceneModule(SceneModule &&) noexcept;
|
||||
SceneModule &operator=(SceneModule &&) noexcept;
|
||||
SceneModule(SceneModule &&) = delete;
|
||||
SceneModule &operator=(SceneModule &&) = delete;
|
||||
|
||||
// Module 接口实现
|
||||
bool init() override;
|
||||
|
|
|
|||
|
|
@ -44,20 +44,18 @@ struct TimerInfo {
|
|||
* 通过事件总线接收更新事件,无需直接依赖
|
||||
*/
|
||||
class TimerModule : public Module {
|
||||
// 自动注册到模块系统,优先级为 5(核心模块)
|
||||
E2D_REGISTER_MODULE(TimerModule, "Timer", 5)
|
||||
// 优先级为 6,在 SceneModule (优先级 5) 之后初始化
|
||||
E2D_REGISTER_MODULE(TimerModule, "Timer", 6)
|
||||
|
||||
public:
|
||||
TimerModule();
|
||||
~TimerModule() override;
|
||||
|
||||
// 禁止拷贝
|
||||
// 禁止拷贝和移动
|
||||
TimerModule(const TimerModule &) = delete;
|
||||
TimerModule &operator=(const TimerModule &) = delete;
|
||||
|
||||
// 允许移动
|
||||
TimerModule(TimerModule &&) noexcept;
|
||||
TimerModule &operator=(TimerModule &&) noexcept;
|
||||
TimerModule(TimerModule &&) = delete;
|
||||
TimerModule &operator=(TimerModule &&) = delete;
|
||||
|
||||
// Module 接口实现
|
||||
bool init() override;
|
||||
|
|
|
|||
|
|
@ -4,14 +4,12 @@ precision highp float;
|
|||
// 从顶点着色器输入
|
||||
in vec2 vTexCoord;
|
||||
in vec4 vColor;
|
||||
in vec4 vTintColor;
|
||||
in float vOpacity;
|
||||
|
||||
// 纹理采样器
|
||||
uniform sampler2D uTexture;
|
||||
|
||||
// 材质参数
|
||||
uniform vec4 uTintColor;
|
||||
uniform float uOpacity;
|
||||
|
||||
// 输出颜色
|
||||
out vec4 fragColor;
|
||||
|
||||
|
|
@ -24,11 +22,16 @@ 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 * uTintColor;
|
||||
fragColor = texColor * vColor * vTintColor;
|
||||
|
||||
// 应用透明度
|
||||
fragColor.a *= uOpacity;
|
||||
fragColor.a *= vOpacity;
|
||||
|
||||
// Alpha 测试:丢弃几乎透明的像素
|
||||
if (fragColor.a < 0.01) {
|
||||
|
|
|
|||
|
|
@ -1,15 +1,26 @@
|
|||
#version 320 es
|
||||
precision highp float;
|
||||
|
||||
// 视图投影矩阵
|
||||
uniform mat4 uViewProjection;
|
||||
// 全局 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;
|
||||
|
||||
// 顶点颜色(覆盖顶点属性中的颜色)
|
||||
uniform vec4 uColor;
|
||||
|
||||
// 顶点属性
|
||||
layout(location = 0) in vec2 aPosition;
|
||||
layout(location = 1) in vec2 aTexCoord;
|
||||
|
|
@ -18,16 +29,20 @@ 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;
|
||||
// 使用 uniform 颜色覆盖顶点属性颜色
|
||||
vColor = uColor;
|
||||
// 混合顶点颜色和材质 UBO 中的颜色
|
||||
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();
|
||||
|
||||
// 存储模块实例到注册表,以便其他模块可以通过 getModule<T>() 获取
|
||||
for (auto &module : modules_) {
|
||||
ModuleRegistry::instance().storeModuleInstance(module.get());
|
||||
}
|
||||
|
||||
// 初始化所有模块
|
||||
for (auto &module : modules_) {
|
||||
if (!module->init()) {
|
||||
|
|
|
|||
|
|
@ -1,14 +1,69 @@
|
|||
#include <algorithm>
|
||||
#include <assets/assets_module.h>
|
||||
#include <assets/loaders/shader_loader.h>
|
||||
#include <assets/loaders/texture_loader.h>
|
||||
#include <event/events.h>
|
||||
#include <algorithm>
|
||||
#include <filesystem>
|
||||
#include <module/module_registry.h>
|
||||
#include <platform/file_module.h>
|
||||
#include <thread>
|
||||
#include <utils/logger.h>
|
||||
|
||||
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::AssetsModule() { g_assetsModule = this; }
|
||||
|
|
@ -177,8 +232,7 @@ Handle<Texture> AssetsModule::loadFromMemory<Texture>(const std::string &key,
|
|||
// Shader 加载特化
|
||||
//===========================================================================
|
||||
|
||||
template <>
|
||||
Handle<Shader> AssetsModule::load<Shader>(const std::string &path) {
|
||||
template <> Handle<Shader> AssetsModule::load<Shader>(const std::string &path) {
|
||||
// 先检查缓存(读锁)
|
||||
{
|
||||
std::shared_lock<std::shared_mutex> lock(mutex_);
|
||||
|
|
@ -293,7 +347,7 @@ AssetsModule::loadDir<Texture>(const std::string &directory,
|
|||
|
||||
try {
|
||||
std::filesystem::path dirPath(directory);
|
||||
if (!std::filesystem::exists(dirPath)) {
|
||||
if (!fileExists(directory)) {
|
||||
E2D_LOG_WARN("Directory not found: {}", directory);
|
||||
return handles;
|
||||
}
|
||||
|
|
@ -342,12 +396,38 @@ AssetsModule::loadDir<Texture>(const std::string &directory,
|
|||
template <>
|
||||
void AssetsModule::loadAsync<Texture>(
|
||||
const std::string &path, std::function<void(Handle<Texture>)> callback) {
|
||||
std::thread([this, path, callback]() {
|
||||
Handle<Texture> handle = load<Texture>(path);
|
||||
if (callback) {
|
||||
callback(handle);
|
||||
}
|
||||
}).detach();
|
||||
// 确保异步加载系统已初始化
|
||||
if (!asyncLoaderRunning_) {
|
||||
initAsyncLoader();
|
||||
}
|
||||
|
||||
// 创建并提交加载任务
|
||||
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";
|
||||
std::filesystem::path fragPath = "shader/default.frag";
|
||||
// 使用 FileModule 的 assetPath 来获取正确的资源路径(支持 RomFS)
|
||||
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 (!std::filesystem::exists(vertPath) ||
|
||||
!std::filesystem::exists(fragPath)) {
|
||||
E2D_LOG_ERROR("Default shader files not found: {}, {}", vertPath.string(),
|
||||
fragPath.string());
|
||||
if (!fileExists(vertPath) || !fileExists(fragPath)) {
|
||||
E2D_LOG_ERROR("Default shader files not found: {}, {}", vertPath,
|
||||
fragPath);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!shader->loadFromFile(vertPath.string(), fragPath.string())) {
|
||||
E2D_LOG_ERROR("Failed to load default shader from files: {}, {}",
|
||||
vertPath.string(), fragPath.string());
|
||||
// 读取着色器文件内容
|
||||
std::string vsSource = readFileToString(vertPath);
|
||||
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;
|
||||
}
|
||||
|
||||
defaultShader_ = shaders_.insert(shader);
|
||||
E2D_LOG_DEBUG("Loaded default shader from files: {}, {}", vertPath.string(),
|
||||
fragPath.string());
|
||||
E2D_LOG_DEBUG("Loaded default shader from files: {}, {}", vertPath,
|
||||
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>();
|
||||
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);
|
||||
E2D_LOG_DEBUG("Created default material");
|
||||
E2D_LOG_DEBUG("Created default material with default texture and layout");
|
||||
}
|
||||
|
||||
{
|
||||
|
|
@ -466,163 +582,171 @@ Mesh *AssetsModule::getDefaultQuadPtr() { return meshes_.get(defaultQuad_); }
|
|||
//===========================================================================
|
||||
|
||||
void AssetsModule::enableHotReload(bool enable) {
|
||||
hotReloadEnabled_ = enable;
|
||||
if (enable) {
|
||||
E2D_LOG_INFO("Hot reload enabled");
|
||||
} else {
|
||||
E2D_LOG_INFO("Hot reload disabled");
|
||||
}
|
||||
hotReloadEnabled_ = enable;
|
||||
if (enable) {
|
||||
E2D_LOG_INFO("Hot reload enabled");
|
||||
} else {
|
||||
E2D_LOG_INFO("Hot reload disabled");
|
||||
}
|
||||
}
|
||||
|
||||
void AssetsModule::setHotReloadInterval(float interval) {
|
||||
hotReloadInterval_ = interval;
|
||||
hotReloadInterval_ = interval;
|
||||
}
|
||||
|
||||
void AssetsModule::addFileWatch(const std::string& path, Handle<Texture> handle) {
|
||||
try {
|
||||
if (!std::filesystem::exists(path)) {
|
||||
return;
|
||||
}
|
||||
|
||||
FileWatchInfo info;
|
||||
info.path = path;
|
||||
info.lastWriteTime = std::filesystem::last_write_time(path);
|
||||
info.textureHandle = handle;
|
||||
info.isTexture = true;
|
||||
|
||||
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::addFileWatchInternal(const std::string &path, FileWatchInfo &&info) {
|
||||
try {
|
||||
if (!fileExists(path)) {
|
||||
return;
|
||||
}
|
||||
|
||||
info.path = 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<Shader> handle) {
|
||||
try {
|
||||
if (!std::filesystem::exists(path)) {
|
||||
return;
|
||||
}
|
||||
|
||||
FileWatchInfo info;
|
||||
info.path = path;
|
||||
info.lastWriteTime = std::filesystem::last_write_time(path);
|
||||
info.shaderHandle = handle;
|
||||
info.isTexture = false;
|
||||
|
||||
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::addFileWatch(const std::string &path, Handle<Texture> handle) {
|
||||
FileWatchInfo info;
|
||||
info.textureHandle = handle;
|
||||
info.isTexture = true;
|
||||
addFileWatchInternal(path, std::move(info));
|
||||
}
|
||||
|
||||
void AssetsModule::reloadTexture(const FileWatchInfo& info) {
|
||||
if (!textureLoader_ || !info.textureHandle.isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
E2D_LOG_INFO("Reloading texture: {}", info.path);
|
||||
|
||||
Ptr<Texture> newTexture = textureLoader_->load(info.path);
|
||||
if (!newTexture) {
|
||||
E2D_LOG_ERROR("Failed to reload texture: {}", info.path);
|
||||
return;
|
||||
}
|
||||
|
||||
// 替换旧纹理的数据
|
||||
Texture* oldTexture = textures_.get(info.textureHandle);
|
||||
if (oldTexture) {
|
||||
// 这里假设 Texture 类有更新数据的方法
|
||||
// 如果没有,可能需要重新设计
|
||||
E2D_LOG_INFO("Texture reloaded: {}", info.path);
|
||||
notifyTextureReloaded(info.textureHandle);
|
||||
}
|
||||
void AssetsModule::addFileWatch(const std::string &path, Handle<Shader> handle) {
|
||||
FileWatchInfo info;
|
||||
info.shaderHandle = handle;
|
||||
info.isTexture = false;
|
||||
addFileWatchInternal(path, std::move(info));
|
||||
}
|
||||
|
||||
void AssetsModule::reloadShader(const FileWatchInfo& info) {
|
||||
if (!shaderLoader_ || !info.shaderHandle.isValid()) {
|
||||
return;
|
||||
void AssetsModule::reloadTexture(const FileWatchInfo &info) {
|
||||
if (!textureLoader_ || !info.textureHandle.isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
E2D_LOG_INFO("Reloading texture: {}", info.path);
|
||||
|
||||
// 获取旧纹理指针
|
||||
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);
|
||||
return;
|
||||
}
|
||||
|
||||
E2D_LOG_INFO("Texture reloaded successfully: {}", info.path);
|
||||
notifyTextureReloaded(info.textureHandle);
|
||||
}
|
||||
|
||||
void AssetsModule::reloadShader(const FileWatchInfo &info) {
|
||||
if (!shaderLoader_ || !info.shaderHandle.isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
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::shared_lock<std::shared_mutex> lock(mutex_);
|
||||
for (const auto &pair : shaderPathCache_) {
|
||||
if (pair.second == info.shaderHandle) {
|
||||
cacheKey = pair.first;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (cacheKey.empty()) {
|
||||
E2D_LOG_WARN("Shader cache key not found for: {}", info.path);
|
||||
return;
|
||||
}
|
||||
|
||||
// 解析顶点/片段着色器路径并重新加载
|
||||
size_t sepPos = cacheKey.find('|');
|
||||
if (sepPos == std::string::npos) {
|
||||
// 单文件模式 - 这里暂不支持,因为 ShaderLoader 目前只支持双文件
|
||||
E2D_LOG_WARN("Single-file shader reload not supported: {}", info.path);
|
||||
} else {
|
||||
// 双文件模式
|
||||
std::string vertPath = cacheKey.substr(0, sepPos);
|
||||
std::string fragPath = cacheKey.substr(sepPos + 1);
|
||||
|
||||
// 读取着色器文件内容
|
||||
std::string vsSource = readFileToString(vertPath);
|
||||
std::string fsSource = readFileToString(fragPath);
|
||||
if (vsSource.empty() || fsSource.empty()) {
|
||||
E2D_LOG_ERROR("Failed to read shader files for reload: {}, {}", vertPath, fragPath);
|
||||
return;
|
||||
}
|
||||
|
||||
E2D_LOG_INFO("Reloading shader: {}", info.path);
|
||||
|
||||
// 查找缓存键
|
||||
std::string cacheKey;
|
||||
{
|
||||
std::shared_lock<std::shared_mutex> lock(mutex_);
|
||||
for (const auto& pair : shaderPathCache_) {
|
||||
if (pair.second == info.shaderHandle) {
|
||||
cacheKey = pair.first;
|
||||
break;
|
||||
}
|
||||
}
|
||||
// 直接在原有着色器对象上重新加载
|
||||
if (!oldShader->reloadFromSource(vsSource, fsSource)) {
|
||||
E2D_LOG_ERROR("Failed to reload shader: {} + {}", vertPath, fragPath);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (cacheKey.empty()) {
|
||||
E2D_LOG_WARN("Shader cache key not found for: {}", info.path);
|
||||
return;
|
||||
}
|
||||
|
||||
// 解析顶点/片段着色器路径
|
||||
size_t sepPos = cacheKey.find('|');
|
||||
if (sepPos == std::string::npos) {
|
||||
// 单文件模式
|
||||
Ptr<Shader> newShader = shaderLoader_->load(info.path);
|
||||
if (!newShader) {
|
||||
E2D_LOG_ERROR("Failed to reload shader: {}", info.path);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// 双文件模式
|
||||
std::string vertPath = cacheKey.substr(0, sepPos);
|
||||
std::string fragPath = cacheKey.substr(sepPos + 1);
|
||||
|
||||
ShaderLoader* loader = static_cast<ShaderLoader*>(shaderLoader_.get());
|
||||
Ptr<Shader> newShader = loader->load(vertPath, fragPath);
|
||||
if (!newShader) {
|
||||
E2D_LOG_ERROR("Failed to reload shader: {} + {}", vertPath, fragPath);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
E2D_LOG_INFO("Shader reloaded: {}", info.path);
|
||||
notifyShaderReloaded(info.shaderHandle);
|
||||
E2D_LOG_INFO("Shader reloaded successfully: {}", info.path);
|
||||
notifyShaderReloaded(info.shaderHandle);
|
||||
}
|
||||
|
||||
void AssetsModule::checkForChanges() {
|
||||
if (!hotReloadEnabled_ || fileWatchList_.empty()) {
|
||||
return;
|
||||
}
|
||||
if (!hotReloadEnabled_ || fileWatchList_.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::unique_lock<std::shared_mutex> lock(mutex_);
|
||||
std::unique_lock<std::shared_mutex> lock(mutex_);
|
||||
|
||||
for (auto& info : fileWatchList_) {
|
||||
try {
|
||||
if (!std::filesystem::exists(info.path)) {
|
||||
continue;
|
||||
}
|
||||
for (auto &info : fileWatchList_) {
|
||||
try {
|
||||
// 跳过 RomFS 路径(只读文件系统)
|
||||
if (isRomfsPath(info.path)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto currentTime = std::filesystem::last_write_time(info.path);
|
||||
if (currentTime != info.lastWriteTime) {
|
||||
info.lastWriteTime = currentTime;
|
||||
if (!fileExists(info.path)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 解锁进行重载操作
|
||||
lock.unlock();
|
||||
auto currentTime = getLastWriteTime(info.path);
|
||||
if (currentTime != info.lastWriteTime) {
|
||||
info.lastWriteTime = currentTime;
|
||||
|
||||
if (info.isTexture) {
|
||||
reloadTexture(info);
|
||||
} else {
|
||||
reloadShader(info);
|
||||
}
|
||||
// 解锁进行重载操作
|
||||
lock.unlock();
|
||||
|
||||
lock.lock();
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
E2D_LOG_ERROR("Error checking file {}: {}", info.path, e.what());
|
||||
if (info.isTexture) {
|
||||
reloadTexture(info);
|
||||
} else {
|
||||
reloadShader(info);
|
||||
}
|
||||
|
||||
lock.lock();
|
||||
}
|
||||
} catch (const std::exception &e) {
|
||||
E2D_LOG_ERROR("Error checking file {}: {}", info.path, e.what());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//===========================================================================
|
||||
|
|
@ -630,216 +754,218 @@ void AssetsModule::checkForChanges() {
|
|||
//===========================================================================
|
||||
|
||||
void AssetsModule::initAsyncLoader(uint32_t threadCount) {
|
||||
if (asyncLoaderRunning_) {
|
||||
return;
|
||||
}
|
||||
if (asyncLoaderRunning_) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (threadCount == 0) {
|
||||
threadCount = std::max(1u, std::thread::hardware_concurrency() / 2);
|
||||
}
|
||||
if (threadCount == 0) {
|
||||
threadCount = std::max(1u, std::thread::hardware_concurrency() / 2);
|
||||
}
|
||||
|
||||
asyncLoaderRunning_ = true;
|
||||
asyncLoaderRunning_ = true;
|
||||
|
||||
for (uint32_t i = 0; i < threadCount; ++i) {
|
||||
workerThreads_.emplace_back(&AssetsModule::workerThreadLoop, this);
|
||||
}
|
||||
for (uint32_t i = 0; i < threadCount; ++i) {
|
||||
workerThreads_.emplace_back(&AssetsModule::workerThreadLoop, this);
|
||||
}
|
||||
|
||||
E2D_LOG_INFO("Async loader initialized with {} threads", threadCount);
|
||||
E2D_LOG_INFO("Async loader initialized with {} threads", threadCount);
|
||||
}
|
||||
|
||||
void AssetsModule::shutdownAsyncLoader() {
|
||||
if (!asyncLoaderRunning_) {
|
||||
return;
|
||||
if (!asyncLoaderRunning_) {
|
||||
return;
|
||||
}
|
||||
|
||||
asyncLoaderRunning_ = false;
|
||||
queueCV_.notify_all();
|
||||
|
||||
for (auto &thread : workerThreads_) {
|
||||
if (thread.joinable()) {
|
||||
thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
asyncLoaderRunning_ = false;
|
||||
queueCV_.notify_all();
|
||||
workerThreads_.clear();
|
||||
|
||||
for (auto& thread : workerThreads_) {
|
||||
if (thread.joinable()) {
|
||||
thread.join();
|
||||
}
|
||||
}
|
||||
std::lock_guard<std::mutex> lock(queueMutex_);
|
||||
loadQueue_.clear();
|
||||
|
||||
workerThreads_.clear();
|
||||
|
||||
std::lock_guard<std::mutex> lock(queueMutex_);
|
||||
loadQueue_.clear();
|
||||
|
||||
E2D_LOG_INFO("Async loader shutdown");
|
||||
E2D_LOG_INFO("Async loader shutdown");
|
||||
}
|
||||
|
||||
void AssetsModule::submitLoadTask(const LoadTask& task) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(queueMutex_);
|
||||
loadQueue_.push_back(task);
|
||||
void AssetsModule::submitLoadTask(const LoadTask &task) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(queueMutex_);
|
||||
loadQueue_.push_back(task);
|
||||
|
||||
// 按优先级排序
|
||||
std::sort(loadQueue_.begin(), loadQueue_.end(),
|
||||
[](const LoadTask& a, const LoadTask& b) {
|
||||
return static_cast<int>(a.priority) > static_cast<int>(b.priority);
|
||||
});
|
||||
}
|
||||
queueCV_.notify_one();
|
||||
// 按优先级排序
|
||||
std::sort(loadQueue_.begin(), loadQueue_.end(),
|
||||
[](const LoadTask &a, const LoadTask &b) {
|
||||
return static_cast<int>(a.priority) >
|
||||
static_cast<int>(b.priority);
|
||||
});
|
||||
}
|
||||
queueCV_.notify_one();
|
||||
}
|
||||
|
||||
void AssetsModule::workerThreadLoop() {
|
||||
while (asyncLoaderRunning_) {
|
||||
LoadTask task;
|
||||
while (asyncLoaderRunning_) {
|
||||
LoadTask task;
|
||||
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(queueMutex_);
|
||||
queueCV_.wait(lock, [this] {
|
||||
return !loadQueue_.empty() || !asyncLoaderRunning_;
|
||||
});
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(queueMutex_);
|
||||
queueCV_.wait(
|
||||
lock, [this] { return !loadQueue_.empty() || !asyncLoaderRunning_; });
|
||||
|
||||
if (!asyncLoaderRunning_) {
|
||||
break;
|
||||
}
|
||||
if (!asyncLoaderRunning_) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (loadQueue_.empty()) {
|
||||
continue;
|
||||
}
|
||||
if (loadQueue_.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
task = std::move(loadQueue_.back());
|
||||
loadQueue_.pop_back();
|
||||
}
|
||||
|
||||
// 执行加载任务
|
||||
if (task.type == LoadTask::Type::Texture) {
|
||||
Handle<Texture> handle = load<Texture>(task.path);
|
||||
|
||||
if (task.textureCallback) {
|
||||
std::lock_guard<std::mutex> callbackLock(callbackMutex_);
|
||||
completedCallbacks_.push_back([handle, callback = task.textureCallback]() {
|
||||
callback(handle);
|
||||
});
|
||||
}
|
||||
} else if (task.type == LoadTask::Type::Shader) {
|
||||
Handle<Shader> handle;
|
||||
if (task.secondaryPath.empty()) {
|
||||
handle = load<Shader>(task.path);
|
||||
} else {
|
||||
handle = load<Shader>(task.path, task.secondaryPath);
|
||||
}
|
||||
|
||||
if (task.shaderCallback) {
|
||||
std::lock_guard<std::mutex> callbackLock(callbackMutex_);
|
||||
completedCallbacks_.push_back([handle, callback = task.shaderCallback]() {
|
||||
callback(handle);
|
||||
});
|
||||
}
|
||||
}
|
||||
task = std::move(loadQueue_.back());
|
||||
loadQueue_.pop_back();
|
||||
}
|
||||
|
||||
// 执行加载任务
|
||||
if (task.type == LoadTask::Type::Texture) {
|
||||
Handle<Texture> handle = load<Texture>(task.path);
|
||||
|
||||
if (task.textureCallback) {
|
||||
std::lock_guard<std::mutex> callbackLock(callbackMutex_);
|
||||
completedCallbacks_.push_back(
|
||||
[handle, callback = task.textureCallback]() { callback(handle); });
|
||||
}
|
||||
} else if (task.type == LoadTask::Type::Shader) {
|
||||
Handle<Shader> handle;
|
||||
if (task.secondaryPath.empty()) {
|
||||
handle = load<Shader>(task.path);
|
||||
} else {
|
||||
handle = load<Shader>(task.path, task.secondaryPath);
|
||||
}
|
||||
|
||||
if (task.shaderCallback) {
|
||||
std::lock_guard<std::mutex> callbackLock(callbackMutex_);
|
||||
completedCallbacks_.push_back(
|
||||
[handle, callback = task.shaderCallback]() { callback(handle); });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AssetsModule::processAsyncCallbacks() {
|
||||
std::vector<std::function<void()>> callbacks;
|
||||
std::vector<std::function<void()>> callbacks;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(callbackMutex_);
|
||||
callbacks = std::move(completedCallbacks_);
|
||||
completedCallbacks_.clear();
|
||||
}
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(callbackMutex_);
|
||||
callbacks = std::move(completedCallbacks_);
|
||||
completedCallbacks_.clear();
|
||||
}
|
||||
|
||||
for (auto& callback : callbacks) {
|
||||
callback();
|
||||
}
|
||||
for (auto &callback : callbacks) {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
|
||||
//===========================================================================
|
||||
// 资源依赖跟踪
|
||||
//===========================================================================
|
||||
|
||||
void AssetsModule::registerMaterialDependency(Handle<Material> material, Handle<Texture> texture) {
|
||||
if (!material.isValid() || !texture.isValid()) {
|
||||
return;
|
||||
}
|
||||
void AssetsModule::registerMaterialDependency(Handle<Material> material,
|
||||
Handle<Texture> texture) {
|
||||
if (!material.isValid() || !texture.isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::unique_lock<std::shared_mutex> lock(dependencyMutex_);
|
||||
std::unique_lock<std::shared_mutex> lock(dependencyMutex_);
|
||||
|
||||
uint32_t textureIndex = texture.index();
|
||||
auto& info = textureDependencies_[textureIndex];
|
||||
info.texture = texture;
|
||||
uint32_t textureIndex = texture.index();
|
||||
auto &info = textureDependencies_[textureIndex];
|
||||
info.texture = texture;
|
||||
|
||||
// 检查是否已存在
|
||||
auto it = std::find(info.dependentMaterials.begin(), info.dependentMaterials.end(), material);
|
||||
if (it == info.dependentMaterials.end()) {
|
||||
info.dependentMaterials.push_back(material);
|
||||
E2D_LOG_DEBUG("Registered material {} dependency on texture {}",
|
||||
material.index(), textureIndex);
|
||||
}
|
||||
// 检查是否已存在
|
||||
auto it = std::find(info.dependentMaterials.begin(),
|
||||
info.dependentMaterials.end(), material);
|
||||
if (it == info.dependentMaterials.end()) {
|
||||
info.dependentMaterials.push_back(material);
|
||||
E2D_LOG_DEBUG("Registered material {} dependency on texture {}",
|
||||
material.index(), textureIndex);
|
||||
}
|
||||
}
|
||||
|
||||
void AssetsModule::registerMaterialDependency(Handle<Material> material, Handle<Shader> shader) {
|
||||
if (!material.isValid() || !shader.isValid()) {
|
||||
return;
|
||||
}
|
||||
void AssetsModule::registerMaterialDependency(Handle<Material> material,
|
||||
Handle<Shader> shader) {
|
||||
if (!material.isValid() || !shader.isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::unique_lock<std::shared_mutex> lock(dependencyMutex_);
|
||||
std::unique_lock<std::shared_mutex> lock(dependencyMutex_);
|
||||
|
||||
uint32_t shaderIndex = shader.index();
|
||||
auto& info = shaderDependencies_[shaderIndex];
|
||||
info.shader = shader;
|
||||
uint32_t shaderIndex = shader.index();
|
||||
auto &info = shaderDependencies_[shaderIndex];
|
||||
info.shader = shader;
|
||||
|
||||
// 检查是否已存在
|
||||
auto it = std::find(info.dependentMaterials.begin(), info.dependentMaterials.end(), material);
|
||||
if (it == info.dependentMaterials.end()) {
|
||||
info.dependentMaterials.push_back(material);
|
||||
E2D_LOG_DEBUG("Registered material {} dependency on shader {}",
|
||||
material.index(), shaderIndex);
|
||||
}
|
||||
// 检查是否已存在
|
||||
auto it = std::find(info.dependentMaterials.begin(),
|
||||
info.dependentMaterials.end(), material);
|
||||
if (it == info.dependentMaterials.end()) {
|
||||
info.dependentMaterials.push_back(material);
|
||||
E2D_LOG_DEBUG("Registered material {} dependency on shader {}",
|
||||
material.index(), shaderIndex);
|
||||
}
|
||||
}
|
||||
|
||||
void AssetsModule::notifyTextureReloaded(Handle<Texture> texture) {
|
||||
if (!texture.isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::shared_lock<std::shared_mutex> lock(dependencyMutex_);
|
||||
|
||||
uint32_t textureIndex = texture.index();
|
||||
auto it = textureDependencies_.find(textureIndex);
|
||||
if (it != textureDependencies_.end()) {
|
||||
E2D_LOG_INFO("Notifying {} materials of texture reload",
|
||||
it->second.dependentMaterials.size());
|
||||
|
||||
// 材质需要更新纹理引用
|
||||
// 这里可以触发材质更新事件
|
||||
for (const auto& materialHandle : it->second.dependentMaterials) {
|
||||
Material* material = materials_.get(materialHandle);
|
||||
if (material) {
|
||||
// 材质自动使用新的纹理数据
|
||||
E2D_LOG_DEBUG("Material {} updated with reloaded texture",
|
||||
materialHandle.index());
|
||||
}
|
||||
}
|
||||
if (!texture.isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::shared_lock<std::shared_mutex> lock(dependencyMutex_);
|
||||
|
||||
uint32_t textureIndex = texture.index();
|
||||
auto it = textureDependencies_.find(textureIndex);
|
||||
if (it != textureDependencies_.end()) {
|
||||
E2D_LOG_INFO("Notifying {} materials of texture reload",
|
||||
it->second.dependentMaterials.size());
|
||||
|
||||
// 材质需要更新纹理引用
|
||||
// 这里可以触发材质更新事件
|
||||
for (const auto &materialHandle : it->second.dependentMaterials) {
|
||||
Material *material = materials_.get(materialHandle);
|
||||
if (material) {
|
||||
// 材质自动使用新的纹理数据
|
||||
E2D_LOG_DEBUG("Material {} updated with reloaded texture",
|
||||
materialHandle.index());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AssetsModule::notifyShaderReloaded(Handle<Shader> shader) {
|
||||
if (!shader.isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::shared_lock<std::shared_mutex> lock(dependencyMutex_);
|
||||
|
||||
uint32_t shaderIndex = shader.index();
|
||||
auto it = shaderDependencies_.find(shaderIndex);
|
||||
if (it != shaderDependencies_.end()) {
|
||||
E2D_LOG_INFO("Notifying {} materials of shader reload",
|
||||
it->second.dependentMaterials.size());
|
||||
|
||||
for (const auto& materialHandle : it->second.dependentMaterials) {
|
||||
Material* material = materials_.get(materialHandle);
|
||||
if (material) {
|
||||
// 更新材质的着色器引用
|
||||
material->setShader(getPtr(shader));
|
||||
E2D_LOG_DEBUG("Material {} updated with reloaded shader",
|
||||
materialHandle.index());
|
||||
}
|
||||
}
|
||||
if (!shader.isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::shared_lock<std::shared_mutex> lock(dependencyMutex_);
|
||||
|
||||
uint32_t shaderIndex = shader.index();
|
||||
auto it = shaderDependencies_.find(shaderIndex);
|
||||
if (it != shaderDependencies_.end()) {
|
||||
E2D_LOG_INFO("Notifying {} materials of shader reload",
|
||||
it->second.dependentMaterials.size());
|
||||
|
||||
for (const auto &materialHandle : it->second.dependentMaterials) {
|
||||
Material *material = materials_.get(materialHandle);
|
||||
if (material) {
|
||||
// 更新材质的着色器引用
|
||||
material->setShader(getPtr(shader));
|
||||
E2D_LOG_DEBUG("Material {} updated with reloaded shader",
|
||||
materialHandle.index());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//===========================================================================
|
||||
|
|
@ -847,14 +973,14 @@ void AssetsModule::notifyShaderReloaded(Handle<Shader> shader) {
|
|||
//===========================================================================
|
||||
|
||||
AssetsModule::Stats AssetsModule::getStats() const {
|
||||
std::shared_lock<std::shared_mutex> lock(mutex_);
|
||||
std::shared_lock<std::shared_mutex> lock(mutex_);
|
||||
|
||||
Stats stats;
|
||||
stats.textureCount = textures_.count();
|
||||
stats.shaderCount = shaders_.count();
|
||||
stats.materialCount = materials_.count();
|
||||
stats.meshCount = meshes_.count();
|
||||
return stats;
|
||||
Stats stats;
|
||||
stats.textureCount = textures_.count();
|
||||
stats.shaderCount = shaders_.count();
|
||||
stats.materialCount = materials_.count();
|
||||
stats.meshCount = meshes_.count();
|
||||
return stats;
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
|
|||
|
|
@ -1,10 +1,44 @@
|
|||
#include <assets/loaders/shader_loader.h>
|
||||
#include <filesystem>
|
||||
#include <module/module_registry.h>
|
||||
#include <platform/file_module.h>
|
||||
#include <renderer/shader.h>
|
||||
#include <utils/logger.h>
|
||||
|
||||
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) {
|
||||
std::string basePath = path;
|
||||
|
||||
|
|
@ -16,10 +50,10 @@ Ptr<Shader> ShaderLoader::load(const std::string &path) {
|
|||
std::string vertPath = basePath + ".vert";
|
||||
std::string fragPath = basePath + ".frag";
|
||||
|
||||
if (!std::filesystem::exists(vertPath)) {
|
||||
if (!fileExists(vertPath)) {
|
||||
vertPath = basePath + ".vs";
|
||||
}
|
||||
if (!std::filesystem::exists(fragPath)) {
|
||||
if (!fileExists(fragPath)) {
|
||||
fragPath = basePath + ".fs";
|
||||
}
|
||||
|
||||
|
|
@ -28,10 +62,24 @@ Ptr<Shader> ShaderLoader::load(const std::string &path) {
|
|||
|
||||
Ptr<Shader> ShaderLoader::load(const std::string &vertPath,
|
||||
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);
|
||||
return Ptr<Shader>();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -26,4 +26,21 @@ std::vector<std::unique_ptr<Module>> ModuleRegistry::createModules() {
|
|||
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
|
||||
|
|
|
|||
|
|
@ -1,31 +1,18 @@
|
|||
#include <SDL.h>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <platform/file_module.h>
|
||||
#include <sstream>
|
||||
#include <sys/stat.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__
|
||||
#include <switch.h>
|
||||
#endif
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
FileModule::FileModule() = default;
|
||||
|
||||
FileModule::~FileModule() = default;
|
||||
|
|
@ -44,11 +31,19 @@ bool FileModule::init() {
|
|||
Result rc = romfsInit();
|
||||
if (R_SUCCEEDED(rc)) {
|
||||
E2D_LOG_INFO("RomFS initialized successfully");
|
||||
// 在 Switch 上设置资源根目录为 RomFS
|
||||
assetRoot_ = "romfs:/";
|
||||
} else {
|
||||
E2D_LOG_WARN("romfsInit failed: {:#08X}", rc);
|
||||
// RomFS 初始化失败,使用当前目录
|
||||
assetRoot_ = "./";
|
||||
}
|
||||
#else
|
||||
// 非 Switch 平台,使用当前目录作为资源根目录
|
||||
assetRoot_ = "./";
|
||||
#endif
|
||||
|
||||
E2D_LOG_INFO("Asset root set to: {}", assetRoot_);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -59,16 +54,16 @@ void FileModule::shutdown() {
|
|||
#endif
|
||||
}
|
||||
|
||||
bool FileModule::isRomfsPath(const std::string &path) const {
|
||||
return path.find(romfsRoot_) == 0;
|
||||
}
|
||||
|
||||
bool FileModule::exists(const std::string &path) const {
|
||||
struct stat st;
|
||||
return stat(path.c_str(), &st) == 0;
|
||||
return fs::exists(path);
|
||||
}
|
||||
|
||||
bool FileModule::isDir(const std::string &path) const {
|
||||
struct stat st;
|
||||
if (stat(path.c_str(), &st) != 0)
|
||||
return false;
|
||||
return (st.st_mode & S_IFDIR) != 0;
|
||||
return fs::is_directory(path);
|
||||
}
|
||||
|
||||
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,
|
||||
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);
|
||||
if (!file.is_open())
|
||||
return false;
|
||||
|
|
@ -121,6 +122,12 @@ bool FileModule::writeString(const std::string &path,
|
|||
|
||||
bool FileModule::append(const std::string &path, const void *data,
|
||||
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);
|
||||
if (!file.is_open())
|
||||
return false;
|
||||
|
|
@ -131,105 +138,69 @@ bool FileModule::append(const std::string &path, const void *data,
|
|||
}
|
||||
|
||||
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 {
|
||||
#ifdef _WIN32
|
||||
return mkdir_impl(path.c_str(), 0755) == 0 || errno == EEXIST;
|
||||
#else
|
||||
return mkdir_impl(path.c_str(), 0755) == 0 || errno == EEXIST;
|
||||
#endif
|
||||
// RomFS 是只读的,不允许创建目录
|
||||
if (isRomfsPath(path)) {
|
||||
E2D_LOG_WARN("Cannot create directory in RomFS: {}", path);
|
||||
return false;
|
||||
}
|
||||
|
||||
return fs::create_directories(path);
|
||||
}
|
||||
|
||||
std::vector<FileInfo> FileModule::listDir(const std::string &path) const {
|
||||
std::vector<FileInfo> result;
|
||||
|
||||
#ifdef _WIN32
|
||||
WIN32_FIND_DATAA findData;
|
||||
std::string searchPath = path + "\\*";
|
||||
HANDLE hFind = FindFirstFileA(searchPath.c_str(), &findData);
|
||||
|
||||
if (hFind == INVALID_HANDLE_VALUE)
|
||||
if (!fs::exists(path) || !fs::is_directory(path)) {
|
||||
return result;
|
||||
}
|
||||
|
||||
do {
|
||||
std::string name = findData.cFileName;
|
||||
for (const auto &entry : fs::directory_iterator(path)) {
|
||||
std::string name = entry.path().filename().string();
|
||||
if (name == "." || name == "..")
|
||||
continue;
|
||||
|
||||
FileInfo info;
|
||||
info.name = name;
|
||||
info.path = join(path, name);
|
||||
info.isDir = (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0;
|
||||
if (!info.isDir) {
|
||||
info.size = static_cast<int64>(findData.nFileSizeLow) |
|
||||
(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);
|
||||
info.path = entry.path().string();
|
||||
info.isDir = entry.is_directory();
|
||||
if (!info.isDir && entry.is_regular_file()) {
|
||||
info.size = static_cast<int64>(entry.file_size());
|
||||
}
|
||||
result.push_back(info);
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
#endif
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
int64 FileModule::fileSize(const std::string &path) const {
|
||||
struct stat st;
|
||||
if (stat(path.c_str(), &st) != 0)
|
||||
if (!fs::exists(path) || !fs::is_regular_file(path)) {
|
||||
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 {
|
||||
size_t pos = path.find_last_of('.');
|
||||
if (pos == std::string::npos || pos == 0)
|
||||
return "";
|
||||
|
||||
size_t lastSep = path.find_last_of("/\\");
|
||||
if (lastSep != std::string::npos && pos < lastSep)
|
||||
return "";
|
||||
|
||||
return path.substr(pos + 1);
|
||||
fs::path p(path);
|
||||
return p.extension().string();
|
||||
}
|
||||
|
||||
std::string FileModule::fileName(const std::string &path) const {
|
||||
size_t pos = path.find_last_of("/\\");
|
||||
if (pos == std::string::npos)
|
||||
return path;
|
||||
return path.substr(pos + 1);
|
||||
fs::path p(path);
|
||||
return p.filename().string();
|
||||
}
|
||||
|
||||
std::string FileModule::dirName(const std::string &path) const {
|
||||
size_t pos = path.find_last_of("/\\");
|
||||
if (pos == std::string::npos)
|
||||
return ".";
|
||||
if (pos == 0)
|
||||
return "/";
|
||||
return path.substr(0, pos);
|
||||
fs::path p(path);
|
||||
return p.parent_path().string();
|
||||
}
|
||||
|
||||
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())
|
||||
return a;
|
||||
|
||||
char last = a.back();
|
||||
if (last == '/' || last == '\\') {
|
||||
return a + b;
|
||||
}
|
||||
return a + "/" + b;
|
||||
fs::path base(a);
|
||||
fs::path sub(b);
|
||||
return (base / sub).string();
|
||||
}
|
||||
|
||||
std::string FileModule::writableDir() const { return writableDir_; }
|
||||
|
|
|
|||
|
|
@ -12,64 +12,6 @@ InputModule::~InputModule() {
|
|||
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() {
|
||||
std::memset(keyState_.data(), 0, KEY_COUNT);
|
||||
std::memset(keyPrev_.data(), 0, KEY_COUNT);
|
||||
|
|
|
|||
|
|
@ -4,39 +4,12 @@
|
|||
#include <platform/window_module.h>
|
||||
#include <utils/logger.h>
|
||||
|
||||
// OpenGL ES 3.2 with GLAD
|
||||
#include <glad/glad.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
WindowModule::WindowModule() = default;
|
||||
|
||||
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() {
|
||||
if (SDL_InitSubSystem(SDL_INIT_VIDEO) != 0) {
|
||||
E2D_LOG_ERROR("Failed to initialize SDL video: {}", SDL_GetError());
|
||||
|
|
@ -58,7 +31,10 @@ bool WindowModule::init() {
|
|||
}
|
||||
|
||||
void WindowModule::shutdown() {
|
||||
destroyGLContext();
|
||||
// 清理事件监听器
|
||||
configListener_.reset();
|
||||
renderPresentListener_.reset();
|
||||
|
||||
if (window_) {
|
||||
SDL_DestroyWindow(window_);
|
||||
window_ = nullptr;
|
||||
|
|
@ -163,14 +139,8 @@ void WindowModule::onModuleConfig(const AppConfig &config) {
|
|||
cfg.resizable = config.resizable;
|
||||
|
||||
if (create(cfg)) {
|
||||
// 创建 OpenGL 上下文
|
||||
if (!createGLContext()) {
|
||||
E2D_LOG_ERROR("Failed to create OpenGL context");
|
||||
return;
|
||||
}
|
||||
|
||||
// GL 上下文创建完成后,再发送窗口显示事件
|
||||
// 这样渲染模块可以在收到事件时安全地使用 GL 函数
|
||||
// 发送窗口显示事件
|
||||
// RHI 模块会监听此事件并创建 OpenGL 上下文
|
||||
events::OnShow::emit();
|
||||
|
||||
setVisible(true);
|
||||
|
|
@ -240,56 +210,9 @@ bool WindowModule::isVisible() const {
|
|||
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() {
|
||||
swapBuffers();
|
||||
// 交换缓冲区由 RHI 模块处理
|
||||
// 这里可以触发呈现事件
|
||||
}
|
||||
|
||||
void *WindowModule::getGLContext() const { return glContext_; }
|
||||
|
||||
} // 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;
|
||||
}
|
||||
|
||||
// 检查是否已存在
|
||||
if (paramIndexMap_.find(name) != paramIndexMap_.end()) {
|
||||
E2D_LOG_WARN("Param '{}' already exists in MaterialLayout", name);
|
||||
return;
|
||||
}
|
||||
|
||||
MaterialParamInfo info;
|
||||
info.type = type;
|
||||
info.size = getMaterialParamSize(type);
|
||||
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 的倍数
|
||||
* - vec3 后面需要填充到 16 字节边界
|
||||
* - 整个结构体大小必须是16的倍数
|
||||
*/
|
||||
void MaterialLayout::finalize() {
|
||||
if (finalized_) return;
|
||||
|
|
@ -44,7 +53,7 @@ void MaterialLayout::finalize() {
|
|||
for (auto& pair : params_) {
|
||||
auto& info = pair.second;
|
||||
|
||||
// 计算对齐要求
|
||||
// 计算对齐要求 - std140规则
|
||||
uint32_t alignment = 4; // 默认 4 字节对齐
|
||||
|
||||
switch (info.type) {
|
||||
|
|
@ -60,21 +69,21 @@ void MaterialLayout::finalize() {
|
|||
alignment = 16; // vec3/vec4/color: 16 字节对齐(std140 规则)
|
||||
break;
|
||||
case MaterialParamType::Mat4:
|
||||
alignment = 16; // mat4: 16 字节对齐
|
||||
alignment = 16; // mat4: 16 字节对齐,每列vec4对齐到16字节
|
||||
break;
|
||||
default:
|
||||
alignment = 4;
|
||||
break;
|
||||
}
|
||||
|
||||
// 对齐偏移
|
||||
// 对齐偏移到alignment的倍数
|
||||
offset = (offset + alignment - 1) & ~(alignment - 1);
|
||||
|
||||
info.offset = offset;
|
||||
offset += info.size;
|
||||
}
|
||||
|
||||
// 最终大小对齐到 16 字节
|
||||
// 最终大小对齐到 16 字节(std140要求结构体总大小是16的倍数)
|
||||
bufferSize_ = (offset + 15) & ~15;
|
||||
finalized_ = true;
|
||||
}
|
||||
|
|
@ -85,9 +94,9 @@ void MaterialLayout::finalize() {
|
|||
* @return 参数信息指针,不存在返回 nullptr
|
||||
*/
|
||||
const MaterialParamInfo* MaterialLayout::getParam(const std::string& name) const {
|
||||
auto it = params_.find(name);
|
||||
if (it != params_.end()) {
|
||||
return &it->second;
|
||||
auto mapIt = paramIndexMap_.find(name);
|
||||
if (mapIt != paramIndexMap_.end()) {
|
||||
return ¶ms_[mapIt->second].second;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
|
@ -117,6 +126,17 @@ void Material::setShader(Ptr<Shader> shader) {
|
|||
shader_ = shader;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取 RHI 管线句柄
|
||||
* @return RHI 管线句柄
|
||||
*/
|
||||
PipelineHandle Material::getPipeline() const {
|
||||
if (shader_) {
|
||||
return shader_->getPipeline();
|
||||
}
|
||||
return PipelineHandle{};
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 设置 float 参数
|
||||
* @param name 参数名称
|
||||
|
|
@ -194,14 +214,14 @@ void Material::setMat4(const std::string& name, const float* value) {
|
|||
/**
|
||||
* @brief 设置纹理
|
||||
* @param uniformName 着色器中的采样器 uniform 名称
|
||||
* @param texture 纹理句柄
|
||||
* @param texture 纹理
|
||||
* @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_) {
|
||||
if (texSlot.uniformName == uniformName) {
|
||||
texSlot.handle = texture;
|
||||
texSlot.texture = texture;
|
||||
texSlot.slot = slot;
|
||||
return;
|
||||
}
|
||||
|
|
@ -218,31 +238,4 @@ void Material::clearTextures() {
|
|||
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
|
||||
|
|
|
|||
|
|
@ -1,171 +1,165 @@
|
|||
#include <renderer/mesh.h>
|
||||
#include <renderer/rhi_module.h>
|
||||
#include <utils/logger.h>
|
||||
#include <glad/glad.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
Mesh::Mesh() = default;
|
||||
|
||||
Mesh::~Mesh() {
|
||||
if (vao_ != 0) {
|
||||
glDeleteVertexArrays(1, &vao_);
|
||||
vao_ = 0;
|
||||
}
|
||||
if (vbo_ != 0) {
|
||||
glDeleteBuffers(1, &vbo_);
|
||||
vbo_ = 0;
|
||||
}
|
||||
if (ibo_ != 0) {
|
||||
glDeleteBuffers(1, &ibo_);
|
||||
ibo_ = 0;
|
||||
}
|
||||
// BufferHandle 是轻量级句柄,不需要显式释放
|
||||
// 实际的缓冲区资源由 RHI 设备管理
|
||||
vertexBuffer_ = BufferHandle();
|
||||
indexBuffer_ = BufferHandle();
|
||||
}
|
||||
|
||||
void Mesh::setVertices(const Vertex* vertices, uint32_t count) {
|
||||
if (vao_ == 0) {
|
||||
glGenVertexArrays(1, &vao_);
|
||||
glGenBuffers(1, &vbo_);
|
||||
void Mesh::setVertices(const Vertex *vertices, uint32_t count) {
|
||||
if (!vertices || count == 0)
|
||||
return;
|
||||
|
||||
E2D_LOG_INFO("Created VAO: {}, VBO: {}", vao_, vbo_);
|
||||
// 获取 RHI 模块
|
||||
auto *rhiModule = RHIModule::get();
|
||||
if (!rhiModule) {
|
||||
E2D_LOG_ERROR("RHIModule not available");
|
||||
return;
|
||||
}
|
||||
|
||||
auto *device = rhiModule->getDevice();
|
||||
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;
|
||||
}
|
||||
|
||||
glBindVertexArray(vao_);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, vbo_);
|
||||
// 获取缓冲区的句柄
|
||||
// 注意:createBuffer 返回的是 unique_ptr,我们需要存储句柄
|
||||
// 这里直接使用缓冲区指针作为句柄,然后释放所有权让 RHI 内部管理
|
||||
vertexBuffer_ = BufferHandle(buffer.release());
|
||||
|
||||
// 如果容量不足,重新分配
|
||||
if (count > vertexCapacity_) {
|
||||
glBufferData(GL_ARRAY_BUFFER, count * sizeof(Vertex), vertices, GL_DYNAMIC_DRAW);
|
||||
vertexCapacity_ = count;
|
||||
} else {
|
||||
glBufferSubData(GL_ARRAY_BUFFER, 0, count * sizeof(Vertex), vertices);
|
||||
}
|
||||
vertexCapacity_ = count;
|
||||
}
|
||||
|
||||
// 设置顶点属性
|
||||
// 位置 (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;
|
||||
|
||||
E2D_LOG_INFO("Set {} vertices for VAO {}", count, vao_);
|
||||
vertexCount_ = count;
|
||||
E2D_LOG_INFO("Set {} vertices", count);
|
||||
}
|
||||
|
||||
void Mesh::setIndices(const uint16_t* indices, uint32_t count) {
|
||||
if (vao_ == 0) return;
|
||||
void Mesh::setIndices(const uint16_t *indices, uint32_t count) {
|
||||
if (!indices || count == 0)
|
||||
return;
|
||||
|
||||
if (ibo_ == 0) {
|
||||
glGenBuffers(1, &ibo_);
|
||||
// 获取 RHI 模块
|
||||
auto *rhiModule = RHIModule::get();
|
||||
if (!rhiModule) {
|
||||
E2D_LOG_ERROR("RHIModule not available");
|
||||
return;
|
||||
}
|
||||
|
||||
auto *device = rhiModule->getDevice();
|
||||
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;
|
||||
}
|
||||
|
||||
glBindVertexArray(vao_);
|
||||
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo_);
|
||||
// 获取缓冲区的句柄
|
||||
indexBuffer_ = BufferHandle(buffer.release());
|
||||
|
||||
// 如果容量不足,重新分配
|
||||
if (count > indexCapacity_) {
|
||||
glBufferData(GL_ELEMENT_ARRAY_BUFFER, count * sizeof(uint16_t), indices, GL_STATIC_DRAW);
|
||||
indexCapacity_ = count;
|
||||
} else {
|
||||
glBufferSubData(GL_ELEMENT_ARRAY_BUFFER, 0, count * sizeof(uint16_t), indices);
|
||||
}
|
||||
indexCapacity_ = count;
|
||||
}
|
||||
|
||||
glBindVertexArray(0);
|
||||
indexCount_ = count;
|
||||
indexCount_ = count;
|
||||
E2D_LOG_INFO("Set {} indices", count);
|
||||
}
|
||||
|
||||
void Mesh::updateVertices(const Vertex* vertices, uint32_t count, uint32_t offset) {
|
||||
if (vao_ == 0 || vbo_ == 0 || vertices == nullptr) return;
|
||||
void Mesh::updateVertices(const Vertex *vertices, uint32_t count,
|
||||
uint32_t offset) {
|
||||
if (!vertices || count == 0 || !vertexBuffer_.isValid())
|
||||
return;
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, vbo_);
|
||||
glBufferSubData(GL_ARRAY_BUFFER, offset * sizeof(Vertex),
|
||||
count * sizeof(Vertex), vertices);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, 0);
|
||||
if (offset + count > vertexCount_) {
|
||||
E2D_LOG_WARN("Vertex update out of bounds");
|
||||
return;
|
||||
}
|
||||
|
||||
// 获取 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 {
|
||||
if (vao_ != 0) {
|
||||
glBindVertexArray(vao_);
|
||||
}
|
||||
}
|
||||
Ptr<Mesh> Mesh::createQuad(const Vec2 &size, const Rect &uv) {
|
||||
Ptr<Mesh> mesh = makePtr<Mesh>();
|
||||
|
||||
void Mesh::unbind() const {
|
||||
glBindVertexArray(0);
|
||||
}
|
||||
float halfW = size.x * 0.5f;
|
||||
float halfH = size.y * 0.5f;
|
||||
|
||||
void Mesh::draw() const {
|
||||
if (vao_ == 0) return;
|
||||
Vertex vertices[4] = {
|
||||
{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), Color::White}, // 右上
|
||||
{Vec2(-halfW, halfH), Vec2(uv.x, uv.y), Color::White} // 左上
|
||||
};
|
||||
|
||||
// 检查 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;
|
||||
}
|
||||
}
|
||||
uint16_t indices[6] = {
|
||||
0, 1, 2, // 第一个三角形
|
||||
0, 2, 3 // 第二个三角形
|
||||
};
|
||||
|
||||
if (indexCount_ > 0) {
|
||||
glDrawElements(GL_TRIANGLES, indexCount_, GL_UNSIGNED_SHORT, nullptr);
|
||||
} else {
|
||||
glDrawArrays(GL_TRIANGLES, 0, vertexCount_);
|
||||
}
|
||||
mesh->setVertices(vertices, 4);
|
||||
mesh->setIndices(indices, 6);
|
||||
|
||||
// 检查绘制后的错误
|
||||
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>();
|
||||
|
||||
float halfW = size.x * 0.5f;
|
||||
float halfH = size.y * 0.5f;
|
||||
|
||||
Vertex vertices[4] = {
|
||||
{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), Color::White}, // 右上
|
||||
{Vec2(-halfW, halfH), Vec2(uv.x, uv.y), Color::White} // 左上
|
||||
};
|
||||
|
||||
uint16_t indices[6] = {
|
||||
0, 1, 2, // 第一个三角形
|
||||
0, 2, 3 // 第二个三角形
|
||||
};
|
||||
|
||||
mesh->setVertices(vertices, 4);
|
||||
mesh->setIndices(indices, 6);
|
||||
|
||||
return mesh;
|
||||
return mesh;
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
|
|||
|
|
@ -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 <event/events.h>
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
#include <platform/window_module.h>
|
||||
#include <renderer/render_graph.h>
|
||||
#include <renderer/renderer_module.h>
|
||||
#include <renderer/rhi_module.h>
|
||||
#include <utils/logger.h>
|
||||
|
||||
#include <SDL.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
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() {
|
||||
E2D_LOG_INFO("Initializing RendererModule...");
|
||||
|
||||
// 绑定事件监听器
|
||||
onRenderBeginListener_.bind([this]() { onRenderBegin(); });
|
||||
onRenderSubmitListener_.bind(
|
||||
[this](const RenderCommand &cmd) { onRenderSubmit(cmd); });
|
||||
|
|
@ -84,69 +22,92 @@ bool RendererModule::init() {
|
|||
[this](const Mat4 &viewProj) { onRenderSetCamera(viewProj); });
|
||||
onRenderEndListener_.bind([this]() { onRenderEnd(); });
|
||||
onResizeListener_.bind([this](int32 w, int32 h) { onResize(w, h); });
|
||||
|
||||
onShowListener_.bind([this]() { onWindowShow(); });
|
||||
|
||||
E2D_LOG_INFO("RendererModule initialized (waiting for GL context)");
|
||||
E2D_LOG_INFO("RendererModule initialized (waiting for window show)");
|
||||
return true;
|
||||
}
|
||||
|
||||
void RendererModule::onWindowShow() {
|
||||
if (glInitialized_) {
|
||||
if (initialized_) {
|
||||
return;
|
||||
}
|
||||
|
||||
E2D_LOG_INFO("Initializing OpenGL context...");
|
||||
E2D_LOG_INFO("Initializing RendererModule context...");
|
||||
|
||||
if (!uniformManager_.initialize()) {
|
||||
E2D_LOG_ERROR("Failed to initialize UniformBufferManager");
|
||||
// 初始化 RHI 模块
|
||||
auto *rhiModule = RHIModule::get();
|
||||
if (!rhiModule) {
|
||||
E2D_LOG_ERROR("RHIModule not available");
|
||||
return;
|
||||
}
|
||||
|
||||
int windowWidth = 800, windowHeight = 600;
|
||||
SDL_Window *sdlWindow = SDL_GL_GetCurrentWindow();
|
||||
if (sdlWindow) {
|
||||
SDL_GetWindowSize(sdlWindow, &windowWidth, &windowHeight);
|
||||
E2D_LOG_INFO("Setting initial viewport to window size: {}x{}", windowWidth,
|
||||
windowHeight);
|
||||
} else {
|
||||
E2D_LOG_WARN("Could not get SDL window, using default viewport 800x600");
|
||||
if (!rhiModule->getDevice()) {
|
||||
// RHI 设备尚未初始化,这是正常的时序问题
|
||||
// RHIModule 会在窗口显示后初始化设备,然后再次触发此函数
|
||||
E2D_LOG_INFO("RHIDevice not ready yet, waiting for RHI initialization...");
|
||||
return;
|
||||
}
|
||||
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;
|
||||
E2D_LOG_INFO("OpenGL context initialized successfully");
|
||||
commandQueue_ = renderGraph_.getCommandQueue();
|
||||
|
||||
// 添加默认的几何渲染通道
|
||||
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() {
|
||||
E2D_LOG_INFO("Shutting down RendererModule...");
|
||||
|
||||
if (glInitialized_) {
|
||||
uniformManager_.shutdown();
|
||||
}
|
||||
renderGraph_.shutdown();
|
||||
commandQueue_ = nullptr;
|
||||
initialized_ = false;
|
||||
|
||||
glInitialized_ = false;
|
||||
// 注意:事件监听器是值类型,会在析构时自动清理
|
||||
|
||||
E2D_LOG_INFO("RendererModule shutdown complete");
|
||||
}
|
||||
|
||||
MaterialHandle RendererModule::getDefaultMaterialHandle() const {
|
||||
auto* assets = getAssets();
|
||||
return assets ? assets->getDefaultMaterial() : MaterialHandle::invalid();
|
||||
RHIContext *RendererModule::getRHIContext() const {
|
||||
auto *rhiModule = RHIModule::get();
|
||||
if (rhiModule && rhiModule->getDevice()) {
|
||||
return rhiModule->getDevice()->getContext();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
MeshHandle RendererModule::getDefaultQuadHandle() const {
|
||||
auto* assets = getAssets();
|
||||
return assets ? assets->getDefaultQuad() : MeshHandle::invalid();
|
||||
Handle<Material> RendererModule::getDefaultMaterialHandle() const {
|
||||
auto *assets = getAssets();
|
||||
return assets ? assets->getDefaultMaterial() : Handle<Material>::invalid();
|
||||
}
|
||||
|
||||
TextureHandle RendererModule::getDefaultTextureHandle() const {
|
||||
auto* assets = getAssets();
|
||||
return assets ? assets->getDefaultTexture() : TextureHandle::invalid();
|
||||
Handle<Mesh> RendererModule::getDefaultQuadHandle() const {
|
||||
auto *assets = getAssets();
|
||||
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) {
|
||||
|
|
@ -157,216 +118,146 @@ void RendererModule::setViewport(int32 x, int32 y, int32 width, int32 height) {
|
|||
|
||||
viewportAdapter_.update(width, height);
|
||||
|
||||
auto result = viewportAdapter_.getResult();
|
||||
glViewport(static_cast<GLint>(result.viewport.x),
|
||||
static_cast<GLint>(result.viewport.y),
|
||||
static_cast<GLsizei>(result.viewport.w),
|
||||
static_cast<GLsizei>(result.viewport.h));
|
||||
// 更新渲染图输出尺寸
|
||||
renderGraph_.setOutputSize(static_cast<uint32_t>(width),
|
||||
static_cast<uint32_t>(height));
|
||||
|
||||
// 通过 RHI 设置视口
|
||||
if (initialized_) {
|
||||
auto *context = getRHIContext();
|
||||
if (context) {
|
||||
auto result = viewportAdapter_.getResult();
|
||||
context->setViewport(static_cast<int32_t>(result.viewport.x),
|
||||
static_cast<int32_t>(result.viewport.y),
|
||||
static_cast<uint32_t>(result.viewport.w),
|
||||
static_cast<uint32_t>(result.viewport.h));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RendererModule::clear(const Color &color, uint32 flags) {
|
||||
GLbitfield mask = 0;
|
||||
if (!initialized_)
|
||||
return;
|
||||
|
||||
if (flags & CLEAR_COLOR_FLAG) {
|
||||
glClearColor(color.r, color.g, color.b, color.a);
|
||||
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);
|
||||
if (commandQueue_) {
|
||||
commandQueue_->submitClear(color, flags);
|
||||
}
|
||||
}
|
||||
|
||||
void RendererModule::onRenderBegin() {
|
||||
if (!glInitialized_) {
|
||||
if (!initialized_) {
|
||||
return;
|
||||
}
|
||||
|
||||
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
|
||||
glClear(GL_COLOR_BUFFER_BIT);
|
||||
|
||||
commandCount_ = 0;
|
||||
|
||||
// 重置统计
|
||||
stats_ = {};
|
||||
|
||||
uniformManager_.resetMaterialUBOs();
|
||||
// 开始渲染帧 - 清空命令队列准备接收新的渲染命令
|
||||
if (commandQueue_) {
|
||||
commandQueue_->beginFrame();
|
||||
}
|
||||
|
||||
// 帧开始
|
||||
}
|
||||
|
||||
void RendererModule::onRenderSubmit(const RenderCommand &cmd) {
|
||||
if (!glInitialized_) {
|
||||
if (!initialized_ || !commandQueue_) {
|
||||
E2D_LOG_WARN(
|
||||
"onRenderSubmit: RendererModule not initialized or no command queue");
|
||||
return;
|
||||
}
|
||||
|
||||
if (commandCount_ >= MAX_RENDER_COMMANDS) {
|
||||
E2D_LOG_WARN("Render command buffer full!");
|
||||
return;
|
||||
(void)cmd; // 避免未使用警告
|
||||
|
||||
// 将渲染命令转换为绘制命令提交到队列
|
||||
switch (cmd.type) {
|
||||
case RenderCommandType::DrawMesh: {
|
||||
auto *assets = getAssets();
|
||||
if (!assets)
|
||||
return;
|
||||
|
||||
// 获取材质,如果没有指定则使用默认材质
|
||||
Material *material = assets->get(cmd.drawMesh.material);
|
||||
if (!material) {
|
||||
material = assets->get(assets->getDefaultMaterial());
|
||||
}
|
||||
|
||||
// 获取网格,如果没有指定则使用默认四边形
|
||||
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++;
|
||||
} else {
|
||||
E2D_LOG_WARN("Failed to submit draw command: material={}, mesh={}",
|
||||
material ? "valid" : "null", mesh ? "valid" : "null");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
commandBuffer_[commandCount_++] = cmd;
|
||||
stats_.commandsSubmitted++;
|
||||
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) {
|
||||
if (!glInitialized_) {
|
||||
if (!initialized_) {
|
||||
return;
|
||||
}
|
||||
|
||||
viewProjectionMatrix_ = viewProj;
|
||||
|
||||
uniformManager_.updateGlobalUBO(&viewProj, sizeof(Mat4));
|
||||
// 更新全局 UBO(通过 RenderGraph 的命令队列)
|
||||
// 实际的 UBO 更新会在 RenderGraph 执行时进行
|
||||
}
|
||||
|
||||
void RendererModule::onRenderEnd() {
|
||||
if (!glInitialized_) {
|
||||
if (!initialized_) {
|
||||
E2D_LOG_WARN("onRenderEnd: RendererModule not initialized");
|
||||
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",
|
||||
stats_.commandsExecuted, stats_.drawCalls, stats_.batches);
|
||||
// 每60帧输出一次统计信息
|
||||
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) {
|
||||
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) {
|
||||
// 此方法保留用于直接执行特定命令
|
||||
// 大部分命令现在通过 RenderGraph 处理
|
||||
switch (cmd.type) {
|
||||
case RenderCommandType::SetViewport:
|
||||
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 <glad/glad.h>
|
||||
#include <renderer/rhi_module.h>
|
||||
#include <renderer/shader.h>
|
||||
#include <sstream>
|
||||
#include <utils/logger.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
|
@ -9,203 +7,181 @@ namespace extra2d {
|
|||
Shader::Shader() = default;
|
||||
|
||||
Shader::~Shader() {
|
||||
if (program_ != 0) {
|
||||
glDeleteProgram(program_);
|
||||
program_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
// RHIHandle 是轻量级句柄,不需要显式释放
|
||||
// 实际的资源由 RHI 设备管理
|
||||
pipeline_ = PipelineHandle();
|
||||
handle_ = ShaderHandle();
|
||||
}
|
||||
|
||||
bool Shader::loadFromSource(const std::string &vsSource,
|
||||
const std::string &fsSource) {
|
||||
// 删除旧程序
|
||||
if (program_ != 0) {
|
||||
glDeleteProgram(program_);
|
||||
program_ = 0;
|
||||
// 创建标准2D顶点布局(位置 + 纹理坐标 + 颜色)
|
||||
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 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 processedFS = addVersionIfNeeded(fsSource, GL_FRAGMENT_SHADER);
|
||||
std::string processedVS = addVersionIfNeeded(vsSource, true);
|
||||
std::string processedFS = addVersionIfNeeded(fsSource, false);
|
||||
|
||||
E2D_LOG_INFO("Compiling vertex shader...");
|
||||
// 编译顶点着色器
|
||||
GLuint vs = compileShader(GL_VERTEX_SHADER, processedVS);
|
||||
if (vs == 0) {
|
||||
E2D_LOG_ERROR("Vertex shader compilation failed");
|
||||
// 创建着色器描述
|
||||
ShaderDesc shaderDesc;
|
||||
shaderDesc.vertexSource = processedVS;
|
||||
shaderDesc.fragmentSource = processedFS;
|
||||
|
||||
// 创建着色器
|
||||
auto shader = device->createShader(shaderDesc);
|
||||
if (!shader) {
|
||||
E2D_LOG_ERROR("Failed to create shader");
|
||||
return false;
|
||||
}
|
||||
|
||||
E2D_LOG_INFO("Compiling fragment shader...");
|
||||
// 编译片段着色器
|
||||
GLuint fs = compileShader(GL_FRAGMENT_SHADER, processedFS);
|
||||
if (fs == 0) {
|
||||
E2D_LOG_ERROR("Fragment shader compilation failed");
|
||||
glDeleteShader(vs);
|
||||
// 获取着色器句柄
|
||||
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");
|
||||
handle_ = ShaderHandle();
|
||||
return false;
|
||||
}
|
||||
|
||||
E2D_LOG_INFO("Linking shader program...");
|
||||
// 链接程序
|
||||
if (!linkProgram(vs, fs)) {
|
||||
E2D_LOG_ERROR("Shader program linking failed");
|
||||
glDeleteShader(vs);
|
||||
glDeleteShader(fs);
|
||||
return false;
|
||||
}
|
||||
// 获取管线句柄
|
||||
pipeline_ = PipelineHandle(pipeline.release());
|
||||
|
||||
// 清理着色器对象
|
||||
glDeleteShader(vs);
|
||||
glDeleteShader(fs);
|
||||
|
||||
E2D_LOG_INFO("Shader program created successfully, program ID: {}", program_);
|
||||
E2D_LOG_INFO("Shader created successfully");
|
||||
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) {
|
||||
if (program_ == 0)
|
||||
return;
|
||||
// 存储 uniform block 绑定信息
|
||||
uniformBlockBindings_[name] = binding;
|
||||
|
||||
GLuint index = glGetUniformBlockIndex(program_, name.c_str());
|
||||
if (index != GL_INVALID_INDEX) {
|
||||
glUniformBlockBinding(program_, index, binding);
|
||||
}
|
||||
// 注意:实际的 uniform block 绑定需要在渲染时通过 RHI 命令列表设置
|
||||
// 这里仅存储绑定信息,供后续渲染使用
|
||||
// 例如:commandList->setUniformBlock(binding, buffer);
|
||||
}
|
||||
|
||||
void Shader::setInt(const std::string &name, int value) {
|
||||
GLint location = getUniformLocation(name);
|
||||
if (location != -1) {
|
||||
glUniform1i(location, value);
|
||||
uint32_t Shader::getUniformBlockBinding(const std::string &name) const {
|
||||
auto it = uniformBlockBindings_.find(name);
|
||||
if (it != uniformBlockBindings_.end()) {
|
||||
return it->second;
|
||||
}
|
||||
return UINT32_MAX; // 未找到
|
||||
}
|
||||
|
||||
void Shader::setFloat(const std::string &name, float value) {
|
||||
GLint location = getUniformLocation(name);
|
||||
if (location != -1) {
|
||||
glUniform1f(location, value);
|
||||
}
|
||||
bool Shader::reloadFromSource(const std::string &vsSource,
|
||||
const std::string &fsSource) {
|
||||
// 创建标准2D顶点布局(位置 + 纹理坐标 + 颜色)
|
||||
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) {
|
||||
GLint location = getUniformLocation(name);
|
||||
if (location != -1) {
|
||||
glUniform2f(location, x, y);
|
||||
}
|
||||
}
|
||||
bool Shader::reloadFromSourceWithLayout(const std::string &vsSource,
|
||||
const std::string &fsSource,
|
||||
const VertexLayout &vertexLayout) {
|
||||
// 释放旧资源
|
||||
pipeline_ = PipelineHandle();
|
||||
handle_ = ShaderHandle();
|
||||
|
||||
void Shader::setVec4(const std::string &name, float x, float y, float z,
|
||||
float w) {
|
||||
GLint location = getUniformLocation(name);
|
||||
if (location != -1) {
|
||||
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;
|
||||
// 获取 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;
|
||||
}
|
||||
|
||||
// 处理源码(添加版本声明)
|
||||
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;
|
||||
}
|
||||
|
||||
GLint Shader::getUniformLocation(const std::string &name) {
|
||||
if (program_ == 0)
|
||||
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) {
|
||||
std::string Shader::addVersionIfNeeded(const std::string &source,
|
||||
bool isVertex) {
|
||||
// 如果已经包含版本声明,直接返回
|
||||
if (source.find("#version") != std::string::npos) {
|
||||
return source;
|
||||
|
|
@ -215,7 +191,7 @@ std::string Shader::addVersionIfNeeded(const std::string &source, GLenum type) {
|
|||
std::string result = "#version 320 es\n";
|
||||
|
||||
// 片段着色器需要添加精度声明
|
||||
if (type == GL_FRAGMENT_SHADER) {
|
||||
if (!isVertex) {
|
||||
result += "precision mediump float;\n";
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,46 +1,17 @@
|
|||
#include <renderer/texture.h>
|
||||
#include <renderer/rhi_module.h>
|
||||
#include <utils/logger.h>
|
||||
#include <glad/glad.h>
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
#include <stb/stb_image.h>
|
||||
|
||||
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() {
|
||||
if (texture_ != 0) {
|
||||
glDeleteTextures(1, &texture_);
|
||||
texture_ = 0;
|
||||
}
|
||||
// RHI 纹理句柄是轻量级句柄,不需要显式释放
|
||||
// 实际的纹理资源由 RHI 设备管理
|
||||
handle_ = TextureHandle();
|
||||
}
|
||||
|
||||
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) {
|
||||
if (texture_ != 0) {
|
||||
glDeleteTextures(1, &texture_);
|
||||
texture_ = 0;
|
||||
}
|
||||
// 释放旧纹理
|
||||
handle_ = TextureHandle();
|
||||
|
||||
width_ = width;
|
||||
height_ = height;
|
||||
format_ = format;
|
||||
|
||||
// 创建纹理
|
||||
glGenTextures(1, &texture_);
|
||||
glBindTexture(GL_TEXTURE_2D, texture_);
|
||||
// 获取 RHI 设备
|
||||
auto* rhiModule = RHIModule::get();
|
||||
if (!rhiModule) {
|
||||
E2D_LOG_ERROR("RHIModule not available");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 设置纹理参数
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
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; // 初始创建 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,
|
||||
getTextureInternalFormat(format_),
|
||||
width_, height_, 0,
|
||||
getTextureFormat(format_),
|
||||
GL_UNSIGNED_BYTE, data);
|
||||
if (data) {
|
||||
// 注意:update 方法的参数需要根据实际 RHI 接口调整
|
||||
// 这里假设 update 接受数据指针
|
||||
texture->update(data, static_cast<size_t>(width * height * getBytesPerPixel(format)));
|
||||
// 生成 mipmap
|
||||
// 注意:generateMipmap 方法需要在 RHI 接口中实现
|
||||
}
|
||||
|
||||
// 生成 mipmap
|
||||
glGenerateMipmap(GL_TEXTURE_2D);
|
||||
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
// 获取纹理句柄
|
||||
handle_ = TextureHandle(texture.release());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Texture::create(int width, int height, TextureFormat format) {
|
||||
if (texture_ != 0) {
|
||||
glDeleteTextures(1, &texture_);
|
||||
texture_ = 0;
|
||||
}
|
||||
// 释放旧纹理
|
||||
handle_ = TextureHandle();
|
||||
|
||||
width_ = width;
|
||||
height_ = height;
|
||||
format_ = format;
|
||||
|
||||
// 创建纹理
|
||||
glGenTextures(1, &texture_);
|
||||
glBindTexture(GL_TEXTURE_2D, texture_);
|
||||
// 获取 RHI 设备
|
||||
auto* rhiModule = RHIModule::get();
|
||||
if (!rhiModule) {
|
||||
E2D_LOG_ERROR("RHIModule not available");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 设置纹理参数
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
auto* device = rhiModule->getDevice();
|
||||
if (!device) {
|
||||
E2D_LOG_ERROR("RHIDevice not available");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 分配纹理存储(无数据)
|
||||
glTexImage2D(GL_TEXTURE_2D, 0,
|
||||
getTextureInternalFormat(format_),
|
||||
width_, height_, 0,
|
||||
getTextureFormat(format_),
|
||||
GL_UNSIGNED_BYTE, nullptr);
|
||||
// 创建纹理描述
|
||||
TextureDesc desc;
|
||||
desc.width = static_cast<uint32_t>(width);
|
||||
desc.height = static_cast<uint32_t>(height);
|
||||
desc.format = format;
|
||||
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;
|
||||
}
|
||||
|
||||
void Texture::bind(uint32_t slot) const {
|
||||
if (texture_ != 0) {
|
||||
glActiveTexture(GL_TEXTURE0 + slot);
|
||||
glBindTexture(GL_TEXTURE_2D, texture_);
|
||||
bool Texture::reloadFromFile(const std::string& path) {
|
||||
// 加载图片
|
||||
int channels;
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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 <utils/logger.h>
|
||||
|
||||
|
|
@ -5,146 +7,212 @@ namespace extra2d {
|
|||
|
||||
UniformBuffer::UniformBuffer() = default;
|
||||
|
||||
UniformBuffer::~UniformBuffer() {
|
||||
destroy();
|
||||
}
|
||||
UniformBuffer::~UniformBuffer() { destroy(); }
|
||||
|
||||
bool UniformBuffer::create(uint32_t size, uint32_t binding) {
|
||||
if (ubo_ != 0) {
|
||||
destroy();
|
||||
}
|
||||
destroy();
|
||||
|
||||
size_ = size;
|
||||
binding_ = binding;
|
||||
// 获取 RHI 设备
|
||||
auto *rhiModule = RHIModule::get();
|
||||
if (!rhiModule) {
|
||||
E2D_LOG_ERROR("RHIModule not available");
|
||||
return false;
|
||||
}
|
||||
|
||||
glGenBuffers(1, &ubo_);
|
||||
if (ubo_ == 0) {
|
||||
E2D_LOG_ERROR("Failed to generate UBO");
|
||||
return false;
|
||||
}
|
||||
auto *device = rhiModule->getDevice();
|
||||
if (!device) {
|
||||
E2D_LOG_ERROR("RHIDevice not available");
|
||||
return false;
|
||||
}
|
||||
|
||||
glBindBuffer(GL_UNIFORM_BUFFER, ubo_);
|
||||
glBufferData(GL_UNIFORM_BUFFER, size, nullptr, GL_DYNAMIC_DRAW);
|
||||
glBindBufferBase(GL_UNIFORM_BUFFER, binding, ubo_);
|
||||
glBindBuffer(GL_UNIFORM_BUFFER, 0);
|
||||
size_ = size;
|
||||
binding_ = binding;
|
||||
|
||||
E2D_LOG_DEBUG("UBO created: size={}, binding={}", size, binding);
|
||||
return true;
|
||||
// 创建 uniform 缓冲区描述
|
||||
BufferDesc desc = BufferDesc::uniform(size);
|
||||
|
||||
// 创建缓冲区
|
||||
auto buffer = device->createBuffer(desc);
|
||||
if (!buffer) {
|
||||
E2D_LOG_ERROR("Failed to create uniform buffer");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 获取缓冲区句柄
|
||||
handle_ = BufferHandle(buffer.release());
|
||||
|
||||
E2D_LOG_DEBUG("UBO created: size={}, binding={}", size, binding);
|
||||
return true;
|
||||
}
|
||||
|
||||
void UniformBuffer::destroy() {
|
||||
if (ubo_ != 0) {
|
||||
glDeleteBuffers(1, &ubo_);
|
||||
ubo_ = 0;
|
||||
size_ = 0;
|
||||
}
|
||||
// RHIHandle 是轻量级句柄,不需要显式释放
|
||||
// 实际的缓冲区资源由 RHI 设备管理
|
||||
handle_ = BufferHandle();
|
||||
size_ = 0;
|
||||
}
|
||||
|
||||
void UniformBuffer::update(const void* data, uint32_t size, uint32_t offset) {
|
||||
if (ubo_ == 0 || data == nullptr) return;
|
||||
void UniformBuffer::update(const void *data, uint32_t size, uint32_t offset) {
|
||||
if (!handle_.isValid() || data == nullptr)
|
||||
return;
|
||||
|
||||
if (offset + size > size_) {
|
||||
E2D_LOG_WARN("UBO update out of bounds: offset={}, size={}, bufferSize={}",
|
||||
offset, size, size_);
|
||||
return;
|
||||
}
|
||||
if (offset + size > size_) {
|
||||
E2D_LOG_WARN("UBO update out of bounds: offset={}, size={}, bufferSize={}",
|
||||
offset, size, size_);
|
||||
return;
|
||||
}
|
||||
|
||||
glBindBuffer(GL_UNIFORM_BUFFER, ubo_);
|
||||
glBufferSubData(GL_UNIFORM_BUFFER, offset, size, data);
|
||||
glBindBuffer(GL_UNIFORM_BUFFER, 0);
|
||||
// 通过 RHI 缓冲区接口直接更新数据
|
||||
RHIBuffer *buffer = handle_.get();
|
||||
if (buffer) {
|
||||
buffer->update(data, size, offset);
|
||||
}
|
||||
}
|
||||
|
||||
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() {
|
||||
shutdown();
|
||||
}
|
||||
UniformBufferManager::~UniformBufferManager() { shutdown(); }
|
||||
|
||||
UniformBufferManager::UniformBufferManager(UniformBufferManager&& other) noexcept
|
||||
: globalUBO_(std::move(other.globalUBO_)),
|
||||
UniformBufferManager::UniformBufferManager(
|
||||
UniformBufferManager &&other) noexcept
|
||||
: globalUBOs_(std::move(other.globalUBOs_)),
|
||||
materialUBOPool_(std::move(other.materialUBOPool_)),
|
||||
currentUBOIndex_(other.currentUBOIndex_) {
|
||||
other.currentUBOIndex_ = 0;
|
||||
currentUBOIndex_(other.currentUBOIndex_),
|
||||
materialUBOBuffer_(std::move(other.materialUBOBuffer_)),
|
||||
materialUBOBufferOffset_(other.materialUBOBufferOffset_),
|
||||
currentMaterialUBO_(other.currentMaterialUBO_) {
|
||||
other.currentUBOIndex_ = 0;
|
||||
other.materialUBOBufferOffset_ = 0;
|
||||
other.currentMaterialUBO_ = nullptr;
|
||||
}
|
||||
|
||||
UniformBufferManager& UniformBufferManager::operator=(UniformBufferManager&& other) noexcept {
|
||||
if (this != &other) {
|
||||
shutdown();
|
||||
UniformBufferManager &
|
||||
UniformBufferManager::operator=(UniformBufferManager &&other) noexcept {
|
||||
if (this != &other) {
|
||||
shutdown();
|
||||
|
||||
globalUBO_ = std::move(other.globalUBO_);
|
||||
materialUBOPool_ = std::move(other.materialUBOPool_);
|
||||
currentUBOIndex_ = other.currentUBOIndex_;
|
||||
globalUBOs_ = std::move(other.globalUBOs_);
|
||||
materialUBOPool_ = std::move(other.materialUBOPool_);
|
||||
currentUBOIndex_ = other.currentUBOIndex_;
|
||||
materialUBOBuffer_ = std::move(other.materialUBOBuffer_);
|
||||
materialUBOBufferOffset_ = other.materialUBOBufferOffset_;
|
||||
currentMaterialUBO_ = other.currentMaterialUBO_;
|
||||
|
||||
other.currentUBOIndex_ = 0;
|
||||
}
|
||||
return *this;
|
||||
other.currentUBOIndex_ = 0;
|
||||
other.materialUBOBufferOffset_ = 0;
|
||||
other.currentMaterialUBO_ = nullptr;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool UniformBufferManager::initialize() {
|
||||
// 创建全局 UBO
|
||||
globalUBO_ = std::make_unique<UniformBuffer>();
|
||||
if (!globalUBO_->create(GLOBAL_UBO_SIZE, GLOBAL_UBO_BINDING)) {
|
||||
E2D_LOG_ERROR("Failed to create global UBO");
|
||||
return false;
|
||||
// 创建全局 UBO 双缓冲
|
||||
for (size_t i = 0; i < globalUBOs_.size(); ++i) {
|
||||
globalUBOs_[i] = std::make_unique<UniformBuffer>();
|
||||
if (!globalUBOs_[i]->create(UniformBufferManager::GLOBAL_UBO_SIZE,
|
||||
UniformBufferManager::GLOBAL_UBO_BINDING)) {
|
||||
E2D_LOG_ERROR("Failed to create global UBO {}", i);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 预分配材质 UBO 池
|
||||
materialUBOPool_.reserve(INITIAL_UBO_POOL_SIZE);
|
||||
// 预分配材质 UBO 池
|
||||
materialUBOPool_.reserve(INITIAL_UBO_POOL_SIZE);
|
||||
|
||||
E2D_LOG_INFO("UniformBufferManager initialized");
|
||||
return true;
|
||||
// 预分配材质 UBO CPU 缓冲区
|
||||
materialUBOBuffer_.resize(MATERIAL_UBO_BUFFER_SIZE);
|
||||
|
||||
E2D_LOG_INFO("UniformBufferManager initialized with double buffering");
|
||||
return true;
|
||||
}
|
||||
|
||||
void UniformBufferManager::shutdown() {
|
||||
materialUBOPool_.clear();
|
||||
globalUBO_.reset();
|
||||
currentUBOIndex_ = 0;
|
||||
materialUBOPool_.clear();
|
||||
for (auto& ubo : globalUBOs_) {
|
||||
ubo.reset();
|
||||
}
|
||||
currentUBOIndex_ = 0;
|
||||
materialUBOBuffer_.clear();
|
||||
materialUBOBufferOffset_ = 0;
|
||||
currentMaterialUBO_ = nullptr;
|
||||
|
||||
E2D_LOG_INFO("UniformBufferManager shutdown");
|
||||
E2D_LOG_INFO("UniformBufferManager shutdown");
|
||||
}
|
||||
|
||||
UniformBuffer* UniformBufferManager::getGlobalUBO() {
|
||||
return globalUBO_.get();
|
||||
UniformBuffer *UniformBufferManager::getGlobalUBO(uint32_t frameIndex) {
|
||||
// 使用双缓冲,根据帧索引选择 UBO
|
||||
size_t index = frameIndex % 2;
|
||||
return globalUBOs_[index].get();
|
||||
}
|
||||
|
||||
UniformBuffer* UniformBufferManager::acquireMaterialUBO(uint32_t size) {
|
||||
// 查找或创建合适大小的 UBO
|
||||
for (uint32_t i = currentUBOIndex_; i < materialUBOPool_.size(); ++i) {
|
||||
if (materialUBOPool_[i]->getSize() >= size) {
|
||||
currentUBOIndex_ = i + 1;
|
||||
return materialUBOPool_[i].get();
|
||||
}
|
||||
UniformBuffer *UniformBufferManager::acquireMaterialUBO(uint32_t size) {
|
||||
// 查找或创建合适大小的 UBO
|
||||
for (uint32_t i = currentUBOIndex_; i < materialUBOPool_.size(); ++i) {
|
||||
if (materialUBOPool_[i]->getSize() >= size) {
|
||||
currentUBOIndex_ = i + 1;
|
||||
return materialUBOPool_[i].get();
|
||||
}
|
||||
}
|
||||
|
||||
// 需要创建新的 UBO
|
||||
auto ubo = std::make_unique<UniformBuffer>();
|
||||
if (!ubo->create(size, MATERIAL_UBO_BINDING)) {
|
||||
E2D_LOG_ERROR("Failed to create material UBO");
|
||||
return nullptr;
|
||||
}
|
||||
// 需要创建新的 UBO
|
||||
auto ubo = std::make_unique<UniformBuffer>();
|
||||
if (!ubo->create(size, UniformBufferManager::MATERIAL_UBO_BINDING)) {
|
||||
E2D_LOG_ERROR("Failed to create material UBO");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
UniformBuffer* result = ubo.get();
|
||||
materialUBOPool_.push_back(std::move(ubo));
|
||||
currentUBOIndex_ = materialUBOPool_.size();
|
||||
UniformBuffer *result = ubo.get();
|
||||
materialUBOPool_.push_back(std::move(ubo));
|
||||
currentUBOIndex_ = materialUBOPool_.size();
|
||||
|
||||
return result;
|
||||
return result;
|
||||
}
|
||||
|
||||
void UniformBufferManager::resetMaterialUBOs() {
|
||||
currentUBOIndex_ = 0;
|
||||
currentUBOIndex_ = 0;
|
||||
materialUBOBufferOffset_ = 0;
|
||||
currentMaterialUBO_ = nullptr;
|
||||
}
|
||||
|
||||
void UniformBufferManager::updateGlobalUBO(const void* data, uint32_t size) {
|
||||
if (globalUBO_) {
|
||||
globalUBO_->update(data, size);
|
||||
globalUBO_->bind(GLOBAL_UBO_BINDING);
|
||||
}
|
||||
void UniformBufferManager::updateGlobalUBO(const void *data, uint32_t size, uint32_t frameIndex) {
|
||||
// 使用双缓冲更新全局 UBO
|
||||
size_t index = frameIndex % 2;
|
||||
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
|
||||
|
|
|
|||
|
|
@ -33,8 +33,9 @@ void SpriteRenderer::render() {
|
|||
uint32_t textureId = texture_.index();
|
||||
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.setColor(color_);
|
||||
|
|
|
|||
|
|
@ -130,8 +130,9 @@ void Director::render() {
|
|||
CameraComponent *camera = runningScene_->getMainCamera();
|
||||
if (camera) {
|
||||
Mat4 viewProj = camera->getViewProjectionMatrix();
|
||||
|
||||
events::OnRenderSetCamera::emit(viewProj);
|
||||
} else {
|
||||
E2D_LOG_WARN("Director::render: No main camera set!");
|
||||
}
|
||||
|
||||
runningScene_->render();
|
||||
|
|
|
|||
|
|
@ -8,10 +8,6 @@ SceneModule::~SceneModule() {
|
|||
shutdown();
|
||||
}
|
||||
|
||||
SceneModule::SceneModule(SceneModule &&) noexcept = default;
|
||||
|
||||
SceneModule &SceneModule::operator=(SceneModule &&) noexcept = default;
|
||||
|
||||
bool SceneModule::init() {
|
||||
// 创建导演
|
||||
director_ = makePtr<Director>();
|
||||
|
|
|
|||
|
|
@ -7,24 +7,6 @@ TimerModule::TimerModule() = default;
|
|||
|
||||
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() {
|
||||
// 初始化完成,等待 OnUpdate 事件
|
||||
return true;
|
||||
|
|
|
|||
Loading…
Reference in New Issue