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 "game_scene.h"
|
||||||
|
#include "instanced_test.h"
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
|
|
@ -79,6 +80,9 @@ void GameScene::onEnter() {
|
||||||
|
|
||||||
// 创建装饰物
|
// 创建装饰物
|
||||||
createDecorations();
|
createDecorations();
|
||||||
|
|
||||||
|
// 创建实例化渲染测试(1000个实例)
|
||||||
|
createInstancedTest();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameScene::onExit() {
|
void GameScene::onExit() {
|
||||||
|
|
@ -103,6 +107,11 @@ void GameScene::update(float dt) {
|
||||||
for (auto &decoration : decorations_) {
|
for (auto &decoration : decorations_) {
|
||||||
decoration->onUpdate(dt);
|
decoration->onUpdate(dt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 更新实例化测试
|
||||||
|
if (instancedTest_) {
|
||||||
|
instancedTest_->update(dt);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameScene::createPlayer() {
|
void GameScene::createPlayer() {
|
||||||
|
|
@ -135,12 +144,9 @@ void GameScene::createDecorations() {
|
||||||
float r = std::abs(hue * 6.0f - 3.0f) - 1.0f;
|
float r = std::abs(hue * 6.0f - 3.0f) - 1.0f;
|
||||||
float g = 2.0f - std::abs(hue * 6.0f - 2.0f);
|
float g = 2.0f - std::abs(hue * 6.0f - 2.0f);
|
||||||
float b = 2.0f - std::abs(hue * 6.0f - 4.0f);
|
float b = 2.0f - std::abs(hue * 6.0f - 4.0f);
|
||||||
sprite->setColor(Color(
|
sprite->setColor(Color(std::max(0.0f, std::min(1.0f, r)),
|
||||||
std::max(0.0f, std::min(1.0f, r)),
|
|
||||||
std::max(0.0f, std::min(1.0f, g)),
|
std::max(0.0f, std::min(1.0f, g)),
|
||||||
std::max(0.0f, std::min(1.0f, b)),
|
std::max(0.0f, std::min(1.0f, b)), 1.0f));
|
||||||
1.0f
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
decorations_.push_back(decoration);
|
decorations_.push_back(decoration);
|
||||||
|
|
@ -168,3 +174,12 @@ void GameScene::createCamera() {
|
||||||
// 添加相机节点到场景
|
// 添加相机节点到场景
|
||||||
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
|
#pragma once
|
||||||
|
|
||||||
#include <extra2d.h>
|
#include <extra2d.h>
|
||||||
|
#include "instanced_test.h"
|
||||||
|
|
||||||
using namespace extra2d;
|
using namespace extra2d;
|
||||||
|
|
||||||
|
|
@ -134,7 +135,13 @@ private:
|
||||||
*/
|
*/
|
||||||
void createCamera();
|
void createCamera();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 创建实例化渲染测试
|
||||||
|
*/
|
||||||
|
void createInstancedTest();
|
||||||
|
|
||||||
Ptr<PlayerNode> player_;
|
Ptr<PlayerNode> player_;
|
||||||
std::vector<Ptr<RotatingDecoration>> decorations_;
|
std::vector<Ptr<RotatingDecoration>> decorations_;
|
||||||
|
Ptr<InstancedTestNode> instancedTest_;
|
||||||
float sceneTime_ = 0.0f;
|
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")
|
target("scene_graph_demo")
|
||||||
set_kind("binary")
|
set_kind("binary")
|
||||||
add_files("main.cpp", "game_scene.cpp")
|
add_files("main.cpp", "game_scene.cpp", "instanced_test.cpp")
|
||||||
add_includedirs("../../include", ".")
|
add_includedirs("../../include", ".")
|
||||||
add_deps("extra2d")
|
add_deps("extra2d")
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -175,6 +175,31 @@ public:
|
||||||
Material *getDefaultMaterialPtr();
|
Material *getDefaultMaterialPtr();
|
||||||
Mesh *getDefaultQuadPtr();
|
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<Material> defaultMaterial_;
|
||||||
Handle<Mesh> defaultQuad_;
|
Handle<Mesh> defaultQuad_;
|
||||||
|
|
||||||
|
// 实例化渲染资源
|
||||||
|
Handle<Shader> instancedShader_;
|
||||||
|
Handle<Material> instancedMaterial_;
|
||||||
|
|
||||||
// 热重载
|
// 热重载
|
||||||
bool hotReloadEnabled_ = false;
|
bool hotReloadEnabled_ = false;
|
||||||
float hotReloadInterval_ = 1.0f;
|
float hotReloadInterval_ = 1.0f;
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <renderer/rhi/rhi.h>
|
#include <renderer/rhi/rhi.h>
|
||||||
#include <renderer/rhi/rhi_command_list.h>
|
#include <renderer/rhi/rhi_command_list.h>
|
||||||
|
#include <renderer/uniform_buffer.h>
|
||||||
#include <types/math/color.h>
|
#include <types/math/color.h>
|
||||||
#include <types/math/transform.h>
|
#include <types/math/transform.h>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
|
@ -15,6 +16,7 @@ namespace extra2d {
|
||||||
class CommandQueue;
|
class CommandQueue;
|
||||||
class Material;
|
class Material;
|
||||||
class Mesh;
|
class Mesh;
|
||||||
|
class UniformBufferManager;
|
||||||
template <typename T> class Ptr;
|
template <typename T> class Ptr;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -68,6 +70,9 @@ struct DrawCommand {
|
||||||
uint32_t textureCount; // 纹理数量
|
uint32_t textureCount; // 纹理数量
|
||||||
BufferHandle materialUBO; // 材质 UBO
|
BufferHandle materialUBO; // 材质 UBO
|
||||||
uint32_t materialUBOSize; // 材质 UBO 大小
|
uint32_t materialUBOSize; // 材质 UBO 大小
|
||||||
|
uint32_t materialUBOOffset; // 材质 UBO 在全局缓冲区中的偏移
|
||||||
|
BufferHandle instanceBuffer; // 实例数据缓冲区(实例化渲染使用)
|
||||||
|
uint32_t instanceBufferStride; // 实例数据步长
|
||||||
|
|
||||||
// 变换和颜色数据(用于设置 shader uniform)
|
// 变换和颜色数据(用于设置 shader uniform)
|
||||||
Mat4 modelMatrix; // 模型矩阵
|
Mat4 modelMatrix; // 模型矩阵
|
||||||
|
|
@ -75,7 +80,8 @@ struct DrawCommand {
|
||||||
|
|
||||||
DrawCommand()
|
DrawCommand()
|
||||||
: vertexCount(0), indexCount(0), instanceCount(1), textureCount(0),
|
: 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; }
|
bool isIndexed() const { return indexCount > 0; }
|
||||||
|
|
@ -265,10 +271,11 @@ public:
|
||||||
* @brief 提交实例化绘制命令
|
* @brief 提交实例化绘制命令
|
||||||
* @param material 材质
|
* @param material 材质
|
||||||
* @param mesh 网格
|
* @param mesh 网格
|
||||||
|
* @param instanceBuffer 实例数据缓冲区
|
||||||
* @param instanceCount 实例数量
|
* @param instanceCount 实例数量
|
||||||
*/
|
*/
|
||||||
void submitDrawInstanced(Ptr<Material> material, Ptr<Mesh> mesh,
|
void submitDrawInstanced(Ptr<Material> material, Ptr<Mesh> mesh,
|
||||||
uint32_t instanceCount);
|
BufferHandle instanceBuffer, uint32_t instanceCount);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 提交清除命令
|
* @brief 提交清除命令
|
||||||
|
|
@ -293,6 +300,16 @@ public:
|
||||||
*/
|
*/
|
||||||
void execute();
|
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 获取当前命令数量
|
* @brief 获取当前命令数量
|
||||||
* @return 命令数量
|
* @return 命令数量
|
||||||
|
|
@ -311,12 +328,23 @@ private:
|
||||||
RHIContext *context_ = nullptr;
|
RHIContext *context_ = nullptr;
|
||||||
std::unique_ptr<RHICommandList> commandList_;
|
std::unique_ptr<RHICommandList> commandList_;
|
||||||
|
|
||||||
// 全局 UBO 数据
|
// UBO 管理器
|
||||||
struct GlobalUBOData {
|
std::unique_ptr<UniformBufferManager> uboManager_;
|
||||||
float viewProjection[16];
|
|
||||||
float time;
|
// 全局 UBO 数据 - 必须与着色器中的 std140 布局完全匹配
|
||||||
float screenSize[2];
|
// layout(std140, binding = 0) uniform GlobalUBO {
|
||||||
float padding;
|
// 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_;
|
} globalUBOData_;
|
||||||
|
|
||||||
// 材质 UBO 数据缓冲区
|
// 材质 UBO 数据缓冲区
|
||||||
|
|
@ -328,6 +356,10 @@ private:
|
||||||
// 材质到 ID 的映射
|
// 材质到 ID 的映射
|
||||||
std::unordered_map<Material *, uint32_t> materialIds_;
|
std::unordered_map<Material *, uint32_t> materialIds_;
|
||||||
|
|
||||||
|
// 当前材质 UBO 缓冲区
|
||||||
|
UniformBuffer* currentMaterialUBO_ = nullptr;
|
||||||
|
uint32_t currentMaterialUBOOffset_ = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 获取或创建材质 ID
|
* @brief 获取或创建材质 ID
|
||||||
* @param material 材质指针
|
* @param material 材质指针
|
||||||
|
|
@ -341,6 +373,13 @@ private:
|
||||||
* @param batch 命令批次
|
* @param batch 命令批次
|
||||||
*/
|
*/
|
||||||
void executeBatch(uint32_t batchIndex, const CommandBatch &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
|
} // 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_; }
|
bool isFinalized() const { return finalized_; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::unordered_map<std::string, MaterialParamInfo> params_;
|
// 使用vector保持参数添加顺序,确保与着色器中的声明顺序一致
|
||||||
|
std::vector<std::pair<std::string, MaterialParamInfo>> params_;
|
||||||
|
std::unordered_map<std::string, size_t> paramIndexMap_; // 用于快速查找
|
||||||
uint32_t bufferSize_ = 0;
|
uint32_t bufferSize_ = 0;
|
||||||
bool finalized_ = false;
|
bool finalized_ = false;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -236,8 +236,9 @@ public:
|
||||||
/**
|
/**
|
||||||
* @brief 执行渲染图
|
* @brief 执行渲染图
|
||||||
* @param deltaTime 帧时间
|
* @param deltaTime 帧时间
|
||||||
|
* @param viewProjection 视图投影矩阵
|
||||||
*/
|
*/
|
||||||
void execute(float deltaTime);
|
void execute(float deltaTime, const Mat4& viewProjection = Mat4(1.0f));
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 获取纹理资源
|
* @brief 获取纹理资源
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,7 @@ struct RenderCommand {
|
||||||
struct DrawInstancedData {
|
struct DrawInstancedData {
|
||||||
Handle<Mesh> mesh; // 网格句柄
|
Handle<Mesh> mesh; // 网格句柄
|
||||||
Handle<Material> material; // 材质句柄
|
Handle<Material> material; // 材质句柄
|
||||||
|
void* instanceBuffer; // 实例数据缓冲区指针 (InstanceBuffer*)
|
||||||
uint32_t instanceCount; // 实例数量
|
uint32_t instanceCount; // 实例数量
|
||||||
uint32_t instanceOffset; // 实例数据偏移
|
uint32_t instanceOffset; // 实例数据偏移
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -51,10 +51,11 @@ public:
|
||||||
//===========================================================================
|
//===========================================================================
|
||||||
|
|
||||||
void setVertexBuffer(uint32_t slot, RHIBuffer *buffer,
|
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,
|
void setIndexBuffer(RHIBuffer *buffer, IndexType type,
|
||||||
uint32_t offset = 0) override;
|
uint32_t offset = 0) override;
|
||||||
void setUniformBuffer(uint32_t slot, RHIBuffer *buffer) 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 setTexture(uint32_t slot, RHITexture *texture) override;
|
||||||
void setSampler(uint32_t slot, TextureFilter minFilter,
|
void setSampler(uint32_t slot, TextureFilter minFilter,
|
||||||
TextureFilter magFilter, TextureWrap wrapS,
|
TextureFilter magFilter, TextureWrap wrapS,
|
||||||
|
|
@ -81,11 +82,11 @@ public:
|
||||||
bool isRecording() const override;
|
bool isRecording() const override;
|
||||||
|
|
||||||
// 设置 uniform 变量
|
// 设置 uniform 变量
|
||||||
void setUniform(const std::string& name, float value);
|
void setUniform(const char* name, float value) override;
|
||||||
void setUniform(const std::string& name, const Vec2& value);
|
void setUniform(const char* name, const Vec2& value) override;
|
||||||
void setUniform(const std::string& name, const Vec3& value);
|
void setUniform(const char* name, const Vec3& value) override;
|
||||||
void setUniform(const std::string& name, const Color& value);
|
void setUniform(const char* name, const Color& value) override;
|
||||||
void setUniform(const std::string& name, const Mat4& value);
|
void setUniform(const char* name, const Mat4& value) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool recording_ = false;
|
bool recording_ = false;
|
||||||
|
|
|
||||||
|
|
@ -89,8 +89,9 @@ public:
|
||||||
* @param slot 槽位
|
* @param slot 槽位
|
||||||
* @param buffer 缓冲区
|
* @param buffer 缓冲区
|
||||||
* @param offset 偏移(字节)
|
* @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 设置索引缓冲区
|
* @brief 设置索引缓冲区
|
||||||
|
|
@ -107,6 +108,15 @@ public:
|
||||||
*/
|
*/
|
||||||
virtual void setUniformBuffer(uint32_t slot, RHIBuffer* buffer) = 0;
|
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 设置纹理
|
* @brief 设置纹理
|
||||||
* @param slot 槽位
|
* @param slot 槽位
|
||||||
|
|
@ -128,6 +138,41 @@ public:
|
||||||
TextureWrap wrapS,
|
TextureWrap wrapS,
|
||||||
TextureWrap wrapT) = 0;
|
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;
|
VertexFormat format = VertexFormat::Float3;
|
||||||
uint32_t offset = 0; // 在顶点结构中的偏移
|
uint32_t offset = 0; // 在顶点结构中的偏移
|
||||||
uint32_t bufferIndex = 0; // 绑定的顶点缓冲区索引
|
uint32_t bufferIndex = 0; // 绑定的顶点缓冲区索引
|
||||||
|
uint32_t divisor = 0; // 实例化除数(0=每顶点,1=每实例,N=每N个实例)
|
||||||
|
|
||||||
static uint32_t getSize(VertexFormat format);
|
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,
|
void addAttribute(uint32_t location, VertexFormat format, uint32_t offset,
|
||||||
uint32_t bufferIndex = 0);
|
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);
|
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 着色器句柄
|
* @brief 获取 RHI 着色器句柄
|
||||||
* @return RHI 着色器句柄
|
* @return RHI 着色器句柄
|
||||||
|
|
@ -61,6 +77,17 @@ public:
|
||||||
*/
|
*/
|
||||||
bool isLoaded() const { return handle_.isValid() && pipeline_.isValid(); }
|
bool isLoaded() const { return handle_.isValid() && pipeline_.isValid(); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 使用自定义顶点布局从源码加载着色器
|
||||||
|
* @param vsSource 顶点着色器源码
|
||||||
|
* @param fsSource 片段着色器源码
|
||||||
|
* @param vertexLayout 顶点布局
|
||||||
|
* @return 加载是否成功
|
||||||
|
*/
|
||||||
|
bool loadFromSourceWithLayout(const std::string &vsSource,
|
||||||
|
const std::string &fsSource,
|
||||||
|
const VertexLayout &vertexLayout);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 设置 Uniform Block 绑定槽位
|
* @brief 设置 Uniform Block 绑定槽位
|
||||||
* @param name Uniform Block 名称
|
* @param name Uniform Block 名称
|
||||||
|
|
|
||||||
|
|
@ -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 渲染(收集渲染命令)
|
* @brief 渲染(收集渲染命令)
|
||||||
*/
|
*/
|
||||||
void render();
|
virtual void render();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::string name_;
|
std::string name_;
|
||||||
|
|
|
||||||
|
|
@ -4,14 +4,12 @@ precision highp float;
|
||||||
// 从顶点着色器输入
|
// 从顶点着色器输入
|
||||||
in vec2 vTexCoord;
|
in vec2 vTexCoord;
|
||||||
in vec4 vColor;
|
in vec4 vColor;
|
||||||
|
in vec4 vTintColor;
|
||||||
|
in float vOpacity;
|
||||||
|
|
||||||
// 纹理采样器
|
// 纹理采样器
|
||||||
uniform sampler2D uTexture;
|
uniform sampler2D uTexture;
|
||||||
|
|
||||||
// 材质参数
|
|
||||||
uniform vec4 uTintColor;
|
|
||||||
uniform float uOpacity;
|
|
||||||
|
|
||||||
// 输出颜色
|
// 输出颜色
|
||||||
out vec4 fragColor;
|
out vec4 fragColor;
|
||||||
|
|
||||||
|
|
@ -21,20 +19,19 @@ out vec4 fragColor;
|
||||||
* 采样纹理并与顶点颜色、色调和透明度混合
|
* 采样纹理并与顶点颜色、色调和透明度混合
|
||||||
*/
|
*/
|
||||||
void main() {
|
void main() {
|
||||||
// 采样纹理(如果没有绑定纹理,texture 会返回 vec4(0,0,0,1) 或 vec4(1,1,1,1) 取决于实现)
|
// 采样纹理
|
||||||
vec4 texColor = texture(uTexture, vTexCoord);
|
vec4 texColor = texture(uTexture, vTexCoord);
|
||||||
|
|
||||||
// 如果纹理采样结果是黑色或透明,使用白色作为默认值
|
// 如果纹理采样结果是黑色或透明,使用白色作为默认值
|
||||||
// 这样即使在没有纹理的情况下也能显示颜色
|
|
||||||
if (texColor.rgb == vec3(0.0) || texColor.a < 0.01) {
|
if (texColor.rgb == vec3(0.0) || texColor.a < 0.01) {
|
||||||
texColor = vec4(1.0, 1.0, 1.0, 1.0);
|
texColor = vec4(1.0, 1.0, 1.0, 1.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 混合:纹理 * 顶点颜色 * 色调
|
// 混合:纹理 * 顶点颜色 * 色调
|
||||||
fragColor = texColor * vColor * uTintColor;
|
fragColor = texColor * vColor * vTintColor;
|
||||||
|
|
||||||
// 应用透明度
|
// 应用透明度
|
||||||
fragColor.a *= uOpacity;
|
fragColor.a *= vOpacity;
|
||||||
|
|
||||||
// Alpha 测试:丢弃几乎透明的像素
|
// Alpha 测试:丢弃几乎透明的像素
|
||||||
if (fragColor.a < 0.01) {
|
if (fragColor.a < 0.01) {
|
||||||
|
|
|
||||||
|
|
@ -1,15 +1,26 @@
|
||||||
#version 320 es
|
#version 320 es
|
||||||
precision highp float;
|
precision highp float;
|
||||||
|
|
||||||
// 视图投影矩阵
|
// 全局 UBO (binding = 0) - 每帧更新一次
|
||||||
uniform mat4 uViewProjection;
|
layout(std140, binding = 0) uniform GlobalUBO {
|
||||||
|
mat4 uViewProjection;
|
||||||
|
vec4 uCameraPosition;
|
||||||
|
float uTime;
|
||||||
|
float uDeltaTime;
|
||||||
|
vec2 uScreenSize;
|
||||||
|
};
|
||||||
|
|
||||||
// 模型矩阵
|
// 材质 UBO (binding = 1) - 每物体更新
|
||||||
|
layout(std140, binding = 1) uniform MaterialUBO {
|
||||||
|
vec4 uColor;
|
||||||
|
vec4 uTintColor;
|
||||||
|
float uOpacity;
|
||||||
|
float uPadding[3]; // std140 对齐填充
|
||||||
|
};
|
||||||
|
|
||||||
|
// 模型矩阵作为单独的统一变量(每个物体设置)
|
||||||
uniform mat4 uModelMatrix;
|
uniform mat4 uModelMatrix;
|
||||||
|
|
||||||
// 顶点颜色(覆盖顶点属性中的颜色)
|
|
||||||
uniform vec4 uColor;
|
|
||||||
|
|
||||||
// 顶点属性
|
// 顶点属性
|
||||||
layout(location = 0) in vec2 aPosition;
|
layout(location = 0) in vec2 aPosition;
|
||||||
layout(location = 1) in vec2 aTexCoord;
|
layout(location = 1) in vec2 aTexCoord;
|
||||||
|
|
@ -18,16 +29,20 @@ layout(location = 2) in vec4 aColor;
|
||||||
// 输出到片段着色器
|
// 输出到片段着色器
|
||||||
out vec2 vTexCoord;
|
out vec2 vTexCoord;
|
||||||
out vec4 vColor;
|
out vec4 vColor;
|
||||||
|
out vec4 vTintColor;
|
||||||
|
out float vOpacity;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 顶点着色器入口
|
* @brief 顶点着色器入口
|
||||||
*
|
*
|
||||||
* 计算顶点在裁剪空间中的位置,
|
* 计算顶点在裁剪空间中的位置,
|
||||||
* 并传递纹理坐标和顶点颜色到片段着色器
|
* 并传递纹理坐标和颜色到片段着色器
|
||||||
*/
|
*/
|
||||||
void main() {
|
void main() {
|
||||||
gl_Position = uViewProjection * uModelMatrix * vec4(aPosition, 0.0, 1.0);
|
gl_Position = uViewProjection * uModelMatrix * vec4(aPosition, 0.0, 1.0);
|
||||||
vTexCoord = aTexCoord;
|
vTexCoord = aTexCoord;
|
||||||
// 使用 uniform 颜色覆盖顶点属性颜色
|
// 混合顶点颜色和材质 UBO 中的颜色
|
||||||
vColor = uColor;
|
vColor = aColor * uColor;
|
||||||
|
vTintColor = uTintColor;
|
||||||
|
vOpacity = uOpacity;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,73 @@
|
||||||
|
#version 320 es
|
||||||
|
precision highp float;
|
||||||
|
|
||||||
|
// 全局 UBO (binding = 0) - 每帧更新一次
|
||||||
|
layout(std140, binding = 0) uniform GlobalUBO {
|
||||||
|
mat4 uViewProjection;
|
||||||
|
vec4 uCameraPosition;
|
||||||
|
float uTime;
|
||||||
|
float uDeltaTime;
|
||||||
|
vec2 uScreenSize;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 材质 UBO (binding = 1) - 每批次更新
|
||||||
|
layout(std140, binding = 1) uniform MaterialUBO {
|
||||||
|
vec4 uColor;
|
||||||
|
vec4 uTintColor;
|
||||||
|
float uOpacity;
|
||||||
|
float uPadding[3]; // std140 对齐填充
|
||||||
|
};
|
||||||
|
|
||||||
|
// 顶点属性 (每个顶点)
|
||||||
|
layout(location = 0) in vec2 aPosition;
|
||||||
|
layout(location = 1) in vec2 aTexCoord;
|
||||||
|
layout(location = 2) in vec4 aColor;
|
||||||
|
|
||||||
|
// 实例属性 (每个实例) - 使用 location 3-6
|
||||||
|
layout(location = 3) in vec2 iPosition; // 实例位置
|
||||||
|
layout(location = 4) in float iRotation; // 实例旋转
|
||||||
|
layout(location = 5) in vec2 iScale; // 实例缩放
|
||||||
|
layout(location = 6) in vec4 iColor; // 实例颜色
|
||||||
|
|
||||||
|
// 输出到片段着色器
|
||||||
|
out vec2 vTexCoord;
|
||||||
|
out vec4 vColor;
|
||||||
|
out vec4 vTintColor;
|
||||||
|
out float vOpacity;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 从旋转角度构建2D变换矩阵
|
||||||
|
* @param angle 旋转角度(弧度)
|
||||||
|
* @return 2x2旋转矩阵
|
||||||
|
*/
|
||||||
|
mat2 rotate2D(float angle) {
|
||||||
|
float c = cos(angle);
|
||||||
|
float s = sin(angle);
|
||||||
|
return mat2(c, -s, s, c);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 顶点着色器入口
|
||||||
|
*
|
||||||
|
* 计算顶点在裁剪空间中的位置,
|
||||||
|
* 应用实例的变换(位置、旋转、缩放),
|
||||||
|
* 并传递纹理坐标和颜色到片段着色器
|
||||||
|
*/
|
||||||
|
void main() {
|
||||||
|
// 应用实例缩放和旋转
|
||||||
|
vec2 localPos = rotate2D(iRotation) * (aPosition * iScale);
|
||||||
|
|
||||||
|
// 应用实例位置偏移
|
||||||
|
vec2 worldPos = localPos + iPosition;
|
||||||
|
|
||||||
|
// 变换到裁剪空间
|
||||||
|
gl_Position = uViewProjection * vec4(worldPos, 0.0, 1.0);
|
||||||
|
|
||||||
|
vTexCoord = aTexCoord;
|
||||||
|
|
||||||
|
// 混合顶点颜色、实例颜色和材质颜色
|
||||||
|
vColor = aColor * iColor * uColor;
|
||||||
|
|
||||||
|
vTintColor = uTintColor;
|
||||||
|
vOpacity = uOpacity;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
#version 320 es
|
||||||
|
precision highp float;
|
||||||
|
|
||||||
|
// 从顶点着色器输入
|
||||||
|
in vec2 vTexCoord;
|
||||||
|
in vec4 vColor;
|
||||||
|
|
||||||
|
// 纹理采样器
|
||||||
|
uniform sampler2D uTexture;
|
||||||
|
|
||||||
|
// 输出颜色
|
||||||
|
out vec4 fragColor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 片段着色器入口(实例化版本)
|
||||||
|
*
|
||||||
|
* 采样纹理并与顶点颜色混合
|
||||||
|
*/
|
||||||
|
void main() {
|
||||||
|
// 采样纹理
|
||||||
|
vec4 texColor = texture(uTexture, vTexCoord);
|
||||||
|
|
||||||
|
// 如果纹理采样结果是黑色或透明,使用白色作为默认值
|
||||||
|
if (texColor.rgb == vec3(0.0) || texColor.a < 0.01) {
|
||||||
|
texColor = vec4(1.0, 1.0, 1.0, 1.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 混合:纹理 * 顶点颜色
|
||||||
|
fragColor = texColor * vColor;
|
||||||
|
|
||||||
|
// Alpha 测试:丢弃几乎透明的像素
|
||||||
|
if (fragColor.a < 0.01) {
|
||||||
|
discard;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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/assets_module.h>
|
||||||
#include <assets/loaders/shader_loader.h>
|
#include <assets/loaders/shader_loader.h>
|
||||||
#include <assets/loaders/texture_loader.h>
|
#include <assets/loaders/texture_loader.h>
|
||||||
#include <event/events.h>
|
#include <event/events.h>
|
||||||
#include <algorithm>
|
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <utils/logger.h>
|
#include <utils/logger.h>
|
||||||
|
|
@ -46,6 +46,13 @@ void AssetsModule::onGLContextReady() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 创建实例化渲染资源
|
||||||
|
if (!createInstancedResources()) {
|
||||||
|
E2D_LOG_WARN("Failed to create instanced resources, instanced rendering "
|
||||||
|
"will not be available");
|
||||||
|
// 不返回错误,实例化渲染是可选功能
|
||||||
|
}
|
||||||
|
|
||||||
defaultResourcesCreated_ = true;
|
defaultResourcesCreated_ = true;
|
||||||
E2D_LOG_INFO("Default resources created successfully");
|
E2D_LOG_INFO("Default resources created successfully");
|
||||||
}
|
}
|
||||||
|
|
@ -177,8 +184,7 @@ Handle<Texture> AssetsModule::loadFromMemory<Texture>(const std::string &key,
|
||||||
// Shader 加载特化
|
// Shader 加载特化
|
||||||
//===========================================================================
|
//===========================================================================
|
||||||
|
|
||||||
template <>
|
template <> Handle<Shader> AssetsModule::load<Shader>(const std::string &path) {
|
||||||
Handle<Shader> AssetsModule::load<Shader>(const std::string &path) {
|
|
||||||
// 先检查缓存(读锁)
|
// 先检查缓存(读锁)
|
||||||
{
|
{
|
||||||
std::shared_lock<std::shared_mutex> lock(mutex_);
|
std::shared_lock<std::shared_mutex> lock(mutex_);
|
||||||
|
|
@ -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>();
|
Ptr<Material> material = makePtr<Material>();
|
||||||
material->setShader(getPtr(defaultShader_));
|
material->setShader(getPtr(defaultShader_));
|
||||||
|
material->setLayout(layout);
|
||||||
|
|
||||||
|
// 设置默认材质参数
|
||||||
|
material->setColor("uColor", Color::White);
|
||||||
|
material->setColor("uTintColor", Color::White);
|
||||||
|
material->setFloat("uOpacity", 1.0f);
|
||||||
|
|
||||||
// 添加默认纹理到材质
|
// 添加默认纹理到材质
|
||||||
material->setTexture("uTexture", getPtr(defaultTexture_), 0);
|
material->setTexture("uTexture", getPtr(defaultTexture_), 0);
|
||||||
defaultMaterial_ = materials_.insert(material);
|
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,6 +489,67 @@ Material *AssetsModule::getDefaultMaterialPtr() {
|
||||||
|
|
||||||
Mesh *AssetsModule::getDefaultQuadPtr() { return meshes_.get(defaultQuad_); }
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
//===========================================================================
|
//===========================================================================
|
||||||
// 热重载
|
// 热重载
|
||||||
//===========================================================================
|
//===========================================================================
|
||||||
|
|
@ -480,7 +567,8 @@ void AssetsModule::setHotReloadInterval(float interval) {
|
||||||
hotReloadInterval_ = interval;
|
hotReloadInterval_ = interval;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AssetsModule::addFileWatch(const std::string& path, Handle<Texture> handle) {
|
void AssetsModule::addFileWatch(const std::string &path,
|
||||||
|
Handle<Texture> handle) {
|
||||||
try {
|
try {
|
||||||
if (!std::filesystem::exists(path)) {
|
if (!std::filesystem::exists(path)) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -500,7 +588,8 @@ void AssetsModule::addFileWatch(const std::string& path, Handle<Texture> handle)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AssetsModule::addFileWatch(const std::string& path, Handle<Shader> handle) {
|
void AssetsModule::addFileWatch(const std::string &path,
|
||||||
|
Handle<Shader> handle) {
|
||||||
try {
|
try {
|
||||||
if (!std::filesystem::exists(path)) {
|
if (!std::filesystem::exists(path)) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -679,7 +768,8 @@ void AssetsModule::submitLoadTask(const LoadTask& task) {
|
||||||
// 按优先级排序
|
// 按优先级排序
|
||||||
std::sort(loadQueue_.begin(), loadQueue_.end(),
|
std::sort(loadQueue_.begin(), loadQueue_.end(),
|
||||||
[](const LoadTask &a, const LoadTask &b) {
|
[](const LoadTask &a, const LoadTask &b) {
|
||||||
return static_cast<int>(a.priority) > static_cast<int>(b.priority);
|
return static_cast<int>(a.priority) >
|
||||||
|
static_cast<int>(b.priority);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
queueCV_.notify_one();
|
queueCV_.notify_one();
|
||||||
|
|
@ -691,9 +781,8 @@ void AssetsModule::workerThreadLoop() {
|
||||||
|
|
||||||
{
|
{
|
||||||
std::unique_lock<std::mutex> lock(queueMutex_);
|
std::unique_lock<std::mutex> lock(queueMutex_);
|
||||||
queueCV_.wait(lock, [this] {
|
queueCV_.wait(
|
||||||
return !loadQueue_.empty() || !asyncLoaderRunning_;
|
lock, [this] { return !loadQueue_.empty() || !asyncLoaderRunning_; });
|
||||||
});
|
|
||||||
|
|
||||||
if (!asyncLoaderRunning_) {
|
if (!asyncLoaderRunning_) {
|
||||||
break;
|
break;
|
||||||
|
|
@ -713,9 +802,8 @@ void AssetsModule::workerThreadLoop() {
|
||||||
|
|
||||||
if (task.textureCallback) {
|
if (task.textureCallback) {
|
||||||
std::lock_guard<std::mutex> callbackLock(callbackMutex_);
|
std::lock_guard<std::mutex> callbackLock(callbackMutex_);
|
||||||
completedCallbacks_.push_back([handle, callback = task.textureCallback]() {
|
completedCallbacks_.push_back(
|
||||||
callback(handle);
|
[handle, callback = task.textureCallback]() { callback(handle); });
|
||||||
});
|
|
||||||
}
|
}
|
||||||
} else if (task.type == LoadTask::Type::Shader) {
|
} else if (task.type == LoadTask::Type::Shader) {
|
||||||
Handle<Shader> handle;
|
Handle<Shader> handle;
|
||||||
|
|
@ -727,9 +815,8 @@ void AssetsModule::workerThreadLoop() {
|
||||||
|
|
||||||
if (task.shaderCallback) {
|
if (task.shaderCallback) {
|
||||||
std::lock_guard<std::mutex> callbackLock(callbackMutex_);
|
std::lock_guard<std::mutex> callbackLock(callbackMutex_);
|
||||||
completedCallbacks_.push_back([handle, callback = task.shaderCallback]() {
|
completedCallbacks_.push_back(
|
||||||
callback(handle);
|
[handle, callback = task.shaderCallback]() { callback(handle); });
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -753,7 +840,8 @@ void AssetsModule::processAsyncCallbacks() {
|
||||||
// 资源依赖跟踪
|
// 资源依赖跟踪
|
||||||
//===========================================================================
|
//===========================================================================
|
||||||
|
|
||||||
void AssetsModule::registerMaterialDependency(Handle<Material> material, Handle<Texture> texture) {
|
void AssetsModule::registerMaterialDependency(Handle<Material> material,
|
||||||
|
Handle<Texture> texture) {
|
||||||
if (!material.isValid() || !texture.isValid()) {
|
if (!material.isValid() || !texture.isValid()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -765,7 +853,8 @@ void AssetsModule::registerMaterialDependency(Handle<Material> material, Handle<
|
||||||
info.texture = texture;
|
info.texture = texture;
|
||||||
|
|
||||||
// 检查是否已存在
|
// 检查是否已存在
|
||||||
auto it = std::find(info.dependentMaterials.begin(), info.dependentMaterials.end(), material);
|
auto it = std::find(info.dependentMaterials.begin(),
|
||||||
|
info.dependentMaterials.end(), material);
|
||||||
if (it == info.dependentMaterials.end()) {
|
if (it == info.dependentMaterials.end()) {
|
||||||
info.dependentMaterials.push_back(material);
|
info.dependentMaterials.push_back(material);
|
||||||
E2D_LOG_DEBUG("Registered material {} dependency on texture {}",
|
E2D_LOG_DEBUG("Registered material {} dependency on texture {}",
|
||||||
|
|
@ -773,7 +862,8 @@ void AssetsModule::registerMaterialDependency(Handle<Material> material, Handle<
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AssetsModule::registerMaterialDependency(Handle<Material> material, Handle<Shader> shader) {
|
void AssetsModule::registerMaterialDependency(Handle<Material> material,
|
||||||
|
Handle<Shader> shader) {
|
||||||
if (!material.isValid() || !shader.isValid()) {
|
if (!material.isValid() || !shader.isValid()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -785,7 +875,8 @@ void AssetsModule::registerMaterialDependency(Handle<Material> material, Handle<
|
||||||
info.shader = shader;
|
info.shader = shader;
|
||||||
|
|
||||||
// 检查是否已存在
|
// 检查是否已存在
|
||||||
auto it = std::find(info.dependentMaterials.begin(), info.dependentMaterials.end(), material);
|
auto it = std::find(info.dependentMaterials.begin(),
|
||||||
|
info.dependentMaterials.end(), material);
|
||||||
if (it == info.dependentMaterials.end()) {
|
if (it == info.dependentMaterials.end()) {
|
||||||
info.dependentMaterials.push_back(material);
|
info.dependentMaterials.push_back(material);
|
||||||
E2D_LOG_DEBUG("Registered material {} dependency on shader {}",
|
E2D_LOG_DEBUG("Registered material {} dependency on shader {}",
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <vector>
|
||||||
#include <renderer/command_queue.h>
|
#include <renderer/command_queue.h>
|
||||||
|
#include <renderer/instance_buffer.h>
|
||||||
#include <renderer/material.h>
|
#include <renderer/material.h>
|
||||||
#include <renderer/mesh.h>
|
#include <renderer/mesh.h>
|
||||||
#include <renderer/rhi_module.h>
|
#include <renderer/rhi_module.h>
|
||||||
|
|
@ -109,13 +111,18 @@ CommandQueue::~CommandQueue() { shutdown(); }
|
||||||
CommandQueue::CommandQueue(CommandQueue &&other) noexcept
|
CommandQueue::CommandQueue(CommandQueue &&other) noexcept
|
||||||
: sorter_(std::move(other.sorter_)), batcher_(std::move(other.batcher_)),
|
: sorter_(std::move(other.sorter_)), batcher_(std::move(other.batcher_)),
|
||||||
context_(other.context_), commandList_(std::move(other.commandList_)),
|
context_(other.context_), commandList_(std::move(other.commandList_)),
|
||||||
|
uboManager_(std::move(other.uboManager_)),
|
||||||
globalUBOData_(other.globalUBOData_),
|
globalUBOData_(other.globalUBOData_),
|
||||||
materialUBOData_(std::move(other.materialUBOData_)),
|
materialUBOData_(std::move(other.materialUBOData_)),
|
||||||
nextMaterialId_(other.nextMaterialId_),
|
nextMaterialId_(other.nextMaterialId_),
|
||||||
materialIds_(std::move(other.materialIds_)) {
|
materialIds_(std::move(other.materialIds_)),
|
||||||
|
currentMaterialUBO_(other.currentMaterialUBO_),
|
||||||
|
currentMaterialUBOOffset_(other.currentMaterialUBOOffset_) {
|
||||||
other.context_ = nullptr;
|
other.context_ = nullptr;
|
||||||
other.globalUBOData_ = {};
|
other.globalUBOData_ = {};
|
||||||
other.nextMaterialId_ = 1;
|
other.nextMaterialId_ = 1;
|
||||||
|
other.currentMaterialUBO_ = nullptr;
|
||||||
|
other.currentMaterialUBOOffset_ = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
CommandQueue &CommandQueue::operator=(CommandQueue &&other) noexcept {
|
CommandQueue &CommandQueue::operator=(CommandQueue &&other) noexcept {
|
||||||
|
|
@ -126,14 +133,19 @@ CommandQueue &CommandQueue::operator=(CommandQueue &&other) noexcept {
|
||||||
batcher_ = std::move(other.batcher_);
|
batcher_ = std::move(other.batcher_);
|
||||||
context_ = other.context_;
|
context_ = other.context_;
|
||||||
commandList_ = std::move(other.commandList_);
|
commandList_ = std::move(other.commandList_);
|
||||||
|
uboManager_ = std::move(other.uboManager_);
|
||||||
globalUBOData_ = other.globalUBOData_;
|
globalUBOData_ = other.globalUBOData_;
|
||||||
materialUBOData_ = std::move(other.materialUBOData_);
|
materialUBOData_ = std::move(other.materialUBOData_);
|
||||||
nextMaterialId_ = other.nextMaterialId_;
|
nextMaterialId_ = other.nextMaterialId_;
|
||||||
materialIds_ = std::move(other.materialIds_);
|
materialIds_ = std::move(other.materialIds_);
|
||||||
|
currentMaterialUBO_ = other.currentMaterialUBO_;
|
||||||
|
currentMaterialUBOOffset_ = other.currentMaterialUBOOffset_;
|
||||||
|
|
||||||
other.context_ = nullptr;
|
other.context_ = nullptr;
|
||||||
other.globalUBOData_ = {};
|
other.globalUBOData_ = {};
|
||||||
other.nextMaterialId_ = 1;
|
other.nextMaterialId_ = 1;
|
||||||
|
other.currentMaterialUBO_ = nullptr;
|
||||||
|
other.currentMaterialUBOOffset_ = 0;
|
||||||
}
|
}
|
||||||
return *this;
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
@ -165,6 +177,13 @@ bool CommandQueue::initialize() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 初始化 UBO 管理器
|
||||||
|
uboManager_ = std::make_unique<UniformBufferManager>();
|
||||||
|
if (!uboManager_->initialize()) {
|
||||||
|
E2D_LOG_ERROR("Failed to initialize UniformBufferManager");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// 预分配材质 UBO 数据缓冲区
|
// 预分配材质 UBO 数据缓冲区
|
||||||
materialUBOData_.reserve(1024 * 1024); // 1MB
|
materialUBOData_.reserve(1024 * 1024); // 1MB
|
||||||
|
|
||||||
|
|
@ -173,6 +192,7 @@ bool CommandQueue::initialize() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void CommandQueue::shutdown() {
|
void CommandQueue::shutdown() {
|
||||||
|
uboManager_.reset();
|
||||||
commandList_.reset();
|
commandList_.reset();
|
||||||
context_ = nullptr;
|
context_ = nullptr;
|
||||||
materialUBOData_.clear();
|
materialUBOData_.clear();
|
||||||
|
|
@ -186,6 +206,15 @@ void CommandQueue::beginFrame() {
|
||||||
nextMaterialId_ = 1;
|
nextMaterialId_ = 1;
|
||||||
materialUBOData_.clear();
|
materialUBOData_.clear();
|
||||||
|
|
||||||
|
// 重置材质 UBO 分配状态
|
||||||
|
currentMaterialUBO_ = nullptr;
|
||||||
|
currentMaterialUBOOffset_ = 0;
|
||||||
|
|
||||||
|
// 重置 UBO 管理器的材质 UBO 池
|
||||||
|
if (uboManager_) {
|
||||||
|
uboManager_->resetMaterialUBOs();
|
||||||
|
}
|
||||||
|
|
||||||
// 开始录制命令
|
// 开始录制命令
|
||||||
if (commandList_) {
|
if (commandList_) {
|
||||||
commandList_->begin();
|
commandList_->begin();
|
||||||
|
|
@ -210,6 +239,31 @@ uint32_t CommandQueue::getMaterialId(Material *material) {
|
||||||
return id;
|
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,
|
void CommandQueue::submitDraw(Ptr<Material> material, Ptr<Mesh> mesh,
|
||||||
const struct Transform &transform,
|
const struct Transform &transform,
|
||||||
const Color &color) {
|
const Color &color) {
|
||||||
|
|
@ -248,22 +302,36 @@ void CommandQueue::submitDraw(Ptr<Material> material, Ptr<Mesh> mesh,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 分配材质 UBO 数据
|
// 分配材质 UBO 空间并更新数据
|
||||||
uint32_t materialDataSize = material->getDataSize();
|
uint32_t materialDataSize = material->getDataSize();
|
||||||
if (materialDataSize > 0) {
|
if (materialDataSize > 0) {
|
||||||
uint32_t uboOffset = static_cast<uint32_t>(materialUBOData_.size());
|
auto [ubo, offset] = allocateMaterialUBO(materialDataSize);
|
||||||
materialUBOData_.resize(uboOffset + materialDataSize);
|
if (ubo) {
|
||||||
std::memcpy(materialUBOData_.data() + uboOffset, material->getData(),
|
// 复制材质数据到临时缓冲区,以便修改颜色
|
||||||
materialDataSize);
|
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.materialUBOSize = materialDataSize;
|
||||||
// 注意:实际的 UBO 句柄需要在执行时从 UBO 管理器获取
|
cmd.materialUBOOffset = offset;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sorter_.addCommand(cmd);
|
sorter_.addCommand(cmd);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CommandQueue::submitDrawInstanced(Ptr<Material> material, Ptr<Mesh> mesh,
|
void CommandQueue::submitDrawInstanced(Ptr<Material> material, Ptr<Mesh> mesh,
|
||||||
uint32_t instanceCount) {
|
BufferHandle instanceBuffer, uint32_t instanceCount) {
|
||||||
if (!material || !mesh || !material->getShader() || instanceCount == 0) {
|
if (!material || !mesh || !material->getShader() || instanceCount == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -284,6 +352,10 @@ void CommandQueue::submitDrawInstanced(Ptr<Material> material, Ptr<Mesh> mesh,
|
||||||
cmd.indexCount = mesh->getIndexCount();
|
cmd.indexCount = mesh->getIndexCount();
|
||||||
cmd.instanceCount = instanceCount;
|
cmd.instanceCount = instanceCount;
|
||||||
|
|
||||||
|
// 设置实例缓冲区
|
||||||
|
cmd.instanceBuffer = instanceBuffer;
|
||||||
|
cmd.instanceBufferStride = sizeof(InstanceData); // 64 bytes
|
||||||
|
|
||||||
// 设置纹理
|
// 设置纹理
|
||||||
const auto &textures = material->getTextures();
|
const auto &textures = material->getTextures();
|
||||||
cmd.textureCount =
|
cmd.textureCount =
|
||||||
|
|
@ -294,14 +366,16 @@ void CommandQueue::submitDrawInstanced(Ptr<Material> material, Ptr<Mesh> mesh,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 材质 UBO
|
// 分配材质 UBO 空间并更新数据
|
||||||
uint32_t materialDataSize = material->getDataSize();
|
uint32_t materialDataSize = material->getDataSize();
|
||||||
if (materialDataSize > 0) {
|
if (materialDataSize > 0) {
|
||||||
uint32_t uboOffset = static_cast<uint32_t>(materialUBOData_.size());
|
auto [ubo, offset] = allocateMaterialUBO(materialDataSize);
|
||||||
materialUBOData_.resize(uboOffset + materialDataSize);
|
if (ubo) {
|
||||||
std::memcpy(materialUBOData_.data() + uboOffset, material->getData(),
|
ubo->update(material->getData(), materialDataSize, offset);
|
||||||
materialDataSize);
|
cmd.materialUBO = BufferHandle(ubo->getRHIBuffer());
|
||||||
cmd.materialUBOSize = materialDataSize;
|
cmd.materialUBOSize = materialDataSize;
|
||||||
|
cmd.materialUBOOffset = offset;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sorter_.addCommand(cmd);
|
sorter_.addCommand(cmd);
|
||||||
|
|
@ -333,6 +407,28 @@ void CommandQueue::setViewport(int32_t x, int32_t y, int32_t width,
|
||||||
commandList_->setViewport(viewport);
|
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() {
|
void CommandQueue::execute() {
|
||||||
if (!commandList_) {
|
if (!commandList_) {
|
||||||
E2D_LOG_ERROR("CommandQueue::execute: commandList is null");
|
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!");
|
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) {
|
if (batch.textureCount > 0) {
|
||||||
for (uint32_t i = 0; i < batch.textureCount; ++i) {
|
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!");
|
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()) {
|
if (cmd.isIndexed() && cmd.indexBuffer.isValid()) {
|
||||||
commandList_->setIndexBuffer(cmd.indexBuffer.get(), IndexType::UInt16, 0);
|
commandList_->setIndexBuffer(cmd.indexBuffer.get(), IndexType::UInt16, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置 shader uniform 变量
|
// 绑定材质 UBO (binding = 1)
|
||||||
auto* glCmdList = dynamic_cast<extra2d::GLCommandList*>(commandList_.get());
|
if (cmd.materialUBO.isValid()) {
|
||||||
if (glCmdList) {
|
commandList_->setUniformBuffer(1, cmd.materialUBO.get(), cmd.materialUBOOffset, cmd.materialUBOSize);
|
||||||
// 设置模型矩阵
|
}
|
||||||
glCmdList->setUniform("uModelMatrix", cmd.modelMatrix);
|
|
||||||
// 设置颜色
|
// 设置模型矩阵(仅在非实例化渲染时使用)
|
||||||
glCmdList->setUniform("uColor", cmd.color);
|
if (!cmd.isInstanced()) {
|
||||||
// 设置 view projection 矩阵
|
commandList_->setUniform("uModelMatrix", cmd.modelMatrix);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 绘制
|
// 绘制
|
||||||
|
|
|
||||||
|
|
@ -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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 检查是否已存在
|
||||||
|
if (paramIndexMap_.find(name) != paramIndexMap_.end()) {
|
||||||
|
E2D_LOG_WARN("Param '{}' already exists in MaterialLayout", name);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
MaterialParamInfo info;
|
MaterialParamInfo info;
|
||||||
info.type = type;
|
info.type = type;
|
||||||
info.size = getMaterialParamSize(type);
|
info.size = getMaterialParamSize(type);
|
||||||
info.offset = 0; // 将在 finalize 时计算
|
info.offset = 0; // 将在 finalize 时计算
|
||||||
|
|
||||||
params_[name] = info;
|
size_t index = params_.size();
|
||||||
|
params_.emplace_back(name, info);
|
||||||
|
paramIndexMap_[name] = index;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -36,6 +44,7 @@ void MaterialLayout::addParam(const std::string& name, MaterialParamType type) {
|
||||||
* - 标量和向量:偏移必须是其大小的倍数
|
* - 标量和向量:偏移必须是其大小的倍数
|
||||||
* - 数组和结构体:偏移必须是 16 的倍数
|
* - 数组和结构体:偏移必须是 16 的倍数
|
||||||
* - vec3 后面需要填充到 16 字节边界
|
* - vec3 后面需要填充到 16 字节边界
|
||||||
|
* - 整个结构体大小必须是16的倍数
|
||||||
*/
|
*/
|
||||||
void MaterialLayout::finalize() {
|
void MaterialLayout::finalize() {
|
||||||
if (finalized_) return;
|
if (finalized_) return;
|
||||||
|
|
@ -44,7 +53,7 @@ void MaterialLayout::finalize() {
|
||||||
for (auto& pair : params_) {
|
for (auto& pair : params_) {
|
||||||
auto& info = pair.second;
|
auto& info = pair.second;
|
||||||
|
|
||||||
// 计算对齐要求
|
// 计算对齐要求 - std140规则
|
||||||
uint32_t alignment = 4; // 默认 4 字节对齐
|
uint32_t alignment = 4; // 默认 4 字节对齐
|
||||||
|
|
||||||
switch (info.type) {
|
switch (info.type) {
|
||||||
|
|
@ -60,21 +69,21 @@ void MaterialLayout::finalize() {
|
||||||
alignment = 16; // vec3/vec4/color: 16 字节对齐(std140 规则)
|
alignment = 16; // vec3/vec4/color: 16 字节对齐(std140 规则)
|
||||||
break;
|
break;
|
||||||
case MaterialParamType::Mat4:
|
case MaterialParamType::Mat4:
|
||||||
alignment = 16; // mat4: 16 字节对齐
|
alignment = 16; // mat4: 16 字节对齐,每列vec4对齐到16字节
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
alignment = 4;
|
alignment = 4;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 对齐偏移
|
// 对齐偏移到alignment的倍数
|
||||||
offset = (offset + alignment - 1) & ~(alignment - 1);
|
offset = (offset + alignment - 1) & ~(alignment - 1);
|
||||||
|
|
||||||
info.offset = offset;
|
info.offset = offset;
|
||||||
offset += info.size;
|
offset += info.size;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 最终大小对齐到 16 字节
|
// 最终大小对齐到 16 字节(std140要求结构体总大小是16的倍数)
|
||||||
bufferSize_ = (offset + 15) & ~15;
|
bufferSize_ = (offset + 15) & ~15;
|
||||||
finalized_ = true;
|
finalized_ = true;
|
||||||
}
|
}
|
||||||
|
|
@ -85,9 +94,9 @@ void MaterialLayout::finalize() {
|
||||||
* @return 参数信息指针,不存在返回 nullptr
|
* @return 参数信息指针,不存在返回 nullptr
|
||||||
*/
|
*/
|
||||||
const MaterialParamInfo* MaterialLayout::getParam(const std::string& name) const {
|
const MaterialParamInfo* MaterialLayout::getParam(const std::string& name) const {
|
||||||
auto it = params_.find(name);
|
auto mapIt = paramIndexMap_.find(name);
|
||||||
if (it != params_.end()) {
|
if (mapIt != paramIndexMap_.end()) {
|
||||||
return &it->second;
|
return ¶ms_[mapIt->second].second;
|
||||||
}
|
}
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -179,7 +179,7 @@ bool RenderGraph::compile() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void RenderGraph::execute(float deltaTime) {
|
void RenderGraph::execute(float deltaTime, const Mat4 &viewProjection) {
|
||||||
if (!compiled_) {
|
if (!compiled_) {
|
||||||
if (!compile()) {
|
if (!compile()) {
|
||||||
E2D_LOG_ERROR("Failed to compile RenderGraph");
|
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 上下文
|
// 获取 RHI 上下文
|
||||||
auto *rhiModule = RHIModule::get();
|
auto *rhiModule = RHIModule::get();
|
||||||
|
|
@ -212,8 +213,6 @@ void RenderGraph::execute(float deltaTime) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 执行命令队列
|
|
||||||
|
|
||||||
// 执行命令队列
|
// 执行命令队列
|
||||||
commandQueue_.execute();
|
commandQueue_.execute();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
#include <assets/assets_module.h>
|
#include <assets/assets_module.h>
|
||||||
#include <platform/window_module.h>
|
#include <platform/window_module.h>
|
||||||
|
#include <renderer/instance_buffer.h>
|
||||||
#include <renderer/render_graph.h>
|
#include <renderer/render_graph.h>
|
||||||
#include <renderer/renderer_module.h>
|
#include <renderer/renderer_module.h>
|
||||||
#include <renderer/rhi_module.h>
|
#include <renderer/rhi_module.h>
|
||||||
|
|
@ -208,10 +209,13 @@ void RendererModule::onRenderSubmit(const RenderCommand &cmd) {
|
||||||
|
|
||||||
Material *material = assets->get(cmd.drawInstanced.material);
|
Material *material = assets->get(cmd.drawInstanced.material);
|
||||||
Mesh *mesh = assets->get(cmd.drawInstanced.mesh);
|
Mesh *mesh = assets->get(cmd.drawInstanced.mesh);
|
||||||
|
InstanceBuffer *instanceBuffer =
|
||||||
|
static_cast<InstanceBuffer *>(cmd.drawInstanced.instanceBuffer);
|
||||||
|
|
||||||
if (material && mesh) {
|
if (material && mesh && instanceBuffer && instanceBuffer->isValid()) {
|
||||||
commandQueue_->submitDrawInstanced(Ptr<Material>(material),
|
commandQueue_->submitDrawInstanced(
|
||||||
Ptr<Mesh>(mesh),
|
Ptr<Material>(material), Ptr<Mesh>(mesh),
|
||||||
|
BufferHandle(instanceBuffer->getRHIBuffer()),
|
||||||
cmd.drawInstanced.instanceCount);
|
cmd.drawInstanced.instanceCount);
|
||||||
stats_.commandsSubmitted++;
|
stats_.commandsSubmitted++;
|
||||||
}
|
}
|
||||||
|
|
@ -248,11 +252,8 @@ void RendererModule::onRenderEnd() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 执行渲染图
|
// 执行渲染图,传递视图投影矩阵
|
||||||
|
renderGraph_.execute(0.016f, viewProjectionMatrix_);
|
||||||
// 执行渲染图
|
|
||||||
// 这里会执行所有渲染通道并提交命令
|
|
||||||
renderGraph_.execute(0.016f); // TODO: 使用实际的 deltaTime
|
|
||||||
|
|
||||||
// 获取统计信息
|
// 获取统计信息
|
||||||
if (commandQueue_) {
|
if (commandQueue_) {
|
||||||
|
|
|
||||||
|
|
@ -85,16 +85,24 @@ void GLCommandList::setPipeline(RHIPipeline *pipeline) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void GLCommandList::setVertexBuffer(uint32_t slot, RHIBuffer *buffer,
|
void GLCommandList::setVertexBuffer(uint32_t slot, RHIBuffer *buffer,
|
||||||
uint32_t offset) {
|
uint32_t offset, uint32_t stride) {
|
||||||
if (buffer) {
|
if (!buffer || !currentPipeline_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
auto *glBuffer = static_cast<GLBuffer *>(buffer);
|
auto *glBuffer = static_cast<GLBuffer *>(buffer);
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, glBuffer->getGLBuffer());
|
glBindBuffer(GL_ARRAY_BUFFER, glBuffer->getGLBuffer());
|
||||||
currentVertexBuffer_ = glBuffer;
|
|
||||||
|
|
||||||
// 如果有当前管线,配置顶点属性
|
// 只配置属于当前 slot 的属性
|
||||||
if (currentPipeline_) {
|
|
||||||
const auto &layout = currentPipeline_->getVertexLayout();
|
const auto &layout = currentPipeline_->getVertexLayout();
|
||||||
|
// 使用传入的步长,如果为0则使用布局中的步长
|
||||||
|
uint32_t actualStride = stride > 0 ? stride : layout.stride;
|
||||||
|
|
||||||
for (const auto &attr : layout.attributes) {
|
for (const auto &attr : layout.attributes) {
|
||||||
|
if (attr.bufferIndex != slot) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
glEnableVertexAttribArray(attr.location);
|
glEnableVertexAttribArray(attr.location);
|
||||||
|
|
||||||
GLenum type = GL_FLOAT;
|
GLenum type = GL_FLOAT;
|
||||||
|
|
@ -118,10 +126,11 @@ void GLCommandList::setVertexBuffer(uint32_t slot, RHIBuffer *buffer,
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
glVertexAttribPointer(attr.location, size, type, normalized, layout.stride,
|
glVertexAttribPointer(attr.location, size, type, normalized, actualStride,
|
||||||
reinterpret_cast<const void *>(attr.offset));
|
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) {
|
void GLCommandList::setTexture(uint32_t slot, RHITexture *texture) {
|
||||||
if (texture) {
|
if (texture) {
|
||||||
auto *glTexture = static_cast<GLTexture *>(texture);
|
auto *glTexture = static_cast<GLTexture *>(texture);
|
||||||
|
|
@ -213,45 +236,45 @@ bool GLCommandList::isRecording() const {
|
||||||
return recording_;
|
return recording_;
|
||||||
}
|
}
|
||||||
|
|
||||||
void GLCommandList::setUniform(const std::string& name, float value) {
|
void GLCommandList::setUniform(const char* name, float value) {
|
||||||
if (currentShaderProgram_ != 0) {
|
if (currentShaderProgram_ != 0) {
|
||||||
GLint location = glGetUniformLocation(currentShaderProgram_, name.c_str());
|
GLint location = glGetUniformLocation(currentShaderProgram_, name);
|
||||||
if (location != -1) {
|
if (location != -1) {
|
||||||
glUniform1f(location, value);
|
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) {
|
if (currentShaderProgram_ != 0) {
|
||||||
GLint location = glGetUniformLocation(currentShaderProgram_, name.c_str());
|
GLint location = glGetUniformLocation(currentShaderProgram_, name);
|
||||||
if (location != -1) {
|
if (location != -1) {
|
||||||
glUniform2f(location, value.x, value.y);
|
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) {
|
if (currentShaderProgram_ != 0) {
|
||||||
GLint location = glGetUniformLocation(currentShaderProgram_, name.c_str());
|
GLint location = glGetUniformLocation(currentShaderProgram_, name);
|
||||||
if (location != -1) {
|
if (location != -1) {
|
||||||
glUniform3f(location, value.x, value.y, value.z);
|
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) {
|
if (currentShaderProgram_ != 0) {
|
||||||
GLint location = glGetUniformLocation(currentShaderProgram_, name.c_str());
|
GLint location = glGetUniformLocation(currentShaderProgram_, name);
|
||||||
if (location != -1) {
|
if (location != -1) {
|
||||||
glUniform4f(location, value.r, value.g, value.b, value.a);
|
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) {
|
if (currentShaderProgram_ != 0) {
|
||||||
GLint location = glGetUniformLocation(currentShaderProgram_, name.c_str());
|
GLint location = glGetUniformLocation(currentShaderProgram_, name);
|
||||||
if (location != -1) {
|
if (location != -1) {
|
||||||
glUniformMatrix4fv(location, 1, GL_FALSE, glm::value_ptr(value));
|
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,
|
bool Shader::loadFromSource(const std::string &vsSource,
|
||||||
const std::string &fsSource) {
|
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();
|
pipeline_ = PipelineHandle();
|
||||||
handle_ = ShaderHandle();
|
handle_ = ShaderHandle();
|
||||||
|
|
@ -78,13 +152,6 @@ bool Shader::loadFromSource(const std::string &vsSource,
|
||||||
// 获取着色器句柄
|
// 获取着色器句柄
|
||||||
handle_ = ShaderHandle(shader.release());
|
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 pipelineDesc;
|
||||||
pipelineDesc.vertexShader = handle_;
|
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();
|
CameraComponent *camera = runningScene_->getMainCamera();
|
||||||
if (camera) {
|
if (camera) {
|
||||||
Mat4 viewProj = camera->getViewProjectionMatrix();
|
Mat4 viewProj = camera->getViewProjectionMatrix();
|
||||||
|
|
||||||
events::OnRenderSetCamera::emit(viewProj);
|
events::OnRenderSetCamera::emit(viewProj);
|
||||||
|
} else {
|
||||||
|
E2D_LOG_WARN("Director::render: No main camera set!");
|
||||||
}
|
}
|
||||||
|
|
||||||
runningScene_->render();
|
runningScene_->render();
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue