diff --git a/examples/scene_graph_demo/game_scene.cpp b/examples/scene_graph_demo/game_scene.cpp index 2fae529..632a71f 100644 --- a/examples/scene_graph_demo/game_scene.cpp +++ b/examples/scene_graph_demo/game_scene.cpp @@ -1,4 +1,5 @@ #include "game_scene.h" +#include "instanced_test.h" #include // ======================================== @@ -6,34 +7,34 @@ // ======================================== PlayerNode::PlayerNode() { - setName("Player"); - setTag(1); + setName("Player"); + setTag(1); - // 设置玩家尺寸 - setSize(64.0f, 64.0f); + // 设置玩家尺寸 + setSize(64.0f, 64.0f); - // 设置锚点为中心点 - setAnchor(0.5f, 0.5f); + // 设置锚点为中心点 + setAnchor(0.5f, 0.5f); - // 添加精灵渲染组件 - auto sprite = makePtr(); - sprite->setColor(Color::Blue); - addComponent(sprite); + // 添加精灵渲染组件 + auto sprite = makePtr(); + sprite->setColor(Color::Blue); + addComponent(sprite); } void PlayerNode::onUpdate(float dt) { - time_ += dt; + time_ += dt; - // 简单的圆周运动 - float radius = 200.0f; - float x = 640.0f + radius * std::cos(time_ * 0.5f); - float y = 360.0f + radius * std::sin(time_ * 0.5f); + // 简单的圆周运动 + float radius = 200.0f; + float x = 640.0f + radius * std::cos(time_ * 0.5f); + float y = 360.0f + radius * std::sin(time_ * 0.5f); - setPosition(x, y); + setPosition(x, y); - // 根据移动方向旋转(顺时针,使用负角度) - float angle = -time_ * 0.5f * 57.2958f + 90.0f; - setRotation(angle); + // 根据移动方向旋转(顺时针,使用负角度) + float angle = -time_ * 0.5f * 57.2958f + 90.0f; + setRotation(angle); } // ======================================== @@ -41,23 +42,23 @@ void PlayerNode::onUpdate(float dt) { // ======================================== RotatingDecoration::RotatingDecoration() { - setName("Decoration"); + setName("Decoration"); - // 设置尺寸 - setSize(32.0f, 32.0f); + // 设置尺寸 + setSize(32.0f, 32.0f); - // 设置锚点为中心 - setAnchor(0.5f, 0.5f); + // 设置锚点为中心 + setAnchor(0.5f, 0.5f); - // 添加精灵渲染组件 - auto sprite = makePtr(); - sprite->setColor(Color::Yellow); - addComponent(sprite); + // 添加精灵渲染组件 + auto sprite = makePtr(); + sprite->setColor(Color::Yellow); + addComponent(sprite); } void RotatingDecoration::onUpdate(float dt) { - // 自转(顺时针,使用负角度) - setRotation(getRotation() - rotationSpeed_ * dt); + // 自转(顺时针,使用负角度) + setRotation(getRotation() - rotationSpeed_ * dt); } // ======================================== @@ -65,106 +66,120 @@ void RotatingDecoration::onUpdate(float dt) { // ======================================== GameScene::GameScene() { - // 场景初始化 + // 场景初始化 } void GameScene::onEnter() { - Scene::onEnter(); + Scene::onEnter(); - // 创建相机 - createCamera(); + // 创建相机 + createCamera(); - // 创建玩家 - createPlayer(); + // 创建玩家 + createPlayer(); - // 创建装饰物 - createDecorations(); + // 创建装饰物 + createDecorations(); + + // 创建实例化渲染测试(1000个实例) + createInstancedTest(); } void GameScene::onExit() { - // 清理资源 - player_.reset(); - decorations_.clear(); + // 清理资源 + player_.reset(); + decorations_.clear(); - Scene::onExit(); + Scene::onExit(); } void GameScene::update(float dt) { - Scene::update(dt); + Scene::update(dt); - sceneTime_ += dt; + sceneTime_ += dt; - // 更新玩家 - if (player_) { - player_->onUpdate(dt); - } + // 更新玩家 + if (player_) { + player_->onUpdate(dt); + } - // 更新装饰物 - for (auto& decoration : decorations_) { - decoration->onUpdate(dt); - } + // 更新装饰物 + for (auto &decoration : decorations_) { + decoration->onUpdate(dt); + } + + // 更新实例化测试 + if (instancedTest_) { + instancedTest_->update(dt); + } } void GameScene::createPlayer() { - player_ = makePtr(); - player_->setPosition(640.0f, 360.0f); - addChild(player_); + player_ = makePtr(); + player_->setPosition(640.0f, 360.0f); + addChild(player_); } void GameScene::createDecorations() { - // 创建围绕玩家旋转的装饰物 - int count = 8; - float radius = 150.0f; + // 创建围绕玩家旋转的装饰物 + int count = 8; + float radius = 150.0f; - for (int i = 0; i < count; ++i) { - auto decoration = makePtr(); + for (int i = 0; i < count; ++i) { + auto decoration = makePtr(); - // 计算位置(圆形分布) - float angle = (2.0f * 3.14159f * i) / count; - float x = 640.0f + radius * std::cos(angle); - float y = 360.0f + radius * std::sin(angle); + // 计算位置(圆形分布) + float angle = (2.0f * 3.14159f * i) / count; + float x = 640.0f + radius * std::cos(angle); + float y = 360.0f + radius * std::sin(angle); - decoration->setPosition(x, y); - decoration->setRotationSpeed(45.0f + i * 10.0f); + decoration->setPosition(x, y); + decoration->setRotationSpeed(45.0f + i * 10.0f); - // 设置不同颜色 - auto sprite = decoration->getComponent(); - if (sprite) { - float hue = static_cast(i) / count; - // 简单的HSV到RGB转换 - float r = std::abs(hue * 6.0f - 3.0f) - 1.0f; - float g = 2.0f - std::abs(hue * 6.0f - 2.0f); - float b = 2.0f - std::abs(hue * 6.0f - 4.0f); - sprite->setColor(Color( - std::max(0.0f, std::min(1.0f, r)), - std::max(0.0f, std::min(1.0f, g)), - std::max(0.0f, std::min(1.0f, b)), - 1.0f - )); - } - - decorations_.push_back(decoration); - addChild(decoration); + // 设置不同颜色 + auto sprite = decoration->getComponent(); + if (sprite) { + float hue = static_cast(i) / count; + // 简单的HSV到RGB转换 + float r = std::abs(hue * 6.0f - 3.0f) - 1.0f; + float g = 2.0f - std::abs(hue * 6.0f - 2.0f); + float b = 2.0f - std::abs(hue * 6.0f - 4.0f); + sprite->setColor(Color(std::max(0.0f, std::min(1.0f, r)), + std::max(0.0f, std::min(1.0f, g)), + std::max(0.0f, std::min(1.0f, b)), 1.0f)); } + + decorations_.push_back(decoration); + addChild(decoration); + } } void GameScene::createCamera() { - // 创建相机节点 - auto cameraNode = makePtr(); - cameraNode->setName("MainCamera"); - // 相机位置在(0, 0),这样世界坐标直接映射到屏幕 - cameraNode->setPosition(0.0f, 0.0f); + // 创建相机节点 + auto cameraNode = makePtr(); + cameraNode->setName("MainCamera"); + // 相机位置在(0, 0),这样世界坐标直接映射到屏幕 + cameraNode->setPosition(0.0f, 0.0f); - // 添加相机组件 - auto camera = makePtr(); - // 使用标准的2D投影:左上角为(0, 0),右下角为(1280, 720) - // Y轴向下:bottom=720, top=0 - camera->setOrtho(0.0f, 1280.0f, 720.0f, 0.0f, -1.0f, 1.0f); - cameraNode->addComponent(camera); + // 添加相机组件 + auto camera = makePtr(); + // 使用标准的2D投影:左上角为(0, 0),右下角为(1280, 720) + // Y轴向下:bottom=720, top=0 + camera->setOrtho(0.0f, 1280.0f, 720.0f, 0.0f, -1.0f, 1.0f); + cameraNode->addComponent(camera); - // 设置为主相机 - setMainCamera(camera); + // 设置为主相机 + setMainCamera(camera); - // 添加相机节点到场景 - addChild(cameraNode); + // 添加相机节点到场景 + addChild(cameraNode); +} + +void GameScene::createInstancedTest() { + // 创建实例化渲染测试节点 + auto instancedTest = makePtr(); + if (instancedTest->initialize(1000)) { + instancedTest_ = instancedTest; + addChild(instancedTest); + } } diff --git a/examples/scene_graph_demo/game_scene.h b/examples/scene_graph_demo/game_scene.h index bd8e5d2..6deaa7e 100644 --- a/examples/scene_graph_demo/game_scene.h +++ b/examples/scene_graph_demo/game_scene.h @@ -1,6 +1,7 @@ #pragma once #include +#include "instanced_test.h" using namespace extra2d; @@ -134,7 +135,13 @@ private: */ void createCamera(); + /** + * @brief 创建实例化渲染测试 + */ + void createInstancedTest(); + Ptr player_; std::vector> decorations_; + Ptr instancedTest_; float sceneTime_ = 0.0f; }; diff --git a/examples/scene_graph_demo/instanced_test.cpp b/examples/scene_graph_demo/instanced_test.cpp new file mode 100644 index 0000000..6f02297 --- /dev/null +++ b/examples/scene_graph_demo/instanced_test.cpp @@ -0,0 +1,139 @@ +#include "instanced_test.h" +#include +#include +#include +#include +#include +#include + +namespace extra2d { + +InstancedTestNode::InstancedTestNode() { + setName("InstancedTest"); +} + +InstancedTestNode::~InstancedTestNode() { + instanceBuffer_.shutdown(); +} + +bool InstancedTestNode::initialize(uint32_t instanceCount) { + instanceCount_ = instanceCount; + + // 获取资源模块 + auto* assets = getModule(); + 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(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 diff --git a/examples/scene_graph_demo/instanced_test.h b/examples/scene_graph_demo/instanced_test.h new file mode 100644 index 0000000..4df7473 --- /dev/null +++ b/examples/scene_graph_demo/instanced_test.h @@ -0,0 +1,58 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +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_; // CPU端实例数据 + uint32_t instanceCount_ = 0; // 实例数量 + float time_ = 0.0f; // 时间累积 + + // 资源句柄 + Handle material_; + Handle mesh_; + Handle texture_; + + /** + * @brief 更新实例变换 + */ + void updateInstances(); +}; + +} // namespace extra2d diff --git a/examples/scene_graph_demo/xmake.lua b/examples/scene_graph_demo/xmake.lua index 64f730c..2383d66 100644 --- a/examples/scene_graph_demo/xmake.lua +++ b/examples/scene_graph_demo/xmake.lua @@ -10,7 +10,7 @@ local example_dir = os.scriptdir() -- 可执行文件目标 target("scene_graph_demo") set_kind("binary") - add_files("main.cpp", "game_scene.cpp") + add_files("main.cpp", "game_scene.cpp", "instanced_test.cpp") add_includedirs("../../include", ".") add_deps("extra2d") diff --git a/include/assets/assets_module.h b/include/assets/assets_module.h index 2321e89..fddd32a 100644 --- a/include/assets/assets_module.h +++ b/include/assets/assets_module.h @@ -175,6 +175,31 @@ public: Material *getDefaultMaterialPtr(); Mesh *getDefaultQuadPtr(); + //=========================================================================== + // 实例化渲染资源 + //=========================================================================== + + /** + * @brief 获取实例化渲染着色器 + * @return 实例化着色器句柄 + */ + Handle getInstancedShader(); + + /** + * @brief 获取实例化渲染材质 + * @return 实例化材质句柄 + */ + Handle getInstancedMaterial(); + + Shader *getInstancedShaderPtr(); + Material *getInstancedMaterialPtr(); + + /** + * @brief 创建实例化渲染资源 + * @return 是否成功 + */ + bool createInstancedResources(); + //=========================================================================== // 热重载 //=========================================================================== @@ -259,6 +284,10 @@ private: Handle defaultMaterial_; Handle defaultQuad_; + // 实例化渲染资源 + Handle instancedShader_; + Handle instancedMaterial_; + // 热重载 bool hotReloadEnabled_ = false; float hotReloadInterval_ = 1.0f; diff --git a/include/renderer/command_queue.h b/include/renderer/command_queue.h index 14813ad..8737139 100644 --- a/include/renderer/command_queue.h +++ b/include/renderer/command_queue.h @@ -4,6 +4,7 @@ #include #include #include +#include #include #include #include @@ -15,6 +16,7 @@ namespace extra2d { class CommandQueue; class Material; class Mesh; +class UniformBufferManager; template class Ptr; /** @@ -68,6 +70,9 @@ struct DrawCommand { uint32_t textureCount; // 纹理数量 BufferHandle materialUBO; // 材质 UBO uint32_t materialUBOSize; // 材质 UBO 大小 + uint32_t materialUBOOffset; // 材质 UBO 在全局缓冲区中的偏移 + BufferHandle instanceBuffer; // 实例数据缓冲区(实例化渲染使用) + uint32_t instanceBufferStride; // 实例数据步长 // 变换和颜色数据(用于设置 shader uniform) Mat4 modelMatrix; // 模型矩阵 @@ -75,7 +80,8 @@ struct DrawCommand { DrawCommand() : vertexCount(0), indexCount(0), instanceCount(1), textureCount(0), - materialUBOSize(0), modelMatrix(glm::identity()), color(Color::White) {} + materialUBOSize(0), materialUBOOffset(0), instanceBufferStride(0), + modelMatrix(glm::identity()), color(Color::White) {} // 检查是否使用索引绘制 bool isIndexed() const { return indexCount > 0; } @@ -265,10 +271,11 @@ public: * @brief 提交实例化绘制命令 * @param material 材质 * @param mesh 网格 + * @param instanceBuffer 实例数据缓冲区 * @param instanceCount 实例数量 */ void submitDrawInstanced(Ptr material, Ptr mesh, - uint32_t instanceCount); + BufferHandle instanceBuffer, uint32_t instanceCount); /** * @brief 提交清除命令 @@ -293,6 +300,16 @@ public: */ void execute(); + /** + * @brief 更新全局 UBO 数据 + * @param viewProjection 视图投影矩阵 + * @param deltaTime 帧时间 + * @param screenWidth 屏幕宽度 + * @param screenHeight 屏幕高度 + */ + void updateGlobalUBO(const Mat4& viewProjection, float deltaTime, + uint32_t screenWidth, uint32_t screenHeight); + /** * @brief 获取当前命令数量 * @return 命令数量 @@ -311,12 +328,23 @@ private: RHIContext *context_ = nullptr; std::unique_ptr commandList_; - // 全局 UBO 数据 - struct GlobalUBOData { - float viewProjection[16]; - float time; - float screenSize[2]; - float padding; + // UBO 管理器 + std::unique_ptr uboManager_; + + // 全局 UBO 数据 - 必须与着色器中的 std140 布局完全匹配 + // layout(std140, binding = 0) uniform GlobalUBO { + // mat4 uViewProjection; // 64 bytes, offset 0 + // vec4 uCameraPosition; // 16 bytes, offset 64 + // float uTime; // 4 bytes, offset 80 + // float uDeltaTime; // 4 bytes, offset 84 + // vec2 uScreenSize; // 8 bytes, offset 88 + // }; // 总大小: 96 bytes (std140 对齐) + struct alignas(16) GlobalUBOData { + float viewProjection[16]; // 64 bytes, offset 0 + float cameraPosition[4]; // 16 bytes, offset 64 + float time; // 4 bytes, offset 80 + float deltaTime; // 4 bytes, offset 84 + float screenSize[2]; // 8 bytes, offset 88 } globalUBOData_; // 材质 UBO 数据缓冲区 @@ -328,6 +356,10 @@ private: // 材质到 ID 的映射 std::unordered_map materialIds_; + // 当前材质 UBO 缓冲区 + UniformBuffer* currentMaterialUBO_ = nullptr; + uint32_t currentMaterialUBOOffset_ = 0; + /** * @brief 获取或创建材质 ID * @param material 材质指针 @@ -341,6 +373,13 @@ private: * @param batch 命令批次 */ void executeBatch(uint32_t batchIndex, const CommandBatch &batch); + + /** + * @brief 分配材质 UBO 空间 + * @param size 需要的空间大小 + * @return 分配的 UBO 指针和偏移量 + */ + std::pair allocateMaterialUBO(uint32_t size); }; } // namespace extra2d diff --git a/include/renderer/instance_buffer.h b/include/renderer/instance_buffer.h new file mode 100644 index 0000000..6fce8f4 --- /dev/null +++ b/include/renderer/instance_buffer.h @@ -0,0 +1,208 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +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 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> bufferPool_; + uint32_t currentBufferIndex_ = 0; + static constexpr uint32_t DEFAULT_BUFFER_SIZE = 1024; +}; + +} // namespace extra2d diff --git a/include/renderer/material.h b/include/renderer/material.h index e384555..f9ff7f0 100644 --- a/include/renderer/material.h +++ b/include/renderer/material.h @@ -109,7 +109,9 @@ public: bool isFinalized() const { return finalized_; } private: - std::unordered_map params_; + // 使用vector保持参数添加顺序,确保与着色器中的声明顺序一致 + std::vector> params_; + std::unordered_map paramIndexMap_; // 用于快速查找 uint32_t bufferSize_ = 0; bool finalized_ = false; }; diff --git a/include/renderer/render_graph.h b/include/renderer/render_graph.h index 5a736c1..cac789b 100644 --- a/include/renderer/render_graph.h +++ b/include/renderer/render_graph.h @@ -236,8 +236,9 @@ public: /** * @brief 执行渲染图 * @param deltaTime 帧时间 + * @param viewProjection 视图投影矩阵 */ - void execute(float deltaTime); + void execute(float deltaTime, const Mat4& viewProjection = Mat4(1.0f)); /** * @brief 获取纹理资源 diff --git a/include/renderer/render_types.h b/include/renderer/render_types.h index c4f0777..eb64c93 100644 --- a/include/renderer/render_types.h +++ b/include/renderer/render_types.h @@ -46,10 +46,11 @@ struct RenderCommand { Color color; // 顶点颜色 }; - // 实例化绘制命令数据 +// 实例化绘制命令数据 struct DrawInstancedData { Handle mesh; // 网格句柄 Handle material; // 材质句柄 + void* instanceBuffer; // 实例数据缓冲区指针 (InstanceBuffer*) uint32_t instanceCount; // 实例数量 uint32_t instanceOffset; // 实例数据偏移 }; diff --git a/include/renderer/rhi/opengl/gl_command_list.h b/include/renderer/rhi/opengl/gl_command_list.h index 24595a0..f35226b 100644 --- a/include/renderer/rhi/opengl/gl_command_list.h +++ b/include/renderer/rhi/opengl/gl_command_list.h @@ -51,10 +51,11 @@ public: //=========================================================================== void setVertexBuffer(uint32_t slot, RHIBuffer *buffer, - uint32_t offset = 0) override; + uint32_t offset = 0, uint32_t stride = 0) override; void setIndexBuffer(RHIBuffer *buffer, IndexType type, uint32_t offset = 0) override; void setUniformBuffer(uint32_t slot, RHIBuffer *buffer) override; + void setUniformBuffer(uint32_t slot, RHIBuffer *buffer, uint32_t offset, uint32_t size = 0) override; void setTexture(uint32_t slot, RHITexture *texture) override; void setSampler(uint32_t slot, TextureFilter minFilter, TextureFilter magFilter, TextureWrap wrapS, @@ -81,11 +82,11 @@ public: bool isRecording() const override; // 设置 uniform 变量 - void setUniform(const std::string& name, float value); - void setUniform(const std::string& name, const Vec2& value); - void setUniform(const std::string& name, const Vec3& value); - void setUniform(const std::string& name, const Color& value); - void setUniform(const std::string& name, const Mat4& value); + void setUniform(const char* name, float value) override; + void setUniform(const char* name, const Vec2& value) override; + void setUniform(const char* name, const Vec3& value) override; + void setUniform(const char* name, const Color& value) override; + void setUniform(const char* name, const Mat4& value) override; private: bool recording_ = false; diff --git a/include/renderer/rhi/rhi_command_list.h b/include/renderer/rhi/rhi_command_list.h index 2579ddc..7b1d3e6 100644 --- a/include/renderer/rhi/rhi_command_list.h +++ b/include/renderer/rhi/rhi_command_list.h @@ -89,8 +89,9 @@ public: * @param slot 槽位 * @param buffer 缓冲区 * @param offset 偏移(字节) + * @param stride 步长(字节,0表示使用布局中的步长) */ - virtual void setVertexBuffer(uint32_t slot, RHIBuffer* buffer, uint32_t offset = 0) = 0; + virtual void setVertexBuffer(uint32_t slot, RHIBuffer* buffer, uint32_t offset = 0, uint32_t stride = 0) = 0; /** * @brief 设置索引缓冲区 @@ -107,6 +108,15 @@ public: */ virtual void setUniformBuffer(uint32_t slot, RHIBuffer* buffer) = 0; + /** + * @brief 设置 Uniform 缓冲区(带偏移) + * @param slot 槽位 + * @param buffer 缓冲区 + * @param offset 缓冲区偏移(字节) + * @param size 绑定大小(字节,0 表示整个缓冲区) + */ + virtual void setUniformBuffer(uint32_t slot, RHIBuffer* buffer, uint32_t offset, uint32_t size = 0) = 0; + /** * @brief 设置纹理 * @param slot 槽位 @@ -128,6 +138,41 @@ public: TextureWrap wrapS, TextureWrap wrapT) = 0; + /** + * @brief 设置 float 类型的 uniform 变量 + * @param name 变量名 + * @param value 值 + */ + virtual void setUniform(const char* name, float value) = 0; + + /** + * @brief 设置 vec2 类型的 uniform 变量 + * @param name 变量名 + * @param value 值 + */ + virtual void setUniform(const char* name, const Vec2& value) = 0; + + /** + * @brief 设置 vec3 类型的 uniform 变量 + * @param name 变量名 + * @param value 值 + */ + virtual void setUniform(const char* name, const Vec3& value) = 0; + + /** + * @brief 设置 vec4/Color 类型的 uniform 变量 + * @param name 变量名 + * @param value 值 + */ + virtual void setUniform(const char* name, const Color& value) = 0; + + /** + * @brief 设置 mat4 类型的 uniform 变量 + * @param name 变量名 + * @param value 值 + */ + virtual void setUniform(const char* name, const Mat4& value) = 0; + //=========================================================================== // 绘制命令 //=========================================================================== diff --git a/include/renderer/rhi/rhi_types.h b/include/renderer/rhi/rhi_types.h index 14aebd5..75718d7 100644 --- a/include/renderer/rhi/rhi_types.h +++ b/include/renderer/rhi/rhi_types.h @@ -229,8 +229,21 @@ struct VertexAttribute { VertexFormat format = VertexFormat::Float3; uint32_t offset = 0; // 在顶点结构中的偏移 uint32_t bufferIndex = 0; // 绑定的顶点缓冲区索引 + uint32_t divisor = 0; // 实例化除数(0=每顶点,1=每实例,N=每N个实例) static uint32_t getSize(VertexFormat format); + + // 创建实例化属性 + static VertexAttribute perInstance(uint32_t location, VertexFormat format, + uint32_t offset, uint32_t bufferIndex = 1) { + VertexAttribute attr; + attr.location = location; + attr.format = format; + attr.offset = offset; + attr.bufferIndex = bufferIndex; + attr.divisor = 1; + return attr; + } }; /** @@ -242,6 +255,14 @@ struct VertexLayout { void addAttribute(uint32_t location, VertexFormat format, uint32_t offset, uint32_t bufferIndex = 0); + + /** + * @brief 添加顶点属性(直接传入VertexAttribute) + * @param attr 顶点属性 + */ + void addAttribute(const VertexAttribute& attr) { + attributes.push_back(attr); + } }; /** diff --git a/include/renderer/shader.h b/include/renderer/shader.h index 673bec2..c6a39b7 100644 --- a/include/renderer/shader.h +++ b/include/renderer/shader.h @@ -43,6 +43,22 @@ public: */ bool loadFromSource(const std::string &vsSource, const std::string &fsSource); + /** + * @brief 从文件加载实例化着色器(支持实例属性) + * @param vsPath 顶点着色器文件路径 + * @param fsPath 片段着色器文件路径 + * @return 加载是否成功 + */ + bool loadInstancedFromFile(const std::string &vsPath, const std::string &fsPath); + + /** + * @brief 从源码加载实例化着色器(支持实例属性) + * @param vsSource 顶点着色器源码 + * @param fsSource 片段着色器源码 + * @return 加载是否成功 + */ + bool loadInstancedFromSource(const std::string &vsSource, const std::string &fsSource); + /** * @brief 获取 RHI 着色器句柄 * @return RHI 着色器句柄 @@ -61,6 +77,17 @@ public: */ bool isLoaded() const { return handle_.isValid() && pipeline_.isValid(); } + /** + * @brief 使用自定义顶点布局从源码加载着色器 + * @param vsSource 顶点着色器源码 + * @param fsSource 片段着色器源码 + * @param vertexLayout 顶点布局 + * @return 加载是否成功 + */ + bool loadFromSourceWithLayout(const std::string &vsSource, + const std::string &fsSource, + const VertexLayout &vertexLayout); + /** * @brief 设置 Uniform Block 绑定槽位 * @param name Uniform Block 名称 diff --git a/include/renderer/texture_atlas.h b/include/renderer/texture_atlas.h new file mode 100644 index 0000000..0149232 --- /dev/null +++ b/include/renderer/texture_atlas.h @@ -0,0 +1,247 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +// 使用 stb_rect_pack 进行矩形打包 +#define STB_RECT_PACK_IMPLEMENTATION +#include + +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(x) / atlasWidth, + static_cast(y) / atlasHeight, + static_cast(width) / atlasWidth, + static_cast(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(x) / atlasWidth; + float v = static_cast(atlasHeight - y - height) / atlasHeight; + float w = static_cast(width) / atlasWidth; + float h = static_cast(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); + + /** + * @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 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 data; + TextureFormat format; + }; + + int width_ = 0; + int height_ = 0; + Ptr atlasTexture_; + std::unordered_map regions_; + std::vector pendingTextures_; + + // stb_rect_pack 上下文 + std::unique_ptr packContext_; + std::vector 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) { + textures_.push_back({name, texture}); + } + + /** + * @brief 构建图集 + * @return 图集指针,失败返回 nullptr + */ + Ptr build(); + + /** + * @brief 自动选择最佳图集大小并构建 + * @return 图集指针,失败返回 nullptr + */ + Ptr buildAuto(); + +private: + int width_ = 2048; + int height_ = 2048; + std::vector>> textures_; +}; + +} // namespace extra2d diff --git a/include/scene/node.h b/include/scene/node.h index 24f6df4..ee10db9 100644 --- a/include/scene/node.h +++ b/include/scene/node.h @@ -307,7 +307,7 @@ public: /** * @brief 渲染(收集渲染命令) */ - void render(); + virtual void render(); private: std::string name_; diff --git a/shader/default.frag b/shader/default.frag index 23bc740..4a235f0 100644 --- a/shader/default.frag +++ b/shader/default.frag @@ -4,14 +4,12 @@ precision highp float; // 从顶点着色器输入 in vec2 vTexCoord; in vec4 vColor; +in vec4 vTintColor; +in float vOpacity; // 纹理采样器 uniform sampler2D uTexture; -// 材质参数 -uniform vec4 uTintColor; -uniform float uOpacity; - // 输出颜色 out vec4 fragColor; @@ -21,20 +19,19 @@ out vec4 fragColor; * 采样纹理并与顶点颜色、色调和透明度混合 */ void main() { - // 采样纹理(如果没有绑定纹理,texture 会返回 vec4(0,0,0,1) 或 vec4(1,1,1,1) 取决于实现) + // 采样纹理 vec4 texColor = texture(uTexture, vTexCoord); // 如果纹理采样结果是黑色或透明,使用白色作为默认值 - // 这样即使在没有纹理的情况下也能显示颜色 if (texColor.rgb == vec3(0.0) || texColor.a < 0.01) { texColor = vec4(1.0, 1.0, 1.0, 1.0); } // 混合:纹理 * 顶点颜色 * 色调 - fragColor = texColor * vColor * uTintColor; + fragColor = texColor * vColor * vTintColor; // 应用透明度 - fragColor.a *= uOpacity; + fragColor.a *= vOpacity; // Alpha 测试:丢弃几乎透明的像素 if (fragColor.a < 0.01) { diff --git a/shader/default.vert b/shader/default.vert index 495259b..95fca8b 100644 --- a/shader/default.vert +++ b/shader/default.vert @@ -1,15 +1,26 @@ #version 320 es precision highp float; -// 视图投影矩阵 -uniform mat4 uViewProjection; +// 全局 UBO (binding = 0) - 每帧更新一次 +layout(std140, binding = 0) uniform GlobalUBO { + mat4 uViewProjection; + vec4 uCameraPosition; + float uTime; + float uDeltaTime; + vec2 uScreenSize; +}; -// 模型矩阵 +// 材质 UBO (binding = 1) - 每物体更新 +layout(std140, binding = 1) uniform MaterialUBO { + vec4 uColor; + vec4 uTintColor; + float uOpacity; + float uPadding[3]; // std140 对齐填充 +}; + +// 模型矩阵作为单独的统一变量(每个物体设置) uniform mat4 uModelMatrix; -// 顶点颜色(覆盖顶点属性中的颜色) -uniform vec4 uColor; - // 顶点属性 layout(location = 0) in vec2 aPosition; layout(location = 1) in vec2 aTexCoord; @@ -18,16 +29,20 @@ layout(location = 2) in vec4 aColor; // 输出到片段着色器 out vec2 vTexCoord; out vec4 vColor; +out vec4 vTintColor; +out float vOpacity; /** * @brief 顶点着色器入口 * * 计算顶点在裁剪空间中的位置, - * 并传递纹理坐标和顶点颜色到片段着色器 + * 并传递纹理坐标和颜色到片段着色器 */ void main() { gl_Position = uViewProjection * uModelMatrix * vec4(aPosition, 0.0, 1.0); vTexCoord = aTexCoord; - // 使用 uniform 颜色覆盖顶点属性颜色 - vColor = uColor; + // 混合顶点颜色和材质 UBO 中的颜色 + vColor = aColor * uColor; + vTintColor = uTintColor; + vOpacity = uOpacity; } diff --git a/shader/instanced.vert b/shader/instanced.vert new file mode 100644 index 0000000..b910b52 --- /dev/null +++ b/shader/instanced.vert @@ -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; +} diff --git a/shader/sprite_instanced.frag b/shader/sprite_instanced.frag new file mode 100644 index 0000000..aad250d --- /dev/null +++ b/shader/sprite_instanced.frag @@ -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; + } +} diff --git a/shader/sprite_instanced.vert b/shader/sprite_instanced.vert new file mode 100644 index 0000000..0675904 --- /dev/null +++ b/shader/sprite_instanced.vert @@ -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; +} diff --git a/src/assets/assets_module.cpp b/src/assets/assets_module.cpp index eaeb614..7e83a8a 100644 --- a/src/assets/assets_module.cpp +++ b/src/assets/assets_module.cpp @@ -1,8 +1,8 @@ +#include #include #include #include #include -#include #include #include #include @@ -46,6 +46,13 @@ void AssetsModule::onGLContextReady() { return; } + // 创建实例化渲染资源 + if (!createInstancedResources()) { + E2D_LOG_WARN("Failed to create instanced resources, instanced rendering " + "will not be available"); + // 不返回错误,实例化渲染是可选功能 + } + defaultResourcesCreated_ = true; E2D_LOG_INFO("Default resources created successfully"); } @@ -123,24 +130,24 @@ Handle AssetsModule::load(const std::string &path) { Handle handle; { std::unique_lock lock(mutex_); - + // 双重检查,避免重复加载 auto it = texturePathCache_.find(path); if (it != texturePathCache_.end() && textures_.isValid(it->second)) { return it->second; } - + handle = textures_.insert(texture); texturePathCache_[path] = handle; } E2D_LOG_DEBUG("Loaded texture: {} -> handle index {}", path, handle.index()); - + // 如果启用了热重载,添加文件监控 if (hotReloadEnabled_) { addFileWatch(path, handle); } - + return handle; } @@ -177,8 +184,7 @@ Handle AssetsModule::loadFromMemory(const std::string &key, // Shader 加载特化 //=========================================================================== -template <> -Handle AssetsModule::load(const std::string &path) { +template <> Handle AssetsModule::load(const std::string &path) { // 先检查缓存(读锁) { std::shared_lock lock(mutex_); @@ -205,24 +211,24 @@ Handle AssetsModule::load(const std::string &path) { Handle handle; { std::unique_lock lock(mutex_); - + // 双重检查 auto it = shaderPathCache_.find(path); if (it != shaderPathCache_.end() && shaders_.isValid(it->second)) { return it->second; } - + handle = shaders_.insert(shader); shaderPathCache_[path] = handle; } E2D_LOG_DEBUG("Loaded shader: {} -> handle index {}", path, handle.index()); - + // 如果启用了热重载,添加文件监控 if (hotReloadEnabled_) { addFileWatch(path, handle); } - + return handle; } @@ -258,26 +264,26 @@ Handle AssetsModule::load(const std::string &vertPath, Handle handle; { std::unique_lock lock(mutex_); - + // 双重检查 auto it = shaderPathCache_.find(cacheKey); if (it != shaderPathCache_.end() && shaders_.isValid(it->second)) { return it->second; } - + handle = shaders_.insert(shader); shaderPathCache_[cacheKey] = handle; } E2D_LOG_DEBUG("Loaded shader: {} + {} -> handle index {}", vertPath, fragPath, handle.index()); - + // 如果启用了热重载,添加文件监控 if (hotReloadEnabled_) { addFileWatch(vertPath, handle); addFileWatch(fragPath, handle); } - + return handle; } @@ -408,12 +414,32 @@ bool AssetsModule::createDefaultResources() { } { + // 创建材质布局(与着色器中的 MaterialUBO 匹配) + // layout(std140, binding = 1) uniform MaterialUBO { + // vec4 uColor; // 16 bytes + // vec4 uTintColor; // 16 bytes + // float uOpacity; // 4 bytes + // float uPadding[3]; // 12 bytes + // }; + Ptr layout = makePtr(); + layout->addParam("uColor", MaterialParamType::Color); + layout->addParam("uTintColor", MaterialParamType::Color); + layout->addParam("uOpacity", MaterialParamType::Float); + layout->finalize(); + Ptr material = makePtr(); material->setShader(getPtr(defaultShader_)); + material->setLayout(layout); + + // 设置默认材质参数 + material->setColor("uColor", Color::White); + material->setColor("uTintColor", Color::White); + material->setFloat("uOpacity", 1.0f); + // 添加默认纹理到材质 material->setTexture("uTexture", getPtr(defaultTexture_), 0); defaultMaterial_ = materials_.insert(material); - E2D_LOG_DEBUG("Created default material with default texture"); + E2D_LOG_DEBUG("Created default material with default texture and layout"); } { @@ -463,168 +489,231 @@ Material *AssetsModule::getDefaultMaterialPtr() { Mesh *AssetsModule::getDefaultQuadPtr() { return meshes_.get(defaultQuad_); } +Handle AssetsModule::getInstancedShader() { return instancedShader_; } + +Handle 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 = makePtr(); + 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 layout = makePtr(); + layout->addParam("uColor", MaterialParamType::Color); + layout->addParam("uTintColor", MaterialParamType::Color); + layout->addParam("uOpacity", MaterialParamType::Float); + layout->finalize(); + + Ptr material = makePtr(); + material->setShader(getPtr(instancedShader_)); + material->setLayout(layout); + + // 设置默认材质参数 + material->setColor("uColor", Color::White); + material->setColor("uTintColor", Color::White); + material->setFloat("uOpacity", 1.0f); + + // 添加默认纹理到材质 + material->setTexture("uTexture", getPtr(defaultTexture_), 0); + instancedMaterial_ = materials_.insert(material); + E2D_LOG_DEBUG("Created instanced material with default texture and layout"); + + return true; +} + //=========================================================================== // 热重载 //=========================================================================== -void AssetsModule::enableHotReload(bool enable) { - hotReloadEnabled_ = enable; - if (enable) { - E2D_LOG_INFO("Hot reload enabled"); - } else { - E2D_LOG_INFO("Hot reload disabled"); - } +void AssetsModule::enableHotReload(bool enable) { + hotReloadEnabled_ = enable; + if (enable) { + E2D_LOG_INFO("Hot reload enabled"); + } else { + E2D_LOG_INFO("Hot reload disabled"); + } } void AssetsModule::setHotReloadInterval(float interval) { - hotReloadInterval_ = interval; + hotReloadInterval_ = interval; } -void AssetsModule::addFileWatch(const std::string& path, Handle handle) { - try { - if (!std::filesystem::exists(path)) { - return; - } - - FileWatchInfo info; - info.path = path; - info.lastWriteTime = std::filesystem::last_write_time(path); - info.textureHandle = handle; - info.isTexture = true; - - std::unique_lock lock(mutex_); - fileWatchList_.push_back(info); - E2D_LOG_DEBUG("Watching texture file: {}", path); - } catch (const std::exception& e) { - E2D_LOG_ERROR("Failed to add file watch for {}: {}", path, e.what()); +void AssetsModule::addFileWatch(const std::string &path, + Handle handle) { + try { + if (!std::filesystem::exists(path)) { + return; } + + FileWatchInfo info; + info.path = path; + info.lastWriteTime = std::filesystem::last_write_time(path); + info.textureHandle = handle; + info.isTexture = true; + + std::unique_lock lock(mutex_); + fileWatchList_.push_back(info); + E2D_LOG_DEBUG("Watching texture file: {}", path); + } catch (const std::exception &e) { + E2D_LOG_ERROR("Failed to add file watch for {}: {}", path, e.what()); + } } -void AssetsModule::addFileWatch(const std::string& path, Handle handle) { - try { - if (!std::filesystem::exists(path)) { - return; - } - - FileWatchInfo info; - info.path = path; - info.lastWriteTime = std::filesystem::last_write_time(path); - info.shaderHandle = handle; - info.isTexture = false; - - std::unique_lock lock(mutex_); - fileWatchList_.push_back(info); - E2D_LOG_DEBUG("Watching shader file: {}", path); - } catch (const std::exception& e) { - E2D_LOG_ERROR("Failed to add file watch for {}: {}", path, e.what()); +void AssetsModule::addFileWatch(const std::string &path, + Handle handle) { + try { + if (!std::filesystem::exists(path)) { + return; } + + FileWatchInfo info; + info.path = path; + info.lastWriteTime = std::filesystem::last_write_time(path); + info.shaderHandle = handle; + info.isTexture = false; + + std::unique_lock lock(mutex_); + fileWatchList_.push_back(info); + E2D_LOG_DEBUG("Watching shader file: {}", path); + } catch (const std::exception &e) { + E2D_LOG_ERROR("Failed to add file watch for {}: {}", path, e.what()); + } } -void AssetsModule::reloadTexture(const FileWatchInfo& info) { - if (!textureLoader_ || !info.textureHandle.isValid()) { - return; - } - - E2D_LOG_INFO("Reloading texture: {}", info.path); - - Ptr newTexture = textureLoader_->load(info.path); - if (!newTexture) { - E2D_LOG_ERROR("Failed to reload texture: {}", info.path); - return; - } - - // 替换旧纹理的数据 - Texture* oldTexture = textures_.get(info.textureHandle); - if (oldTexture) { - // 这里假设 Texture 类有更新数据的方法 - // 如果没有,可能需要重新设计 - E2D_LOG_INFO("Texture reloaded: {}", info.path); - notifyTextureReloaded(info.textureHandle); - } +void AssetsModule::reloadTexture(const FileWatchInfo &info) { + if (!textureLoader_ || !info.textureHandle.isValid()) { + return; + } + + E2D_LOG_INFO("Reloading texture: {}", info.path); + + Ptr newTexture = textureLoader_->load(info.path); + if (!newTexture) { + E2D_LOG_ERROR("Failed to reload texture: {}", info.path); + return; + } + + // 替换旧纹理的数据 + Texture *oldTexture = textures_.get(info.textureHandle); + if (oldTexture) { + // 这里假设 Texture 类有更新数据的方法 + // 如果没有,可能需要重新设计 + E2D_LOG_INFO("Texture reloaded: {}", info.path); + notifyTextureReloaded(info.textureHandle); + } } -void AssetsModule::reloadShader(const FileWatchInfo& info) { - if (!shaderLoader_ || !info.shaderHandle.isValid()) { - return; +void AssetsModule::reloadShader(const FileWatchInfo &info) { + if (!shaderLoader_ || !info.shaderHandle.isValid()) { + return; + } + + E2D_LOG_INFO("Reloading shader: {}", info.path); + + // 查找缓存键 + std::string cacheKey; + { + std::shared_lock lock(mutex_); + for (const auto &pair : shaderPathCache_) { + if (pair.second == info.shaderHandle) { + cacheKey = pair.first; + break; + } } - - E2D_LOG_INFO("Reloading shader: {}", info.path); - - // 查找缓存键 - std::string cacheKey; - { - std::shared_lock lock(mutex_); - for (const auto& pair : shaderPathCache_) { - if (pair.second == info.shaderHandle) { - cacheKey = pair.first; - break; - } - } + } + + if (cacheKey.empty()) { + E2D_LOG_WARN("Shader cache key not found for: {}", info.path); + return; + } + + // 解析顶点/片段着色器路径 + size_t sepPos = cacheKey.find('|'); + if (sepPos == std::string::npos) { + // 单文件模式 + Ptr newShader = shaderLoader_->load(info.path); + if (!newShader) { + E2D_LOG_ERROR("Failed to reload shader: {}", info.path); + return; } - - if (cacheKey.empty()) { - E2D_LOG_WARN("Shader cache key not found for: {}", info.path); - return; + } else { + // 双文件模式 + std::string vertPath = cacheKey.substr(0, sepPos); + std::string fragPath = cacheKey.substr(sepPos + 1); + + ShaderLoader *loader = static_cast(shaderLoader_.get()); + Ptr newShader = loader->load(vertPath, fragPath); + if (!newShader) { + E2D_LOG_ERROR("Failed to reload shader: {} + {}", vertPath, fragPath); + return; } - - // 解析顶点/片段着色器路径 - size_t sepPos = cacheKey.find('|'); - if (sepPos == std::string::npos) { - // 单文件模式 - Ptr newShader = shaderLoader_->load(info.path); - if (!newShader) { - E2D_LOG_ERROR("Failed to reload shader: {}", info.path); - return; - } - } else { - // 双文件模式 - std::string vertPath = cacheKey.substr(0, sepPos); - std::string fragPath = cacheKey.substr(sepPos + 1); - - ShaderLoader* loader = static_cast(shaderLoader_.get()); - Ptr newShader = loader->load(vertPath, fragPath); - if (!newShader) { - E2D_LOG_ERROR("Failed to reload shader: {} + {}", vertPath, fragPath); - return; - } - } - - E2D_LOG_INFO("Shader reloaded: {}", info.path); - notifyShaderReloaded(info.shaderHandle); + } + + E2D_LOG_INFO("Shader reloaded: {}", info.path); + notifyShaderReloaded(info.shaderHandle); } void AssetsModule::checkForChanges() { - if (!hotReloadEnabled_ || fileWatchList_.empty()) { - return; - } - - std::unique_lock lock(mutex_); - - for (auto& info : fileWatchList_) { - try { - if (!std::filesystem::exists(info.path)) { - continue; - } - - auto currentTime = std::filesystem::last_write_time(info.path); - if (currentTime != info.lastWriteTime) { - info.lastWriteTime = currentTime; - - // 解锁进行重载操作 - lock.unlock(); - - if (info.isTexture) { - reloadTexture(info); - } else { - reloadShader(info); - } - - lock.lock(); - } - } catch (const std::exception& e) { - E2D_LOG_ERROR("Error checking file {}: {}", info.path, e.what()); + if (!hotReloadEnabled_ || fileWatchList_.empty()) { + return; + } + + std::unique_lock lock(mutex_); + + for (auto &info : fileWatchList_) { + try { + if (!std::filesystem::exists(info.path)) { + continue; + } + + auto currentTime = std::filesystem::last_write_time(info.path); + if (currentTime != info.lastWriteTime) { + info.lastWriteTime = currentTime; + + // 解锁进行重载操作 + lock.unlock(); + + if (info.isTexture) { + reloadTexture(info); + } else { + reloadShader(info); } + + lock.lock(); + } + } catch (const std::exception &e) { + E2D_LOG_ERROR("Error checking file {}: {}", info.path, e.what()); } + } } //=========================================================================== @@ -632,216 +721,218 @@ void AssetsModule::checkForChanges() { //=========================================================================== void AssetsModule::initAsyncLoader(uint32_t threadCount) { - if (asyncLoaderRunning_) { - return; - } - - if (threadCount == 0) { - threadCount = std::max(1u, std::thread::hardware_concurrency() / 2); - } - - asyncLoaderRunning_ = true; - - for (uint32_t i = 0; i < threadCount; ++i) { - workerThreads_.emplace_back(&AssetsModule::workerThreadLoop, this); - } - - E2D_LOG_INFO("Async loader initialized with {} threads", threadCount); + if (asyncLoaderRunning_) { + return; + } + + if (threadCount == 0) { + threadCount = std::max(1u, std::thread::hardware_concurrency() / 2); + } + + asyncLoaderRunning_ = true; + + for (uint32_t i = 0; i < threadCount; ++i) { + workerThreads_.emplace_back(&AssetsModule::workerThreadLoop, this); + } + + E2D_LOG_INFO("Async loader initialized with {} threads", threadCount); } void AssetsModule::shutdownAsyncLoader() { - if (!asyncLoaderRunning_) { - return; + if (!asyncLoaderRunning_) { + return; + } + + asyncLoaderRunning_ = false; + queueCV_.notify_all(); + + for (auto &thread : workerThreads_) { + if (thread.joinable()) { + thread.join(); } - - asyncLoaderRunning_ = false; - queueCV_.notify_all(); - - for (auto& thread : workerThreads_) { - if (thread.joinable()) { - thread.join(); - } - } - - workerThreads_.clear(); - - std::lock_guard lock(queueMutex_); - loadQueue_.clear(); - - E2D_LOG_INFO("Async loader shutdown"); + } + + workerThreads_.clear(); + + std::lock_guard lock(queueMutex_); + loadQueue_.clear(); + + E2D_LOG_INFO("Async loader shutdown"); } -void AssetsModule::submitLoadTask(const LoadTask& task) { - { - std::lock_guard lock(queueMutex_); - loadQueue_.push_back(task); - - // 按优先级排序 - std::sort(loadQueue_.begin(), loadQueue_.end(), - [](const LoadTask& a, const LoadTask& b) { - return static_cast(a.priority) > static_cast(b.priority); - }); - } - queueCV_.notify_one(); +void AssetsModule::submitLoadTask(const LoadTask &task) { + { + std::lock_guard lock(queueMutex_); + loadQueue_.push_back(task); + + // 按优先级排序 + std::sort(loadQueue_.begin(), loadQueue_.end(), + [](const LoadTask &a, const LoadTask &b) { + return static_cast(a.priority) > + static_cast(b.priority); + }); + } + queueCV_.notify_one(); } void AssetsModule::workerThreadLoop() { - while (asyncLoaderRunning_) { - LoadTask task; - - { - std::unique_lock lock(queueMutex_); - queueCV_.wait(lock, [this] { - return !loadQueue_.empty() || !asyncLoaderRunning_; - }); - - if (!asyncLoaderRunning_) { - break; - } - - if (loadQueue_.empty()) { - continue; - } - - task = std::move(loadQueue_.back()); - loadQueue_.pop_back(); - } - - // 执行加载任务 - if (task.type == LoadTask::Type::Texture) { - Handle handle = load(task.path); - - if (task.textureCallback) { - std::lock_guard callbackLock(callbackMutex_); - completedCallbacks_.push_back([handle, callback = task.textureCallback]() { - callback(handle); - }); - } - } else if (task.type == LoadTask::Type::Shader) { - Handle handle; - if (task.secondaryPath.empty()) { - handle = load(task.path); - } else { - handle = load(task.path, task.secondaryPath); - } - - if (task.shaderCallback) { - std::lock_guard callbackLock(callbackMutex_); - completedCallbacks_.push_back([handle, callback = task.shaderCallback]() { - callback(handle); - }); - } - } + while (asyncLoaderRunning_) { + LoadTask task; + + { + std::unique_lock lock(queueMutex_); + queueCV_.wait( + lock, [this] { return !loadQueue_.empty() || !asyncLoaderRunning_; }); + + if (!asyncLoaderRunning_) { + break; + } + + if (loadQueue_.empty()) { + continue; + } + + task = std::move(loadQueue_.back()); + loadQueue_.pop_back(); } + + // 执行加载任务 + if (task.type == LoadTask::Type::Texture) { + Handle handle = load(task.path); + + if (task.textureCallback) { + std::lock_guard callbackLock(callbackMutex_); + completedCallbacks_.push_back( + [handle, callback = task.textureCallback]() { callback(handle); }); + } + } else if (task.type == LoadTask::Type::Shader) { + Handle handle; + if (task.secondaryPath.empty()) { + handle = load(task.path); + } else { + handle = load(task.path, task.secondaryPath); + } + + if (task.shaderCallback) { + std::lock_guard callbackLock(callbackMutex_); + completedCallbacks_.push_back( + [handle, callback = task.shaderCallback]() { callback(handle); }); + } + } + } } void AssetsModule::processAsyncCallbacks() { - std::vector> callbacks; - - { - std::lock_guard lock(callbackMutex_); - callbacks = std::move(completedCallbacks_); - completedCallbacks_.clear(); - } - - for (auto& callback : callbacks) { - callback(); - } + std::vector> callbacks; + + { + std::lock_guard lock(callbackMutex_); + callbacks = std::move(completedCallbacks_); + completedCallbacks_.clear(); + } + + for (auto &callback : callbacks) { + callback(); + } } //=========================================================================== // 资源依赖跟踪 //=========================================================================== -void AssetsModule::registerMaterialDependency(Handle material, Handle texture) { - if (!material.isValid() || !texture.isValid()) { - return; - } - - std::unique_lock lock(dependencyMutex_); - - uint32_t textureIndex = texture.index(); - auto& info = textureDependencies_[textureIndex]; - info.texture = texture; - - // 检查是否已存在 - auto it = std::find(info.dependentMaterials.begin(), info.dependentMaterials.end(), material); - if (it == info.dependentMaterials.end()) { - info.dependentMaterials.push_back(material); - E2D_LOG_DEBUG("Registered material {} dependency on texture {}", - material.index(), textureIndex); - } +void AssetsModule::registerMaterialDependency(Handle material, + Handle texture) { + if (!material.isValid() || !texture.isValid()) { + return; + } + + std::unique_lock lock(dependencyMutex_); + + uint32_t textureIndex = texture.index(); + auto &info = textureDependencies_[textureIndex]; + info.texture = texture; + + // 检查是否已存在 + auto it = std::find(info.dependentMaterials.begin(), + info.dependentMaterials.end(), material); + if (it == info.dependentMaterials.end()) { + info.dependentMaterials.push_back(material); + E2D_LOG_DEBUG("Registered material {} dependency on texture {}", + material.index(), textureIndex); + } } -void AssetsModule::registerMaterialDependency(Handle material, Handle shader) { - if (!material.isValid() || !shader.isValid()) { - return; - } - - std::unique_lock lock(dependencyMutex_); - - uint32_t shaderIndex = shader.index(); - auto& info = shaderDependencies_[shaderIndex]; - info.shader = shader; - - // 检查是否已存在 - auto it = std::find(info.dependentMaterials.begin(), info.dependentMaterials.end(), material); - if (it == info.dependentMaterials.end()) { - info.dependentMaterials.push_back(material); - E2D_LOG_DEBUG("Registered material {} dependency on shader {}", - material.index(), shaderIndex); - } +void AssetsModule::registerMaterialDependency(Handle material, + Handle shader) { + if (!material.isValid() || !shader.isValid()) { + return; + } + + std::unique_lock lock(dependencyMutex_); + + uint32_t shaderIndex = shader.index(); + auto &info = shaderDependencies_[shaderIndex]; + info.shader = shader; + + // 检查是否已存在 + auto it = std::find(info.dependentMaterials.begin(), + info.dependentMaterials.end(), material); + if (it == info.dependentMaterials.end()) { + info.dependentMaterials.push_back(material); + E2D_LOG_DEBUG("Registered material {} dependency on shader {}", + material.index(), shaderIndex); + } } void AssetsModule::notifyTextureReloaded(Handle texture) { - if (!texture.isValid()) { - return; - } - - std::shared_lock lock(dependencyMutex_); - - uint32_t textureIndex = texture.index(); - auto it = textureDependencies_.find(textureIndex); - if (it != textureDependencies_.end()) { - E2D_LOG_INFO("Notifying {} materials of texture reload", - it->second.dependentMaterials.size()); - - // 材质需要更新纹理引用 - // 这里可以触发材质更新事件 - for (const auto& materialHandle : it->second.dependentMaterials) { - Material* material = materials_.get(materialHandle); - if (material) { - // 材质自动使用新的纹理数据 - E2D_LOG_DEBUG("Material {} updated with reloaded texture", - materialHandle.index()); - } - } + if (!texture.isValid()) { + return; + } + + std::shared_lock lock(dependencyMutex_); + + uint32_t textureIndex = texture.index(); + auto it = textureDependencies_.find(textureIndex); + if (it != textureDependencies_.end()) { + E2D_LOG_INFO("Notifying {} materials of texture reload", + it->second.dependentMaterials.size()); + + // 材质需要更新纹理引用 + // 这里可以触发材质更新事件 + for (const auto &materialHandle : it->second.dependentMaterials) { + Material *material = materials_.get(materialHandle); + if (material) { + // 材质自动使用新的纹理数据 + E2D_LOG_DEBUG("Material {} updated with reloaded texture", + materialHandle.index()); + } } + } } void AssetsModule::notifyShaderReloaded(Handle shader) { - if (!shader.isValid()) { - return; - } - - std::shared_lock lock(dependencyMutex_); - - uint32_t shaderIndex = shader.index(); - auto it = shaderDependencies_.find(shaderIndex); - if (it != shaderDependencies_.end()) { - E2D_LOG_INFO("Notifying {} materials of shader reload", - it->second.dependentMaterials.size()); - - for (const auto& materialHandle : it->second.dependentMaterials) { - Material* material = materials_.get(materialHandle); - if (material) { - // 更新材质的着色器引用 - material->setShader(getPtr(shader)); - E2D_LOG_DEBUG("Material {} updated with reloaded shader", - materialHandle.index()); - } - } + if (!shader.isValid()) { + return; + } + + std::shared_lock lock(dependencyMutex_); + + uint32_t shaderIndex = shader.index(); + auto it = shaderDependencies_.find(shaderIndex); + if (it != shaderDependencies_.end()) { + E2D_LOG_INFO("Notifying {} materials of shader reload", + it->second.dependentMaterials.size()); + + for (const auto &materialHandle : it->second.dependentMaterials) { + Material *material = materials_.get(materialHandle); + if (material) { + // 更新材质的着色器引用 + material->setShader(getPtr(shader)); + E2D_LOG_DEBUG("Material {} updated with reloaded shader", + materialHandle.index()); + } } + } } //=========================================================================== @@ -849,14 +940,14 @@ void AssetsModule::notifyShaderReloaded(Handle shader) { //=========================================================================== AssetsModule::Stats AssetsModule::getStats() const { - std::shared_lock lock(mutex_); - - Stats stats; - stats.textureCount = textures_.count(); - stats.shaderCount = shaders_.count(); - stats.materialCount = materials_.count(); - stats.meshCount = meshes_.count(); - return stats; + std::shared_lock lock(mutex_); + + Stats stats; + stats.textureCount = textures_.count(); + stats.shaderCount = shaders_.count(); + stats.materialCount = materials_.count(); + stats.meshCount = meshes_.count(); + return stats; } } // namespace extra2d diff --git a/src/renderer/command_queue.cpp b/src/renderer/command_queue.cpp index 2ed1f08..8fcac87 100644 --- a/src/renderer/command_queue.cpp +++ b/src/renderer/command_queue.cpp @@ -1,6 +1,8 @@ #include #include +#include #include +#include #include #include #include @@ -109,13 +111,18 @@ CommandQueue::~CommandQueue() { shutdown(); } CommandQueue::CommandQueue(CommandQueue &&other) noexcept : sorter_(std::move(other.sorter_)), batcher_(std::move(other.batcher_)), context_(other.context_), commandList_(std::move(other.commandList_)), + uboManager_(std::move(other.uboManager_)), globalUBOData_(other.globalUBOData_), materialUBOData_(std::move(other.materialUBOData_)), nextMaterialId_(other.nextMaterialId_), - materialIds_(std::move(other.materialIds_)) { + materialIds_(std::move(other.materialIds_)), + currentMaterialUBO_(other.currentMaterialUBO_), + currentMaterialUBOOffset_(other.currentMaterialUBOOffset_) { other.context_ = nullptr; other.globalUBOData_ = {}; other.nextMaterialId_ = 1; + other.currentMaterialUBO_ = nullptr; + other.currentMaterialUBOOffset_ = 0; } CommandQueue &CommandQueue::operator=(CommandQueue &&other) noexcept { @@ -126,14 +133,19 @@ CommandQueue &CommandQueue::operator=(CommandQueue &&other) noexcept { batcher_ = std::move(other.batcher_); context_ = other.context_; commandList_ = std::move(other.commandList_); + uboManager_ = std::move(other.uboManager_); globalUBOData_ = other.globalUBOData_; materialUBOData_ = std::move(other.materialUBOData_); nextMaterialId_ = other.nextMaterialId_; materialIds_ = std::move(other.materialIds_); + currentMaterialUBO_ = other.currentMaterialUBO_; + currentMaterialUBOOffset_ = other.currentMaterialUBOOffset_; other.context_ = nullptr; other.globalUBOData_ = {}; other.nextMaterialId_ = 1; + other.currentMaterialUBO_ = nullptr; + other.currentMaterialUBOOffset_ = 0; } return *this; } @@ -165,6 +177,13 @@ bool CommandQueue::initialize() { return false; } + // 初始化 UBO 管理器 + uboManager_ = std::make_unique(); + if (!uboManager_->initialize()) { + E2D_LOG_ERROR("Failed to initialize UniformBufferManager"); + return false; + } + // 预分配材质 UBO 数据缓冲区 materialUBOData_.reserve(1024 * 1024); // 1MB @@ -173,6 +192,7 @@ bool CommandQueue::initialize() { } void CommandQueue::shutdown() { + uboManager_.reset(); commandList_.reset(); context_ = nullptr; materialUBOData_.clear(); @@ -186,6 +206,15 @@ void CommandQueue::beginFrame() { nextMaterialId_ = 1; materialUBOData_.clear(); + // 重置材质 UBO 分配状态 + currentMaterialUBO_ = nullptr; + currentMaterialUBOOffset_ = 0; + + // 重置 UBO 管理器的材质 UBO 池 + if (uboManager_) { + uboManager_->resetMaterialUBOs(); + } + // 开始录制命令 if (commandList_) { commandList_->begin(); @@ -210,6 +239,31 @@ uint32_t CommandQueue::getMaterialId(Material *material) { return id; } +std::pair 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, Ptr mesh, const struct Transform &transform, const Color &color) { @@ -248,22 +302,36 @@ void CommandQueue::submitDraw(Ptr material, Ptr mesh, } } - // 分配材质 UBO 数据 + // 分配材质 UBO 空间并更新数据 uint32_t materialDataSize = material->getDataSize(); if (materialDataSize > 0) { - uint32_t uboOffset = static_cast(materialUBOData_.size()); - materialUBOData_.resize(uboOffset + materialDataSize); - std::memcpy(materialUBOData_.data() + uboOffset, material->getData(), - materialDataSize); - cmd.materialUBOSize = materialDataSize; - // 注意:实际的 UBO 句柄需要在执行时从 UBO 管理器获取 + auto [ubo, offset] = allocateMaterialUBO(materialDataSize); + if (ubo) { + // 复制材质数据到临时缓冲区,以便修改颜色 + std::vector uboData(materialDataSize); + std::memcpy(uboData.data(), material->getData(), materialDataSize); + + // 将实例颜色应用到 UBO 数据中的 uColor 参数 + auto layout = material->getLayout(); + if (layout) { + const auto* param = layout->getParam("uColor"); + if (param && param->type == MaterialParamType::Color) { + std::memcpy(uboData.data() + param->offset, &color.r, sizeof(float) * 4); + } + } + + ubo->update(uboData.data(), materialDataSize, offset); + cmd.materialUBO = BufferHandle(ubo->getRHIBuffer()); + cmd.materialUBOSize = materialDataSize; + cmd.materialUBOOffset = offset; + } } sorter_.addCommand(cmd); } void CommandQueue::submitDrawInstanced(Ptr material, Ptr mesh, - uint32_t instanceCount) { + BufferHandle instanceBuffer, uint32_t instanceCount) { if (!material || !mesh || !material->getShader() || instanceCount == 0) { return; } @@ -284,6 +352,10 @@ void CommandQueue::submitDrawInstanced(Ptr material, Ptr mesh, cmd.indexCount = mesh->getIndexCount(); cmd.instanceCount = instanceCount; + // 设置实例缓冲区 + cmd.instanceBuffer = instanceBuffer; + cmd.instanceBufferStride = sizeof(InstanceData); // 64 bytes + // 设置纹理 const auto &textures = material->getTextures(); cmd.textureCount = @@ -294,14 +366,16 @@ void CommandQueue::submitDrawInstanced(Ptr material, Ptr mesh, } } - // 材质 UBO + // 分配材质 UBO 空间并更新数据 uint32_t materialDataSize = material->getDataSize(); if (materialDataSize > 0) { - uint32_t uboOffset = static_cast(materialUBOData_.size()); - materialUBOData_.resize(uboOffset + materialDataSize); - std::memcpy(materialUBOData_.data() + uboOffset, material->getData(), - materialDataSize); - cmd.materialUBOSize = materialDataSize; + auto [ubo, offset] = allocateMaterialUBO(materialDataSize); + if (ubo) { + ubo->update(material->getData(), materialDataSize, offset); + cmd.materialUBO = BufferHandle(ubo->getRHIBuffer()); + cmd.materialUBOSize = materialDataSize; + cmd.materialUBOOffset = offset; + } } sorter_.addCommand(cmd); @@ -333,6 +407,28 @@ void CommandQueue::setViewport(int32_t x, int32_t y, int32_t width, commandList_->setViewport(viewport); } +void CommandQueue::updateGlobalUBO(const Mat4& viewProjection, float deltaTime, + uint32_t screenWidth, uint32_t screenHeight) { + if (!uboManager_) { + E2D_LOG_WARN("CommandQueue::updateGlobalUBO: uboManager is null"); + return; + } + + // 填充全局 UBO 数据 + std::memcpy(globalUBOData_.viewProjection, glm::value_ptr(viewProjection), sizeof(float) * 16); + globalUBOData_.cameraPosition[0] = 0.0f; + globalUBOData_.cameraPosition[1] = 0.0f; + globalUBOData_.cameraPosition[2] = 0.0f; + globalUBOData_.cameraPosition[3] = 1.0f; + globalUBOData_.time = 0.0f; // TODO: 传递实际时间 + globalUBOData_.deltaTime = deltaTime; + globalUBOData_.screenSize[0] = static_cast(screenWidth); + globalUBOData_.screenSize[1] = static_cast(screenHeight); + + // 更新 UBO + uboManager_->updateGlobalUBO(&globalUBOData_, sizeof(globalUBOData_)); +} + void CommandQueue::execute() { if (!commandList_) { E2D_LOG_ERROR("CommandQueue::execute: commandList is null"); @@ -366,6 +462,14 @@ void CommandQueue::executeBatch(uint32_t batchIndex, const CommandBatch &batch) E2D_LOG_WARN("Batch has no valid pipeline!"); } + // 绑定全局 UBO (binding = 0) + if (uboManager_) { + UniformBuffer* globalUBO = uboManager_->getGlobalUBO(); + if (globalUBO) { + commandList_->setUniformBuffer(0, globalUBO->getRHIBuffer()); + } + } + // 绑定纹理 if (batch.textureCount > 0) { for (uint32_t i = 0; i < batch.textureCount; ++i) { @@ -394,25 +498,24 @@ void CommandQueue::executeBatch(uint32_t batchIndex, const CommandBatch &batch) E2D_LOG_WARN("Draw command has no valid vertex buffer!"); } + // 绑定实例缓冲区(实例化渲染) + if (cmd.isInstanced() && cmd.instanceBuffer.isValid()) { + commandList_->setVertexBuffer(1, cmd.instanceBuffer.get(), 0, cmd.instanceBufferStride); + } + // 绑定索引缓冲区(如果有) if (cmd.isIndexed() && cmd.indexBuffer.isValid()) { commandList_->setIndexBuffer(cmd.indexBuffer.get(), IndexType::UInt16, 0); } - // 设置 shader uniform 变量 - auto* glCmdList = dynamic_cast(commandList_.get()); - if (glCmdList) { - // 设置模型矩阵 - glCmdList->setUniform("uModelMatrix", cmd.modelMatrix); - // 设置颜色 - glCmdList->setUniform("uColor", cmd.color); - // 设置 view projection 矩阵 - Mat4 viewProj = glm::ortho(0.0f, 1280.0f, 0.0f, 720.0f, -1.0f, 1.0f); - glCmdList->setUniform("uViewProjection", viewProj); - // 设置色调颜色 - glCmdList->setUniform("uTintColor", Color::White); - // 设置透明度 - glCmdList->setUniform("uOpacity", 1.0f); + // 绑定材质 UBO (binding = 1) + if (cmd.materialUBO.isValid()) { + commandList_->setUniformBuffer(1, cmd.materialUBO.get(), cmd.materialUBOOffset, cmd.materialUBOSize); + } + + // 设置模型矩阵(仅在非实例化渲染时使用) + if (!cmd.isInstanced()) { + commandList_->setUniform("uModelMatrix", cmd.modelMatrix); } // 绘制 diff --git a/src/renderer/instance_buffer.cpp b/src/renderer/instance_buffer.cpp new file mode 100644 index 0000000..34003e3 --- /dev/null +++ b/src/renderer/instance_buffer.cpp @@ -0,0 +1,216 @@ +#include +#include +#include +#include +#include + +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(); + 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 diff --git a/src/renderer/material.cpp b/src/renderer/material.cpp index af54c70..fa54557 100644 --- a/src/renderer/material.cpp +++ b/src/renderer/material.cpp @@ -21,12 +21,20 @@ void MaterialLayout::addParam(const std::string& name, MaterialParamType type) { return; } + // 检查是否已存在 + if (paramIndexMap_.find(name) != paramIndexMap_.end()) { + E2D_LOG_WARN("Param '{}' already exists in MaterialLayout", name); + return; + } + MaterialParamInfo info; info.type = type; info.size = getMaterialParamSize(type); info.offset = 0; // 将在 finalize 时计算 - params_[name] = info; + size_t index = params_.size(); + params_.emplace_back(name, info); + paramIndexMap_[name] = index; } /** @@ -36,6 +44,7 @@ void MaterialLayout::addParam(const std::string& name, MaterialParamType type) { * - 标量和向量:偏移必须是其大小的倍数 * - 数组和结构体:偏移必须是 16 的倍数 * - vec3 后面需要填充到 16 字节边界 + * - 整个结构体大小必须是16的倍数 */ void MaterialLayout::finalize() { if (finalized_) return; @@ -44,7 +53,7 @@ void MaterialLayout::finalize() { for (auto& pair : params_) { auto& info = pair.second; - // 计算对齐要求 + // 计算对齐要求 - std140规则 uint32_t alignment = 4; // 默认 4 字节对齐 switch (info.type) { @@ -60,21 +69,21 @@ void MaterialLayout::finalize() { alignment = 16; // vec3/vec4/color: 16 字节对齐(std140 规则) break; case MaterialParamType::Mat4: - alignment = 16; // mat4: 16 字节对齐 + alignment = 16; // mat4: 16 字节对齐,每列vec4对齐到16字节 break; default: alignment = 4; break; } - // 对齐偏移 + // 对齐偏移到alignment的倍数 offset = (offset + alignment - 1) & ~(alignment - 1); info.offset = offset; offset += info.size; } - // 最终大小对齐到 16 字节 + // 最终大小对齐到 16 字节(std140要求结构体总大小是16的倍数) bufferSize_ = (offset + 15) & ~15; finalized_ = true; } @@ -85,9 +94,9 @@ void MaterialLayout::finalize() { * @return 参数信息指针,不存在返回 nullptr */ const MaterialParamInfo* MaterialLayout::getParam(const std::string& name) const { - auto it = params_.find(name); - if (it != params_.end()) { - return &it->second; + auto mapIt = paramIndexMap_.find(name); + if (mapIt != paramIndexMap_.end()) { + return ¶ms_[mapIt->second].second; } return nullptr; } diff --git a/src/renderer/render_graph.cpp b/src/renderer/render_graph.cpp index 9d6ee6e..1eb2aa3 100644 --- a/src/renderer/render_graph.cpp +++ b/src/renderer/render_graph.cpp @@ -179,7 +179,7 @@ bool RenderGraph::compile() { return true; } -void RenderGraph::execute(float deltaTime) { +void RenderGraph::execute(float deltaTime, const Mat4 &viewProjection) { if (!compiled_) { if (!compile()) { E2D_LOG_ERROR("Failed to compile RenderGraph"); @@ -187,8 +187,9 @@ void RenderGraph::execute(float deltaTime) { } } - // 注意:beginFrame 现在由 RendererModule::onRenderBegin 调用 - // 这里不再调用,以避免清空已经提交的渲染命令 + // 更新全局 UBO + commandQueue_.updateGlobalUBO(viewProjection, deltaTime, outputWidth_, + outputHeight_); // 获取 RHI 上下文 auto *rhiModule = RHIModule::get(); @@ -212,8 +213,6 @@ void RenderGraph::execute(float deltaTime) { } } - // 执行命令队列 - // 执行命令队列 commandQueue_.execute(); diff --git a/src/renderer/renderer_module.cpp b/src/renderer/renderer_module.cpp index 4c9264c..c4cfe52 100644 --- a/src/renderer/renderer_module.cpp +++ b/src/renderer/renderer_module.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -208,11 +209,14 @@ void RendererModule::onRenderSubmit(const RenderCommand &cmd) { Material *material = assets->get(cmd.drawInstanced.material); Mesh *mesh = assets->get(cmd.drawInstanced.mesh); + InstanceBuffer *instanceBuffer = + static_cast(cmd.drawInstanced.instanceBuffer); - if (material && mesh) { - commandQueue_->submitDrawInstanced(Ptr(material), - Ptr(mesh), - cmd.drawInstanced.instanceCount); + if (material && mesh && instanceBuffer && instanceBuffer->isValid()) { + commandQueue_->submitDrawInstanced( + Ptr(material), Ptr(mesh), + BufferHandle(instanceBuffer->getRHIBuffer()), + cmd.drawInstanced.instanceCount); stats_.commandsSubmitted++; } break; @@ -248,11 +252,8 @@ void RendererModule::onRenderEnd() { return; } - // 执行渲染图 - - // 执行渲染图 - // 这里会执行所有渲染通道并提交命令 - renderGraph_.execute(0.016f); // TODO: 使用实际的 deltaTime + // 执行渲染图,传递视图投影矩阵 + renderGraph_.execute(0.016f, viewProjectionMatrix_); // 获取统计信息 if (commandQueue_) { diff --git a/src/renderer/rhi/opengl/gl_command_list.cpp b/src/renderer/rhi/opengl/gl_command_list.cpp index d2c6627..e5b70e6 100644 --- a/src/renderer/rhi/opengl/gl_command_list.cpp +++ b/src/renderer/rhi/opengl/gl_command_list.cpp @@ -85,43 +85,52 @@ void GLCommandList::setPipeline(RHIPipeline *pipeline) { } void GLCommandList::setVertexBuffer(uint32_t slot, RHIBuffer *buffer, - uint32_t offset) { - if (buffer) { - auto *glBuffer = static_cast(buffer); - glBindBuffer(GL_ARRAY_BUFFER, glBuffer->getGLBuffer()); - currentVertexBuffer_ = glBuffer; + uint32_t offset, uint32_t stride) { + if (!buffer || !currentPipeline_) { + return; + } - // 如果有当前管线,配置顶点属性 - if (currentPipeline_) { - const auto &layout = currentPipeline_->getVertexLayout(); - for (const auto &attr : layout.attributes) { - glEnableVertexAttribArray(attr.location); + auto *glBuffer = static_cast(buffer); + glBindBuffer(GL_ARRAY_BUFFER, glBuffer->getGLBuffer()); - GLenum type = GL_FLOAT; - GLint size = 1; - GLboolean normalized = GL_FALSE; + // 只配置属于当前 slot 的属性 + const auto &layout = currentPipeline_->getVertexLayout(); + // 使用传入的步长,如果为0则使用布局中的步长 + uint32_t actualStride = stride > 0 ? stride : layout.stride; - switch (attr.format) { - case VertexFormat::Float1: - size = 1; - break; - case VertexFormat::Float2: - size = 2; - break; - case VertexFormat::Float3: - size = 3; - break; - case VertexFormat::Float4: - size = 4; - break; - default: - break; - } - - glVertexAttribPointer(attr.location, size, type, normalized, layout.stride, - reinterpret_cast(attr.offset)); - } + for (const auto &attr : layout.attributes) { + if (attr.bufferIndex != slot) { + continue; } + + glEnableVertexAttribArray(attr.location); + + GLenum type = GL_FLOAT; + GLint size = 1; + GLboolean normalized = GL_FALSE; + + switch (attr.format) { + case VertexFormat::Float1: + size = 1; + break; + case VertexFormat::Float2: + size = 2; + break; + case VertexFormat::Float3: + size = 3; + break; + case VertexFormat::Float4: + size = 4; + break; + default: + break; + } + + glVertexAttribPointer(attr.location, size, type, normalized, actualStride, + reinterpret_cast(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(buffer); + GLuint glBufferId = glBuffer->getGLBuffer(); + if (size == 0) { + // 绑定整个缓冲区 + glBindBufferBase(GL_UNIFORM_BUFFER, slot, glBufferId); + } else { + // 绑定缓冲区范围 + glBindBufferRange(GL_UNIFORM_BUFFER, slot, glBufferId, offset, size); + } + } +} + void GLCommandList::setTexture(uint32_t slot, RHITexture *texture) { if (texture) { auto *glTexture = static_cast(texture); @@ -213,45 +236,45 @@ bool GLCommandList::isRecording() const { return recording_; } -void GLCommandList::setUniform(const std::string& name, float value) { +void GLCommandList::setUniform(const char* name, float value) { if (currentShaderProgram_ != 0) { - GLint location = glGetUniformLocation(currentShaderProgram_, name.c_str()); + GLint location = glGetUniformLocation(currentShaderProgram_, name); if (location != -1) { glUniform1f(location, value); } } } -void GLCommandList::setUniform(const std::string& name, const Vec2& value) { +void GLCommandList::setUniform(const char* name, const Vec2& value) { if (currentShaderProgram_ != 0) { - GLint location = glGetUniformLocation(currentShaderProgram_, name.c_str()); + GLint location = glGetUniformLocation(currentShaderProgram_, name); if (location != -1) { glUniform2f(location, value.x, value.y); } } } -void GLCommandList::setUniform(const std::string& name, const Vec3& value) { +void GLCommandList::setUniform(const char* name, const Vec3& value) { if (currentShaderProgram_ != 0) { - GLint location = glGetUniformLocation(currentShaderProgram_, name.c_str()); + GLint location = glGetUniformLocation(currentShaderProgram_, name); if (location != -1) { glUniform3f(location, value.x, value.y, value.z); } } } -void GLCommandList::setUniform(const std::string& name, const Color& value) { +void GLCommandList::setUniform(const char* name, const Color& value) { if (currentShaderProgram_ != 0) { - GLint location = glGetUniformLocation(currentShaderProgram_, name.c_str()); + GLint location = glGetUniformLocation(currentShaderProgram_, name); if (location != -1) { glUniform4f(location, value.r, value.g, value.b, value.a); } } } -void GLCommandList::setUniform(const std::string& name, const Mat4& value) { +void GLCommandList::setUniform(const char* name, const Mat4& value) { if (currentShaderProgram_ != 0) { - GLint location = glGetUniformLocation(currentShaderProgram_, name.c_str()); + GLint location = glGetUniformLocation(currentShaderProgram_, name); if (location != -1) { glUniformMatrix4fv(location, 1, GL_FALSE, glm::value_ptr(value)); } diff --git a/src/renderer/shader.cpp b/src/renderer/shader.cpp index c7cd5b7..9f297a2 100644 --- a/src/renderer/shader.cpp +++ b/src/renderer/shader.cpp @@ -42,6 +42,80 @@ bool Shader::loadFromFile(const std::string &vsPath, bool Shader::loadFromSource(const std::string &vsSource, const std::string &fsSource) { + // 创建标准2D顶点布局(位置 + 纹理坐标 + 颜色) + VertexLayout vertexLayout; + vertexLayout.stride = sizeof(float) * 8; // 2 (pos) + 2 (uv) + 4 (color) + vertexLayout.addAttribute(0, VertexFormat::Float2, 0); // 位置 + vertexLayout.addAttribute(1, VertexFormat::Float2, 8); // 纹理坐标 + vertexLayout.addAttribute(2, VertexFormat::Float4, 16); // 颜色 + + return loadFromSourceWithLayout(vsSource, fsSource, vertexLayout); +} + +bool Shader::loadInstancedFromFile(const std::string &vsPath, + const std::string &fsPath) { + // 读取顶点着色器 + std::ifstream vsFile(vsPath); + if (!vsFile.is_open()) { + E2D_LOG_ERROR("Failed to open instanced vertex shader: {}", vsPath); + return false; + } + std::stringstream vsStream; + vsStream << vsFile.rdbuf(); + std::string vsSource = vsStream.str(); + + // 读取片段着色器 + std::ifstream fsFile(fsPath); + if (!fsFile.is_open()) { + E2D_LOG_ERROR("Failed to open instanced fragment shader: {}", fsPath); + return false; + } + std::stringstream fsStream; + fsStream << fsFile.rdbuf(); + std::string fsSource = fsStream.str(); + + return loadInstancedFromSource(vsSource, fsSource); +} + +bool Shader::loadInstancedFromSource(const std::string &vsSource, + const std::string &fsSource) { + // 创建实例化渲染顶点布局 + // 槽位0:顶点属性(位置、UV、颜色) + // 槽位1:实例属性(位置、旋转、缩放、颜色、UV区域) + VertexLayout vertexLayout; + + // 顶点属性(每个顶点)- 槽位0 + vertexLayout.addAttribute(0, VertexFormat::Float2, 0); // 位置 + vertexLayout.addAttribute(1, VertexFormat::Float2, 8); // UV + vertexLayout.addAttribute(2, VertexFormat::Float4, 16); // 颜色 + + // 实例属性(每个实例)- 槽位1 + // InstanceData 布局: + // Vec2 position; // offset 0 + // float rotation; // offset 8 + // float padding0; // offset 12 + // Vec2 scale; // offset 16 + // float padding1[2]; // offset 24 + // Color color; // offset 32 + // float uvX; // offset 48 + // float uvY; // offset 52 + // float uvWidth; // offset 56 + // float uvHeight; // offset 60 + vertexLayout.addAttribute(VertexAttribute::perInstance(3, VertexFormat::Float2, 0, 1)); // iPosition + vertexLayout.addAttribute(VertexAttribute::perInstance(4, VertexFormat::Float1, 8, 1)); // iRotation + vertexLayout.addAttribute(VertexAttribute::perInstance(5, VertexFormat::Float2, 16, 1)); // iScale + vertexLayout.addAttribute(VertexAttribute::perInstance(6, VertexFormat::Float4, 32, 1)); // iColor + vertexLayout.addAttribute(VertexAttribute::perInstance(7, VertexFormat::Float4, 48, 1)); // iUVRect + + // 注意:stride 在实例化渲染中由 buffer 的 stride 参数控制 + vertexLayout.stride = sizeof(float) * 8; // 顶点缓冲区步长 + + return loadFromSourceWithLayout(vsSource, fsSource, vertexLayout); +} + +bool Shader::loadFromSourceWithLayout(const std::string &vsSource, + const std::string &fsSource, + const VertexLayout &vertexLayout) { // 释放旧资源 pipeline_ = PipelineHandle(); handle_ = ShaderHandle(); @@ -78,13 +152,6 @@ bool Shader::loadFromSource(const std::string &vsSource, // 获取着色器句柄 handle_ = ShaderHandle(shader.release()); - // 创建顶点布局(2D 标准布局:位置 + 纹理坐标 + 颜色) - VertexLayout vertexLayout; - vertexLayout.stride = sizeof(float) * 8; // 2 (pos) + 2 (uv) + 4 (color) - vertexLayout.addAttribute(0, VertexFormat::Float2, 0); // 位置 - vertexLayout.addAttribute(1, VertexFormat::Float2, 8); // 纹理坐标 - vertexLayout.addAttribute(2, VertexFormat::Float4, 16); // 颜色 - // 创建管线描述 PipelineDesc pipelineDesc; pipelineDesc.vertexShader = handle_; diff --git a/src/renderer/texture_atlas.cpp b/src/renderer/texture_atlas.cpp new file mode 100644 index 0000000..80f7ace --- /dev/null +++ b/src/renderer/texture_atlas.cpp @@ -0,0 +1,367 @@ +#include +#include +#include +#include + +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(); + 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) { + 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(texture->getWidth()); + int texHeight = static_cast(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 rects; + rects.reserve(pendingTextures_.size()); + + for (size_t i = 0; i < pendingTextures_.size(); ++i) { + stbrp_rect rect; + rect.id = static_cast(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(rects.size())); + + if (!result) { + E2D_LOG_ERROR("TextureAtlas::finalize: Failed to pack all textures"); + return false; + } + + // 创建图集纹理数据 + std::vector 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(pending.data.size()) && + dstIdx + 3 < static_cast(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(); + 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(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(totalArea) / (width_ * height_); +} + +// ======================================== +// AtlasBuilder 实现 +// ======================================== + +Ptr AtlasBuilder::build() { + if (textures_.empty()) { + E2D_LOG_WARN("AtlasBuilder::build: No textures to build"); + return nullptr; + } + + auto atlas = makePtr(); + 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 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(pair.second->getWidth()); + int h = static_cast(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 diff --git a/src/scene/director.cpp b/src/scene/director.cpp index 4dbdab5..eaf0b43 100644 --- a/src/scene/director.cpp +++ b/src/scene/director.cpp @@ -130,8 +130,9 @@ void Director::render() { CameraComponent *camera = runningScene_->getMainCamera(); if (camera) { Mat4 viewProj = camera->getViewProjectionMatrix(); - events::OnRenderSetCamera::emit(viewProj); + } else { + E2D_LOG_WARN("Director::render: No main camera set!"); } runningScene_->render();