feat(渲染): 实现实例化渲染功能并优化渲染管线
- 添加实例化渲染支持,包括InstanceBuffer、InstanceBufferManager和相关着色器 - 重构渲染命令队列以支持实例化绘制 - 优化材质系统,使用vector保持参数顺序并添加快速查找 - 改进顶点布局系统,支持实例属性 - 添加全局UBO管理,优化uniform数据传递 - 实现新的实例化测试场景节点 - 更新着色器以支持实例化渲染和UBO - 改进GL命令列表,支持实例属性绑定 - 添加AssetsModule对实例化资源的支持 - 修复Director在没有主相机时的警告日志
This commit is contained in:
parent
9041833430
commit
91e3e8fe57
|
|
@ -1,4 +1,5 @@
|
|||
#include "game_scene.h"
|
||||
#include "instanced_test.h"
|
||||
#include <cmath>
|
||||
|
||||
// ========================================
|
||||
|
|
@ -6,34 +7,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 +42,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 +66,120 @@ void RotatingDecoration::onUpdate(float dt) {
|
|||
// ========================================
|
||||
|
||||
GameScene::GameScene() {
|
||||
// 场景初始化
|
||||
// 场景初始化
|
||||
}
|
||||
|
||||
void GameScene::onEnter() {
|
||||
Scene::onEnter();
|
||||
Scene::onEnter();
|
||||
|
||||
// 创建相机
|
||||
createCamera();
|
||||
// 创建相机
|
||||
createCamera();
|
||||
|
||||
// 创建玩家
|
||||
createPlayer();
|
||||
// 创建玩家
|
||||
createPlayer();
|
||||
|
||||
// 创建装饰物
|
||||
createDecorations();
|
||||
// 创建装饰物
|
||||
createDecorations();
|
||||
|
||||
// 创建实例化渲染测试(1000个实例)
|
||||
createInstancedTest();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
// 更新实例化测试
|
||||
if (instancedTest_) {
|
||||
instancedTest_->update(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);
|
||||
}
|
||||
|
||||
void GameScene::createInstancedTest() {
|
||||
// 创建实例化渲染测试节点
|
||||
auto instancedTest = makePtr<InstancedTestNode>();
|
||||
if (instancedTest->initialize(1000)) {
|
||||
instancedTest_ = instancedTest;
|
||||
addChild(instancedTest);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d.h>
|
||||
#include "instanced_test.h"
|
||||
|
||||
using namespace extra2d;
|
||||
|
||||
|
|
@ -134,7 +135,13 @@ private:
|
|||
*/
|
||||
void createCamera();
|
||||
|
||||
/**
|
||||
* @brief 创建实例化渲染测试
|
||||
*/
|
||||
void createInstancedTest();
|
||||
|
||||
Ptr<PlayerNode> player_;
|
||||
std::vector<Ptr<RotatingDecoration>> decorations_;
|
||||
Ptr<InstancedTestNode> instancedTest_;
|
||||
float sceneTime_ = 0.0f;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,139 @@
|
|||
#include "instanced_test.h"
|
||||
#include <assets/assets_module.h>
|
||||
#include <event/events.h>
|
||||
#include <module/module_registry.h>
|
||||
#include <renderer/renderer_module.h>
|
||||
#include <utils/logger.h>
|
||||
#include <cmath>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
InstancedTestNode::InstancedTestNode() {
|
||||
setName("InstancedTest");
|
||||
}
|
||||
|
||||
InstancedTestNode::~InstancedTestNode() {
|
||||
instanceBuffer_.shutdown();
|
||||
}
|
||||
|
||||
bool InstancedTestNode::initialize(uint32_t instanceCount) {
|
||||
instanceCount_ = instanceCount;
|
||||
|
||||
// 获取资源模块
|
||||
auto* assets = getModule<AssetsModule>();
|
||||
if (!assets) {
|
||||
E2D_LOG_ERROR("InstancedTestNode: AssetsModule not available");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 使用默认网格(四边形)
|
||||
mesh_ = assets->getDefaultQuad();
|
||||
if (!mesh_.isValid()) {
|
||||
E2D_LOG_ERROR("InstancedTestNode: Failed to get default quad mesh");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 使用默认纹理
|
||||
texture_ = assets->getDefaultTexture();
|
||||
|
||||
// 获取实例化渲染材质
|
||||
material_ = assets->getInstancedMaterial();
|
||||
if (!material_.isValid()) {
|
||||
E2D_LOG_ERROR("InstancedTestNode: Failed to get instanced material");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 初始化实例缓冲区
|
||||
if (!instanceBuffer_.initialize(instanceCount)) {
|
||||
E2D_LOG_ERROR("InstancedTestNode: Failed to initialize instance buffer");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 预分配实例数据
|
||||
instanceData_.resize(instanceCount);
|
||||
|
||||
// 初始化实例数据
|
||||
updateInstances();
|
||||
|
||||
E2D_LOG_INFO("InstancedTestNode initialized with {} instances", instanceCount);
|
||||
return true;
|
||||
}
|
||||
|
||||
void InstancedTestNode::update(float dt) {
|
||||
time_ += dt;
|
||||
|
||||
// 更新实例变换
|
||||
updateInstances();
|
||||
|
||||
// 更新GPU缓冲区
|
||||
if (!instanceData_.empty()) {
|
||||
instanceBuffer_.updateInstances(instanceData_.data(), instanceCount_);
|
||||
}
|
||||
}
|
||||
|
||||
void InstancedTestNode::updateInstances() {
|
||||
// 创建螺旋分布的实例
|
||||
float radius = 200.0f;
|
||||
float centerX = 640.0f;
|
||||
float centerY = 360.0f;
|
||||
|
||||
for (uint32_t i = 0; i < instanceCount_; ++i) {
|
||||
float t = static_cast<float>(i) / instanceCount_;
|
||||
float angle = t * 6.28318f * 3.0f + time_; // 3圈螺旋
|
||||
float r = radius * (0.2f + 0.8f * t);
|
||||
|
||||
// 位置
|
||||
instanceData_[i].position.x = centerX + r * std::cos(angle);
|
||||
instanceData_[i].position.y = centerY + r * std::sin(angle);
|
||||
|
||||
// 旋转(朝向中心)
|
||||
instanceData_[i].rotation = angle + 1.5708f;
|
||||
|
||||
// 缩放(随距离变化)
|
||||
float scale = 0.5f + 0.5f * t;
|
||||
instanceData_[i].scale.x = scale * 32.0f;
|
||||
instanceData_[i].scale.y = scale * 32.0f;
|
||||
|
||||
// 颜色(彩虹色)
|
||||
float hue = t + time_ * 0.1f;
|
||||
float r_color = std::abs(std::fmod(hue * 6.0f, 2.0f) - 1.0f);
|
||||
float g_color = std::abs(std::fmod(hue * 6.0f + 2.0f, 2.0f) - 1.0f);
|
||||
float b_color = std::abs(std::fmod(hue * 6.0f + 4.0f, 2.0f) - 1.0f);
|
||||
|
||||
instanceData_[i].color.r = r_color;
|
||||
instanceData_[i].color.g = g_color;
|
||||
instanceData_[i].color.b = b_color;
|
||||
instanceData_[i].color.a = 1.0f;
|
||||
|
||||
// UV坐标(使用完整纹理)
|
||||
instanceData_[i].uvX = 0.0f;
|
||||
instanceData_[i].uvY = 0.0f;
|
||||
instanceData_[i].uvWidth = 1.0f;
|
||||
instanceData_[i].uvHeight = 1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
void InstancedTestNode::render() {
|
||||
if (!isVisible() || instanceCount_ == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查资源有效性
|
||||
if (!mesh_.isValid() || !material_.isValid() || !instanceBuffer_.isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 提交实例化渲染命令
|
||||
RenderCommand cmd;
|
||||
cmd.type = RenderCommandType::DrawMeshInstanced;
|
||||
cmd.sortKey = 0;
|
||||
cmd.drawInstanced.mesh = mesh_;
|
||||
cmd.drawInstanced.material = material_;
|
||||
cmd.drawInstanced.instanceBuffer = &instanceBuffer_;
|
||||
cmd.drawInstanced.instanceCount = instanceCount_;
|
||||
cmd.drawInstanced.instanceOffset = 0;
|
||||
|
||||
events::OnRenderSubmit::emit(cmd);
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
#pragma once
|
||||
|
||||
#include <scene/node.h>
|
||||
#include <renderer/instance_buffer.h>
|
||||
#include <renderer/material.h>
|
||||
#include <renderer/mesh.h>
|
||||
#include <renderer/texture.h>
|
||||
#include <assets/handle.h>
|
||||
#include <vector>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
/**
|
||||
* @brief 实例化渲染测试节点
|
||||
*
|
||||
* 测试实例化渲染功能,渲染大量相同精灵但使用不同变换
|
||||
*/
|
||||
class InstancedTestNode : public Node {
|
||||
public:
|
||||
InstancedTestNode();
|
||||
~InstancedTestNode() override;
|
||||
|
||||
/**
|
||||
* @brief 初始化实例化渲染资源
|
||||
* @param instanceCount 实例数量
|
||||
* @return 初始化是否成功
|
||||
*/
|
||||
bool initialize(uint32_t instanceCount = 1000);
|
||||
|
||||
/**
|
||||
* @brief 每帧更新实例数据
|
||||
* @param dt 时间增量
|
||||
*/
|
||||
void update(float dt);
|
||||
|
||||
/**
|
||||
* @brief 渲染实例(收集渲染命令)
|
||||
*/
|
||||
void render() override;
|
||||
|
||||
private:
|
||||
InstanceBuffer instanceBuffer_; // 实例缓冲区
|
||||
std::vector<InstanceData> instanceData_; // CPU端实例数据
|
||||
uint32_t instanceCount_ = 0; // 实例数量
|
||||
float time_ = 0.0f; // 时间累积
|
||||
|
||||
// 资源句柄
|
||||
Handle<Material> material_;
|
||||
Handle<Mesh> mesh_;
|
||||
Handle<Texture> texture_;
|
||||
|
||||
/**
|
||||
* @brief 更新实例变换
|
||||
*/
|
||||
void updateInstances();
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -10,7 +10,7 @@ local example_dir = os.scriptdir()
|
|||
-- 可执行文件目标
|
||||
target("scene_graph_demo")
|
||||
set_kind("binary")
|
||||
add_files("main.cpp", "game_scene.cpp")
|
||||
add_files("main.cpp", "game_scene.cpp", "instanced_test.cpp")
|
||||
add_includedirs("../../include", ".")
|
||||
add_deps("extra2d")
|
||||
|
||||
|
|
|
|||
|
|
@ -175,6 +175,31 @@ public:
|
|||
Material *getDefaultMaterialPtr();
|
||||
Mesh *getDefaultQuadPtr();
|
||||
|
||||
//===========================================================================
|
||||
// 实例化渲染资源
|
||||
//===========================================================================
|
||||
|
||||
/**
|
||||
* @brief 获取实例化渲染着色器
|
||||
* @return 实例化着色器句柄
|
||||
*/
|
||||
Handle<Shader> getInstancedShader();
|
||||
|
||||
/**
|
||||
* @brief 获取实例化渲染材质
|
||||
* @return 实例化材质句柄
|
||||
*/
|
||||
Handle<Material> getInstancedMaterial();
|
||||
|
||||
Shader *getInstancedShaderPtr();
|
||||
Material *getInstancedMaterialPtr();
|
||||
|
||||
/**
|
||||
* @brief 创建实例化渲染资源
|
||||
* @return 是否成功
|
||||
*/
|
||||
bool createInstancedResources();
|
||||
|
||||
//===========================================================================
|
||||
// 热重载
|
||||
//===========================================================================
|
||||
|
|
@ -259,6 +284,10 @@ private:
|
|||
Handle<Material> defaultMaterial_;
|
||||
Handle<Mesh> defaultQuad_;
|
||||
|
||||
// 实例化渲染资源
|
||||
Handle<Shader> instancedShader_;
|
||||
Handle<Material> instancedMaterial_;
|
||||
|
||||
// 热重载
|
||||
bool hotReloadEnabled_ = false;
|
||||
float hotReloadInterval_ = 1.0f;
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
#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>
|
||||
|
|
@ -15,6 +16,7 @@ namespace extra2d {
|
|||
class CommandQueue;
|
||||
class Material;
|
||||
class Mesh;
|
||||
class UniformBufferManager;
|
||||
template <typename T> class Ptr;
|
||||
|
||||
/**
|
||||
|
|
@ -68,6 +70,9 @@ struct DrawCommand {
|
|||
uint32_t textureCount; // 纹理数量
|
||||
BufferHandle materialUBO; // 材质 UBO
|
||||
uint32_t materialUBOSize; // 材质 UBO 大小
|
||||
uint32_t materialUBOOffset; // 材质 UBO 在全局缓冲区中的偏移
|
||||
BufferHandle instanceBuffer; // 实例数据缓冲区(实例化渲染使用)
|
||||
uint32_t instanceBufferStride; // 实例数据步长
|
||||
|
||||
// 变换和颜色数据(用于设置 shader uniform)
|
||||
Mat4 modelMatrix; // 模型矩阵
|
||||
|
|
@ -75,7 +80,8 @@ struct DrawCommand {
|
|||
|
||||
DrawCommand()
|
||||
: vertexCount(0), indexCount(0), instanceCount(1), textureCount(0),
|
||||
materialUBOSize(0), modelMatrix(glm::identity<Mat4>()), color(Color::White) {}
|
||||
materialUBOSize(0), materialUBOOffset(0), instanceBufferStride(0),
|
||||
modelMatrix(glm::identity<Mat4>()), color(Color::White) {}
|
||||
|
||||
// 检查是否使用索引绘制
|
||||
bool isIndexed() const { return indexCount > 0; }
|
||||
|
|
@ -265,10 +271,11 @@ public:
|
|||
* @brief 提交实例化绘制命令
|
||||
* @param material 材质
|
||||
* @param mesh 网格
|
||||
* @param instanceBuffer 实例数据缓冲区
|
||||
* @param instanceCount 实例数量
|
||||
*/
|
||||
void submitDrawInstanced(Ptr<Material> material, Ptr<Mesh> mesh,
|
||||
uint32_t instanceCount);
|
||||
BufferHandle instanceBuffer, uint32_t instanceCount);
|
||||
|
||||
/**
|
||||
* @brief 提交清除命令
|
||||
|
|
@ -293,6 +300,16 @@ public:
|
|||
*/
|
||||
void execute();
|
||||
|
||||
/**
|
||||
* @brief 更新全局 UBO 数据
|
||||
* @param viewProjection 视图投影矩阵
|
||||
* @param deltaTime 帧时间
|
||||
* @param screenWidth 屏幕宽度
|
||||
* @param screenHeight 屏幕高度
|
||||
*/
|
||||
void updateGlobalUBO(const Mat4& viewProjection, float deltaTime,
|
||||
uint32_t screenWidth, uint32_t screenHeight);
|
||||
|
||||
/**
|
||||
* @brief 获取当前命令数量
|
||||
* @return 命令数量
|
||||
|
|
@ -311,12 +328,23 @@ private:
|
|||
RHIContext *context_ = nullptr;
|
||||
std::unique_ptr<RHICommandList> commandList_;
|
||||
|
||||
// 全局 UBO 数据
|
||||
struct GlobalUBOData {
|
||||
float viewProjection[16];
|
||||
float time;
|
||||
float screenSize[2];
|
||||
float padding;
|
||||
// UBO 管理器
|
||||
std::unique_ptr<UniformBufferManager> uboManager_;
|
||||
|
||||
// 全局 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 数据缓冲区
|
||||
|
|
@ -328,6 +356,10 @@ private:
|
|||
// 材质到 ID 的映射
|
||||
std::unordered_map<Material *, uint32_t> materialIds_;
|
||||
|
||||
// 当前材质 UBO 缓冲区
|
||||
UniformBuffer* currentMaterialUBO_ = nullptr;
|
||||
uint32_t currentMaterialUBOOffset_ = 0;
|
||||
|
||||
/**
|
||||
* @brief 获取或创建材质 ID
|
||||
* @param material 材质指针
|
||||
|
|
@ -341,6 +373,13 @@ private:
|
|||
* @param batch 命令批次
|
||||
*/
|
||||
void executeBatch(uint32_t batchIndex, const CommandBatch &batch);
|
||||
|
||||
/**
|
||||
* @brief 分配材质 UBO 空间
|
||||
* @param size 需要的空间大小
|
||||
* @return 分配的 UBO 指针和偏移量
|
||||
*/
|
||||
std::pair<UniformBuffer*, uint32_t> allocateMaterialUBO(uint32_t size);
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
|
|||
|
|
@ -0,0 +1,208 @@
|
|||
#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 对齐规则
|
||||
*/
|
||||
struct InstanceData {
|
||||
// 第一组 16 字节
|
||||
Vec2 position; // 位置偏移 (8 bytes)
|
||||
float rotation; // 旋转角度 (4 bytes)
|
||||
float padding0; // 填充到 16 字节对齐 (4 bytes)
|
||||
|
||||
// 第二组 16 字节
|
||||
Vec2 scale; // 缩放 (8 bytes)
|
||||
float padding1[2]; // 填充到 16 字节对齐 (8 bytes)
|
||||
|
||||
// 第三组 16 字节
|
||||
Color color; // 颜色 (16 bytes) - r, g, b, a
|
||||
|
||||
// 第四组 16 字节
|
||||
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)
|
||||
, rotation(0.0f)
|
||||
, padding0(0.0f)
|
||||
, 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) {}
|
||||
};
|
||||
|
||||
static_assert(sizeof(InstanceData) == 64, "InstanceData size should be 64 bytes for std140 alignment");
|
||||
|
||||
/**
|
||||
* @brief 实例缓冲区
|
||||
*
|
||||
* 管理实例化渲染的实例数据缓冲区
|
||||
* 支持动态更新和双缓冲
|
||||
*/
|
||||
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 instance 实例数据
|
||||
* @return 实例索引
|
||||
*/
|
||||
uint32_t addInstance(const InstanceData& instance);
|
||||
|
||||
/**
|
||||
* @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(); }
|
||||
|
||||
private:
|
||||
BufferHandle bufferHandle_; // RHI 缓冲区句柄
|
||||
uint32_t maxInstances_ = 0; // 最大实例数量
|
||||
uint32_t instanceCount_ = 0; // 当前实例数量
|
||||
std::vector<InstanceData> cpuBuffer_; // CPU 端缓冲区
|
||||
};
|
||||
|
||||
/**
|
||||
* @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
|
||||
|
|
@ -109,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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -236,8 +236,9 @@ public:
|
|||
/**
|
||||
* @brief 执行渲染图
|
||||
* @param deltaTime 帧时间
|
||||
* @param viewProjection 视图投影矩阵
|
||||
*/
|
||||
void execute(float deltaTime);
|
||||
void execute(float deltaTime, const Mat4& viewProjection = Mat4(1.0f));
|
||||
|
||||
/**
|
||||
* @brief 获取纹理资源
|
||||
|
|
|
|||
|
|
@ -46,10 +46,11 @@ struct RenderCommand {
|
|||
Color color; // 顶点颜色
|
||||
};
|
||||
|
||||
// 实例化绘制命令数据
|
||||
// 实例化绘制命令数据
|
||||
struct DrawInstancedData {
|
||||
Handle<Mesh> mesh; // 网格句柄
|
||||
Handle<Material> material; // 材质句柄
|
||||
void* instanceBuffer; // 实例数据缓冲区指针 (InstanceBuffer*)
|
||||
uint32_t instanceCount; // 实例数量
|
||||
uint32_t instanceOffset; // 实例数据偏移
|
||||
};
|
||||
|
|
|
|||
|
|
@ -51,10 +51,11 @@ public:
|
|||
//===========================================================================
|
||||
|
||||
void setVertexBuffer(uint32_t slot, RHIBuffer *buffer,
|
||||
uint32_t offset = 0) override;
|
||||
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,
|
||||
|
|
@ -81,11 +82,11 @@ public:
|
|||
bool isRecording() const override;
|
||||
|
||||
// 设置 uniform 变量
|
||||
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);
|
||||
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:
|
||||
bool recording_ = false;
|
||||
|
|
|
|||
|
|
@ -89,8 +89,9 @@ public:
|
|||
* @param slot 槽位
|
||||
* @param buffer 缓冲区
|
||||
* @param offset 偏移(字节)
|
||||
* @param stride 步长(字节,0表示使用布局中的步长)
|
||||
*/
|
||||
virtual void setVertexBuffer(uint32_t slot, RHIBuffer* buffer, uint32_t offset = 0) = 0;
|
||||
virtual void setVertexBuffer(uint32_t slot, RHIBuffer* buffer, uint32_t offset = 0, uint32_t stride = 0) = 0;
|
||||
|
||||
/**
|
||||
* @brief 设置索引缓冲区
|
||||
|
|
@ -107,6 +108,15 @@ public:
|
|||
*/
|
||||
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 槽位
|
||||
|
|
@ -128,6 +138,41 @@ public:
|
|||
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;
|
||||
|
||||
//===========================================================================
|
||||
// 绘制命令
|
||||
//===========================================================================
|
||||
|
|
|
|||
|
|
@ -229,8 +229,21 @@ struct VertexAttribute {
|
|||
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;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -242,6 +255,14 @@ struct VertexLayout {
|
|||
|
||||
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);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -43,6 +43,22 @@ public:
|
|||
*/
|
||||
bool loadFromSource(const std::string &vsSource, const std::string &fsSource);
|
||||
|
||||
/**
|
||||
* @brief 从文件加载实例化着色器(支持实例属性)
|
||||
* @param vsPath 顶点着色器文件路径
|
||||
* @param fsPath 片段着色器文件路径
|
||||
* @return 加载是否成功
|
||||
*/
|
||||
bool loadInstancedFromFile(const std::string &vsPath, const std::string &fsPath);
|
||||
|
||||
/**
|
||||
* @brief 从源码加载实例化着色器(支持实例属性)
|
||||
* @param vsSource 顶点着色器源码
|
||||
* @param fsSource 片段着色器源码
|
||||
* @return 加载是否成功
|
||||
*/
|
||||
bool loadInstancedFromSource(const std::string &vsSource, const std::string &fsSource);
|
||||
|
||||
/**
|
||||
* @brief 获取 RHI 着色器句柄
|
||||
* @return RHI 着色器句柄
|
||||
|
|
@ -61,6 +77,17 @@ public:
|
|||
*/
|
||||
bool isLoaded() const { return handle_.isValid() && pipeline_.isValid(); }
|
||||
|
||||
/**
|
||||
* @brief 使用自定义顶点布局从源码加载着色器
|
||||
* @param vsSource 顶点着色器源码
|
||||
* @param fsSource 片段着色器源码
|
||||
* @param vertexLayout 顶点布局
|
||||
* @return 加载是否成功
|
||||
*/
|
||||
bool loadFromSourceWithLayout(const std::string &vsSource,
|
||||
const std::string &fsSource,
|
||||
const VertexLayout &vertexLayout);
|
||||
|
||||
/**
|
||||
* @brief 设置 Uniform Block 绑定槽位
|
||||
* @param name Uniform Block 名称
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -307,7 +307,7 @@ public:
|
|||
/**
|
||||
* @brief 渲染(收集渲染命令)
|
||||
*/
|
||||
void render();
|
||||
virtual void render();
|
||||
|
||||
private:
|
||||
std::string name_;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
@ -21,20 +19,19 @@ out vec4 fragColor;
|
|||
* 采样纹理并与顶点颜色、色调和透明度混合
|
||||
*/
|
||||
void main() {
|
||||
// 采样纹理(如果没有绑定纹理,texture 会返回 vec4(0,0,0,1) 或 vec4(1,1,1,1) 取决于实现)
|
||||
// 采样纹理
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
#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;
|
||||
};
|
||||
|
||||
// 顶点属性
|
||||
layout(location = 0) in vec2 aPosition; // 基础顶点位置
|
||||
layout(location = 1) in vec2 aTexCoord; // 基础 UV
|
||||
layout(location = 2) in vec4 aColor; // 基础颜色
|
||||
|
||||
// 实例属性 (每个实例)
|
||||
layout(location = 3) in vec2 aInstancePos; // 实例位置偏移
|
||||
layout(location = 4) in float aInstanceRot; // 实例旋转
|
||||
layout(location = 5) in vec2 aInstanceScale; // 实例缩放
|
||||
layout(location = 6) in vec4 aInstanceColor; // 实例颜色
|
||||
layout(location = 7) in vec4 aInstanceUV; // 实例 UV 区域
|
||||
|
||||
// 输出到片段着色器
|
||||
out vec2 vTexCoord;
|
||||
out vec4 vColor;
|
||||
|
||||
/**
|
||||
* @brief 顶点着色器入口(实例化版本)
|
||||
*
|
||||
* 计算顶点在裁剪空间中的位置,应用实例的变换
|
||||
*/
|
||||
void main() {
|
||||
// 构建旋转矩阵
|
||||
float cosRot = cos(aInstanceRot);
|
||||
float sinRot = sin(aInstanceRot);
|
||||
mat2 rotation = mat2(
|
||||
cosRot, -sinRot,
|
||||
sinRot, cosRot
|
||||
);
|
||||
|
||||
// 应用缩放和旋转
|
||||
vec2 scaledPos = aPosition * aInstanceScale;
|
||||
vec2 rotatedPos = rotation * scaledPos;
|
||||
|
||||
// 应用实例位置偏移
|
||||
vec2 worldPos = rotatedPos + aInstancePos;
|
||||
|
||||
// 计算裁剪空间位置
|
||||
gl_Position = uViewProjection * vec4(worldPos, 0.0, 1.0);
|
||||
|
||||
// 计算 UV 坐标(应用实例 UV 区域)
|
||||
vTexCoord = aTexCoord * aInstanceUV.zw + aInstanceUV.xy;
|
||||
|
||||
// 混合顶点颜色和实例颜色
|
||||
vColor = aColor * aInstanceColor;
|
||||
}
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
#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 <thread>
|
||||
#include <utils/logger.h>
|
||||
|
|
@ -46,6 +46,13 @@ void AssetsModule::onGLContextReady() {
|
|||
return;
|
||||
}
|
||||
|
||||
// 创建实例化渲染资源
|
||||
if (!createInstancedResources()) {
|
||||
E2D_LOG_WARN("Failed to create instanced resources, instanced rendering "
|
||||
"will not be available");
|
||||
// 不返回错误,实例化渲染是可选功能
|
||||
}
|
||||
|
||||
defaultResourcesCreated_ = true;
|
||||
E2D_LOG_INFO("Default resources created successfully");
|
||||
}
|
||||
|
|
@ -123,24 +130,24 @@ Handle<Texture> AssetsModule::load<Texture>(const std::string &path) {
|
|||
Handle<Texture> handle;
|
||||
{
|
||||
std::unique_lock<std::shared_mutex> lock(mutex_);
|
||||
|
||||
|
||||
// 双重检查,避免重复加载
|
||||
auto it = texturePathCache_.find(path);
|
||||
if (it != texturePathCache_.end() && textures_.isValid(it->second)) {
|
||||
return it->second;
|
||||
}
|
||||
|
||||
|
||||
handle = textures_.insert(texture);
|
||||
texturePathCache_[path] = handle;
|
||||
}
|
||||
|
||||
E2D_LOG_DEBUG("Loaded texture: {} -> handle index {}", path, handle.index());
|
||||
|
||||
|
||||
// 如果启用了热重载,添加文件监控
|
||||
if (hotReloadEnabled_) {
|
||||
addFileWatch(path, handle);
|
||||
}
|
||||
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
|
|
@ -177,8 +184,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_);
|
||||
|
|
@ -205,24 +211,24 @@ Handle<Shader> AssetsModule::load<Shader>(const std::string &path) {
|
|||
Handle<Shader> handle;
|
||||
{
|
||||
std::unique_lock<std::shared_mutex> lock(mutex_);
|
||||
|
||||
|
||||
// 双重检查
|
||||
auto it = shaderPathCache_.find(path);
|
||||
if (it != shaderPathCache_.end() && shaders_.isValid(it->second)) {
|
||||
return it->second;
|
||||
}
|
||||
|
||||
|
||||
handle = shaders_.insert(shader);
|
||||
shaderPathCache_[path] = handle;
|
||||
}
|
||||
|
||||
E2D_LOG_DEBUG("Loaded shader: {} -> handle index {}", path, handle.index());
|
||||
|
||||
|
||||
// 如果启用了热重载,添加文件监控
|
||||
if (hotReloadEnabled_) {
|
||||
addFileWatch(path, handle);
|
||||
}
|
||||
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
|
|
@ -258,26 +264,26 @@ Handle<Shader> AssetsModule::load<Shader>(const std::string &vertPath,
|
|||
Handle<Shader> handle;
|
||||
{
|
||||
std::unique_lock<std::shared_mutex> lock(mutex_);
|
||||
|
||||
|
||||
// 双重检查
|
||||
auto it = shaderPathCache_.find(cacheKey);
|
||||
if (it != shaderPathCache_.end() && shaders_.isValid(it->second)) {
|
||||
return it->second;
|
||||
}
|
||||
|
||||
|
||||
handle = shaders_.insert(shader);
|
||||
shaderPathCache_[cacheKey] = handle;
|
||||
}
|
||||
|
||||
E2D_LOG_DEBUG("Loaded shader: {} + {} -> handle index {}", vertPath, fragPath,
|
||||
handle.index());
|
||||
|
||||
|
||||
// 如果启用了热重载,添加文件监控
|
||||
if (hotReloadEnabled_) {
|
||||
addFileWatch(vertPath, handle);
|
||||
addFileWatch(fragPath, handle);
|
||||
}
|
||||
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
|
|
@ -408,12 +414,32 @@ bool AssetsModule::createDefaultResources() {
|
|||
}
|
||||
|
||||
{
|
||||
// 创建材质布局(与着色器中的 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 with default texture");
|
||||
E2D_LOG_DEBUG("Created default material with default texture and layout");
|
||||
}
|
||||
|
||||
{
|
||||
|
|
@ -463,168 +489,231 @@ Material *AssetsModule::getDefaultMaterialPtr() {
|
|||
|
||||
Mesh *AssetsModule::getDefaultQuadPtr() { return meshes_.get(defaultQuad_); }
|
||||
|
||||
Handle<Shader> AssetsModule::getInstancedShader() { return instancedShader_; }
|
||||
|
||||
Handle<Material> AssetsModule::getInstancedMaterial() {
|
||||
return instancedMaterial_;
|
||||
}
|
||||
|
||||
Shader *AssetsModule::getInstancedShaderPtr() {
|
||||
return shaders_.get(instancedShader_);
|
||||
}
|
||||
|
||||
Material *AssetsModule::getInstancedMaterialPtr() {
|
||||
return materials_.get(instancedMaterial_);
|
||||
}
|
||||
|
||||
bool AssetsModule::createInstancedResources() {
|
||||
// 加载实例化着色器
|
||||
std::filesystem::path vertPath = "shader/sprite_instanced.vert";
|
||||
std::filesystem::path fragPath = "shader/sprite_instanced.frag";
|
||||
|
||||
if (!std::filesystem::exists(vertPath) ||
|
||||
!std::filesystem::exists(fragPath)) {
|
||||
E2D_LOG_ERROR("Instanced shader files not found: {}, {}", vertPath.string(),
|
||||
fragPath.string());
|
||||
return false;
|
||||
}
|
||||
|
||||
Ptr<Shader> shader = makePtr<Shader>();
|
||||
if (!shader->loadInstancedFromFile(vertPath.string(), fragPath.string())) {
|
||||
E2D_LOG_ERROR("Failed to load instanced shader from files: {}, {}",
|
||||
vertPath.string(), fragPath.string());
|
||||
return false;
|
||||
}
|
||||
|
||||
instancedShader_ = shaders_.insert(shader);
|
||||
E2D_LOG_DEBUG("Loaded instanced shader from files: {}, {}", vertPath.string(),
|
||||
fragPath.string());
|
||||
|
||||
// 创建实例化材质布局
|
||||
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(instancedShader_));
|
||||
material->setLayout(layout);
|
||||
|
||||
// 设置默认材质参数
|
||||
material->setColor("uColor", Color::White);
|
||||
material->setColor("uTintColor", Color::White);
|
||||
material->setFloat("uOpacity", 1.0f);
|
||||
|
||||
// 添加默认纹理到材质
|
||||
material->setTexture("uTexture", getPtr(defaultTexture_), 0);
|
||||
instancedMaterial_ = materials_.insert(material);
|
||||
E2D_LOG_DEBUG("Created instanced material with default texture and layout");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//===========================================================================
|
||||
// 热重载
|
||||
//===========================================================================
|
||||
|
||||
void AssetsModule::enableHotReload(bool enable) {
|
||||
hotReloadEnabled_ = enable;
|
||||
if (enable) {
|
||||
E2D_LOG_INFO("Hot reload enabled");
|
||||
} else {
|
||||
E2D_LOG_INFO("Hot reload disabled");
|
||||
}
|
||||
void AssetsModule::enableHotReload(bool enable) {
|
||||
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::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::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<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::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::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::reloadShader(const FileWatchInfo& info) {
|
||||
if (!shaderLoader_ || !info.shaderHandle.isValid()) {
|
||||
return;
|
||||
void AssetsModule::reloadShader(const FileWatchInfo &info) {
|
||||
if (!shaderLoader_ || !info.shaderHandle.isValid()) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
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 (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;
|
||||
}
|
||||
|
||||
if (cacheKey.empty()) {
|
||||
E2D_LOG_WARN("Shader cache key not found for: {}", 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;
|
||||
}
|
||||
|
||||
// 解析顶点/片段着色器路径
|
||||
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: {}", info.path);
|
||||
notifyShaderReloaded(info.shaderHandle);
|
||||
}
|
||||
|
||||
void AssetsModule::checkForChanges() {
|
||||
if (!hotReloadEnabled_ || fileWatchList_.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::unique_lock<std::shared_mutex> lock(mutex_);
|
||||
|
||||
for (auto& info : fileWatchList_) {
|
||||
try {
|
||||
if (!std::filesystem::exists(info.path)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto currentTime = std::filesystem::last_write_time(info.path);
|
||||
if (currentTime != info.lastWriteTime) {
|
||||
info.lastWriteTime = currentTime;
|
||||
|
||||
// 解锁进行重载操作
|
||||
lock.unlock();
|
||||
|
||||
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());
|
||||
if (!hotReloadEnabled_ || fileWatchList_.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::unique_lock<std::shared_mutex> lock(mutex_);
|
||||
|
||||
for (auto &info : fileWatchList_) {
|
||||
try {
|
||||
if (!std::filesystem::exists(info.path)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto currentTime = std::filesystem::last_write_time(info.path);
|
||||
if (currentTime != info.lastWriteTime) {
|
||||
info.lastWriteTime = currentTime;
|
||||
|
||||
// 解锁进行重载操作
|
||||
lock.unlock();
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//===========================================================================
|
||||
|
|
@ -632,216 +721,218 @@ void AssetsModule::checkForChanges() {
|
|||
//===========================================================================
|
||||
|
||||
void AssetsModule::initAsyncLoader(uint32_t threadCount) {
|
||||
if (asyncLoaderRunning_) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (threadCount == 0) {
|
||||
threadCount = std::max(1u, std::thread::hardware_concurrency() / 2);
|
||||
}
|
||||
|
||||
asyncLoaderRunning_ = true;
|
||||
|
||||
for (uint32_t i = 0; i < threadCount; ++i) {
|
||||
workerThreads_.emplace_back(&AssetsModule::workerThreadLoop, this);
|
||||
}
|
||||
|
||||
E2D_LOG_INFO("Async loader initialized with {} threads", threadCount);
|
||||
if (asyncLoaderRunning_) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (threadCount == 0) {
|
||||
threadCount = std::max(1u, std::thread::hardware_concurrency() / 2);
|
||||
}
|
||||
|
||||
asyncLoaderRunning_ = true;
|
||||
|
||||
for (uint32_t i = 0; i < threadCount; ++i) {
|
||||
workerThreads_.emplace_back(&AssetsModule::workerThreadLoop, this);
|
||||
}
|
||||
|
||||
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();
|
||||
|
||||
for (auto& thread : workerThreads_) {
|
||||
if (thread.joinable()) {
|
||||
thread.join();
|
||||
}
|
||||
}
|
||||
|
||||
workerThreads_.clear();
|
||||
|
||||
std::lock_guard<std::mutex> lock(queueMutex_);
|
||||
loadQueue_.clear();
|
||||
|
||||
E2D_LOG_INFO("Async loader shutdown");
|
||||
}
|
||||
|
||||
workerThreads_.clear();
|
||||
|
||||
std::lock_guard<std::mutex> lock(queueMutex_);
|
||||
loadQueue_.clear();
|
||||
|
||||
E2D_LOG_INFO("Async loader shutdown");
|
||||
}
|
||||
|
||||
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();
|
||||
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();
|
||||
}
|
||||
|
||||
void AssetsModule::workerThreadLoop() {
|
||||
while (asyncLoaderRunning_) {
|
||||
LoadTask task;
|
||||
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(queueMutex_);
|
||||
queueCV_.wait(lock, [this] {
|
||||
return !loadQueue_.empty() || !asyncLoaderRunning_;
|
||||
});
|
||||
|
||||
if (!asyncLoaderRunning_) {
|
||||
break;
|
||||
}
|
||||
|
||||
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);
|
||||
});
|
||||
}
|
||||
}
|
||||
while (asyncLoaderRunning_) {
|
||||
LoadTask task;
|
||||
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(queueMutex_);
|
||||
queueCV_.wait(
|
||||
lock, [this] { return !loadQueue_.empty() || !asyncLoaderRunning_; });
|
||||
|
||||
if (!asyncLoaderRunning_) {
|
||||
break;
|
||||
}
|
||||
|
||||
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); });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AssetsModule::processAsyncCallbacks() {
|
||||
std::vector<std::function<void()>> callbacks;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(callbackMutex_);
|
||||
callbacks = std::move(completedCallbacks_);
|
||||
completedCallbacks_.clear();
|
||||
}
|
||||
|
||||
for (auto& callback : callbacks) {
|
||||
callback();
|
||||
}
|
||||
std::vector<std::function<void()>> callbacks;
|
||||
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(callbackMutex_);
|
||||
callbacks = std::move(completedCallbacks_);
|
||||
completedCallbacks_.clear();
|
||||
}
|
||||
|
||||
for (auto &callback : callbacks) {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
|
||||
//===========================================================================
|
||||
// 资源依赖跟踪
|
||||
//===========================================================================
|
||||
|
||||
void AssetsModule::registerMaterialDependency(Handle<Material> material, Handle<Texture> texture) {
|
||||
if (!material.isValid() || !texture.isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::unique_lock<std::shared_mutex> lock(dependencyMutex_);
|
||||
|
||||
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);
|
||||
}
|
||||
void AssetsModule::registerMaterialDependency(Handle<Material> material,
|
||||
Handle<Texture> texture) {
|
||||
if (!material.isValid() || !texture.isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::unique_lock<std::shared_mutex> lock(dependencyMutex_);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
void AssetsModule::registerMaterialDependency(Handle<Material> material, Handle<Shader> shader) {
|
||||
if (!material.isValid() || !shader.isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::unique_lock<std::shared_mutex> lock(dependencyMutex_);
|
||||
|
||||
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);
|
||||
}
|
||||
void AssetsModule::registerMaterialDependency(Handle<Material> material,
|
||||
Handle<Shader> shader) {
|
||||
if (!material.isValid() || !shader.isValid()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::unique_lock<std::shared_mutex> lock(dependencyMutex_);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//===========================================================================
|
||||
|
|
@ -849,14 +940,14 @@ void AssetsModule::notifyShaderReloaded(Handle<Shader> shader) {
|
|||
//===========================================================================
|
||||
|
||||
AssetsModule::Stats AssetsModule::getStats() const {
|
||||
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;
|
||||
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;
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
#include <renderer/command_queue.h>
|
||||
#include <renderer/instance_buffer.h>
|
||||
#include <renderer/material.h>
|
||||
#include <renderer/mesh.h>
|
||||
#include <renderer/rhi_module.h>
|
||||
|
|
@ -109,13 +111,18 @@ 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_)) {
|
||||
materialIds_(std::move(other.materialIds_)),
|
||||
currentMaterialUBO_(other.currentMaterialUBO_),
|
||||
currentMaterialUBOOffset_(other.currentMaterialUBOOffset_) {
|
||||
other.context_ = nullptr;
|
||||
other.globalUBOData_ = {};
|
||||
other.nextMaterialId_ = 1;
|
||||
other.currentMaterialUBO_ = nullptr;
|
||||
other.currentMaterialUBOOffset_ = 0;
|
||||
}
|
||||
|
||||
CommandQueue &CommandQueue::operator=(CommandQueue &&other) noexcept {
|
||||
|
|
@ -126,14 +133,19 @@ CommandQueue &CommandQueue::operator=(CommandQueue &&other) noexcept {
|
|||
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_;
|
||||
currentMaterialUBOOffset_ = other.currentMaterialUBOOffset_;
|
||||
|
||||
other.context_ = nullptr;
|
||||
other.globalUBOData_ = {};
|
||||
other.nextMaterialId_ = 1;
|
||||
other.currentMaterialUBO_ = nullptr;
|
||||
other.currentMaterialUBOOffset_ = 0;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
|
@ -165,6 +177,13 @@ bool CommandQueue::initialize() {
|
|||
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
|
||||
|
||||
|
|
@ -173,6 +192,7 @@ bool CommandQueue::initialize() {
|
|||
}
|
||||
|
||||
void CommandQueue::shutdown() {
|
||||
uboManager_.reset();
|
||||
commandList_.reset();
|
||||
context_ = nullptr;
|
||||
materialUBOData_.clear();
|
||||
|
|
@ -186,6 +206,15 @@ void CommandQueue::beginFrame() {
|
|||
nextMaterialId_ = 1;
|
||||
materialUBOData_.clear();
|
||||
|
||||
// 重置材质 UBO 分配状态
|
||||
currentMaterialUBO_ = nullptr;
|
||||
currentMaterialUBOOffset_ = 0;
|
||||
|
||||
// 重置 UBO 管理器的材质 UBO 池
|
||||
if (uboManager_) {
|
||||
uboManager_->resetMaterialUBOs();
|
||||
}
|
||||
|
||||
// 开始录制命令
|
||||
if (commandList_) {
|
||||
commandList_->begin();
|
||||
|
|
@ -210,6 +239,31 @@ uint32_t CommandQueue::getMaterialId(Material *material) {
|
|||
return id;
|
||||
}
|
||||
|
||||
std::pair<UniformBuffer*, uint32_t> CommandQueue::allocateMaterialUBO(uint32_t size) {
|
||||
if (!uboManager_ || size == 0) {
|
||||
return {nullptr, 0};
|
||||
}
|
||||
|
||||
// 如果当前 UBO 没有足够的空间,获取一个新的
|
||||
if (currentMaterialUBO_ == nullptr ||
|
||||
currentMaterialUBOOffset_ + size > currentMaterialUBO_->getSize()) {
|
||||
currentMaterialUBO_ = uboManager_->acquireMaterialUBO(size);
|
||||
currentMaterialUBOOffset_ = 0;
|
||||
}
|
||||
|
||||
if (!currentMaterialUBO_) {
|
||||
return {nullptr, 0};
|
||||
}
|
||||
|
||||
uint32_t offset = currentMaterialUBOOffset_;
|
||||
currentMaterialUBOOffset_ += size;
|
||||
|
||||
// 对齐到 16 字节(std140 要求)
|
||||
currentMaterialUBOOffset_ = (currentMaterialUBOOffset_ + 15) & ~15;
|
||||
|
||||
return {currentMaterialUBO_, offset};
|
||||
}
|
||||
|
||||
void CommandQueue::submitDraw(Ptr<Material> material, Ptr<Mesh> mesh,
|
||||
const struct Transform &transform,
|
||||
const Color &color) {
|
||||
|
|
@ -248,22 +302,36 @@ void CommandQueue::submitDraw(Ptr<Material> material, Ptr<Mesh> mesh,
|
|||
}
|
||||
}
|
||||
|
||||
// 分配材质 UBO 数据
|
||||
// 分配材质 UBO 空间并更新数据
|
||||
uint32_t materialDataSize = material->getDataSize();
|
||||
if (materialDataSize > 0) {
|
||||
uint32_t uboOffset = static_cast<uint32_t>(materialUBOData_.size());
|
||||
materialUBOData_.resize(uboOffset + materialDataSize);
|
||||
std::memcpy(materialUBOData_.data() + uboOffset, material->getData(),
|
||||
materialDataSize);
|
||||
cmd.materialUBOSize = materialDataSize;
|
||||
// 注意:实际的 UBO 句柄需要在执行时从 UBO 管理器获取
|
||||
auto [ubo, offset] = allocateMaterialUBO(materialDataSize);
|
||||
if (ubo) {
|
||||
// 复制材质数据到临时缓冲区,以便修改颜色
|
||||
std::vector<uint8_t> uboData(materialDataSize);
|
||||
std::memcpy(uboData.data(), 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(uboData.data() + param->offset, &color.r, sizeof(float) * 4);
|
||||
}
|
||||
}
|
||||
|
||||
ubo->update(uboData.data(), materialDataSize, offset);
|
||||
cmd.materialUBO = BufferHandle(ubo->getRHIBuffer());
|
||||
cmd.materialUBOSize = materialDataSize;
|
||||
cmd.materialUBOOffset = offset;
|
||||
}
|
||||
}
|
||||
|
||||
sorter_.addCommand(cmd);
|
||||
}
|
||||
|
||||
void CommandQueue::submitDrawInstanced(Ptr<Material> material, Ptr<Mesh> mesh,
|
||||
uint32_t instanceCount) {
|
||||
BufferHandle instanceBuffer, uint32_t instanceCount) {
|
||||
if (!material || !mesh || !material->getShader() || instanceCount == 0) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -284,6 +352,10 @@ void CommandQueue::submitDrawInstanced(Ptr<Material> material, Ptr<Mesh> mesh,
|
|||
cmd.indexCount = mesh->getIndexCount();
|
||||
cmd.instanceCount = instanceCount;
|
||||
|
||||
// 设置实例缓冲区
|
||||
cmd.instanceBuffer = instanceBuffer;
|
||||
cmd.instanceBufferStride = sizeof(InstanceData); // 64 bytes
|
||||
|
||||
// 设置纹理
|
||||
const auto &textures = material->getTextures();
|
||||
cmd.textureCount =
|
||||
|
|
@ -294,14 +366,16 @@ void CommandQueue::submitDrawInstanced(Ptr<Material> material, Ptr<Mesh> mesh,
|
|||
}
|
||||
}
|
||||
|
||||
// 材质 UBO
|
||||
// 分配材质 UBO 空间并更新数据
|
||||
uint32_t materialDataSize = material->getDataSize();
|
||||
if (materialDataSize > 0) {
|
||||
uint32_t uboOffset = static_cast<uint32_t>(materialUBOData_.size());
|
||||
materialUBOData_.resize(uboOffset + materialDataSize);
|
||||
std::memcpy(materialUBOData_.data() + uboOffset, material->getData(),
|
||||
materialDataSize);
|
||||
cmd.materialUBOSize = materialDataSize;
|
||||
auto [ubo, offset] = allocateMaterialUBO(materialDataSize);
|
||||
if (ubo) {
|
||||
ubo->update(material->getData(), materialDataSize, offset);
|
||||
cmd.materialUBO = BufferHandle(ubo->getRHIBuffer());
|
||||
cmd.materialUBOSize = materialDataSize;
|
||||
cmd.materialUBOOffset = offset;
|
||||
}
|
||||
}
|
||||
|
||||
sorter_.addCommand(cmd);
|
||||
|
|
@ -333,6 +407,28 @@ void CommandQueue::setViewport(int32_t x, int32_t y, int32_t width,
|
|||
commandList_->setViewport(viewport);
|
||||
}
|
||||
|
||||
void CommandQueue::updateGlobalUBO(const Mat4& viewProjection, float deltaTime,
|
||||
uint32_t screenWidth, uint32_t screenHeight) {
|
||||
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_));
|
||||
}
|
||||
|
||||
void CommandQueue::execute() {
|
||||
if (!commandList_) {
|
||||
E2D_LOG_ERROR("CommandQueue::execute: commandList is null");
|
||||
|
|
@ -366,6 +462,14 @@ void CommandQueue::executeBatch(uint32_t batchIndex, const CommandBatch &batch)
|
|||
E2D_LOG_WARN("Batch has no valid pipeline!");
|
||||
}
|
||||
|
||||
// 绑定全局 UBO (binding = 0)
|
||||
if (uboManager_) {
|
||||
UniformBuffer* globalUBO = uboManager_->getGlobalUBO();
|
||||
if (globalUBO) {
|
||||
commandList_->setUniformBuffer(0, globalUBO->getRHIBuffer());
|
||||
}
|
||||
}
|
||||
|
||||
// 绑定纹理
|
||||
if (batch.textureCount > 0) {
|
||||
for (uint32_t i = 0; i < batch.textureCount; ++i) {
|
||||
|
|
@ -394,25 +498,24 @@ void CommandQueue::executeBatch(uint32_t batchIndex, const CommandBatch &batch)
|
|||
E2D_LOG_WARN("Draw command has no valid vertex buffer!");
|
||||
}
|
||||
|
||||
// 绑定实例缓冲区(实例化渲染)
|
||||
if (cmd.isInstanced() && cmd.instanceBuffer.isValid()) {
|
||||
commandList_->setVertexBuffer(1, cmd.instanceBuffer.get(), 0, cmd.instanceBufferStride);
|
||||
}
|
||||
|
||||
// 绑定索引缓冲区(如果有)
|
||||
if (cmd.isIndexed() && cmd.indexBuffer.isValid()) {
|
||||
commandList_->setIndexBuffer(cmd.indexBuffer.get(), IndexType::UInt16, 0);
|
||||
}
|
||||
|
||||
// 设置 shader uniform 变量
|
||||
auto* glCmdList = dynamic_cast<extra2d::GLCommandList*>(commandList_.get());
|
||||
if (glCmdList) {
|
||||
// 设置模型矩阵
|
||||
glCmdList->setUniform("uModelMatrix", cmd.modelMatrix);
|
||||
// 设置颜色
|
||||
glCmdList->setUniform("uColor", cmd.color);
|
||||
// 设置 view projection 矩阵
|
||||
Mat4 viewProj = glm::ortho(0.0f, 1280.0f, 0.0f, 720.0f, -1.0f, 1.0f);
|
||||
glCmdList->setUniform("uViewProjection", viewProj);
|
||||
// 设置色调颜色
|
||||
glCmdList->setUniform("uTintColor", Color::White);
|
||||
// 设置透明度
|
||||
glCmdList->setUniform("uOpacity", 1.0f);
|
||||
// 绑定材质 UBO (binding = 1)
|
||||
if (cmd.materialUBO.isValid()) {
|
||||
commandList_->setUniformBuffer(1, cmd.materialUBO.get(), cmd.materialUBOOffset, cmd.materialUBOSize);
|
||||
}
|
||||
|
||||
// 设置模型矩阵(仅在非实例化渲染时使用)
|
||||
if (!cmd.isInstanced()) {
|
||||
commandList_->setUniform("uModelMatrix", cmd.modelMatrix);
|
||||
}
|
||||
|
||||
// 绘制
|
||||
|
|
|
|||
|
|
@ -0,0 +1,216 @@
|
|||
#include <renderer/instance_buffer.h>
|
||||
#include <renderer/rhi_module.h>
|
||||
#include <renderer/rhi/rhi_device.h>
|
||||
#include <utils/logger.h>
|
||||
#include <cstring>
|
||||
|
||||
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_)) {
|
||||
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_);
|
||||
|
||||
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);
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
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_;
|
||||
}
|
||||
|
||||
RHIBuffer* rhiBuffer = bufferHandle_.get();
|
||||
if (rhiBuffer) {
|
||||
rhiBuffer->update(instances, count * sizeof(InstanceData), 0);
|
||||
}
|
||||
|
||||
instanceCount_ = 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;
|
||||
}
|
||||
|
||||
// 更新 GPU 缓冲区
|
||||
RHIBuffer* rhiBuffer = bufferHandle_.get();
|
||||
if (rhiBuffer) {
|
||||
rhiBuffer->update(&instance, sizeof(InstanceData), index * sizeof(InstanceData));
|
||||
}
|
||||
|
||||
return index;
|
||||
}
|
||||
|
||||
void InstanceBuffer::clear() {
|
||||
instanceCount_ = 0;
|
||||
cpuBuffer_.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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -179,7 +179,7 @@ bool RenderGraph::compile() {
|
|||
return true;
|
||||
}
|
||||
|
||||
void RenderGraph::execute(float deltaTime) {
|
||||
void RenderGraph::execute(float deltaTime, const Mat4 &viewProjection) {
|
||||
if (!compiled_) {
|
||||
if (!compile()) {
|
||||
E2D_LOG_ERROR("Failed to compile RenderGraph");
|
||||
|
|
@ -187,8 +187,9 @@ void RenderGraph::execute(float deltaTime) {
|
|||
}
|
||||
}
|
||||
|
||||
// 注意:beginFrame 现在由 RendererModule::onRenderBegin 调用
|
||||
// 这里不再调用,以避免清空已经提交的渲染命令
|
||||
// 更新全局 UBO
|
||||
commandQueue_.updateGlobalUBO(viewProjection, deltaTime, outputWidth_,
|
||||
outputHeight_);
|
||||
|
||||
// 获取 RHI 上下文
|
||||
auto *rhiModule = RHIModule::get();
|
||||
|
|
@ -212,8 +213,6 @@ void RenderGraph::execute(float deltaTime) {
|
|||
}
|
||||
}
|
||||
|
||||
// 执行命令队列
|
||||
|
||||
// 执行命令队列
|
||||
commandQueue_.execute();
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
#include <assets/assets_module.h>
|
||||
#include <platform/window_module.h>
|
||||
#include <renderer/instance_buffer.h>
|
||||
#include <renderer/render_graph.h>
|
||||
#include <renderer/renderer_module.h>
|
||||
#include <renderer/rhi_module.h>
|
||||
|
|
@ -208,11 +209,14 @@ void RendererModule::onRenderSubmit(const RenderCommand &cmd) {
|
|||
|
||||
Material *material = assets->get(cmd.drawInstanced.material);
|
||||
Mesh *mesh = assets->get(cmd.drawInstanced.mesh);
|
||||
InstanceBuffer *instanceBuffer =
|
||||
static_cast<InstanceBuffer *>(cmd.drawInstanced.instanceBuffer);
|
||||
|
||||
if (material && mesh) {
|
||||
commandQueue_->submitDrawInstanced(Ptr<Material>(material),
|
||||
Ptr<Mesh>(mesh),
|
||||
cmd.drawInstanced.instanceCount);
|
||||
if (material && mesh && instanceBuffer && instanceBuffer->isValid()) {
|
||||
commandQueue_->submitDrawInstanced(
|
||||
Ptr<Material>(material), Ptr<Mesh>(mesh),
|
||||
BufferHandle(instanceBuffer->getRHIBuffer()),
|
||||
cmd.drawInstanced.instanceCount);
|
||||
stats_.commandsSubmitted++;
|
||||
}
|
||||
break;
|
||||
|
|
@ -248,11 +252,8 @@ void RendererModule::onRenderEnd() {
|
|||
return;
|
||||
}
|
||||
|
||||
// 执行渲染图
|
||||
|
||||
// 执行渲染图
|
||||
// 这里会执行所有渲染通道并提交命令
|
||||
renderGraph_.execute(0.016f); // TODO: 使用实际的 deltaTime
|
||||
// 执行渲染图,传递视图投影矩阵
|
||||
renderGraph_.execute(0.016f, viewProjectionMatrix_);
|
||||
|
||||
// 获取统计信息
|
||||
if (commandQueue_) {
|
||||
|
|
|
|||
|
|
@ -85,43 +85,52 @@ void GLCommandList::setPipeline(RHIPipeline *pipeline) {
|
|||
}
|
||||
|
||||
void GLCommandList::setVertexBuffer(uint32_t slot, RHIBuffer *buffer,
|
||||
uint32_t offset) {
|
||||
if (buffer) {
|
||||
auto *glBuffer = static_cast<GLBuffer *>(buffer);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, glBuffer->getGLBuffer());
|
||||
currentVertexBuffer_ = glBuffer;
|
||||
uint32_t offset, uint32_t stride) {
|
||||
if (!buffer || !currentPipeline_) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果有当前管线,配置顶点属性
|
||||
if (currentPipeline_) {
|
||||
const auto &layout = currentPipeline_->getVertexLayout();
|
||||
for (const auto &attr : layout.attributes) {
|
||||
glEnableVertexAttribArray(attr.location);
|
||||
auto *glBuffer = static_cast<GLBuffer *>(buffer);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, glBuffer->getGLBuffer());
|
||||
|
||||
GLenum type = GL_FLOAT;
|
||||
GLint size = 1;
|
||||
GLboolean normalized = GL_FALSE;
|
||||
// 只配置属于当前 slot 的属性
|
||||
const auto &layout = currentPipeline_->getVertexLayout();
|
||||
// 使用传入的步长,如果为0则使用布局中的步长
|
||||
uint32_t actualStride = stride > 0 ? stride : layout.stride;
|
||||
|
||||
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));
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -141,6 +150,20 @@ void GLCommandList::setUniformBuffer(uint32_t slot, RHIBuffer *buffer) {
|
|||
}
|
||||
}
|
||||
|
||||
void GLCommandList::setUniformBuffer(uint32_t slot, RHIBuffer *buffer, uint32_t offset, uint32_t size) {
|
||||
if (buffer) {
|
||||
auto *glBuffer = static_cast<GLBuffer *>(buffer);
|
||||
GLuint glBufferId = glBuffer->getGLBuffer();
|
||||
if (size == 0) {
|
||||
// 绑定整个缓冲区
|
||||
glBindBufferBase(GL_UNIFORM_BUFFER, slot, glBufferId);
|
||||
} else {
|
||||
// 绑定缓冲区范围
|
||||
glBindBufferRange(GL_UNIFORM_BUFFER, slot, glBufferId, offset, size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GLCommandList::setTexture(uint32_t slot, RHITexture *texture) {
|
||||
if (texture) {
|
||||
auto *glTexture = static_cast<GLTexture *>(texture);
|
||||
|
|
@ -213,45 +236,45 @@ bool GLCommandList::isRecording() const {
|
|||
return recording_;
|
||||
}
|
||||
|
||||
void GLCommandList::setUniform(const std::string& name, float value) {
|
||||
void GLCommandList::setUniform(const char* name, float value) {
|
||||
if (currentShaderProgram_ != 0) {
|
||||
GLint location = glGetUniformLocation(currentShaderProgram_, name.c_str());
|
||||
GLint location = glGetUniformLocation(currentShaderProgram_, name);
|
||||
if (location != -1) {
|
||||
glUniform1f(location, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GLCommandList::setUniform(const std::string& name, const Vec2& value) {
|
||||
void GLCommandList::setUniform(const char* name, const Vec2& value) {
|
||||
if (currentShaderProgram_ != 0) {
|
||||
GLint location = glGetUniformLocation(currentShaderProgram_, name.c_str());
|
||||
GLint location = glGetUniformLocation(currentShaderProgram_, name);
|
||||
if (location != -1) {
|
||||
glUniform2f(location, value.x, value.y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GLCommandList::setUniform(const std::string& name, const Vec3& value) {
|
||||
void GLCommandList::setUniform(const char* name, const Vec3& value) {
|
||||
if (currentShaderProgram_ != 0) {
|
||||
GLint location = glGetUniformLocation(currentShaderProgram_, name.c_str());
|
||||
GLint location = glGetUniformLocation(currentShaderProgram_, name);
|
||||
if (location != -1) {
|
||||
glUniform3f(location, value.x, value.y, value.z);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GLCommandList::setUniform(const std::string& name, const Color& value) {
|
||||
void GLCommandList::setUniform(const char* name, const Color& value) {
|
||||
if (currentShaderProgram_ != 0) {
|
||||
GLint location = glGetUniformLocation(currentShaderProgram_, name.c_str());
|
||||
GLint location = glGetUniformLocation(currentShaderProgram_, name);
|
||||
if (location != -1) {
|
||||
glUniform4f(location, value.r, value.g, value.b, value.a);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GLCommandList::setUniform(const std::string& name, const Mat4& value) {
|
||||
void GLCommandList::setUniform(const char* name, const Mat4& value) {
|
||||
if (currentShaderProgram_ != 0) {
|
||||
GLint location = glGetUniformLocation(currentShaderProgram_, name.c_str());
|
||||
GLint location = glGetUniformLocation(currentShaderProgram_, name);
|
||||
if (location != -1) {
|
||||
glUniformMatrix4fv(location, 1, GL_FALSE, glm::value_ptr(value));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,6 +42,80 @@ bool Shader::loadFromFile(const std::string &vsPath,
|
|||
|
||||
bool Shader::loadFromSource(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 loadFromSourceWithLayout(vsSource, fsSource, vertexLayout);
|
||||
}
|
||||
|
||||
bool Shader::loadInstancedFromFile(const std::string &vsPath,
|
||||
const std::string &fsPath) {
|
||||
// 读取顶点着色器
|
||||
std::ifstream vsFile(vsPath);
|
||||
if (!vsFile.is_open()) {
|
||||
E2D_LOG_ERROR("Failed to open instanced 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 instanced fragment shader: {}", fsPath);
|
||||
return false;
|
||||
}
|
||||
std::stringstream fsStream;
|
||||
fsStream << fsFile.rdbuf();
|
||||
std::string fsSource = fsStream.str();
|
||||
|
||||
return loadInstancedFromSource(vsSource, fsSource);
|
||||
}
|
||||
|
||||
bool Shader::loadInstancedFromSource(const std::string &vsSource,
|
||||
const std::string &fsSource) {
|
||||
// 创建实例化渲染顶点布局
|
||||
// 槽位0:顶点属性(位置、UV、颜色)
|
||||
// 槽位1:实例属性(位置、旋转、缩放、颜色、UV区域)
|
||||
VertexLayout vertexLayout;
|
||||
|
||||
// 顶点属性(每个顶点)- 槽位0
|
||||
vertexLayout.addAttribute(0, VertexFormat::Float2, 0); // 位置
|
||||
vertexLayout.addAttribute(1, VertexFormat::Float2, 8); // UV
|
||||
vertexLayout.addAttribute(2, VertexFormat::Float4, 16); // 颜色
|
||||
|
||||
// 实例属性(每个实例)- 槽位1
|
||||
// InstanceData 布局:
|
||||
// Vec2 position; // offset 0
|
||||
// float rotation; // offset 8
|
||||
// float padding0; // offset 12
|
||||
// Vec2 scale; // offset 16
|
||||
// float padding1[2]; // offset 24
|
||||
// Color color; // offset 32
|
||||
// float uvX; // offset 48
|
||||
// float uvY; // offset 52
|
||||
// float uvWidth; // offset 56
|
||||
// float uvHeight; // offset 60
|
||||
vertexLayout.addAttribute(VertexAttribute::perInstance(3, VertexFormat::Float2, 0, 1)); // iPosition
|
||||
vertexLayout.addAttribute(VertexAttribute::perInstance(4, VertexFormat::Float1, 8, 1)); // iRotation
|
||||
vertexLayout.addAttribute(VertexAttribute::perInstance(5, VertexFormat::Float2, 16, 1)); // iScale
|
||||
vertexLayout.addAttribute(VertexAttribute::perInstance(6, VertexFormat::Float4, 32, 1)); // iColor
|
||||
vertexLayout.addAttribute(VertexAttribute::perInstance(7, VertexFormat::Float4, 48, 1)); // iUVRect
|
||||
|
||||
// 注意:stride 在实例化渲染中由 buffer 的 stride 参数控制
|
||||
vertexLayout.stride = sizeof(float) * 8; // 顶点缓冲区步长
|
||||
|
||||
return loadFromSourceWithLayout(vsSource, fsSource, vertexLayout);
|
||||
}
|
||||
|
||||
bool Shader::loadFromSourceWithLayout(const std::string &vsSource,
|
||||
const std::string &fsSource,
|
||||
const VertexLayout &vertexLayout) {
|
||||
// 释放旧资源
|
||||
pipeline_ = PipelineHandle();
|
||||
handle_ = ShaderHandle();
|
||||
|
|
@ -78,13 +152,6 @@ bool Shader::loadFromSource(const std::string &vsSource,
|
|||
// 获取着色器句柄
|
||||
handle_ = ShaderHandle(shader.release());
|
||||
|
||||
// 创建顶点布局(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); // 颜色
|
||||
|
||||
// 创建管线描述
|
||||
PipelineDesc pipelineDesc;
|
||||
pipelineDesc.vertexShader = handle_;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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();
|
||||
|
|
|
|||
Loading…
Reference in New Issue