From 5fddc4a209a357b64a0d6b64c433cd63859922b4 Mon Sep 17 00:00:00 2001 From: ChestnutYueyue <952134128@qq.com> Date: Wed, 25 Feb 2026 04:57:36 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E6=A0=B8=E5=BF=83?= =?UTF-8?q?=E6=A8=A1=E5=9D=97=E5=92=8C=E5=9B=BE=E5=BD=A2=E6=B8=B2=E6=9F=93?= =?UTF-8?q?=E5=9F=BA=E7=A1=80=E6=9E=B6=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 实现核心对象、资源管理、场景图和图形渲染管线的基础架构,包括: 1. 核心模块:Object、Asset、RefCounted 等基础类 2. 场景图:Node、Component、Scene 等场景管理类 3. 图形渲染:GFXObject、GFXDevice、GFXBuffer 等渲染抽象层 4. OpenGL 实现:GLBuffer、GLTexture、GLShader 等具体实现 5. 2D 组件:SpriteFrame 等 2D 渲染相关组件 这些改动为引擎提供了基本的对象生命周期管理、资源加载和图形渲染能力 --- Docs/asset_service.md | 528 -------------- Examples/hello_world.cpp | 147 ---- .../include/extra2d/2d/components/Sprite.h | 160 +++++ .../extra2d/2d/components/SpriteFrame.h | 134 ++++ .../include/extra2d/2d/renderer/Batcher2d.h | 180 +++++ .../extra2d/2d/renderer/RenderDrawInfo.h | 273 +++++++ .../extra2d/2d/renderer/RenderEntity.h | 250 +++++++ .../extra2d/2d/renderer/StencilManager.h | 218 ++++++ .../extra2d/2d/renderer/UIMeshBuffer.h | 99 +++ Extra2D/include/extra2d/app/application.h | 113 --- .../include/extra2d/application/Application.h | 144 ++++ Extra2D/include/extra2d/asset/asset.h | 477 ------------- Extra2D/include/extra2d/asset/asset_cache.h | 263 ------- Extra2D/include/extra2d/asset/asset_handle.h | 304 -------- Extra2D/include/extra2d/asset/asset_loader.h | 312 -------- Extra2D/include/extra2d/asset/asset_pack.h | 399 ----------- Extra2D/include/extra2d/asset/asset_types.h | 270 ------- .../include/extra2d/asset/data_processor.h | 397 ----------- .../include/extra2d/base/AutoreleasePool.h | 53 ++ Extra2D/include/extra2d/base/RefCounted.h | 160 +++++ Extra2D/include/extra2d/base/Scheduler.h | 162 +++++ Extra2D/include/extra2d/core/Object.h | 157 +++++ Extra2D/include/extra2d/core/Root.h | 257 +++++++ Extra2D/include/extra2d/core/assets/Asset.h | 132 ++++ .../extra2d/core/assets/AssetManager.h | 180 +++++ .../include/extra2d/core/assets/ImageAsset.h | 173 +++++ .../include/extra2d/core/assets/Material.h | 384 ++++++++++ .../include/extra2d/core/assets/Texture2D.h | 238 +++++++ Extra2D/include/extra2d/core/event/EventBus.h | 343 +++++++++ .../include/extra2d/core/event/EventTarget.h | 113 +++ Extra2D/include/extra2d/core/module.h | 82 --- Extra2D/include/extra2d/core/object_pool.h | 63 -- Extra2D/include/extra2d/core/registry.h | 170 ----- Extra2D/include/extra2d/core/result.h | 398 ----------- Extra2D/include/extra2d/core/ring_buffer.h | 103 --- .../include/extra2d/core/scene-graph/Camera.h | 383 ++++++++++ .../extra2d/core/scene-graph/Component.h | 110 +++ .../include/extra2d/core/scene-graph/Layers.h | 128 ++++ .../include/extra2d/core/scene-graph/Model.h | 241 +++++++ .../include/extra2d/core/scene-graph/Node.h | 598 ++++++++++++++++ .../extra2d/core/scene-graph/RenderScene.h | 209 ++++++ .../include/extra2d/core/scene-graph/Scene.h | 120 ++++ .../include/extra2d/core/service_interface.h | 144 ---- .../include/extra2d/core/service_locator.h | 307 -------- .../include/extra2d/core/service_registry.h | 137 ---- Extra2D/include/extra2d/engine/Engine.h | 172 +++++ Extra2D/include/extra2d/event/event.h | 172 ----- .../include/extra2d/event/event_dispatcher.h | 56 -- Extra2D/include/extra2d/event/event_queue.h | 43 -- Extra2D/include/extra2d/extra2d.h | 88 ++- Extra2D/include/extra2d/gfx/GFXBuffer.h | 170 +++++ .../include/extra2d/gfx/GFXCommandBuffer.h | 206 ++++++ Extra2D/include/extra2d/gfx/GFXDef.h | 450 ++++++++++++ Extra2D/include/extra2d/gfx/GFXDevice.h | 276 ++++++++ .../include/extra2d/gfx/GFXInputAssembler.h | 139 ++++ Extra2D/include/extra2d/gfx/GFXObject.h | 50 ++ Extra2D/include/extra2d/gfx/GFXPipeline.h | 197 ++++++ Extra2D/include/extra2d/gfx/GFXShader.h | 145 ++++ Extra2D/include/extra2d/gfx/GFXTexture.h | 183 +++++ Extra2D/include/extra2d/gfx/opengl/GLBuffer.h | 64 ++ .../extra2d/gfx/opengl/GLCommandBuffer.h | 180 +++++ Extra2D/include/extra2d/gfx/opengl/GLDevice.h | 75 ++ .../extra2d/gfx/opengl/GLInputAssembler.h | 43 ++ .../extra2d/gfx/opengl/GLPipelineState.h | 46 ++ Extra2D/include/extra2d/gfx/opengl/GLShader.h | 80 +++ .../include/extra2d/gfx/opengl/GLTexture.h | 77 ++ Extra2D/include/extra2d/platform/Input.h | 542 ++++++++++++++ Extra2D/include/extra2d/platform/SDLHelper.h | 193 +++++ Extra2D/include/extra2d/platform/Window.h | 233 ++++++ .../extra2d/platform/glfw/glfw_window.h | 226 ------ Extra2D/include/extra2d/platform/keys.h | 72 -- .../include/extra2d/platform/window_module.h | 59 -- .../include/extra2d/services/asset_service.h | 334 --------- .../include/extra2d/services/event_service.h | 77 -- .../include/extra2d/services/logger_service.h | 310 -------- .../include/extra2d/services/timer_service.h | 57 -- Extra2D/src/2d/components/Sprite.cpp | 169 +++++ Extra2D/src/2d/components/SpriteFrame.cpp | 104 +++ Extra2D/src/2d/renderer/Batcher2d.cpp | 228 ++++++ Extra2D/src/2d/renderer/RenderDrawInfo.cpp | 142 ++++ Extra2D/src/2d/renderer/RenderEntity.cpp | 160 +++++ Extra2D/src/2d/renderer/StencilManager.cpp | 171 +++++ Extra2D/src/2d/renderer/UIMeshBuffer.cpp | 240 +++++++ Extra2D/src/app/application.cpp | 152 ---- Extra2D/src/application/Application.cpp | 183 +++++ Extra2D/src/asset/asset.cpp | 62 -- Extra2D/src/asset/asset_cache.cpp | 121 ---- Extra2D/src/asset/asset_loader.cpp | 418 ----------- Extra2D/src/asset/asset_pack.cpp | 439 ------------ Extra2D/src/asset/data_processor.cpp | 449 ------------ Extra2D/src/base/AutoreleasePool.cpp | 40 ++ Extra2D/src/base/RefCounted.cpp | 36 + Extra2D/src/base/Scheduler.cpp | 185 +++++ Extra2D/src/core/Object.cpp | 75 ++ Extra2D/src/core/assets/Asset.cpp | 84 +++ Extra2D/src/core/assets/AssetManager.cpp | 106 +++ Extra2D/src/core/assets/ImageAsset.cpp | 147 ++++ Extra2D/src/core/assets/Material.cpp | 284 ++++++++ Extra2D/src/core/assets/Texture2D.cpp | 184 +++++ Extra2D/src/core/event/EventBus.cpp | 29 + Extra2D/src/core/event/EventTarget.cpp | 15 + Extra2D/src/core/registry.cpp | 223 ------ Extra2D/src/core/scene-graph/Camera.cpp | 275 ++++++++ Extra2D/src/core/scene-graph/Component.cpp | 77 ++ Extra2D/src/core/scene-graph/Layers.cpp | 71 ++ Extra2D/src/core/scene-graph/Model.cpp | 155 ++++ Extra2D/src/core/scene-graph/Node.cpp | 526 ++++++++++++++ Extra2D/src/core/scene-graph/RenderScene.cpp | 175 +++++ Extra2D/src/core/scene-graph/Scene.cpp | 102 +++ Extra2D/src/core/service_locator.cpp | 110 --- Extra2D/src/core/service_registry.cpp | 37 - Extra2D/src/engine/Engine.cpp | 168 +++++ Extra2D/src/event/event.cpp | 60 -- Extra2D/src/event/event_dispatcher.cpp | 67 -- Extra2D/src/event/event_queue.cpp | 45 -- Extra2D/src/gfx/GFXBuffer.cpp | 113 +++ Extra2D/src/gfx/GFXDevice.cpp | 76 ++ Extra2D/src/gfx/GFXInputAssembler.cpp | 67 ++ Extra2D/src/gfx/GFXObject.cpp | 21 + Extra2D/src/gfx/GFXPipeline.cpp | 58 ++ Extra2D/src/gfx/GFXShader.cpp | 51 ++ Extra2D/src/gfx/GFXTexture.cpp | 80 +++ Extra2D/src/gfx/opengl/GLBuffer.cpp | 82 +++ Extra2D/src/gfx/opengl/GLCommandBuffer.cpp | 194 +++++ Extra2D/src/gfx/opengl/GLDevice.cpp | 238 +++++++ Extra2D/src/gfx/opengl/GLInputAssembler.cpp | 90 +++ Extra2D/src/gfx/opengl/GLPipelineState.cpp | 147 ++++ Extra2D/src/gfx/opengl/GLShader.cpp | 102 +++ Extra2D/src/gfx/opengl/GLTexture.cpp | 173 +++++ Extra2D/src/platform/Input.cpp | 380 ++++++++++ Extra2D/src/platform/SDLHelper.cpp | 417 +++++++++++ Extra2D/src/platform/Window.cpp | 256 +++++++ Extra2D/src/platform/glfw/glfw_window.cpp | 666 ------------------ Extra2D/src/platform/window_module.cpp | 58 -- Extra2D/src/services/asset_service.cpp | 332 --------- Extra2D/src/services/event_service.cpp | 64 -- Extra2D/src/services/logger_service.cpp | 254 ------- Extra2D/src/services/timer_service.cpp | 51 -- Tests/test_asset_cache.cpp | 307 -------- Tests/test_asset_pack.cpp | 427 ----------- Tests/test_asset_service.cpp | 500 ------------- Tests/test_data_processor.cpp | 260 ------- Tools/asset_packer.cpp | 460 ------------ xmake.lua | 59 +- 144 files changed, 15860 insertions(+), 11665 deletions(-) delete mode 100644 Docs/asset_service.md delete mode 100644 Examples/hello_world.cpp create mode 100644 Extra2D/include/extra2d/2d/components/Sprite.h create mode 100644 Extra2D/include/extra2d/2d/components/SpriteFrame.h create mode 100644 Extra2D/include/extra2d/2d/renderer/Batcher2d.h create mode 100644 Extra2D/include/extra2d/2d/renderer/RenderDrawInfo.h create mode 100644 Extra2D/include/extra2d/2d/renderer/RenderEntity.h create mode 100644 Extra2D/include/extra2d/2d/renderer/StencilManager.h create mode 100644 Extra2D/include/extra2d/2d/renderer/UIMeshBuffer.h delete mode 100644 Extra2D/include/extra2d/app/application.h create mode 100644 Extra2D/include/extra2d/application/Application.h delete mode 100644 Extra2D/include/extra2d/asset/asset.h delete mode 100644 Extra2D/include/extra2d/asset/asset_cache.h delete mode 100644 Extra2D/include/extra2d/asset/asset_handle.h delete mode 100644 Extra2D/include/extra2d/asset/asset_loader.h delete mode 100644 Extra2D/include/extra2d/asset/asset_pack.h delete mode 100644 Extra2D/include/extra2d/asset/asset_types.h delete mode 100644 Extra2D/include/extra2d/asset/data_processor.h create mode 100644 Extra2D/include/extra2d/base/AutoreleasePool.h create mode 100644 Extra2D/include/extra2d/base/RefCounted.h create mode 100644 Extra2D/include/extra2d/base/Scheduler.h create mode 100644 Extra2D/include/extra2d/core/Object.h create mode 100644 Extra2D/include/extra2d/core/Root.h create mode 100644 Extra2D/include/extra2d/core/assets/Asset.h create mode 100644 Extra2D/include/extra2d/core/assets/AssetManager.h create mode 100644 Extra2D/include/extra2d/core/assets/ImageAsset.h create mode 100644 Extra2D/include/extra2d/core/assets/Material.h create mode 100644 Extra2D/include/extra2d/core/assets/Texture2D.h create mode 100644 Extra2D/include/extra2d/core/event/EventBus.h create mode 100644 Extra2D/include/extra2d/core/event/EventTarget.h delete mode 100644 Extra2D/include/extra2d/core/module.h delete mode 100644 Extra2D/include/extra2d/core/object_pool.h delete mode 100644 Extra2D/include/extra2d/core/registry.h delete mode 100644 Extra2D/include/extra2d/core/result.h delete mode 100644 Extra2D/include/extra2d/core/ring_buffer.h create mode 100644 Extra2D/include/extra2d/core/scene-graph/Camera.h create mode 100644 Extra2D/include/extra2d/core/scene-graph/Component.h create mode 100644 Extra2D/include/extra2d/core/scene-graph/Layers.h create mode 100644 Extra2D/include/extra2d/core/scene-graph/Model.h create mode 100644 Extra2D/include/extra2d/core/scene-graph/Node.h create mode 100644 Extra2D/include/extra2d/core/scene-graph/RenderScene.h create mode 100644 Extra2D/include/extra2d/core/scene-graph/Scene.h delete mode 100644 Extra2D/include/extra2d/core/service_interface.h delete mode 100644 Extra2D/include/extra2d/core/service_locator.h delete mode 100644 Extra2D/include/extra2d/core/service_registry.h create mode 100644 Extra2D/include/extra2d/engine/Engine.h delete mode 100644 Extra2D/include/extra2d/event/event.h delete mode 100644 Extra2D/include/extra2d/event/event_dispatcher.h delete mode 100644 Extra2D/include/extra2d/event/event_queue.h create mode 100644 Extra2D/include/extra2d/gfx/GFXBuffer.h create mode 100644 Extra2D/include/extra2d/gfx/GFXCommandBuffer.h create mode 100644 Extra2D/include/extra2d/gfx/GFXDef.h create mode 100644 Extra2D/include/extra2d/gfx/GFXDevice.h create mode 100644 Extra2D/include/extra2d/gfx/GFXInputAssembler.h create mode 100644 Extra2D/include/extra2d/gfx/GFXObject.h create mode 100644 Extra2D/include/extra2d/gfx/GFXPipeline.h create mode 100644 Extra2D/include/extra2d/gfx/GFXShader.h create mode 100644 Extra2D/include/extra2d/gfx/GFXTexture.h create mode 100644 Extra2D/include/extra2d/gfx/opengl/GLBuffer.h create mode 100644 Extra2D/include/extra2d/gfx/opengl/GLCommandBuffer.h create mode 100644 Extra2D/include/extra2d/gfx/opengl/GLDevice.h create mode 100644 Extra2D/include/extra2d/gfx/opengl/GLInputAssembler.h create mode 100644 Extra2D/include/extra2d/gfx/opengl/GLPipelineState.h create mode 100644 Extra2D/include/extra2d/gfx/opengl/GLShader.h create mode 100644 Extra2D/include/extra2d/gfx/opengl/GLTexture.h create mode 100644 Extra2D/include/extra2d/platform/Input.h create mode 100644 Extra2D/include/extra2d/platform/SDLHelper.h create mode 100644 Extra2D/include/extra2d/platform/Window.h delete mode 100644 Extra2D/include/extra2d/platform/glfw/glfw_window.h delete mode 100644 Extra2D/include/extra2d/platform/keys.h delete mode 100644 Extra2D/include/extra2d/platform/window_module.h delete mode 100644 Extra2D/include/extra2d/services/asset_service.h delete mode 100644 Extra2D/include/extra2d/services/event_service.h delete mode 100644 Extra2D/include/extra2d/services/logger_service.h delete mode 100644 Extra2D/include/extra2d/services/timer_service.h create mode 100644 Extra2D/src/2d/components/Sprite.cpp create mode 100644 Extra2D/src/2d/components/SpriteFrame.cpp create mode 100644 Extra2D/src/2d/renderer/Batcher2d.cpp create mode 100644 Extra2D/src/2d/renderer/RenderDrawInfo.cpp create mode 100644 Extra2D/src/2d/renderer/RenderEntity.cpp create mode 100644 Extra2D/src/2d/renderer/StencilManager.cpp create mode 100644 Extra2D/src/2d/renderer/UIMeshBuffer.cpp delete mode 100644 Extra2D/src/app/application.cpp create mode 100644 Extra2D/src/application/Application.cpp delete mode 100644 Extra2D/src/asset/asset.cpp delete mode 100644 Extra2D/src/asset/asset_cache.cpp delete mode 100644 Extra2D/src/asset/asset_loader.cpp delete mode 100644 Extra2D/src/asset/asset_pack.cpp delete mode 100644 Extra2D/src/asset/data_processor.cpp create mode 100644 Extra2D/src/base/AutoreleasePool.cpp create mode 100644 Extra2D/src/base/RefCounted.cpp create mode 100644 Extra2D/src/base/Scheduler.cpp create mode 100644 Extra2D/src/core/Object.cpp create mode 100644 Extra2D/src/core/assets/Asset.cpp create mode 100644 Extra2D/src/core/assets/AssetManager.cpp create mode 100644 Extra2D/src/core/assets/ImageAsset.cpp create mode 100644 Extra2D/src/core/assets/Material.cpp create mode 100644 Extra2D/src/core/assets/Texture2D.cpp create mode 100644 Extra2D/src/core/event/EventBus.cpp create mode 100644 Extra2D/src/core/event/EventTarget.cpp delete mode 100644 Extra2D/src/core/registry.cpp create mode 100644 Extra2D/src/core/scene-graph/Camera.cpp create mode 100644 Extra2D/src/core/scene-graph/Component.cpp create mode 100644 Extra2D/src/core/scene-graph/Layers.cpp create mode 100644 Extra2D/src/core/scene-graph/Model.cpp create mode 100644 Extra2D/src/core/scene-graph/Node.cpp create mode 100644 Extra2D/src/core/scene-graph/RenderScene.cpp create mode 100644 Extra2D/src/core/scene-graph/Scene.cpp delete mode 100644 Extra2D/src/core/service_locator.cpp delete mode 100644 Extra2D/src/core/service_registry.cpp create mode 100644 Extra2D/src/engine/Engine.cpp delete mode 100644 Extra2D/src/event/event.cpp delete mode 100644 Extra2D/src/event/event_dispatcher.cpp delete mode 100644 Extra2D/src/event/event_queue.cpp create mode 100644 Extra2D/src/gfx/GFXBuffer.cpp create mode 100644 Extra2D/src/gfx/GFXDevice.cpp create mode 100644 Extra2D/src/gfx/GFXInputAssembler.cpp create mode 100644 Extra2D/src/gfx/GFXObject.cpp create mode 100644 Extra2D/src/gfx/GFXPipeline.cpp create mode 100644 Extra2D/src/gfx/GFXShader.cpp create mode 100644 Extra2D/src/gfx/GFXTexture.cpp create mode 100644 Extra2D/src/gfx/opengl/GLBuffer.cpp create mode 100644 Extra2D/src/gfx/opengl/GLCommandBuffer.cpp create mode 100644 Extra2D/src/gfx/opengl/GLDevice.cpp create mode 100644 Extra2D/src/gfx/opengl/GLInputAssembler.cpp create mode 100644 Extra2D/src/gfx/opengl/GLPipelineState.cpp create mode 100644 Extra2D/src/gfx/opengl/GLShader.cpp create mode 100644 Extra2D/src/gfx/opengl/GLTexture.cpp create mode 100644 Extra2D/src/platform/Input.cpp create mode 100644 Extra2D/src/platform/SDLHelper.cpp create mode 100644 Extra2D/src/platform/Window.cpp delete mode 100644 Extra2D/src/platform/glfw/glfw_window.cpp delete mode 100644 Extra2D/src/platform/window_module.cpp delete mode 100644 Extra2D/src/services/asset_service.cpp delete mode 100644 Extra2D/src/services/event_service.cpp delete mode 100644 Extra2D/src/services/logger_service.cpp delete mode 100644 Extra2D/src/services/timer_service.cpp delete mode 100644 Tests/test_asset_cache.cpp delete mode 100644 Tests/test_asset_pack.cpp delete mode 100644 Tests/test_asset_service.cpp delete mode 100644 Tests/test_data_processor.cpp delete mode 100644 Tools/asset_packer.cpp diff --git a/Docs/asset_service.md b/Docs/asset_service.md deleted file mode 100644 index 92ece0f..0000000 --- a/Docs/asset_service.md +++ /dev/null @@ -1,528 +0,0 @@ -# Extra2D 资源服务系统 - -## 概述 - -Extra2D 资源服务系统提供了一套完整的资源管理解决方案,包括资源加载、缓存、打包和加密压缩功能。 - -### 主要特性 - -- **类型安全**:使用模板和强类型 ID 避免类型错误 -- **自动缓存**:LRU 缓存策略,自动管理内存 -- **异步加载**:后台线程加载,不阻塞主线程 -- **资源打包**:支持压缩和加密的资源包格式 -- **多格式支持**:纹理、字体、着色器、音频等 - -## 模块结构 - -``` -extra2d/asset/ -├── asset_types.h # 资源类型定义 -├── asset.h # 资源基类 -├── asset_handle.h # 资源句柄 -├── asset_cache.h # 资源缓存 -├── asset_loader.h # 资源加载器 -├── asset_pack.h # 资源包 -├── data_processor.h # 数据处理器 -└── asset_service.h # 资源服务 -``` - -## 快速开始 - -### 1. 初始化资源服务 - -```cpp -#include - -using namespace extra2d; - -// 创建资源服务 -AssetService service; -service.init(); -service.setRoot("assets"); - -// 注册加载器 -service.registerLoader(AssetLoaderFactory::createTextureLoader()); -service.registerLoader(AssetLoaderFactory::createFontLoader()); -service.registerLoader(AssetLoaderFactory::createShaderLoader()); -``` - -### 2. 同步加载资源 - -```cpp -// 加载纹理 -AssetHandle texture = service.load("sprites/player.png"); -if (texture.valid()) { - int width = texture->width(); - int height = texture->height(); - const u8* data = texture->data(); -} -``` - -### 3. 异步加载资源 - -```cpp -// 异步加载 -service.loadAsync("sprites/background.png", - [](AssetHandle handle) { - if (handle.valid()) { - // 资源加载完成,在主线程处理 - } - }); - -// 在主线程处理回调 -service.process(); -``` - -### 4. 使用资源包 - -```cpp -// 挂载资源包 -service.mount("data.pak"); - -// 从资源包加载 -auto texture = service.load("textures/hero.png"); - -// 卸载资源包 -service.unmount("data.pak"); -``` - -### 5. 缓存管理 - -```cpp -// 设置缓存上限 (100MB) -service.setLimit(100 * 1024 * 1024); - -// 获取缓存统计 -CacheStats stats = service.stats(); -std::cout << "缓存使用: " << stats.bytes << " 字节\n"; -std::cout << "命中率: " << (stats.hitRate() * 100) << "%\n"; - -// 清理无引用资源 -service.purge(); - -// 清空缓存 -service.clear(); -``` - -## API 参考 - -### AssetID - -资源标识符,使用哈希值进行快速比较。 - -```cpp -struct AssetID { - u64 hash; // 哈希值 - std::string path; // 原始路径 - - explicit AssetID(const std::string& path); - bool valid() const; - bool operator==(const AssetID& other) const; -}; -``` - -### AssetHandle - -类型安全的资源句柄,使用弱引用避免阻止资源回收。 - -```cpp -template -class AssetHandle { -public: - bool valid() const; // 检查是否有效 - Ref get() const; // 获取强引用 - T* operator->() const; // 解引用 - bool loaded() const; // 检查是否已加载 - AssetState state() const; // 获取状态 -}; -``` - -### AssetCache - -LRU 缓存管理器。 - -```cpp -class AssetCache { -public: - explicit AssetCache(size_t limit = 0); - - template - AssetHandle add(Ref asset); - - template - AssetHandle get(const AssetID& id); - - bool has(const AssetID& id) const; - bool remove(const AssetID& id); - - void setLimit(size_t limit); - size_t bytes() const; - size_t count() const; - - size_t purge(); // 清理无引用资源 - void clear(); // 清空缓存 - CacheStats stats() const; -}; -``` - -### IAssetService - -资源服务接口。 - -```cpp -class IAssetService : public IService { -public: - // 同步加载 - template - AssetHandle load(const std::string& path); - - // 异步加载 - template - void loadAsync(const std::string& path, AssetLoadCallback callback); - - // 获取已缓存资源 - template - AssetHandle get(const std::string& path); - - // 预加载 - template - void preload(const std::string& path); - - // 状态查询 - bool isLoaded(const std::string& path) const; - bool isLoading(const std::string& path) const; - - // 资源管理 - void unload(const std::string& path); - void setLimit(size_t maxBytes); - size_t size() const; - void purge(); - void clear(); - CacheStats stats() const; - - // 加载器注册 - template - void registerLoader(Unique> loader); - - // 资源包管理 - bool mount(const std::string& path); - void unmount(const std::string& path); - - // 数据处理管道 - void setPipe(DataPipe pipe); - - // 根目录 - void setRoot(const std::string& path); - std::string root() const; - - // 处理异步回调 - void process(); -}; -``` - -### DataPipe - -数据处理管道,支持链式调用。 - -```cpp -class DataPipe { -public: - DataPipe& decrypt(const std::string& key, Decryptor::Type type = Decryptor::Type::XOR); - DataPipe& decompress(Compression algo); - DataPipe& encrypt(const std::string& key, Decryptor::Type type = Decryptor::Type::XOR); - DataPipe& compress(Compression algo, int level = 3); - DataPipe& add(Unique processor); - - std::vector process(const std::vector& input); - void clear(); - bool empty() const; - size_t size() const; -}; -``` - -### AssetPackBuilder - -资源包构建器。 - -```cpp -class AssetPackBuilder { -public: - explicit AssetPackBuilder(Compression compression = Compression::None, int level = 3); - - void add(const std::string& path, const std::vector& data); - void add(const std::string& path, std::vector&& data); - bool addFile(const std::string& filePath, const std::string& packPath = ""); - size_t addDirectory(const std::string& dirPath, const std::string& prefix = ""); - - void setEncryption(const std::string& key, Decryptor::Type type = Decryptor::Type::XOR); - bool build(const std::string& outputPath); - - void clear(); - size_t count() const; - size_t totalOriginalSize() const; - size_t totalCompressedSize() const; -}; -``` - -## 资源类型 - -### TextureAsset - -纹理资源,使用 stb_image 加载。 - -```cpp -class TextureAsset : public Asset { -public: - AssetType type() const override { return AssetType::Texture; } - - int width() const; - int height() const; - int channels() const; - const u8* data() const; - size_t dataSize() const; -}; -``` - -### FontAsset - -字体资源,支持 TrueType 字体。 - -```cpp -class FontAsset : public Asset { -public: - AssetType type() const override { return AssetType::Font; } - - float scaleForPixelHeight(float pixels) const; - const u8* data() const; - size_t dataSize() const; -}; -``` - -### ShaderAsset - -着色器资源。 - -```cpp -class ShaderAsset : public Asset { -public: - AssetType type() const override { return AssetType::Shader; } - - const std::string& vertexSource() const; - const std::string& fragmentSource() const; -}; -``` - -### AudioAsset - -音频资源。 - -```cpp -class AudioAsset : public Asset { -public: - AssetType type() const override { return AssetType::Audio; } - - AudioFormat format() const; - int channels() const; - int sampleRate() const; - int bitsPerSample() const; - float duration() const; - const u8* data() const; - size_t dataSize() const; - bool streaming() const; -}; -``` - -### DataAsset - -通用二进制数据。 - -```cpp -class DataAsset : public Asset { -public: - AssetType type() const override { return AssetType::Data; } - - const u8* data() const; - size_t size() const; -}; -``` - -## 资源打包工具 - -### 安装 - -```bash -xmake build asset_packer -``` - -### 用法 - -``` -Extra2D 资源打包工具 v1.0.0 - -用法: - asset_packer create [options] - asset_packer list - asset_packer extract - -命令: - create 创建资源包 - list 列出资源包内容 - extract 提取资源包内容 - -选项: - -c, --compression 压缩算法 (none, zstd, lz4, zlib),默认 zstd - -l, --level 压缩级别 (1-22),默认 3 - -e, --encrypt 加密密钥 - -t, --encrypt-type 加密类型 (xor, aes),默认 xor - -v, --verbose 详细输出 - -h, --help 显示帮助 -``` - -### 示例 - -```bash -# 创建资源包(使用 Zstd 压缩) -asset_packer create game.pak -c zstd -v assets/ - -# 创建加密资源包 -asset_packer create game.pak -c zstd -e "secret_key" -t xor -v assets/ - -# 列出资源包内容 -asset_packer list game.pak - -# 提取资源包 -asset_packer extract game.pak extracted/ - -# 使用不同压缩算法 -asset_packer create game.pak -c lz4 -v assets/ -asset_packer create game.pak -c zlib -l 9 -v assets/ -``` - -## 压缩算法对比 - -| 算法 | 压缩率 | 压缩速度 | 解压速度 | 适用场景 | -|------|--------|----------|----------|----------| -| Zstd | 高 | 快 | 很快 | 通用推荐 | -| LZ4 | 中 | 很快 | 很快 | 实时解压 | -| Zlib | 高 | 中 | 中 | 兼容性好 | -| None | - | - | - | 已压缩资源 | - -## 设计模式 - -### 策略模式 (AssetLoader) - -不同资源类型使用不同的加载策略。 - -```cpp -class AssetLoader { -public: - virtual Ref load(const std::string& path) = 0; - virtual Ref loadFromMemory(const u8* data, size_t size) = 0; - virtual bool canLoad(const std::string& path) const = 0; - virtual AssetType type() const = 0; - virtual std::vector extensions() const = 0; -}; -``` - -### 享元模式 (AssetCache) - -共享资源实例,减少内存占用。 - -### 装饰器模式 (DataProcessor) - -链式处理数据流。 - -```cpp -DataPipe pipe; -pipe.encrypt("key") - .compress(Compression::Zstd); -``` - -### 服务定位器模式 - -全局访问资源服务。 - -```cpp -auto* service = ServiceLocator::get(); -``` - -## 线程安全 - -- `AssetCache` 使用读写锁 (`std::shared_mutex`) -- `AssetService` 使用读写锁保护资源映射 -- 异步加载使用独立的工作线程 -- 回调在主线程执行(需要调用 `process()`) - -## 最佳实践 - -### 1. 资源路径约定 - -``` -assets/ -├── textures/ -│ ├── sprites/ -│ └── backgrounds/ -├── fonts/ -├── shaders/ -└── audio/ -``` - -### 2. 预加载关键资源 - -```cpp -// 游戏启动时预加载 -service.preload("textures/loading.png"); -service.preload("fonts/main.ttf"); -``` - -### 3. 合理设置缓存上限 - -```cpp -// 根据目标平台设置 -#ifdef MOBILE - service.setLimit(50 * 1024 * 1024); // 50MB -#else - service.setLimit(200 * 1024 * 1024); // 200MB -#endif -``` - -### 4. 定期清理缓存 - -```cpp -// 场景切换时清理 -void onSceneChange() { - service.purge(); -} -``` - -### 5. 使用资源包减少文件数量 - -```cpp -// 将小文件打包成资源包 -// 减少文件 I/O 操作,提高加载速度 -service.mount("textures.pak"); -service.mount("audio.pak"); -``` - -## 错误处理 - -```cpp -auto handle = service.load("missing.png"); -if (!handle.valid()) { - // 资源加载失败 - std::cerr << "Failed to load texture\n"; -} - -// 检查加载状态 -if (handle.state() == AssetState::Failed) { - // 处理失败情况 -} -``` - -## 版本历史 - -- **v1.0.0** - 初始版本 - - 资源加载和缓存 - - 资源打包和加密 - - 异步加载支持 - - 多种压缩算法 diff --git a/Examples/hello_world.cpp b/Examples/hello_world.cpp deleted file mode 100644 index e906993..0000000 --- a/Examples/hello_world.cpp +++ /dev/null @@ -1,147 +0,0 @@ -/** - * @file hello_world.cpp - * @brief Hello World 示例程序 - */ - -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace extra2d; - -int main(int argc, char* argv[]) { - std::cout << "========================================" << std::endl; - std::cout << " Extra2D - Hello World Example" << std::endl; - std::cout << "========================================" << std::endl; - - auto& app = Application::get(); - app.appName = "Hello World - Extra2D"; - app.appVersion = "1.0.0"; - - app.use([](WindowCfg& cfg) { - cfg.title = "Hello World - Extra2D"; - cfg.w = 1024; - cfg.h = 768; - cfg.vsync = true; - }); - - if (!app.init()) { - std::cerr << "Failed to initialize application" << std::endl; - return 1; - } - - auto logger = ServiceLocator::instance().get(); - if (logger) { - logger->info("Hello World example initialized"); - } - - auto renderer = IRenderer2D::create(true); - if (!renderer) { - std::cerr << "Failed to create renderer" << std::endl; - return 1; - } - - auto window = app.window(); - if (!window) { - std::cerr << "Failed to get window" << std::endl; - return 1; - } - - if (!renderer->init(window->native())) { - std::cerr << "Failed to initialize renderer" << std::endl; - return 1; - } - - if (logger) { - logger->info("Renderer: %s (Vulkan: %s)", - renderer->backendName(), - renderer->isVulkan() ? "yes" : "no"); - } - - float totalTime = 0.0f; - - while (app.running() && !window->shouldClose()) { - float dt = app.dt(); - totalTime += dt; - - renderer->beginFrame(); - - i32 width = renderer->width(); - i32 height = renderer->height(); - - Viewport viewport; - viewport.x = 0; - viewport.y = 0; - viewport.width = width; - viewport.height = height; - renderer->setViewport(viewport); - - ClearCommand clearCmd; - clearCmd.color = Color(20, 20, 30, 255); - clearCmd.clearColor = true; - clearCmd.clearDepth = true; - renderer->clear(clearCmd); - - float centerX = static_cast(width) / 2.0f; - float centerY = static_cast(height) / 2.0f; - - float pulse = 0.5f + 0.5f * std::sin(totalTime * 2.0f); - Color textColor( - static_cast(100 + 155 * pulse), - static_cast(200 + 55 * pulse), - static_cast(255), - 255 - ); - - float rectWidth = 320.0f; - float rectHeight = 70.0f; - float rectX = centerX - rectWidth / 2; - float rectY = centerY - rectHeight / 2; - - Color rectColor(50, 50, 70, 255); - renderer->drawRect(Vec2{rectX, rectY}, Vec2{rectWidth, rectHeight}, rectColor, true); - - Color borderColor( - static_cast(100 + 100 * pulse), - static_cast(180 + 50 * pulse), - 255, - 255 - ); - renderer->drawRect(Vec2{rectX, rectY}, Vec2{rectWidth, rectHeight}, borderColor, false); - - for (int i = 0; i < 5; ++i) { - float x = 100.0f + i * 200.0f; - float y = static_cast(height) - 50.0f; - float radius = 20.0f; - - float phase = totalTime * 1.5f + i * 0.5f; - float offsetY = std::sin(phase) * 20.0f; - - Color circleColor( - static_cast(50 + i * 30), - static_cast(100 + i * 20), - static_cast(150 + i * 20), - 200 - ); - - renderer->drawCircle(Vec2{x, y + offsetY}, radius, circleColor, true, 16); - } - - renderer->flushBatch(); - renderer->endFrame(); - renderer->present(); - - window->poll(); - } - - renderer->shutdown(); - app.shutdown(); - - std::cout << "Application terminated successfully" << std::endl; - return 0; -} diff --git a/Extra2D/include/extra2d/2d/components/Sprite.h b/Extra2D/include/extra2d/2d/components/Sprite.h new file mode 100644 index 0000000..56e5605 --- /dev/null +++ b/Extra2D/include/extra2d/2d/components/Sprite.h @@ -0,0 +1,160 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace extra2d { + +/** + * @brief 精灵组件 + * + * 用于渲染 2D 精灵图像的组件。 + * 参考 Cocos Creator 的 Sprite 组件设计。 + */ +class Sprite : public Component { +public: + /** + * @brief 构造函数 + */ + Sprite(); + + /** + * @brief 析构函数 + */ + ~Sprite() override; + + // ==================== 精灵帧 ==================== + + /** + * @brief 获取精灵帧 + * @return 精灵帧指针 + */ + SpriteFrame* getSpriteFrame() const; + + /** + * @brief 设置精灵帧 + * @param frame 精灵帧指针 + */ + void setSpriteFrame(SpriteFrame* frame); + + // ==================== 颜色和透明度 ==================== + + /** + * @brief 获取颜色 + * @return 颜色 + */ + Vec4 getColor() const; + + /** + * @brief 设置颜色 + * @param color 颜色 + */ + void setColor(const Vec4& color); + + /** + * @brief 设置颜色(RGB) + * @param r 红色 + * @param g 绿色 + * @param b 蓝色 + */ + void setColor(float r, float g, float b); + + /** + * @brief 设置颜色(RGBA) + * @param r 红色 + * @param g 绿色 + * @param b 蓝色 + * @param a 透明度 + */ + void setColor(float r, float g, float b, float a); + + // ==================== 尺寸 ==================== + + /** + * @brief 获取宽度 + * @return 宽度 + */ + float getWidth() const; + + /** + * @brief 设置宽度 + * @param width 宽度 + */ + void setWidth(float width); + + /** + * @brief 获取高度 + * @return 高度 + */ + float getHeight() const; + + /** + * @brief 设置高度 + * @param height 高度 + */ + void setHeight(float height); + + /** + * @brief 获取尺寸 + * @return 尺寸 + */ + Vec2 getSize() const; + + /** + * @brief 设置尺寸 + * @param size 尺寸 + */ + void setSize(const Vec2& size); + + // ==================== 渲染状态 ==================== + + /** + * @brief 检查是否脏 + * @return 如果脏返回 true + */ + bool isDirty() const; + + /** + * @brief 设置脏标记 + * @param dirty 是否脏 + */ + void setDirty(bool dirty); + + /** + * @brief 标记需要更新 + */ + void markForUpdate(); + + // ==================== 生命周期回调 ==================== + + void onAdd() override; + void onRemove() override; + void update(f32 dt) override; + void render() override; + + /** + * @brief 获取类型名称 + * @return "Sprite" + */ + const char* getTypeName() const override { return "Sprite"; } + +protected: + void updateGeometry(); + void updateRenderEntity(); + void updateVertexData(); + +private: + SpriteFrame* spriteFrame_{nullptr}; + RenderEntity* renderEntity_{nullptr}; + RenderDrawInfo* drawInfo_{nullptr}; + + Vec4 color_{1, 1, 1, 1}; + Vec2 size_{0, 0}; + bool dirty_{true}; + bool sizeSetByUser_{false}; +}; + +} // namespace extra2d diff --git a/Extra2D/include/extra2d/2d/components/SpriteFrame.h b/Extra2D/include/extra2d/2d/components/SpriteFrame.h new file mode 100644 index 0000000..25baf68 --- /dev/null +++ b/Extra2D/include/extra2d/2d/components/SpriteFrame.h @@ -0,0 +1,134 @@ +#pragma once + +#include +#include +#include + +namespace extra2d { + +/** + * @brief 精灵帧 + * + * 表示精灵的一个帧,包含纹理区域信息。 + */ +class SpriteFrame : public Asset { +public: + /** + * @brief 构造函数 + */ + SpriteFrame(); + + /** + * @brief 析构函数 + */ + ~SpriteFrame() override; + + // ==================== 纹理区域 ==================== + + /** + * @brief 获取关联纹理 + * @return 纹理指针 + */ + Texture2D* getTexture() const; + + /** + * @brief 设置关联纹理 + * @param texture 纹理指针 + */ + void setTexture(Texture2D* texture); + + /** + * @brief 获取纹理区域矩形 + * @return 区域矩形 + */ + Rect getRect() const; + + /** + * @brief 设置纹理区域矩形 + * @param rect 区域矩形 + */ + void setRect(const Rect& rect); + + /** + * @brief 获取未旋转区域 + * @return 未旋转区域 + */ + Rect getUnrotatedRect() const; + + /** + * @brief 设置未旋转区域 + * @param rect 未旋转区域 + */ + void setUnrotatedRect(const Rect& rect); + + // ==================== 旋转信息 ==================== + + /** + * @brief 检查是否旋转 + * @return 如果旋转返回 true + */ + bool isRotated() const; + + /** + * @brief 设置是否旋转 + * @param rotated 是否旋转 + */ + void setRotated(bool rotated); + + // ==================== 偏移和大小 ==================== + + /** + * @brief 获取偏移 + * @return 偏移向量 + */ + Vec2 getOffset() const; + + /** + * @brief 设置偏移 + * @param offset 偏移向量 + */ + void setOffset(const Vec2& offset); + + /** + * @brief 获取原始大小 + * @return 原始大小 + */ + Vec2 getOriginalSize() const; + + /** + * @brief 设置原始大小 + * @param size 原始大小 + */ + void setOriginalSize(const Vec2& size); + + // ==================== UV 计算 ==================== + + /** + * @brief 获取 UV 坐标 + * @return UV 坐标数组 + */ + std::array getUVs() const; + + /** + * @brief 计算 UV 坐标 + */ + void calculateUV(); + + /** + * @brief 获取类型名称 + * @return "SpriteFrame" + */ + const char* getTypeName() const override { return "SpriteFrame"; } + +private: + Texture2D* texture_{nullptr}; + Rect rect_{0, 0, 0, 0}; + Rect unrotatedRect_{0, 0, 0, 0}; + Vec2 offset_{0, 0}; + Vec2 originalSize_{0, 0}; + bool rotated_{false}; + + std::array uvs_; +}; + +} // namespace extra2d diff --git a/Extra2D/include/extra2d/2d/renderer/Batcher2d.h b/Extra2D/include/extra2d/2d/renderer/Batcher2d.h new file mode 100644 index 0000000..5c24f24 --- /dev/null +++ b/Extra2D/include/extra2d/2d/renderer/Batcher2d.h @@ -0,0 +1,180 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace extra2d { + +namespace gfx { + class Device; + class Shader; + class PipelineState; + class InputAssembler; +} + +class Node; +class Material; + +/** + * @brief 绘制批次 + */ +struct DrawBatch2D { + gfx::InputAssembler* inputAssembler{nullptr}; + gfx::Shader* shader{nullptr}; + gfx::Texture* texture{nullptr}; + u32 vertexOffset{0}; + u32 indexOffset{0}; + u32 vbCount{0}; + u32 ibCount{0}; + u32 stencilStage{0}; + u32 stencilValue{0}; + u32 priority{0}; +}; + +/** + * @brief 2D 批处理器 + * + * 管理 2D 渲染的批处理合并和渲染流程。 + * 参考 Cocos Creator 的 Batcher2d 设计。 + */ +class Batcher2d { +public: + /** + * @brief 获取单例实例 + * @return 批处理器实例 + */ + static Batcher2d* getInstance(); + + /** + * @brief 初始化批处理器 + * @param device GFX 设备 + * @return 成功返回 true + */ + bool initialize(gfx::Device* device); + + /** + * @brief 销毁批处理器 + */ + void destroy(); + + /** + * @brief 更新批处理器 + */ + void update(); + + /** + * @brief 上传缓冲区数据 + */ + void uploadBuffers(); + + /** + * @brief 重置批处理器 + */ + void reset(); + + // ==================== 渲染流程 ==================== + + /** + * @brief 填充缓冲区并合并批次 + */ + void fillBuffersAndMergeBatches(); + + /** + * @brief 遍历节点树 + * @param node 节点 + * @param parentOpacity 父节点透明度 + * @param parentColorDirty 父节点颜色是否脏 + */ + void walk(Node* node, float parentOpacity, bool parentColorDirty); + + /** + * @brief 处理绘制信息 + * @param entity 渲染实体 + * @param drawInfo 绘制信息 + * @param node 节点 + */ + void handleDrawInfo(RenderEntity* entity, RenderDrawInfo* drawInfo, Node* node); + + /** + * @brief 生成批次 + * @param entity 渲染实体 + * @param drawInfo 绘制信息 + */ + void generateBatch(RenderEntity* entity, RenderDrawInfo* drawInfo); + + // ==================== 缓冲区管理 ==================== + + /** + * @brief 获取网格缓冲区 + * @param accId 账户 ID + * @param bufferId 缓冲区 ID + * @return 网格缓冲区指针 + */ + UIMeshBuffer* getMeshBuffer(u16 accId, u16 bufferId); + + /** + * @brief 同步网格缓冲区 + * @param accId 账户 ID + * @param buffers 缓冲区列表 + */ + void syncMeshBuffers(u16 accId, std::vector&& buffers); + + /** + * @brief 同步根节点 + * @param rootNodes 根节点列表 + */ + void syncRootNodes(std::vector&& rootNodes); + + // ==================== 渲染执行 ==================== + + /** + * @brief 渲染所有批次 + */ + void renderBatches(); + + /** + * @brief 获取 GFX 设备 + * @return 设备指针 + */ + gfx::Device* getDevice(); + + /** + * @brief 获取默认属性列表 + * @return 属性列表 + */ + const std::vector& getDefaultAttributes() const; + +private: + Batcher2d(); + ~Batcher2d(); + + Batcher2d(const Batcher2d&) = delete; + Batcher2d& operator=(const Batcher2d&) = delete; + + void initDefaultAttributes(); + void updateBatch(DrawBatch2D* batch); + + static Batcher2d* instance_; + + gfx::Device* device_{nullptr}; + std::vector defaultAttributes_; + + std::vector batches_; + std::unordered_map> meshBuffersMap_; + std::vector rootNodes_; + + RenderEntity* currEntity_{nullptr}; + RenderDrawInfo* currDrawInfo_{nullptr}; + UIMeshBuffer* currMeshBuffer_{nullptr}; + + u32 currStencilStage_{0}; + u32 currStencilValue_{0}; +}; + +} // namespace extra2d diff --git a/Extra2D/include/extra2d/2d/renderer/RenderDrawInfo.h b/Extra2D/include/extra2d/2d/renderer/RenderDrawInfo.h new file mode 100644 index 0000000..1ccd464 --- /dev/null +++ b/Extra2D/include/extra2d/2d/renderer/RenderDrawInfo.h @@ -0,0 +1,273 @@ +#pragma once + +#include +#include +#include + +namespace extra2d { + +class Material; +class UIMeshBuffer; +namespace gfx { + class Texture; + class InputAssembler; + class Buffer; +} + +/** + * @brief 渲染绘制信息类型 + */ +enum class RenderDrawInfoType : u8 { + Component, + Model, + Middleware, + SubNode, +}; + +/** + * @brief 2D 渲染顶点布局 + */ +struct Render2dVertex { + Vec3 position{0, 0, 0}; + Vec2 uv{0, 0}; + Vec4 color{1, 1, 1, 1}; +}; + +/** + * @brief 绘制属性 + */ +struct DrawInfoAttrs { + RenderDrawInfoType type{RenderDrawInfoType::Component}; + bool vertDirty{false}; + bool isMeshBuffer{false}; + bool isVertexPositionInWorld{false}; + u8 stride{0}; + u16 bufferId{0}; + u16 accId{0}; + u32 vertexOffset{0}; + u32 indexOffset{0}; + u32 vbCount{0}; + u32 ibCount{0}; + u64 dataHash{0}; +}; + +/** + * @brief 渲染绘制信息 + * + * 封装单次绘制所需的所有信息。 + * 参考 Cocos Creator 的 RenderDrawInfo 设计。 + */ +class RenderDrawInfo { +public: + /** + * @brief 构造函数 + */ + RenderDrawInfo(); + + /** + * @brief 析构函数 + */ + ~RenderDrawInfo(); + + // ==================== 类型与标识 ==================== + + /** + * @brief 获取绘制类型 + * @return 类型 + */ + RenderDrawInfoType getDrawInfoType() const; + + /** + * @brief 设置绘制类型 + * @param type 类型 + */ + void setDrawInfoType(RenderDrawInfoType type); + + /** + * @brief 获取账户 ID + * @return 账户 ID + */ + u16 getAccId() const; + + /** + * @brief 设置账户 ID + * @param accId 账户 ID + */ + void setAccId(u16 accId); + + /** + * @brief 获取缓冲区 ID + * @return 缓冲区 ID + */ + u16 getBufferId() const; + + /** + * @brief 设置缓冲区 ID + * @param bufferId 缓冲区 ID + */ + void setBufferId(u16 bufferId); + + // ==================== 缓冲区信息 ==================== + + /** + * @brief 获取顶点偏移 + * @return 顶点偏移 + */ + u32 getVertexOffset() const; + + /** + * @brief 设置顶点偏移 + * @param offset 偏移 + */ + void setVertexOffset(u32 offset); + + /** + * @brief 获取索引偏移 + * @return 索引偏移 + */ + u32 getIndexOffset() const; + + /** + * @brief 设置索引偏移 + * @param offset 偏移 + */ + void setIndexOffset(u32 offset); + + /** + * @brief 获取顶点数量 + * @return 顶点数量 + */ + u32 getVbCount() const; + + /** + * @brief 设置顶点数量 + * @param count 数量 + */ + void setVbCount(u32 count); + + /** + * @brief 获取索引数量 + * @return 索引数量 + */ + u32 getIbCount() const; + + /** + * @brief 设置索引数量 + * @param count 数量 + */ + void setIbCount(u32 count); + + // ==================== 资源绑定 ==================== + + /** + * @brief 获取材质 + * @return 材质指针 + */ + Material* getMaterial() const; + + /** + * @brief 设置材质 + * @param material 材质指针 + */ + void setMaterial(Material* material); + + /** + * @brief 获取网格缓冲区 + * @return 网格缓冲区指针 + */ + UIMeshBuffer* getMeshBuffer() const; + + /** + * @brief 设置网格缓冲区 + * @param buffer 网格缓冲区指针 + */ + void setMeshBuffer(UIMeshBuffer* buffer); + + /** + * @brief 获取纹理 + * @return 纹理指针 + */ + gfx::Texture* getTexture() const; + + /** + * @brief 设置纹理 + * @param texture 纹理指针 + */ + void setTexture(gfx::Texture* texture); + + // ==================== 数据缓冲区 ==================== + + /** + * @brief 获取顶点数据 + * @return 顶点数据指针 + */ + float* getVDataBuffer() const; + + /** + * @brief 设置顶点数据 + * @param data 数据指针 + */ + void setVDataBuffer(float* data); + + /** + * @brief 获取索引数据 + * @return 索引数据指针 + */ + u16* getIDataBuffer() const; + + /** + * @brief 设置索引数据 + * @param data 数据指针 + */ + void setIDataBuffer(u16* data); + + // ==================== 渲染操作 ==================== + + /** + * @brief 请求输入装配器 + * @param device GFX 设备 + * @return 输入装配器指针 + */ + gfx::InputAssembler* requestIA(gfx::Device* device); + + /** + * @brief 上传缓冲区数据 + */ + void uploadBuffers(); + + /** + * @brief 检查顶点是否脏 + * @return 如果脏返回 true + */ + bool isVertDirty() const; + + /** + * @brief 设置顶点脏标记 + * @param dirty 是否脏 + */ + void setVertDirty(bool dirty); + + /** + * @brief 获取数据哈希 + * @return 哈希值 + */ + u64 getDataHash() const; + + /** + * @brief 设置数据哈希 + * @param hash 哈希值 + */ + void setDataHash(u64 hash); + +private: + DrawInfoAttrs attrs_; + Material* material_{nullptr}; + UIMeshBuffer* meshBuffer_{nullptr}; + gfx::Texture* texture_{nullptr}; + gfx::InputAssembler* inputAssembler_{nullptr}; + + float* vData_{nullptr}; + u16* iData_{nullptr}; +}; + +} // namespace extra2d diff --git a/Extra2D/include/extra2d/2d/renderer/RenderEntity.h b/Extra2D/include/extra2d/2d/renderer/RenderEntity.h new file mode 100644 index 0000000..5312e41 --- /dev/null +++ b/Extra2D/include/extra2d/2d/renderer/RenderEntity.h @@ -0,0 +1,250 @@ +#pragma once + +#include +#include +#include +#include + +namespace extra2d { + +class Node; +class RenderDrawInfo; +class UIMeshBuffer; + +/** + * @brief 渲染实体类型 + */ +enum class RenderEntityType : u8 { + Static, + Dynamic, +}; + +/** + * @brief 遮罩模式 + */ +enum class MaskMode : u8 { + None, + Mask, + MaskInverted, + MaskNode, + MaskNodeInverted, +}; + +/** + * @brief 填充颜色类型 + */ +enum class FillColorType : u8 { + Color, + Vertex, +}; + +/** + * @brief 实体属性布局 + */ +struct EntityAttrLayout { + u32 priority{0}; + u8 colorR{255}; + u8 colorG{255}; + u8 colorB{255}; + u8 colorA{255}; + u8 maskMode{0}; + FillColorType fillColorType{FillColorType::Color}; + u8 enabledIndex : 1; + u8 useLocal : 1; + u8 paddings : 6; +}; + +/** + * @brief 渲染实体 + * + * 表示一个可渲染的实体,管理渲染状态和绘制信息集合。 + * 参考 Cocos Creator 的 RenderEntity 设计。 + */ +class RenderEntity { +public: + static constexpr u32 STATIC_DRAW_INFO_CAPACITY = 4; + + /** + * @brief 构造函数 + */ + RenderEntity(); + + /** + * @brief 析构函数 + */ + ~RenderEntity(); + + // ==================== DrawInfo 管理 ==================== + + /** + * @brief 添加动态绘制信息 + * @param drawInfo 绘制信息 + */ + void addDynamicRenderDrawInfo(RenderDrawInfo* drawInfo); + + /** + * @brief 设置动态绘制信息 + * @param drawInfo 绘制信息 + * @param index 索引 + */ + void setDynamicRenderDrawInfo(RenderDrawInfo* drawInfo, u32 index); + + /** + * @brief 移除动态绘制信息 + */ + void removeDynamicRenderDrawInfo(); + + /** + * @brief 清除动态绘制信息 + */ + void clearDynamicRenderDrawInfos(); + + /** + * @brief 清除静态绘制信息 + */ + void clearStaticRenderDrawInfos(); + + /** + * @brief 获取绘制信息 + * @param index 索引 + * @return 绘制信息指针 + */ + RenderDrawInfo* getRenderDrawInfoAt(u32 index); + + /** + * @brief 获取绘制信息数量 + * @return 数量 + */ + u32 getRenderDrawInfoCount() const; + + // ==================== 属性访问 ==================== + + /** + * @brief 获取关联节点 + * @return 节点指针 + */ + Node* getNode() const; + + /** + * @brief 设置关联节点 + * @param node 节点指针 + */ + void setNode(Node* node); + + /** + * @brief 获取渲染实体类型 + * @return 类型 + */ + RenderEntityType getRenderEntityType() const; + + /** + * @brief 设置渲染实体类型 + * @param type 类型 + */ + void setRenderEntityType(RenderEntityType type); + + /** + * @brief 获取优先级 + * @return 优先级 + */ + u32 getPriority() const; + + /** + * @brief 设置优先级 + * @param priority 优先级 + */ + void setPriority(u32 priority); + + /** + * @brief 获取遮罩模式 + * @return 遮罩模式 + */ + MaskMode getMaskMode() const; + + /** + * @brief 设置遮罩模式 + * @param mode 遮罩模式 + */ + void setMaskMode(MaskMode mode); + + /** + * @brief 检查是否为遮罩 + * @return 如果是遮罩返回 true + */ + bool isMask() const; + + /** + * @brief 检查是否为子遮罩 + * @return 如果是子遮罩返回 true + */ + bool isSubMask() const; + + /** + * @brief 检查是否为反转遮罩 + * @return 如果是反转遮罩返回 true + */ + bool isMaskInverted() const; + + /** + * @brief 获取填充颜色类型 + * @return 填充颜色类型 + */ + FillColorType getFillColorType() const; + + /** + * @brief 设置填充颜色类型 + * @param type 填充颜色类型 + */ + void setFillColorType(FillColorType type); + + /** + * @brief 获取颜色 + * @return 颜色(RGBA) + */ + Vec4 getColor() const; + + /** + * @brief 设置颜色 + * @param r 红色 + * @param g 绿色 + * @param b 蓝色 + * @param a 透明度 + */ + void setColor(u8 r, u8 g, u8 b, u8 a); + + /** + * @brief 获取模板阶段 + * @return 模板阶段 + */ + u32 getStencilStage() const; + + /** + * @brief 设置模板阶段 + * @param stage 模板阶段 + */ + void setStencilStage(u32 stage); + + /** + * @brief 检查是否使用本地坐标 + * @return 如果使用本地坐标返回 true + */ + bool useLocal() const; + + /** + * @brief 设置是否使用本地坐标 + * @param useLocal 是否使用本地坐标 + */ + void setUseLocal(bool useLocal); + +private: + Node* node_{nullptr}; + RenderEntityType entityType_{RenderEntityType::Static}; + EntityAttrLayout attr_{}; + + std::vector dynamicDrawInfos_; + u32 drawInfoCount_{0}; + + u32 stencilStage_{0}; +}; + +} // namespace extra2d diff --git a/Extra2D/include/extra2d/2d/renderer/StencilManager.h b/Extra2D/include/extra2d/2d/renderer/StencilManager.h new file mode 100644 index 0000000..87cdac3 --- /dev/null +++ b/Extra2D/include/extra2d/2d/renderer/StencilManager.h @@ -0,0 +1,218 @@ +#pragma once + +#include +#include +#include + +namespace extra2d { + +class RenderEntity; + +/** + * @brief 模板阶段枚举 + */ +enum class StencilStage : u8 { + Disabled = 0, + Clear = 1, + EnterLevel = 2, + Enabled = 3, + ExitLevel = 4, + ClearInverted = 5, + EnterLevelInverted = 6, +}; + +/** + * @brief 模板实体结构 + */ +struct StencilEntity { + u32 stencilTest{0}; + gfx::ComparisonFunc func{gfx::ComparisonFunc::Always}; + u32 stencilMask{0}; + u32 writeMask{0}; + gfx::StencilOp failOp{gfx::StencilOp::Keep}; + gfx::StencilOp zFailOp{gfx::StencilOp::Keep}; + gfx::StencilOp passOp{gfx::StencilOp::Keep}; + u32 ref{0}; +}; + +/** + * @brief 模板缓冲区管理器 + * + * 管理 2D 渲染中的模板缓冲区操作,用于遮罩效果。 + * 参考 Cocos Creator 的 StencilManager 设计。 + */ +class StencilManager final { +public: + /** + * @brief 获取单例实例 + * @return 模板管理器实例 + */ + static StencilManager* getInstance(); + + /** + * @brief 构造函数 + */ + StencilManager() = default; + + /** + * @brief 析构函数 + */ + ~StencilManager(); + + /** + * @brief 获取当前模板阶段 + * @return 模板阶段 + */ + StencilStage getStencilStage() const; + + /** + * @brief 获取深度模板状态 + * @param stage 模板阶段 + * @return 深度模板状态 + */ + gfx::DepthStencilState* getDepthStencilState(StencilStage stage); + + /** + * @brief 从阶段设置深度模板状态 + * @param stage 模板阶段 + */ + void setDepthStencilStateFromStage(StencilStage stage); + + /** + * @brief 获取遮罩栈大小 + * @return 遮罩栈大小 + */ + u32 getMaskStackSize() const; + + /** + * @brief 设置遮罩栈大小 + * @param size 大小 + */ + void setMaskStackSize(u32 size); + + /** + * @brief 压入遮罩 + */ + void pushMask(); + + /** + * @brief 清除模板 + * @param entity 渲染实体 + * @return 模板阶段 + */ + StencilStage clear(RenderEntity* entity); + + /** + * @brief 进入层级 + * @param entity 渲染实体 + */ + void enterLevel(RenderEntity* entity); + + /** + * @brief 启用遮罩 + */ + void enableMask(); + + /** + * @brief 退出遮罩 + */ + void exitMask(); + + /** + * @brief 获取写入掩码 + * @return 写入掩码 + */ + u32 getWriteMask() const; + + /** + * @brief 获取退出写入掩码 + * @return 退出写入掩码 + */ + u32 getExitWriteMask() const; + + /** + * @brief 获取模板引用值 + * @return 模板引用值 + */ + u32 getStencilRef() const; + + /** + * @brief 获取模板哈希 + * @param stage 模板阶段 + * @return 哈希值 + */ + u32 getStencilHash(StencilStage stage) const; + + /** + * @brief 设置模板阶段 + * @param stageIndex 阶段索引 + */ + void setStencilStage(u32 stageIndex); + +private: + StencilManager(const StencilManager&) = delete; + StencilManager& operator=(const StencilManager&) = delete; + + StencilEntity stencilPattern_; + StencilStage stage_{StencilStage::Disabled}; + u32 maskStackSize_{0}; + + std::unordered_map cacheStateMap_; + + static StencilManager* instance_; +}; + +inline StencilStage StencilManager::getStencilStage() const { + return stage_; +} + +inline u32 StencilManager::getMaskStackSize() const { + return maskStackSize_; +} + +inline void StencilManager::setMaskStackSize(u32 size) { + maskStackSize_ = size; +} + +inline void StencilManager::pushMask() { + ++maskStackSize_; +} + +inline void StencilManager::enableMask() { + stage_ = StencilStage::Enabled; +} + +inline void StencilManager::exitMask() { + if (maskStackSize_ == 0) { + return; + } + + --maskStackSize_; + if (maskStackSize_ == 0) { + stage_ = StencilStage::Disabled; + } else { + stage_ = StencilStage::Enabled; + } +} + +inline u32 StencilManager::getWriteMask() const { + return 1 << (maskStackSize_ - 1); +} + +inline u32 StencilManager::getExitWriteMask() const { + return 1 << maskStackSize_; +} + +inline u32 StencilManager::getStencilRef() const { + u32 result = 0; + for (u32 i = 0; i < maskStackSize_; i++) { + result += (1 << i); + } + return result; +} + +inline u32 StencilManager::getStencilHash(StencilStage stage) const { + return (static_cast(stage) << 8) | maskStackSize_; +} + +} // namespace extra2d diff --git a/Extra2D/include/extra2d/2d/renderer/UIMeshBuffer.h b/Extra2D/include/extra2d/2d/renderer/UIMeshBuffer.h new file mode 100644 index 0000000..e565048 --- /dev/null +++ b/Extra2D/include/extra2d/2d/renderer/UIMeshBuffer.h @@ -0,0 +1,99 @@ +#pragma once + +#include +#include +#include +#include + +namespace extra2d { + +namespace gfx { + class Buffer; + class InputAssembler; + class Device; +} + +/** + * @brief 网格缓冲区布局 + */ +struct MeshBufferLayout { + u32 byteOffset{0}; + u32 vertexOffset{0}; + u32 indexOffset{0}; + u32 dirtyMark{0}; +}; + +/** + * @brief UI 网格缓冲区 + * + * 管理顶点和索引数据的 GPU 缓冲区。 + */ +class UIMeshBuffer { +public: + UIMeshBuffer(); + ~UIMeshBuffer(); + + bool initialize(gfx::Device* device, + const std::vector& attrs, + u32 vertexCapacity = 65536, + u32 indexCapacity = 196608); + + void reset(); + void destroy(); + + float* getVData() const; + void setVData(float* vData); + + u16* getIData() const; + void setIData(u16* iData); + + u32 getByteOffset() const; + void setByteOffset(u32 offset); + + u32 getVertexOffset() const; + void setVertexOffset(u32 offset); + + u32 getIndexOffset() const; + void setIndexOffset(u32 offset); + + bool isDirty() const; + void setDirty(bool dirty); + + u32 getVertexCapacity() const; + u32 getIndexCapacity() const; + u32 getVertexFormatBytes() const; + + gfx::Buffer* getVertexBuffer() const; + gfx::Buffer* getIndexBuffer() const; + gfx::InputAssembler* getInputAssembler() const; + + gfx::InputAssembler* requireFreeIA(gfx::Device* device); + void uploadBuffers(); + void resetIA(); + +private: + bool createBuffers(); + bool createInputAssembler(); + + gfx::Device* device_{nullptr}; + std::vector attributes_; + + float* vData_{nullptr}; + u16* iData_{nullptr}; + bool ownsData_{false}; + + u32 vertexCapacity_{65536}; + u32 indexCapacity_{196608}; + u32 vertexFormatBytes_{36}; + + u32 byteOffset_{0}; + u32 vertexOffset_{0}; + u32 indexOffset_{0}; + bool dirty_{false}; + + gfx::Buffer* vertexBuffer_{nullptr}; + gfx::Buffer* indexBuffer_{nullptr}; + gfx::InputAssembler* inputAssembler_{nullptr}; +}; + +} // namespace extra2d diff --git a/Extra2D/include/extra2d/app/application.h b/Extra2D/include/extra2d/app/application.h deleted file mode 100644 index 52774e5..0000000 --- a/Extra2D/include/extra2d/app/application.h +++ /dev/null @@ -1,113 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -namespace extra2d { - -class GLFWWindow; -class WindowModule; - -/** - * @brief 应用程序类 - */ -class Application { -public: - static Application &get(); - - Application(const Application &) = delete; - Application &operator=(const Application &) = delete; - - /** - * @brief 应用信息 - */ - std::string appName = "Extra2D App"; - std::string appVersion = "1.0.0"; - std::string organization = ""; - - /** - * @brief 注册模块 - * @tparam T 模块类型 - * @tparam Args 构造函数参数 - * @return 模块指针 - */ - template T *use(Args &&...args) { - return Registry::instance().use(std::forward(args)...); - } - - /** - * @brief 获取模块 - * @tparam T 模块类型 - * @return 模块指针 - */ - template T *get() const { return Registry::instance().get(); } - - /** - * @brief 初始化 - * @return 初始化成功返回 true - */ - bool init(); - - /** - * @brief 关闭 - */ - void shutdown(); - - /** - * @brief 运行主循环 - */ - void run(); - - /** - * @brief 请求退出 - */ - void quit(); - - /** - * @brief 暂停 - */ - void pause(); - - /** - * @brief 恢复 - */ - void resume(); - - bool paused() const { return paused_; } - bool running() const { return running_; } - - /** - * @brief 获取窗口 - * @return 窗口指针 - */ - GLFWWindow *window(); - - f32 dt() const { return dt_; } - f32 totalTime() const { return totalTime_; } - int fps() const { return fps_; } - -private: - Application(); - ~Application(); - - void mainLoop(); - void update(); - void render(); - - bool initialized_ = false; - bool running_ = false; - bool paused_ = false; - bool shouldQuit_ = false; - - f32 dt_ = 0.0f; - f32 totalTime_ = 0.0f; - f64 lastFrameTime_ = 0.0; - int frameCount_ = 0; - f32 fpsTimer_ = 0.0f; - int fps_ = 0; -}; - -} // namespace extra2d diff --git a/Extra2D/include/extra2d/application/Application.h b/Extra2D/include/extra2d/application/Application.h new file mode 100644 index 0000000..4580d80 --- /dev/null +++ b/Extra2D/include/extra2d/application/Application.h @@ -0,0 +1,144 @@ +#pragma once + +#include +#include +#include +#include + +namespace extra2d { + +class Engine; +class Window; +class Input; + +/** + * @brief 应用程序配置 + */ +struct AppConfig { + std::string title = "Extra2D Application"; + i32 width = 1280; + i32 height = 720; + bool fullscreen = false; + bool resizable = true; + bool vsync = true; + bool borderless = false; + bool highDPI = true; + i32 fps = 60; +}; + +/** + * @brief 应用程序类 + * + * 引擎的入口点,管理主循环、窗口和引擎生命周期。 + * 参考 Cocos Creator 的 Application 设计。 + */ +class Application { +public: + /** + * @brief 获取单例实例 + * @return 应用程序实例 + */ + static Application* getInstance(); + + /** + * @brief 初始化应用程序 + * @param config 应用程序配置 + * @return 成功返回 0,失败返回错误码 + */ + i32 init(const AppConfig& config); + + /** + * @brief 运行主循环 + * @return 退出码 + */ + i32 run(); + + /** + * @brief 暂停应用程序 + */ + void pause(); + + /** + * @brief 恢复应用程序 + */ + void resume(); + + /** + * @brief 关闭应用程序 + */ + void close(); + + /** + * @brief 检查是否正在运行 + * @return 如果正在运行返回 true + */ + bool isRunning() const; + + /** + * @brief 检查是否暂停 + * @return 如果暂停返回 true + */ + bool isPaused() const; + + /** + * @brief 获取引擎实例 + * @return 引擎指针 + */ + Engine* getEngine(); + + /** + * @brief 获取窗口实例 + * @return 窗口指针 + */ + Window* getWindow(); + + /** + * @brief 获取输入实例 + * @return 输入指针 + */ + Input* getInput(); + + /** + * @brief 获取命令行参数 + * @return 参数列表 + */ + const std::vector& getArguments() const; + + /** + * @brief 设置命令行参数 + * @param argc 参数数量 + * @param argv 参数数组 + */ + void setArguments(i32 argc, char* argv[]); + + /** + * @brief 设置帧回调 + * @param callback 每帧调用的回调函数 + */ + void setFrameCallback(const std::function& callback); + +private: + Application(); + ~Application(); + + Application(const Application&) = delete; + Application& operator=(const Application&) = delete; + + void mainLoop(); + void processEvents(); + + static Application* instance_; + + Engine* engine_{nullptr}; + Window* window_{nullptr}; + Input* input_{nullptr}; + + std::vector arguments_; + std::function frameCallback_; + + bool running_{false}; + bool paused_{false}; + bool initialized_{false}; +}; + +} // namespace extra2d diff --git a/Extra2D/include/extra2d/asset/asset.h b/Extra2D/include/extra2d/asset/asset.h deleted file mode 100644 index b6e929e..0000000 --- a/Extra2D/include/extra2d/asset/asset.h +++ /dev/null @@ -1,477 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include - -namespace extra2d { - -// --------------------------------------------------------------------------- -// Asset - 资源基类 -// --------------------------------------------------------------------------- - -/** - * @brief 资源基类 - * - * 所有资源类型的基类,继承自 enable_shared_from_this 支持自动引用计数。 - * 提供资源的基本属性和生命周期管理接口。 - */ -class Asset : public std::enable_shared_from_this { -public: - virtual ~Asset() = default; - - /** - * @brief 获取资源类型 - * @return 资源类型枚举值 - */ - virtual AssetType type() const = 0; - - /** - * @brief 检查资源是否已加载 - * @return 已加载返回 true - */ - virtual bool loaded() const = 0; - - /** - * @brief 获取资源内存占用大小 - * @return 内存占用字节数 - */ - virtual size_t memSize() const = 0; - - /** - * @brief 获取资源ID - * @return 资源ID - */ - const AssetID &id() const { return id_; } - - /** - * @brief 获取资源路径 - * @return 资源路径 - */ - const std::string &path() const { return path_; } - - /** - * @brief 获取资源状态 - * @return 资源状态 - */ - AssetState state() const { return state_; } - - /** - * @brief 获取当前引用计数 - * @return 引用计数(用于调试和监控) - */ - long refs() const { return shared_from_this().use_count(); } - -protected: - AssetID id_; - std::string path_; - std::atomic state_{AssetState::Unloaded}; - - /** - * @brief 设置资源状态 - * @param state 新状态 - */ - void setState(AssetState state) { - state_.store(state, std::memory_order_release); - } - - /** - * @brief 设置资源ID - * @param id 资源ID - */ - void setId(const AssetID &id) { id_ = id; } - - /** - * @brief 设置资源路径 - * @param path 资源路径 - */ - void setPath(const std::string &path) { path_ = path; } - - friend class AssetCache; - friend class AssetService; -}; - -// --------------------------------------------------------------------------- -// TextureAsset - 纹理资源 -// --------------------------------------------------------------------------- - -/** - * @brief 纹理资源类 - * - * 存储纹理图像数据,支持多种像素格式。 - */ -class TextureAsset : public Asset { -public: - AssetType type() const override { return AssetType::Texture; } - - bool loaded() const override { - return state_.load(std::memory_order_acquire) == AssetState::Loaded && - data_ != nullptr; - } - - size_t memSize() const override { - return static_cast(width_) * height_ * channels_; - } - - /** - * @brief 获取纹理宽度 - * @return 宽度(像素) - */ - int width() const { return width_; } - - /** - * @brief 获取纹理高度 - * @return 高度(像素) - */ - int height() const { return height_; } - - /** - * @brief 获取通道数 - * @return 通道数(1-4) - */ - int channels() const { return channels_; } - - /** - * @brief 获取像素数据 - * @return 像素数据指针 - */ - const u8 *data() const { return data_.get(); } - - /** - * @brief 获取像素数据大小 - * @return 数据大小(字节) - */ - size_t dataSize() const { return memSize(); } - - /** - * @brief 设置纹理数据 - * @param width 宽度 - * @param height 高度 - * @param channels 通道数 - * @param data 像素数据(转移所有权) - */ - void setData(int width, int height, int channels, Unique data) { - width_ = width; - height_ = height; - channels_ = channels; - data_ = std::move(data); - setState(AssetState::Loaded); - } - - /** - * @brief 释放纹理数据 - */ - void release() { - data_.reset(); - width_ = 0; - height_ = 0; - channels_ = 0; - setState(AssetState::Unloaded); - } - -private: - int width_ = 0; - int height_ = 0; - int channels_ = 0; - Unique data_; -}; - -// --------------------------------------------------------------------------- -// FontAsset - 字体资源 -// --------------------------------------------------------------------------- - -/** - * @brief 字体资源类 - * - * 存储TrueType字体数据,支持字形渲染。 - * 使用 Pimpl 模式隐藏 stbtt_fontinfo 实现细节。 - */ -class FontAsset : public Asset { -public: - FontAsset(); - ~FontAsset() override; - - AssetType type() const override { return AssetType::Font; } - - bool loaded() const override; - - size_t memSize() const override { return data_.size(); } - - /** - * @brief 获取指定像素高度的缩放因子 - * @param pixels 像素高度 - * @return 缩放因子 - */ - float scaleForPixelHeight(float pixels) const; - - /** - * @brief 获取字体数据 - * @return 字体数据指针 - */ - const u8 *data() const { return data_.data(); } - - /** - * @brief 获取字体数据大小 - * @return 数据大小(字节) - */ - size_t dataSize() const { return data_.size(); } - - /** - * @brief 设置字体数据 - * @param data 字体数据 - * @return 成功返回 true - */ - bool setData(std::vector data); - - /** - * @brief 释放字体数据 - */ - void release(); - -private: - std::vector data_; - class Impl; - Unique impl_; -}; - -// --------------------------------------------------------------------------- -// ShaderAsset - 着色器资源 -// --------------------------------------------------------------------------- - -/** - * @brief 着色器资源类 - * - * 存储顶点和片段着色器源代码。 - */ -class ShaderAsset : public Asset { -public: - AssetType type() const override { return AssetType::Shader; } - - bool loaded() const override { - return state_.load(std::memory_order_acquire) == AssetState::Loaded; - } - - size_t memSize() const override { - return vertexSrc_.size() + fragmentSrc_.size(); - } - - /** - * @brief 获取顶点着色器源码 - * @return 顶点着色器源码 - */ - const std::string &vertexSource() const { return vertexSrc_; } - - /** - * @brief 获取片段着色器源码 - * @return 片段着色器源码 - */ - const std::string &fragmentSource() const { return fragmentSrc_; } - - /** - * @brief 设置着色器源码 - * @param vertex 顶点着色器源码 - * @param fragment 片段着色器源码 - */ - void setSource(std::string vertex, std::string fragment) { - vertexSrc_ = std::move(vertex); - fragmentSrc_ = std::move(fragment); - setState(AssetState::Loaded); - } - - /** - * @brief 释放着色器源码 - */ - void release() { - vertexSrc_.clear(); - fragmentSrc_.clear(); - setState(AssetState::Unloaded); - } - -private: - std::string vertexSrc_; - std::string fragmentSrc_; -}; - -// --------------------------------------------------------------------------- -// AudioAsset - 音频资源 -// --------------------------------------------------------------------------- - -/** - * @brief 音频格式枚举 - */ -enum class AudioFormat : u8 { PCM = 0, MP3 = 1, OGG = 2, WAV = 3 }; - -/** - * @brief 音频资源类 - * - * 存储音频数据,支持多种格式。 - */ -class AudioAsset : public Asset { -public: - AssetType type() const override { return AssetType::Audio; } - - bool loaded() const override { - return state_.load(std::memory_order_acquire) == AssetState::Loaded && - !data_.empty(); - } - - size_t memSize() const override { return data_.size(); } - - /** - * @brief 获取音频格式 - * @return 音频格式 - */ - AudioFormat format() const { return format_; } - - /** - * @brief 获取声道数 - * @return 声道数 - */ - int channels() const { return channels_; } - - /** - * @brief 获取采样率 - * @return 采样率 - */ - int sampleRate() const { return sampleRate_; } - - /** - * @brief 获取每样本位数 - * @return 每样本位数 - */ - int bitsPerSample() const { return bitsPerSample_; } - - /** - * @brief 获取时长(秒) - * @return 时长 - */ - float duration() const { return duration_; } - - /** - * @brief 获取音频数据 - * @return 音频数据指针 - */ - const u8 *data() const { return data_.data(); } - - /** - * @brief 获取音频数据大小 - * @return 数据大小(字节) - */ - size_t dataSize() const { return data_.size(); } - - /** - * @brief 是否为流式音频 - * @return 流式音频返回 true - */ - bool streaming() const { return streaming_; } - - /** - * @brief 设置音频数据 - * @param format 音频格式 - * @param channels 声道数 - * @param sampleRate 采样率 - * @param bitsPerSample 每样本位数 - * @param data 音频数据 - */ - void setData(AudioFormat format, int channels, int sampleRate, - int bitsPerSample, std::vector data) { - format_ = format; - channels_ = channels; - sampleRate_ = sampleRate; - bitsPerSample_ = bitsPerSample; - data_ = std::move(data); - - if (sampleRate > 0 && channels > 0 && bitsPerSample > 0) { - size_t bytesPerSecond = - static_cast(sampleRate) * channels * (bitsPerSample / 8); - if (bytesPerSecond > 0) { - duration_ = static_cast(data_.size()) / - static_cast(bytesPerSecond); - } - } - - streaming_ = duration_ > 5.0f; - setState(AssetState::Loaded); - } - - /** - * @brief 释放音频数据 - */ - void release() { - data_.clear(); - format_ = AudioFormat::PCM; - channels_ = 0; - sampleRate_ = 0; - bitsPerSample_ = 0; - duration_ = 0.0f; - streaming_ = false; - setState(AssetState::Unloaded); - } - -private: - AudioFormat format_ = AudioFormat::PCM; - int channels_ = 0; - int sampleRate_ = 0; - int bitsPerSample_ = 0; - float duration_ = 0.0f; - std::vector data_; - bool streaming_ = false; -}; - -// --------------------------------------------------------------------------- -// DataAsset - 通用数据资源 -// --------------------------------------------------------------------------- - -/** - * @brief 通用数据资源类 - * - * 存储任意二进制数据。 - */ -class DataAsset : public Asset { -public: - AssetType type() const override { return AssetType::Data; } - - bool loaded() const override { - return state_.load(std::memory_order_acquire) == AssetState::Loaded; - } - - size_t memSize() const override { return data_.size(); } - - /** - * @brief 获取数据 - * @return 数据指针 - */ - const u8 *data() const { return data_.data(); } - - /** - * @brief 获取数据大小 - * @return 数据大小(字节) - */ - size_t size() const { return data_.size(); } - - /** - * @brief 设置数据 - * @param data 数据 - */ - void setData(std::vector data) { - data_ = std::move(data); - setState(AssetState::Loaded); - } - - /** - * @brief 释放数据 - */ - void release() { - data_.clear(); - setState(AssetState::Unloaded); - } - -private: - std::vector data_; -}; - -} // namespace extra2d diff --git a/Extra2D/include/extra2d/asset/asset_cache.h b/Extra2D/include/extra2d/asset/asset_cache.h deleted file mode 100644 index 4fb5d14..0000000 --- a/Extra2D/include/extra2d/asset/asset_cache.h +++ /dev/null @@ -1,263 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace extra2d { - -// --------------------------------------------------------------------------- -// CacheEntry - 缓存条目 -// --------------------------------------------------------------------------- - -/** - * @brief 缓存条目结构 - * - * 存储资源引用和访问信息,用于LRU缓存管理。 - */ -struct CacheEntry { - Ref asset; - std::chrono::steady_clock::time_point lastAccess; - size_t accessCount = 0; - - /** - * @brief 构造缓存条目 - * @param a 资源引用 - */ - explicit CacheEntry(Ref a) - : asset(std::move(a)), - lastAccess(std::chrono::steady_clock::now()), - accessCount(1) {} - - /** - * @brief 更新访问信息 - */ - void touch() { - lastAccess = std::chrono::steady_clock::now(); - ++accessCount; - } -}; - -// --------------------------------------------------------------------------- -// AssetCache - 资源缓存 -// --------------------------------------------------------------------------- - -/** - * @brief 资源缓存类 - * - * 实现享元模式,提供资源共享和缓存管理功能。 - * - * 特性: - * - LRU缓存淘汰策略 - * - 线程安全(读写锁) - * - 引用计数自动回收 - * - 缓存统计和监控 - * - 内存上限管理 - */ -class AssetCache { -public: - /** - * @brief 构造函数 - * @param limit 缓存内存上限(字节),0表示无限制 - */ - explicit AssetCache(size_t limit = 0); - - ~AssetCache() = default; - - AssetCache(const AssetCache&) = delete; - AssetCache& operator=(const AssetCache&) = delete; - - // ------------------------------------------------------------------------- - // 资源管理 - // ------------------------------------------------------------------------- - - /** - * @brief 添加资源到缓存 - * @tparam T 资源类型 - * @param asset 资源引用 - * @return 资源句柄 - */ - template - AssetHandle add(Ref asset) { - static_assert(std::is_base_of_v, - "T must derive from Asset"); - - if (!asset) { - return AssetHandle(); - } - - std::unique_lock lock(mutex_); - - AssetID id = asset->id(); - - size_t memSize = asset->memSize(); - bytes_ += memSize; - - auto it = lruList_.insert(lruList_.end(), id); - - entries_[id] = CacheEntryData{ - ptr::makeUnique(std::static_pointer_cast(asset)), - it - }; - - ++stats_.count; - - if (limit_ > 0 && bytes_ > limit_) { - evict(); - } - - return AssetHandle(id, Weak(asset)); - } - - /** - * @brief 从缓存获取资源 - * @tparam T 资源类型 - * @param id 资源ID - * @return 资源句柄 - */ - template - AssetHandle get(const AssetID& id) { - static_assert(std::is_base_of_v, - "T must derive from Asset"); - - std::unique_lock lock(mutex_); - - auto it = entries_.find(id); - if (it == entries_.end()) { - ++stats_.misses; - return AssetHandle(); - } - - it->second.entry->touch(); - - lruList_.erase(it->second.lruIterator); - it->second.lruIterator = lruList_.insert(lruList_.end(), id); - - ++stats_.hits; - - auto typedAsset = std::static_pointer_cast(it->second.entry->asset); - return AssetHandle(id, Weak(typedAsset)); - } - - /** - * @brief 检查缓存是否包含资源 - * @param id 资源ID - * @return 包含返回 true - */ - bool has(const AssetID& id) const; - - /** - * @brief 从缓存移除资源 - * @param id 资源ID - * @return 移除成功返回 true - */ - bool remove(const AssetID& id); - - // ------------------------------------------------------------------------- - // 缓存管理 - // ------------------------------------------------------------------------- - - /** - * @brief 设置缓存内存上限 - * @param limit 上限(字节),0表示无限制 - */ - void setLimit(size_t limit); - - /** - * @brief 获取缓存内存上限 - * @return 上限(字节) - */ - size_t limit() const { return limit_; } - - /** - * @brief 获取当前缓存内存使用量 - * @return 使用量(字节) - */ - size_t bytes() const { return bytes_; } - - /** - * @brief 获取缓存条目数量 - * @return 条目数量 - */ - size_t count() const; - - /** - * @brief 获取当前缓存内存使用量(别名) - * @return 使用量(字节) - */ - size_t size() const { return bytes_; } - - /** - * @brief 记录缓存命中 - */ - void hit() { ++stats_.hits; } - - /** - * @brief 记录缓存未命中 - */ - void miss() { ++stats_.misses; } - - /** - * @brief 清理无外部引用的资源 - * @return 清理的资源数量 - */ - size_t purge(); - - /** - * @brief 清空所有缓存 - */ - void clear(); - - /** - * @brief 获取缓存统计信息 - * @return 统计信息 - */ - CacheStats stats() const; - - /** - * @brief 重置统计信息 - */ - void resetStats(); - -private: - /** - * @brief 缓存条目数据(包含LRU迭代器) - */ - struct CacheEntryData { - Ref entry; - std::list::iterator lruIterator; - }; - - /** - * @brief 淘汰资源(LRU策略) - */ - void evict(); - - /** - * @brief 检查资源是否可以被淘汰 - * @param entry 缓存条目 - * @return 可淘汰返回 true - */ - bool canEvict(const CacheEntry& entry) const; - - mutable std::shared_mutex mutex_; - std::unordered_map entries_; - std::list lruList_; - - size_t limit_ = 0; - size_t bytes_ = 0; - mutable CacheStats stats_; -}; - -} diff --git a/Extra2D/include/extra2d/asset/asset_handle.h b/Extra2D/include/extra2d/asset/asset_handle.h deleted file mode 100644 index 7bdce63..0000000 --- a/Extra2D/include/extra2d/asset/asset_handle.h +++ /dev/null @@ -1,304 +0,0 @@ -#pragma once - -#include -#include -#include - -namespace extra2d { - -// --------------------------------------------------------------------------- -// AssetHandleBase - 资源句柄非模板基类 -// --------------------------------------------------------------------------- - -/** - * @brief 资源句柄非模板基类 - * - * 用于类型擦除,允许在容器中存储不同类型的句柄。 - */ -class AssetHandleBase { -public: - AssetHandleBase() = default; - - /** - * @brief 从资源ID和弱引用构造 - * @param id 资源ID - * @param ref 资源弱引用 - */ - AssetHandleBase(const AssetID& id, Weak ref) - : id_(id), cacheRef_(std::move(ref)) {} - - /** - * @brief 从强引用构造 - * @param ptr 资源强引用 - */ - explicit AssetHandleBase(Ref ptr) - : id_(ptr ? ptr->id() : AssetID()), - cacheRef_(ptr) {} - - /** - * @brief 检查句柄是否有效 - * @return 有效返回 true - */ - bool valid() const { - return !cacheRef_.expired(); - } - - /** - * @brief 获取资源强引用 - * @return 资源强引用 - */ - Ref get() const { - return cacheRef_.lock(); - } - - /** - * @brief 获取资源ID - * @return 资源ID - */ - const AssetID& id() const { return id_; } - - /** - * @brief 获取资源路径 - * @return 资源路径 - */ - const std::string& path() const { return id_.path; } - - /** - * @brief 隐式转换为bool - */ - explicit operator bool() const { return valid(); } - - /** - * @brief 重置句柄 - */ - void reset() { - id_ = AssetID(); - cacheRef_.reset(); - } - -protected: - AssetID id_; - Weak cacheRef_; -}; - -// --------------------------------------------------------------------------- -// AssetHandle - 资源句柄 -// --------------------------------------------------------------------------- - -/** - * @brief 资源句柄模板类 - * - * 使用强类型句柄替代裸指针,提供类型安全和自动生命周期管理。 - * 内部使用 weak_ptr 弱引用,不阻止资源回收。 - * - * 特性: - * - 类型安全:编译期检查资源类型 - * - 自动生命周期:资源无引用时自动回收 - * - 弱引用:不阻止缓存清理 - * - 空安全:使用前检查 valid() - * - * @tparam T 资源类型,必须继承自 Asset - */ -template -class AssetHandle : public AssetHandleBase { - static_assert(std::is_base_of_v, - "T must derive from Asset"); - -public: - /** - * @brief 默认构造函数 - */ - AssetHandle() = default; - - /** - * @brief 从基类句柄构造 - * @param base 基类句柄 - */ - explicit AssetHandle(const AssetHandleBase& base) - : AssetHandleBase(base) {} - - /** - * @brief 从资源ID和弱引用构造 - * @param id 资源ID - * @param ref 资源弱引用 - */ - AssetHandle(const AssetID& id, Weak ref) - : AssetHandleBase(id, std::move(ref)) {} - - /** - * @brief 从强引用构造 - * @param ptr 资源强引用 - */ - explicit AssetHandle(Ref ptr) - : AssetHandleBase(ptr) {} - - /** - * @brief 获取资源强引用 - * @return 资源强引用,如果资源已被回收返回 nullptr - */ - Ref get() const { - return std::static_pointer_cast(cacheRef_.lock()); - } - - /** - * @brief 解引用操作符 - * @return 资源指针 - * @note 使用前应检查 valid() - */ - T* operator->() const { - auto ptr = get(); - return ptr ? ptr.get() : nullptr; - } - - /** - * @brief 解引用操作符 - * @return 资源引用 - * @note 使用前应检查 valid() - */ - T& operator*() const { - auto ptr = get(); - return *ptr; - } - - /** - * @brief 相等比较 - * @param other 其他句柄 - * @return 相等返回 true - */ - bool operator==(const AssetHandle& other) const { - return id_ == other.id_; - } - - /** - * @brief 不等比较 - * @param other 其他句柄 - * @return 不等返回 true - */ - bool operator!=(const AssetHandle& other) const { - return id_ != other.id_; - } - - /** - * @brief 小于比较(用于有序容器) - * @param other 其他句柄 - * @return 小于返回 true - */ - bool operator<(const AssetHandle& other) const { - return id_ < other.id_; - } - - /** - * @brief 重置句柄 - */ - void reset() { - id_ = AssetID(); - cacheRef_.reset(); - } - - /** - * @brief 检查资源是否已加载 - * @return 已加载返回 true - */ - bool loaded() const { - auto ptr = get(); - return ptr && ptr->loaded(); - } - - /** - * @brief 获取资源状态 - * @return 资源状态 - */ - AssetState state() const { - auto ptr = get(); - return ptr ? ptr->state() : AssetState::Unloaded; - } -}; - -// --------------------------------------------------------------------------- -// AssetLoadResult - 资源加载结果 -// --------------------------------------------------------------------------- - -/** - * @brief 资源加载结果 - * - * 封装资源加载的结果状态,支持成功、失败、加载中等状态。 - * - * @tparam T 资源类型 - */ -template -struct AssetLoadResult { - AssetHandle handle; - AssetState state = AssetState::Unloaded; - std::string error; - - /** - * @brief 检查是否成功 - * @return 成功返回 true - */ - bool success() const { - return state == AssetState::Loaded && handle.valid(); - } - - /** - * @brief 检查是否失败 - * @return 失败返回 true - */ - bool failed() const { - return state == AssetState::Failed; - } - - /** - * @brief 检查是否正在加载 - * @return 正在加载返回 true - */ - bool loading() const { - return state == AssetState::Loading; - } - - /** - * @brief 创建成功结果 - * @param handle 资源句柄 - * @return 加载结果 - */ - static AssetLoadResult ok(AssetHandle handle) { - return { std::move(handle), AssetState::Loaded, "" }; - } - - /** - * @brief 创建失败结果 - * @param error 错误信息 - * @return 加载结果 - */ - static AssetLoadResult err(const std::string& error) { - return { AssetHandle(), AssetState::Failed, error }; - } - - /** - * @brief 创建加载中结果 - * @param handle 资源句柄(可能为空) - * @return 加载结果 - */ - static AssetLoadResult pending(AssetHandle handle = {}) { - return { std::move(handle), AssetState::Loading, "" }; - } -}; - -// --------------------------------------------------------------------------- -// AssetLoadCallback - 资源加载回调 -// --------------------------------------------------------------------------- - -/** - * @brief 资源加载回调类型 - * @tparam T 资源类型 - */ -template -using AssetLoadCallback = Fn)>; - -/** - * @brief 资源加载结果回调类型 - * @tparam T 资源类型 - */ -template -using AssetLoadResultCallback = Fn)>; - -} diff --git a/Extra2D/include/extra2d/asset/asset_loader.h b/Extra2D/include/extra2d/asset/asset_loader.h deleted file mode 100644 index 158a5ee..0000000 --- a/Extra2D/include/extra2d/asset/asset_loader.h +++ /dev/null @@ -1,312 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include - -namespace extra2d { - -// --------------------------------------------------------------------------- -// AssetLoaderBase - 资源加载器非模板基类 -// --------------------------------------------------------------------------- - -/** - * @brief 资源加载器非模板基类 - * - * 用于类型擦除,允许在容器中存储不同类型的加载器。 - */ -class AssetLoaderBase { -public: - virtual ~AssetLoaderBase() = default; - - /** - * @brief 从文件加载资源(返回 Asset 基类指针) - * @param path 文件路径 - * @return 资源实例 - */ - virtual Ref loadBase(const std::string& path) = 0; - - /** - * @brief 从内存数据加载资源 - * @param data 数据指针 - * @param size 数据大小 - * @return 资源实例 - */ - virtual Ref loadFromMemoryBase(const u8* data, size_t size) = 0; - - /** - * @brief 检查是否能加载指定路径的资源 - * @param path 文件路径 - * @return 能加载返回 true - */ - virtual bool canLoad(const std::string& path) const = 0; - - /** - * @brief 获取资源类型 - * @return 资源类型枚举值 - */ - virtual AssetType type() const = 0; - - /** - * @brief 获取支持的文件扩展名列表 - * @return 扩展名列表 - */ - virtual std::vector extensions() const = 0; -}; - -// --------------------------------------------------------------------------- -// AssetLoader - 资源加载器接口 -// --------------------------------------------------------------------------- - -/** - * @brief 资源加载器接口模板 - * - * 使用策略模式支持不同资源类型的加载。 - * 每种资源类型可以实现自己的加载器。 - * - * @tparam T 资源类型 - */ -template -class AssetLoader : public AssetLoaderBase { - static_assert(std::is_base_of_v, - "T must derive from Asset"); - -public: - virtual ~AssetLoader() = default; - - /** - * @brief 从文件加载资源 - * @param path 文件路径 - * @return 资源实例,失败返回 nullptr - */ - virtual Ref load(const std::string& path) = 0; - - /** - * @brief 从内存数据加载资源 - * @param data 数据指针 - * @param size 数据大小 - * @return 资源实例,失败返回 nullptr - */ - virtual Ref loadFromMemory(const u8* data, size_t size) = 0; - - Ref loadBase(const std::string& path) override { - return load(path); - } - - Ref loadFromMemoryBase(const u8* data, size_t size) override { - return loadFromMemory(data, size); - } -}; - -// --------------------------------------------------------------------------- -// TextureLoader - 纹理加载器 -// --------------------------------------------------------------------------- - -/** - * @brief 纹理加载器 - * - * 使用 stb_image 加载各种格式的图片文件。 - * 支持 PNG, JPG, BMP, TGA, GIF, PSD, HDR, PIC 等格式。 - */ -class TextureLoader : public AssetLoader { -public: - TextureLoader(); - ~TextureLoader() override; - - Ref load(const std::string& path) override; - Ref loadFromMemory(const u8* data, size_t size) override; - bool canLoad(const std::string& path) const override; - AssetType type() const override { return AssetType::Texture; } - std::vector extensions() const override; - - /** - * @brief 设置期望的通道数 - * @param channels 通道数(1-4),0表示自动 - */ - void setDesiredChannels(int channels); - - /** - * @brief 获取期望的通道数 - * @return 通道数 - */ - int desiredChannels() const { return desiredChannels_; } - -private: - int desiredChannels_ = 4; -}; - -// --------------------------------------------------------------------------- -// FontLoader - 字体加载器 -// --------------------------------------------------------------------------- - -/** - * @brief 字体加载器 - * - * 加载 TrueType 字体文件(.ttf, .otf)。 - */ -class FontLoader : public AssetLoader { -public: - Ref load(const std::string& path) override; - Ref loadFromMemory(const u8* data, size_t size) override; - bool canLoad(const std::string& path) const override; - AssetType type() const override { return AssetType::Font; } - std::vector extensions() const override; -}; - -// --------------------------------------------------------------------------- -// ShaderLoader - 着色器加载器 -// --------------------------------------------------------------------------- - -/** - * @brief 着色器加载器 - * - * 加载着色器源文件,支持以下格式: - * - .vert/.frag: 分离的顶点/片段着色器 - * - .glsl: 合并的着色器文件(使用标记分隔) - */ -class ShaderLoader : public AssetLoader { -public: - Ref load(const std::string& path) override; - Ref loadFromMemory(const u8* data, size_t size) override; - bool canLoad(const std::string& path) const override; - AssetType type() const override { return AssetType::Shader; } - std::vector extensions() const override; - - /** - * @brief 设置顶点着色器标记 - * @param marker 标记字符串(默认 "[VERTEX]") - */ - void setVertexMarker(const std::string& marker) { vertexMarker_ = marker; } - - /** - * @brief 设置片段着色器标记 - * @param marker 标记字符串(默认 "[FRAGMENT]") - */ - void setFragmentMarker(const std::string& marker) { fragmentMarker_ = marker; } - -private: - std::string vertexMarker_ = "[VERTEX]"; - std::string fragmentMarker_ = "[FRAGMENT]"; - - /** - * @brief 解析合并的着色器文件 - * @param content 文件内容 - * @param vertex 输出顶点着色器源码 - * @param fragment 输出片段着色器源码 - * @return 成功返回 true - */ - bool parseCombined(const std::string& content, - std::string& vertex, - std::string& fragment); -}; - -// --------------------------------------------------------------------------- -// AudioLoader - 音频加载器 -// --------------------------------------------------------------------------- - -/** - * @brief 音频加载器 - * - * 加载音频文件,支持 WAV 格式。 - * 可扩展支持 MP3, OGG 等格式。 - */ -class AudioLoader : public AssetLoader { -public: - Ref load(const std::string& path) override; - Ref loadFromMemory(const u8* data, size_t size) override; - bool canLoad(const std::string& path) const override; - AssetType type() const override { return AssetType::Audio; } - std::vector extensions() const override; - -private: - /** - * @brief 加载 WAV 格式音频 - * @param data 数据指针 - * @param size 数据大小 - * @return 音频资源 - */ - Ref loadWav(const u8* data, size_t size); -}; - -// --------------------------------------------------------------------------- -// DataLoader - 通用数据加载器 -// --------------------------------------------------------------------------- - -/** - * @brief 通用数据加载器 - * - * 加载任意二进制数据文件。 - */ -class DataLoader : public AssetLoader { -public: - Ref load(const std::string& path) override; - Ref loadFromMemory(const u8* data, size_t size) override; - bool canLoad(const std::string& path) const override; - AssetType type() const override { return AssetType::Data; } - std::vector extensions() const override; -}; - -// --------------------------------------------------------------------------- -// AssetLoaderFactory - 加载器工厂 -// --------------------------------------------------------------------------- - -/** - * @brief 加载器工厂 - * - * 根据资源类型或文件扩展名创建对应的加载器。 - * 使用模板方法返回具体类型的加载器。 - */ -class AssetLoaderFactory { -public: - /** - * @brief 根据资源类型创建纹理加载器 - * @return 纹理加载器实例 - */ - static Unique createTextureLoader() { - return ptr::makeUnique(); - } - - /** - * @brief 根据资源类型创建字体加载器 - * @return 字体加载器实例 - */ - static Unique createFontLoader() { - return ptr::makeUnique(); - } - - /** - * @brief 根据资源类型创建着色器加载器 - * @return 着色器加载器实例 - */ - static Unique createShaderLoader() { - return ptr::makeUnique(); - } - - /** - * @brief 根据资源类型创建音频加载器 - * @return 音频加载器实例 - */ - static Unique createAudioLoader() { - return ptr::makeUnique(); - } - - /** - * @brief 根据资源类型创建数据加载器 - * @return 数据加载器实例 - */ - static Unique createDataLoader() { - return ptr::makeUnique(); - } - - /** - * @brief 根据文件扩展名获取资源类型 - * @param extension 文件扩展名(包含点,如 ".png") - * @return 资源类型,无法识别返回 AssetType::Unknown - */ - static AssetType getTypeByExtension(const std::string& extension); -}; - -} diff --git a/Extra2D/include/extra2d/asset/asset_pack.h b/Extra2D/include/extra2d/asset/asset_pack.h deleted file mode 100644 index a2801bb..0000000 --- a/Extra2D/include/extra2d/asset/asset_pack.h +++ /dev/null @@ -1,399 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace extra2d { - -// --------------------------------------------------------------------------- -// AssetPack - 资源包 -// --------------------------------------------------------------------------- - -/** - * @brief 资源包类 - * - * 支持从打包文件中加载资源。 - * 资源包格式: - * - 头部:AssetPackageHeader - * - 索引表:条目数量 + AssetPackageEntry 数组 - * - 数据区:按索引顺序存储的资源数据 - */ -class AssetPack { -public: - AssetPack() = default; - - /** - * @brief 移动构造函数 - * @param other 其他资源包 - */ - AssetPack(AssetPack&& other) noexcept; - - /** - * @brief 移动赋值操作符 - * @param other 其他资源包 - * @return 当前资源包引用 - */ - AssetPack& operator=(AssetPack&& other) noexcept; - - /** - * @brief 析构函数,自动关闭文件 - */ - ~AssetPack(); - - /** - * @brief 打开资源包 - * @param path 资源包文件路径 - * @return 成功返回 true - */ - bool open(const std::string& path); - - /** - * @brief 关闭资源包 - */ - void close(); - - /** - * @brief 检查资源包是否已打开 - * @return 已打开返回 true - */ - bool isOpen() const { return file_.is_open(); } - - /** - * @brief 检查资源是否存在 - * @param id 资源ID - * @return 存在返回 true - */ - bool has(const AssetID& id) const; - - /** - * @brief 检查资源是否存在 - * @param path 资源路径 - * @return 存在返回 true - */ - bool has(const std::string& path) const; - - /** - * @brief 读取资源数据(自动解压/解密) - * @param id 资源ID - * @return 资源数据,失败返回空 - */ - std::vector read(const AssetID& id); - - /** - * @brief 读取资源数据(自动解压/解密) - * @param path 资源路径 - * @return 资源数据,失败返回空 - */ - std::vector read(const std::string& path); - - /** - * @brief 读取原始资源数据(不解压/不解密) - * @param id 资源ID - * @return 原始资源数据 - */ - std::vector readRaw(const AssetID& id); - - /** - * @brief 获取所有资源ID - * @return 资源ID列表 - */ - std::vector assets() const; - - /** - * @brief 获取资源包路径 - * @return 资源包路径 - */ - const std::string& path() const { return path_; } - - /** - * @brief 获取资源包头部 - * @return 头部信息 - */ - const AssetPackageHeader& header() const { return header_; } - - /** - * @brief 获取资源条目数量 - * @return 条目数量 - */ - size_t count() const { return entries_.size(); } - - /** - * @brief 设置数据处理管道 - * @param pipe 处理管道 - */ - void setPipe(DataPipe pipe) { pipe_ = std::move(pipe); } - - /** - * @brief 获取资源条目信息 - * @param id 资源ID - * @return 条目指针,不存在返回 nullptr - */ - const AssetPackageEntry* getEntry(const AssetID& id) const; - -private: - std::string path_; - mutable std::ifstream file_; - AssetPackageHeader header_; - std::unordered_map entries_; - DataPipe pipe_; - - /** - * @brief 读取并解析头部 - * @return 成功返回 true - */ - bool readHeader(); - - /** - * @brief 读取并解析索引表 - * @return 成功返回 true - */ - bool readIndex(); - - /** - * @brief 读取条目数据 - * @param entry 条目信息 - * @return 原始数据 - */ - std::vector readEntryData(const AssetPackageEntry& entry); - - AssetPack(const AssetPack&) = delete; - AssetPack& operator=(const AssetPack&) = delete; -}; - -// --------------------------------------------------------------------------- -// PackManager - 资源包管理器 -// --------------------------------------------------------------------------- - -/** - * @brief 资源包管理器 - * - * 管理多个资源包,支持资源查找和加载。 - * 支持挂载/卸载资源包。 - */ -class PackManager { -public: - PackManager() = default; - - /** - * @brief 析构函数,自动卸载所有资源包 - */ - ~PackManager() = default; - - /** - * @brief 挂载资源包 - * @param path 资源包路径 - * @return 成功返回 true - */ - bool mount(const std::string& path); - - /** - * @brief 卸载资源包 - * @param path 资源包路径 - */ - void unmount(const std::string& path); - - /** - * @brief 卸载所有资源包 - */ - void unmountAll(); - - /** - * @brief 查找资源所在的包 - * @param id 资源ID - * @return 资源包指针,未找到返回 nullptr - */ - AssetPack* find(const AssetID& id); - - /** - * @brief 查找资源所在的包 - * @param path 资源路径 - * @return 资源包指针,未找到返回 nullptr - */ - AssetPack* find(const std::string& path); - - /** - * @brief 检查资源是否存在 - * @param id 资源ID - * @return 存在返回 true - */ - bool has(const AssetID& id) const; - - /** - * @brief 检查资源是否存在 - * @param path 资源路径 - * @return 存在返回 true - */ - bool has(const std::string& path) const; - - /** - * @brief 读取资源数据 - * @param id 资源ID - * @return 资源数据 - */ - std::vector read(const AssetID& id); - - /** - * @brief 读取资源数据 - * @param path 资源路径 - * @return 资源数据 - */ - std::vector read(const std::string& path); - - /** - * @brief 设置默认处理管道 - * @param pipe 处理管道 - */ - void setPipe(DataPipe pipe) { defaultPipe_ = std::move(pipe); } - - /** - * @brief 获取默认处理管道 - * @return 处理管道引用 - */ - const DataPipe& pipe() const { return defaultPipe_; } - - /** - * @brief 获取已挂载的资源包数量 - * @return 资源包数量 - */ - size_t count() const { return packs_.size(); } - - /** - * @brief 获取所有资源ID - * @return 资源ID列表 - */ - std::vector allAssets() const; - - /** - * @brief 获取已挂载的资源包路径列表 - * @return 路径列表 - */ - std::vector mountedPacks() const; - -private: - std::vector> packs_; - DataPipe defaultPipe_; - - PackManager(const PackManager&) = delete; - PackManager& operator=(const PackManager&) = delete; -}; - -// --------------------------------------------------------------------------- -// AssetPackBuilder - 资源包构建器(用于打包工具) -// --------------------------------------------------------------------------- - -/** - * @brief 资源包构建器 - * - * 用于创建资源包文件。 - * 支持添加资源、压缩、加密等操作。 - */ -class AssetPackBuilder { -public: - /** - * @brief 构造函数 - * @param compression 压缩算法 - * @param level 压缩级别 - */ - explicit AssetPackBuilder(Compression compression = Compression::None, int level = 3); - - /** - * @brief 添加资源 - * @param path 资源路径(包内路径) - * @param data 资源数据 - */ - void add(const std::string& path, const std::vector& data); - - /** - * @brief 添加资源(移动语义) - * @param path 资源路径 - * @param data 资源数据 - */ - void add(const std::string& path, std::vector&& data); - - /** - * @brief 添加文件 - * @param filePath 文件路径 - * @param packPath 包内路径(可选,默认使用文件名) - * @return 成功返回 true - */ - bool addFile(const std::string& filePath, const std::string& packPath = ""); - - /** - * @brief 添加目录 - * @param dirPath 目录路径 - * @param prefix 包内路径前缀(可选) - * @return 添加的文件数量 - */ - size_t addDirectory(const std::string& dirPath, const std::string& prefix = ""); - - /** - * @brief 设置加密密钥 - * @param key 加密密钥 - * @param type 加密类型 - */ - void setEncryption(const std::string& key, Decryptor::Type type = Decryptor::Type::XOR); - - /** - * @brief 构建资源包 - * @param outputPath 输出文件路径 - * @return 成功返回 true - */ - bool build(const std::string& outputPath); - - /** - * @brief 清空所有资源 - */ - void clear(); - - /** - * @brief 获取资源数量 - * @return 资源数量 - */ - size_t count() const { return entries_.size(); } - - /** - * @brief 获取总原始大小 - * @return 总原始大小 - */ - size_t totalOriginalSize() const { return totalOriginalSize_; } - - /** - * @brief 获取总压缩大小 - * @return 总压缩大小 - */ - size_t totalCompressedSize() const { return totalCompressedSize_; } - -private: - struct BuilderEntry { - AssetID id; - std::vector data; - std::vector compressedData; - u64 offset; - u32 compression; - u32 flags; - }; - - Compression compression_; - int level_; - std::string encryptKey_; - Decryptor::Type encryptType_ = Decryptor::Type::None; - std::vector entries_; - size_t totalOriginalSize_ = 0; - size_t totalCompressedSize_ = 0; - - /** - * @brief 处理数据(压缩/加密) - * @param data 原始数据 - * @return 处理后的数据 - */ - std::vector processData(const std::vector& data); -}; - -} diff --git a/Extra2D/include/extra2d/asset/asset_types.h b/Extra2D/include/extra2d/asset/asset_types.h deleted file mode 100644 index b621b08..0000000 --- a/Extra2D/include/extra2d/asset/asset_types.h +++ /dev/null @@ -1,270 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -namespace extra2d { - -// --------------------------------------------------------------------------- -// AssetID - 强类型资源标识符 -// --------------------------------------------------------------------------- - -/** - * @brief 强类型资源ID - * - * 使用类型安全的ID替代裸字符串,支持: - * - 哈希计算(用于快速比较和查找) - * - 原始路径存储(用于调试和日志) - * - 隐式转换和比较操作 - */ -struct AssetID { - u64 hash = 0; - std::string path; - - AssetID() = default; - - /** - * @brief 从路径构造资源ID - * @param p 资源路径 - */ - explicit AssetID(const std::string& p) - : hash(hashPath(p)), path(p) {} - - /** - * @brief 从路径构造资源ID(移动语义) - * @param p 资源路径 - */ - explicit AssetID(std::string&& p) - : hash(hashPath(p)), path(std::move(p)) {} - - /** - * @brief 计算路径哈希值 - * @param p 路径字符串 - * @return 64位哈希值 - */ - static u64 hashPath(const std::string& p) { - u64 result = 14695981039346656037ULL; - for (char c : p) { - result ^= static_cast(static_cast(c)); - result *= 1099511628211ULL; - } - return result; - } - - /** - * @brief 检查ID是否有效 - * @return 有效返回 true - */ - bool valid() const { return hash != 0 || !path.empty(); } - - /** - * @brief 布尔转换操作符 - */ - explicit operator bool() const { return valid(); } - - /** - * @brief 相等比较 - */ - bool operator==(const AssetID& other) const { return hash == other.hash; } - - /** - * @brief 不等比较 - */ - bool operator!=(const AssetID& other) const { return hash != other.hash; } - - /** - * @brief 小于比较(用于有序容器) - */ - bool operator<(const AssetID& other) const { return hash < other.hash; } -}; - -// --------------------------------------------------------------------------- -// AssetType - 资源类型枚举 -// --------------------------------------------------------------------------- - -/** - * @brief 资源类型枚举 - * 定义支持的资源类型,用于类型安全的资源加载和管理 - */ -enum class AssetType : u8 { - Unknown = 0, - Texture = 1, - Font = 2, - Shader = 3, - Audio = 4, - Data = 5, - Custom = 255 -}; - -/** - * @brief 获取资源类型名称 - * @param type 资源类型 - * @return 类型名称字符串 - */ -inline const char* assetTypeName(AssetType type) { - switch (type) { - case AssetType::Texture: return "Texture"; - case AssetType::Font: return "Font"; - case AssetType::Shader: return "Shader"; - case AssetType::Audio: return "Audio"; - case AssetType::Data: return "Data"; - case AssetType::Custom: return "Custom"; - default: return "Unknown"; - } -} - -// --------------------------------------------------------------------------- -// AssetState - 资源状态枚举 -// --------------------------------------------------------------------------- - -/** - * @brief 资源状态枚举 - * 定义资源的生命周期状态 - */ -enum class AssetState : u8 { - Unloaded = 0, - Loading = 1, - Loaded = 2, - Failed = 3, - Unloading = 4 -}; - -/** - * @brief 获取资源状态名称 - * @param state 资源状态 - * @return 状态名称字符串 - */ -inline const char* assetStateName(AssetState state) { - switch (state) { - case AssetState::Unloaded: return "Unloaded"; - case AssetState::Loading: return "Loading"; - case AssetState::Loaded: return "Loaded"; - case AssetState::Failed: return "Failed"; - case AssetState::Unloading: return "Unloading"; - default: return "Unknown"; - } -} - -// --------------------------------------------------------------------------- -// Compression - 压缩算法枚举 -// --------------------------------------------------------------------------- - -/** - * @brief 压缩算法枚举 - * 定义支持的压缩算法类型 - */ -enum class Compression : u8 { - None = 0, - Zstd = 1, - LZ4 = 2, - Zlib = 3 -}; - -/** - * @brief 获取压缩算法名称 - * @param comp 压缩算法 - * @return 算法名称字符串 - */ -inline const char* compressionName(Compression comp) { - switch (comp) { - case Compression::Zstd: return "Zstd"; - case Compression::LZ4: return "LZ4"; - case Compression::Zlib: return "Zlib"; - default: return "None"; - } -} - -// --------------------------------------------------------------------------- -// CacheStats - 缓存统计结构 -// --------------------------------------------------------------------------- - -/** - * @brief 缓存统计信息 - * 用于监控资源缓存的使用情况和性能 - */ -struct CacheStats { - size_t bytes = 0; - size_t limit = 0; - size_t count = 0; - size_t hits = 0; - size_t misses = 0; - - /** - * @brief 计算缓存命中率 - * @return 命中率(0.0 - 1.0) - */ - float hitRate() const { - size_t total = hits + misses; - return total > 0 ? static_cast(hits) / static_cast(total) : 0.0f; - } - - /** - * @brief 计算缓存使用率 - * @return 使用率(0.0 - 1.0) - */ - float usage() const { - return limit > 0 ? static_cast(bytes) / static_cast(limit) : 0.0f; - } -}; - -// --------------------------------------------------------------------------- -// AssetPackageHeader - 资源包头部信息 -// --------------------------------------------------------------------------- - -/** - * @brief 资源包头部信息 - * 用于识别压缩和加密类型 - */ -struct AssetPackageHeader { - u32 magic = 0; - u32 version = 0; - u32 compressionType = 0; - u32 encryptionType = 0; - u64 originalSize = 0; - u64 compressedSize = 0; - u8 checksum[32] = {}; - - static constexpr u32 MAGIC = 0x4B325045; // 'E2PK' - - /** - * @brief 检查头部是否有效 - * @return 有效返回 true - */ - bool valid() const { return magic == MAGIC; } -}; - -// --------------------------------------------------------------------------- -// AssetPackageEntry - 资源包索引项 -// --------------------------------------------------------------------------- - -/** - * @brief 资源包索引项 - * 描述资源在包中的位置和属性 - */ -struct AssetPackageEntry { - AssetID id; - u64 offset = 0; - u64 size = 0; - u64 originalSize = 0; - u32 compression = 0; - u32 flags = 0; -}; - -} - -// --------------------------------------------------------------------------- -// std::hash 特化 - 支持在 unordered_map/unordered_set 中使用 AssetID -// --------------------------------------------------------------------------- - -namespace std { - -template<> -struct hash { - size_t operator()(const extra2d::AssetID& id) const noexcept { - return static_cast(id.hash); - } -}; - -} diff --git a/Extra2D/include/extra2d/asset/data_processor.h b/Extra2D/include/extra2d/asset/data_processor.h deleted file mode 100644 index 5311572..0000000 --- a/Extra2D/include/extra2d/asset/data_processor.h +++ /dev/null @@ -1,397 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -namespace extra2d { - -// --------------------------------------------------------------------------- -// DataProcessor - 数据处理器接口(装饰器模式) -// --------------------------------------------------------------------------- - -/** - * @brief 数据处理器接口 - * - * 使用装饰器模式支持链式处理数据流。 - * 支持压缩、解压、加密、解密等操作的组合。 - */ -class DataProcessor { -public: - virtual ~DataProcessor() = default; - - /** - * @brief 处理输入数据 - * @param input 输入数据 - * @return 处理后的数据 - */ - virtual std::vector process(const std::vector& input) = 0; - - /** - * @brief 设置下一个处理器 - * @param next 下一个处理器 - */ - void setNext(Unique next) { next_ = std::move(next); } - - /** - * @brief 获取下一个处理器 - * @return 下一个处理器的指针 - */ - DataProcessor* next() const { return next_.get(); } - -protected: - /** - * @brief 调用下一个处理器处理数据 - * @param input 输入数据 - * @return 处理后的数据,如果没有下一个处理器则返回原数据 - */ - std::vector processNext(const std::vector& input) { - return next_ ? next_->process(input) : input; - } - - Unique next_; -}; - -// --------------------------------------------------------------------------- -// Decryptor - 解密器 -// --------------------------------------------------------------------------- - -/** - * @brief 解密处理器 - * - * 使用 XOR 或 AES 算法解密数据。 - * 支持简单的 XOR 加密和 AES-256 加密。 - */ -class Decryptor : public DataProcessor { -public: - /** - * @brief 加密类型枚举 - */ - enum class Type : u8 { - None = 0, - XOR = 1, - AES256 = 2 - }; - - /** - * @brief 构造解密器 - * @param key 解密密钥 - * @param type 加密类型 - */ - explicit Decryptor(const std::string& key, Type type = Type::XOR); - - /** - * @brief 处理(解密)输入数据 - * @param input 加密的输入数据 - * @return 解密后的数据 - */ - std::vector process(const std::vector& input) override; - - /** - * @brief 获取加密类型 - * @return 加密类型 - */ - Type type() const { return type_; } - -private: - std::string key_; - Type type_; - - /** - * @brief XOR 解密 - * @param input 输入数据 - * @return 解密后的数据 - */ - std::vector decryptXOR(const std::vector& input); - - /** - * @brief AES-256 解密 - * @param input 输入数据 - * @return 解密后的数据 - */ - std::vector decryptAES256(const std::vector& input); -}; - -// --------------------------------------------------------------------------- -// Decompressor - 解压器 -// --------------------------------------------------------------------------- - -/** - * @brief 解压处理器 - * - * 支持多种压缩算法的解压操作。 - * 目前支持 Zstd、LZ4 和 Zlib。 - */ -class Decompressor : public DataProcessor { -public: - /** - * @brief 构造解压器 - * @param algo 压缩算法 - */ - explicit Decompressor(Compression algo = Compression::Zstd); - - /** - * @brief 处理(解压)输入数据 - * @param input 压缩的输入数据 - * @return 解压后的数据 - */ - std::vector process(const std::vector& input) override; - - /** - * @brief 获取压缩算法 - * @return 压缩算法 - */ - Compression algorithm() const { return algo_; } - -private: - Compression algo_; - - /** - * @brief Zstd 解压 - * @param input 压缩数据 - * @return 解压后的数据 - */ - std::vector decompressZstd(const std::vector& input); - - /** - * @brief LZ4 解压 - * @param input 压缩数据 - * @return 解压后的数据 - */ - std::vector decompressLZ4(const std::vector& input); - - /** - * @brief Zlib 解压 - * @param input 压缩数据 - * @return 解压后的数据 - */ - std::vector decompressZlib(const std::vector& input); -}; - -// --------------------------------------------------------------------------- -// Encryptor - 加密器 -// --------------------------------------------------------------------------- - -/** - * @brief 加密处理器 - * - * 使用 XOR 或 AES 算法加密数据。 - */ -class Encryptor : public DataProcessor { -public: - /** - * @brief 构造加密器 - * @param key 加密密钥 - * @param type 加密类型 - */ - explicit Encryptor(const std::string& key, Decryptor::Type type = Decryptor::Type::XOR); - - /** - * @brief 处理(加密)输入数据 - * @param input 原始输入数据 - * @return 加密后的数据 - */ - std::vector process(const std::vector& input) override; - - /** - * @brief 获取加密类型 - * @return 加密类型 - */ - Decryptor::Type type() const { return type_; } - -private: - std::string key_; - Decryptor::Type type_; - - /** - * @brief XOR 加密 - * @param input 输入数据 - * @return 加密后的数据 - */ - std::vector encryptXOR(const std::vector& input); - - /** - * @brief AES-256 加密 - * @param input 输入数据 - * @return 加密后的数据 - */ - std::vector encryptAES256(const std::vector& input); -}; - -// --------------------------------------------------------------------------- -// Compressor - 压缩器 -// --------------------------------------------------------------------------- - -/** - * @brief 压缩处理器 - * - * 支持多种压缩算法的压缩操作。 - */ -class Compressor : public DataProcessor { -public: - /** - * @brief 构造压缩器 - * @param algo 压缩算法 - * @param level 压缩级别(1-22,仅对某些算法有效) - */ - explicit Compressor(Compression algo = Compression::Zstd, int level = 3); - - /** - * @brief 处理(压缩)输入数据 - * @param input 原始输入数据 - * @return 压缩后的数据 - */ - std::vector process(const std::vector& input) override; - - /** - * @brief 获取压缩算法 - * @return 压缩算法 - */ - Compression algorithm() const { return algo_; } - -private: - Compression algo_; - int level_; - - /** - * @brief Zstd 压缩 - * @param input 原始数据 - * @return 压缩后的数据 - */ - std::vector compressZstd(const std::vector& input); - - /** - * @brief LZ4 压缩 - * @param input 原始数据 - * @return 压缩后的数据 - */ - std::vector compressLZ4(const std::vector& input); - - /** - * @brief Zlib 压缩 - * @param input 原始数据 - * @return 压缩后的数据 - */ - std::vector compressZlib(const std::vector& input); -}; - -// --------------------------------------------------------------------------- -// DataPipe - 数据处理管道 -// --------------------------------------------------------------------------- - -/** - * @brief 数据处理管道 - * - * 使用流式 API 构建数据处理链。 - * 支持链式调用添加多个处理器。 - * - * @example - * DataPipe pipe; - * pipe.decrypt("secret-key").decompress(Compression::Zstd); - * auto result = pipe.process(data); - */ -class DataPipe { -public: - DataPipe() = default; - - /** - * @brief 移动构造函数 - * @param other 其他管道 - */ - DataPipe(DataPipe&& other) noexcept = default; - - /** - * @brief 移动赋值操作符 - * @param other 其他管道 - * @return 当前管道引用 - */ - DataPipe& operator=(DataPipe&& other) noexcept = default; - - /** - * @brief 添加解密处理器 - * @param key 解密密钥 - * @param type 加密类型 - * @return 当前管道引用(支持链式调用) - */ - DataPipe& decrypt(const std::string& key, Decryptor::Type type = Decryptor::Type::XOR); - - /** - * @brief 添加解压处理器 - * @param algo 压缩算法 - * @return 当前管道引用(支持链式调用) - */ - DataPipe& decompress(Compression algo); - - /** - * @brief 添加加密处理器 - * @param key 加密密钥 - * @param type 加密类型 - * @return 当前管道引用(支持链式调用) - */ - DataPipe& encrypt(const std::string& key, Decryptor::Type type = Decryptor::Type::XOR); - - /** - * @brief 添加压缩处理器 - * @param algo 压缩算法 - * @param level 压缩级别 - * @return 当前管道引用(支持链式调用) - */ - DataPipe& compress(Compression algo, int level = 3); - - /** - * @brief 添加自定义处理器 - * @param processor 处理器 - * @return 当前管道引用(支持链式调用) - */ - DataPipe& add(Unique processor); - - /** - * @brief 处理数据 - * @param input 输入数据 - * @return 处理后的数据 - */ - std::vector process(const std::vector& input); - - /** - * @brief 清空所有处理器 - */ - void clear(); - - /** - * @brief 检查管道是否为空 - * @return 为空返回 true - */ - bool empty() const { return processors_.empty(); } - - /** - * @brief 获取处理器数量 - * @return 处理器数量 - */ - size_t size() const { return processors_.size(); } - -private: - std::vector> processors_; -}; - -// --------------------------------------------------------------------------- -// 工具函数 -// --------------------------------------------------------------------------- - -/** - * @brief 计算数据的 SHA-256 校验和 - * @param data 输入数据 - * @return 32字节的校验和 - */ -std::vector computeChecksum(const std::vector& data); - -/** - * @brief 验证数据的 SHA-256 校验和 - * @param data 输入数据 - * @param checksum 预期的校验和 - * @return 匹配返回 true - */ -bool verifyChecksum(const std::vector& data, const std::vector& checksum); - -} diff --git a/Extra2D/include/extra2d/base/AutoreleasePool.h b/Extra2D/include/extra2d/base/AutoreleasePool.h new file mode 100644 index 0000000..9377a7f --- /dev/null +++ b/Extra2D/include/extra2d/base/AutoreleasePool.h @@ -0,0 +1,53 @@ +#pragma once + +#include +#include + +namespace extra2d { + +class RefCounted; + +/** + * @brief 自动释放池 + * + * 管理标记为 autorelease 的对象,在帧结束时自动释放 + */ +class AutoreleasePool { +public: + /** + * @brief 获取单例实例 + * @return 自动释放池实例 + */ + static AutoreleasePool* getInstance(); + + /** + * @brief 添加对象到自动释放池 + * @param object 要添加的对象 + */ + void addObject(RefCounted* object); + + /** + * @brief 清空自动释放池 + * + * 释放池中所有对象的引用 + */ + void clear(); + + /** + * @brief 获取池中对象数量 + * @return 对象数量 + */ + size_t getObjectCount() const; + +private: + AutoreleasePool(); + ~AutoreleasePool(); + + AutoreleasePool(const AutoreleasePool&) = delete; + AutoreleasePool& operator=(const AutoreleasePool&) = delete; + + static AutoreleasePool* instance_; + std::vector objects_; +}; + +} // namespace extra2d diff --git a/Extra2D/include/extra2d/base/RefCounted.h b/Extra2D/include/extra2d/base/RefCounted.h new file mode 100644 index 0000000..75bfe2a --- /dev/null +++ b/Extra2D/include/extra2d/base/RefCounted.h @@ -0,0 +1,160 @@ +#pragma once + +#include +#include + +namespace extra2d { + +/** + * @brief 引用计数基类 + * + * 提供自动内存管理功能,通过引用计数跟踪对象生命周期。 + * 当引用计数降为 0 时,对象自动销毁。 + */ +class RefCounted { +public: + RefCounted(); + virtual ~RefCounted(); + + /** + * @brief 增加引用计数 + */ + void addRef(); + + /** + * @brief 减少引用计数 + * @return 如果引用计数降为 0 返回 true + */ + bool release(); + + /** + * @brief 获取当前引用计数 + * @return 引用计数值 + */ + u32 getRefCount() const; + + /** + * @brief 自动释放管理 + * + * 标记对象为自动释放状态,在当前帧结束时自动释放 + */ + void autorelease(); + + /** + * @brief 检查是否为自动释放状态 + * @return 如果标记为自动释放返回 true + */ + bool isAutorelease() const; + +protected: + std::atomic refCount_{1}; + bool autorelease_{false}; + +private: + RefCounted(const RefCounted&) = delete; + RefCounted& operator=(const RefCounted&) = delete; +}; + +/** + * @brief 引用计数智能指针 + * + * 自动管理 RefCounted 对象的生命周期 + */ +template +class IntrusivePtr { +public: + IntrusivePtr() : ptr_(nullptr) {} + + IntrusivePtr(T* ptr) : ptr_(ptr) { + if (ptr_) { + ptr_->addRef(); + } + } + + IntrusivePtr(const IntrusivePtr& other) : ptr_(other.ptr_) { + if (ptr_) { + ptr_->addRef(); + } + } + + IntrusivePtr(IntrusivePtr&& other) noexcept : ptr_(other.ptr_) { + other.ptr_ = nullptr; + } + + ~IntrusivePtr() { + if (ptr_) { + ptr_->release(); + } + } + + IntrusivePtr& operator=(const IntrusivePtr& other) { + if (this != &other) { + if (ptr_) { + ptr_->release(); + } + ptr_ = other.ptr_; + if (ptr_) { + ptr_->addRef(); + } + } + return *this; + } + + IntrusivePtr& operator=(IntrusivePtr&& other) noexcept { + if (this != &other) { + if (ptr_) { + ptr_->release(); + } + ptr_ = other.ptr_; + other.ptr_ = nullptr; + } + return *this; + } + + IntrusivePtr& operator=(T* ptr) { + if (ptr_ != ptr) { + if (ptr_) { + ptr_->release(); + } + ptr_ = ptr; + if (ptr_) { + ptr_->addRef(); + } + } + return *this; + } + + T* get() const { return ptr_; } + T* operator->() const { return ptr_; } + T& operator*() const { return *ptr_; } + explicit operator bool() const { return ptr_ != nullptr; } + bool operator!() const { return ptr_ == nullptr; } + + bool operator==(const IntrusivePtr& other) const { return ptr_ == other.ptr_; } + bool operator!=(const IntrusivePtr& other) const { return ptr_ != other.ptr_; } + bool operator==(T* ptr) const { return ptr_ == ptr; } + bool operator!=(T* ptr) const { return ptr_ != ptr; } + + void reset() { + if (ptr_) { + ptr_->release(); + ptr_ = nullptr; + } + } + + void swap(IntrusivePtr& other) noexcept { + T* tmp = ptr_; + ptr_ = other.ptr_; + other.ptr_ = tmp; + } + +private: + T* ptr_; +}; + +template +IntrusivePtr makeIntrusive(Args&&... args) { + return IntrusivePtr(new T(std::forward(args)...)); +} + +} // namespace extra2d diff --git a/Extra2D/include/extra2d/base/Scheduler.h b/Extra2D/include/extra2d/base/Scheduler.h new file mode 100644 index 0000000..1c787d5 --- /dev/null +++ b/Extra2D/include/extra2d/base/Scheduler.h @@ -0,0 +1,162 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace extra2d { + +using SchedulerCallback = std::function; + +/** + * @brief 定时器结构 + */ +struct Timer { + SchedulerCallback callback; + f32 interval{0.0f}; + f32 delay{0.0f}; + u32 repeat{0}; + u32 elapsed{0}; + bool paused{false}; + std::string key; + void* target{nullptr}; + f32 accumulator{0.0f}; +}; + +/** + * @brief 调度器 + * + * 管理定时任务和回调调度。 + * 参考 Cocos Creator 的 Scheduler 设计。 + */ +class Scheduler { +public: + Scheduler(); + ~Scheduler(); + + /** + * @brief 每帧更新 + * @param dt 帧间隔时间 + */ + void update(f32 dt); + + /** + * @brief 调度定时回调 + * @param callback 回调函数 + * @param target 目标对象指针 + * @param interval 间隔时间(秒) + * @param repeat 重复次数(0 = 无限) + * @param delay 延迟时间(秒) + * @param paused 是否暂停 + * @param key 标识键 + */ + void schedule(const SchedulerCallback& callback, void* target, + f32 interval, u32 repeat, f32 delay, + bool paused, const std::string& key); + + /** + * @brief 调度定时回调(简化版) + * @param callback 回调函数 + * @param target 目标对象指针 + * @param interval 间隔时间(秒) + * @param paused 是否暂停 + * @param key 标识键 + */ + void schedule(const SchedulerCallback& callback, void* target, + f32 interval, bool paused, const std::string& key); + + /** + * @brief 调度单次回调 + * @param callback 回调函数 + * @param target 目标对象指针 + * @param delay 延迟时间(秒) + * @param key 标识键 + */ + void scheduleOnce(const SchedulerCallback& callback, void* target, + f32 delay, const std::string& key); + + /** + * @brief 取消调度 + * @param key 标识键 + * @param target 目标对象指针 + */ + void unschedule(const std::string& key, void* target); + + /** + * @brief 取消目标对象的所有调度 + * @param target 目标对象指针 + */ + void unscheduleAllForTarget(void* target); + + /** + * @brief 取消所有调度 + */ + void unscheduleAll(); + + /** + * @brief 检查是否已调度 + * @param key 标识键 + * @param target 目标对象指针 + * @return 如果已调度返回 true + */ + bool isScheduled(const std::string& key, void* target); + + /** + * @brief 暂停目标对象的所有调度 + * @param target 目标对象指针 + */ + void pauseTarget(void* target); + + /** + * @brief 恢复目标对象的所有调度 + * @param target 目标对象指针 + */ + void resumeTarget(void* target); + + /** + * @brief 检查目标对象是否暂停 + * @param target 目标对象指针 + * @return 如果暂停返回 true + */ + bool isTargetPaused(void* target); + + /** + * @brief 在主线程执行函数 + * @param func 要执行的函数 + */ + void performFunctionInMainThread(const std::function& func); + + /** + * @brief 执行所有待执行的主线程函数 + */ + void runFunctionsToBePerformed(); + + /** + * @brief 获取时间缩放因子 + * @return 时间缩放因子 + */ + f32 getTimeScale() const; + + /** + * @brief 设置时间缩放因子 + * @param scale 时间缩放因子 + */ + void setTimeScale(f32 scale); + +private: + struct TargetEntry { + std::vector timers; + bool paused{false}; + }; + + std::unordered_map targets_; + std::vector> functionsToPerform_; + std::mutex performMutex_; + f32 timeScale_{1.0f}; + bool updateLocked_{false}; +}; + +} // namespace extra2d diff --git a/Extra2D/include/extra2d/core/Object.h b/Extra2D/include/extra2d/core/Object.h new file mode 100644 index 0000000..29d4fe1 --- /dev/null +++ b/Extra2D/include/extra2d/core/Object.h @@ -0,0 +1,157 @@ +#pragma once + +#include +#include + +namespace extra2d { + +/** + * @brief 对象标志位 + */ +enum class ObjectFlags : u32 { + None = 0, + Destroyed = 1 << 0, + DontDestroy = 1 << 1, + Dirty = 1 << 2, + HideInHierarchy = 1 << 3, + EditorOnly = 1 << 4, +}; + +/** + * @brief 实现枚举标志位的位运算 + */ +inline ObjectFlags operator|(ObjectFlags a, ObjectFlags b) { + return static_cast(static_cast(a) | static_cast(b)); +} + +inline ObjectFlags operator&(ObjectFlags a, ObjectFlags b) { + return static_cast(static_cast(a) & static_cast(b)); +} + +inline ObjectFlags operator~(ObjectFlags a) { + return static_cast(~static_cast(a)); +} + +inline ObjectFlags& operator|=(ObjectFlags& a, ObjectFlags b) { + a = a | b; + return a; +} + +inline ObjectFlags& operator&=(ObjectFlags& a, ObjectFlags b) { + a = a & b; + return a; +} + +inline bool hasFlag(ObjectFlags flags, ObjectFlags flag) { + return (static_cast(flags) & static_cast(flag)) != 0; +} + +/** + * @brief 对象基类 + * + * 所有引擎对象的基类,提供名称管理、生命周期控制和标志位管理。 + * 参考 Cocos Creator 的 CCObject 设计。 + */ +class Object : public RefCounted { +public: + /** + * @brief 构造函数 + * @param name 对象名称 + */ + explicit Object(const std::string& name = ""); + + /** + * @brief 析构函数 + */ + ~Object() override; + + /** + * @brief 获取对象名称 + * @return 对象名称 + */ + const std::string& getName() const; + + /** + * @brief 设置对象名称 + * @param name 新名称 + */ + void setName(const std::string& name); + + /** + * @brief 销毁对象 + * @return 销毁成功返回 true + */ + virtual bool destroy(); + + /** + * @brief 检查对象是否有效 + * @return 如果对象未被销毁返回 true + */ + bool isValid() const; + + /** + * @brief 检查对象是否已销毁 + * @return 如果对象已销毁返回 true + */ + bool isDestroyed() const; + + /** + * @brief 获取对象标志位 + * @return 标志位 + */ + ObjectFlags getFlags() const; + + /** + * @brief 设置对象标志位 + * @param flags 标志位 + */ + void setFlags(ObjectFlags flags); + + /** + * @brief 添加标志位 + * @param flag 要添加的标志 + */ + void addFlag(ObjectFlags flag); + + /** + * @brief 移除标志位 + * @param flag 要移除的标志 + */ + void removeFlag(ObjectFlags flag); + + /** + * @brief 检查是否有指定标志位 + * @param flag 要检查的标志 + * @return 如果有该标志返回 true + */ + bool hasFlag(ObjectFlags flag) const; + + /** + * @brief 获取对象 ID + * @return 对象唯一 ID + */ + u64 getObjectId() const; + + /** + * @brief 转换为字符串表示 + * @return 对象的字符串表示 + */ + virtual std::string toString() const; + +protected: + /** + * @brief 销毁时的回调 + * + * 子类可重写此方法进行清理工作 + */ + virtual void onDestroy(); + + std::string name_; + ObjectFlags flags_{ObjectFlags::None}; + u64 objectId_{0}; + +private: + static u64 nextObjectId_; +}; + +} // namespace extra2d diff --git a/Extra2D/include/extra2d/core/Root.h b/Extra2D/include/extra2d/core/Root.h new file mode 100644 index 0000000..3f038ad --- /dev/null +++ b/Extra2D/include/extra2d/core/Root.h @@ -0,0 +1,257 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace extra2d { + +namespace gfx { +class Device; +class Swapchain; +} + +class Batcher2d; +class RenderWindow; + +/** + * @brief Root 事件类型 + */ +struct RootBeforeCommitEvent {}; +struct RootBeforeRenderEvent {}; +struct RootAfterRenderEvent {}; +struct RootAfterPresentEvent {}; +struct RootPipelineChangedEvent {}; + +/** + * @brief 引擎根对象 + * + * 管理渲染管线、窗口、场景等核心资源。 + * 参考 Cocos Creator 的 Root 设计。 + */ +class Root : public Object { +public: + /** + * @brief 获取单例实例 + * @return Root 实例 + */ + static Root* getInstance(); + + /** + * @brief 构造函数 + * @param device GFX 设备 + */ + explicit Root(gfx::Device* device); + + /** + * @brief 析构函数 + */ + ~Root() override; + + /** + * @brief 初始化 Root + * @param swapchain 交换链 + */ + void initialize(gfx::Swapchain* swapchain); + + /** + * @brief 销毁 Root + */ + void destroy(); + + /** + * @brief 重置大小 + * @param width 宽度 + * @param height 高度 + * @param windowId 窗口 ID + */ + void resize(u32 width, u32 height, u32 windowId = 0); + + /** + * @brief 全局管线状态改变回调 + */ + void onGlobalPipelineStateChanged(); + + /** + * @brief 重置累计时间 + */ + void resetCumulativeTime(); + + /** + * @brief 每帧执行函数 + * @param deltaTime 帧间隔时间 + * @param totalFrames 总帧数 + */ + void frameMove(float deltaTime, i32 totalFrames); + + // ==================== 场景管理 ==================== + + /** + * @brief 创建渲染场景 + * @param info 场景信息 + * @return 渲染场景 + */ + RenderScene* createScene(const RenderSceneInfo& info); + + /** + * @brief 销毁渲染场景 + * @param scene 渲染场景 + */ + void destroyScene(RenderScene* scene); + + /** + * @brief 销毁所有场景 + */ + void destroyScenes(); + + /** + * @brief 获取所有场景 + * @return 场景列表 + */ + const std::vector>& getScenes() const; + + // ==================== 模型管理 ==================== + + /** + * @brief 创建模型 + * @return 模型指针 + */ + template + T* createModel() { + T* model = new T(); + model->initialize(ModelInfo{}); + return model; + } + + /** + * @brief 销毁模型 + * @param model 模型 + */ + void destroyModel(Model* model); + + // ==================== 相机管理 ==================== + + /** + * @brief 创建相机 + * @return 相机指针 + */ + Camera* createCamera() const; + + // ==================== 属性访问器 ==================== + + /** + * @brief 获取 GFX 设备 + * @return 设备指针 + */ + gfx::Device* getDevice() const; + + /** + * @brief 设置 GFX 设备 + * @param device 设备指针 + */ + void setDevice(gfx::Device* device); + + /** + * @brief 获取主窗口 + * @return 主窗口指针 + */ + RenderWindow* getMainWindow() const; + + /** + * @brief 获取 2D 批处理器 + * @return 批处理器指针 + */ + Batcher2d* getBatcher2D() const; + + /** + * @brief 获取累计时间 + * @return 累计时间(秒) + */ + float getCumulativeTime() const; + + /** + * @brief 获取帧时间 + * @return 帧时间(秒) + */ + float getFrameTime() const; + + /** + * @brief 获取帧计数 + * @return 帧计数 + */ + u32 getFrameCount() const; + + /** + * @brief 获取 FPS + * @return 每秒帧率 + */ + u32 getFps() const; + + /** + * @brief 设置固定 FPS + * @param fps 固定帧率 + */ + void setFixedFPS(u32 fps); + + /** + * @brief 获取固定 FPS + * @return 固定帧率 + */ + u32 getFixedFPS() const; + + // ==================== 事件系统 ==================== + + /** + * @brief 订阅事件 + */ + template + EventListenerID on(Fn&& callback) { + return eventTarget_.on(std::forward(callback)); + } + + /** + * @brief 取消订阅事件 + */ + template + void off(EventListenerID id) { + eventTarget_.off(id); + } + + /** + * @brief 发布事件 + */ + template + void emit(const EventT& event) { + eventTarget_.emit(event); + } + +private: + void frameMoveBegin(); + void frameMoveProcess(bool isNeedUpdateScene, i32 totalFrames); + void frameMoveEnd(); + + static Root* s_instance; + + gfx::Device* device_{nullptr}; + gfx::Swapchain* swapchain_{nullptr}; + Batcher2d* batcher_{nullptr}; + + std::vector> scenes_; + RenderWindow* mainWindow_{nullptr}; + + float cumulativeTime_{0.0f}; + float frameTime_{0.0f}; + float fpsTime_{0.0f}; + u32 frameCount_{0}; + u32 fps_{0}; + u32 fixedFPS_{0}; + + EventTarget eventTarget_; +}; + +} // namespace extra2d diff --git a/Extra2D/include/extra2d/core/assets/Asset.h b/Extra2D/include/extra2d/core/assets/Asset.h new file mode 100644 index 0000000..03cd16d --- /dev/null +++ b/Extra2D/include/extra2d/core/assets/Asset.h @@ -0,0 +1,132 @@ +#pragma once + +#include +#include + +namespace extra2d { + +/** + * @brief 资源基类 + * + * 所有资源类型的基类,提供资源标识、引用计数和生命周期管理。 + * 参考 Cocos Creator 的 Asset 设计。 + */ +class Asset : public Object { +public: + /** + * @brief 构造函数 + * @param name 资源名称 + */ + explicit Asset(const std::string& name = ""); + + /** + * @brief 析构函数 + */ + ~Asset() override; + + // ==================== 资源标识 ==================== + + /** + * @brief 获取资源 UUID + * @return UUID 字符串 + */ + const std::string& getUuid() const; + + /** + * @brief 设置资源 UUID + * @param uuid UUID 字符串 + */ + void setUuid(const std::string& uuid); + + /** + * @brief 获取原生资源 URL + * @return URL 字符串 + */ + const std::string& getNativeUrl() const; + + /** + * @brief 设置原生资源 URL + * @param url URL 字符串 + */ + void setNativeUrl(const std::string& url); + + // ==================== 引用计数 ==================== + + /** + * @brief 增加资源引用计数 + */ + void addAssetRef(); + + /** + * @brief 减少资源引用计数 + * @param autoRelease 是否自动释放 + */ + void decAssetRef(bool autoRelease = true); + + /** + * @brief 获取资源引用计数 + * @return 引用计数值 + */ + u32 getAssetRefCount() const; + + // ==================== 加载状态 ==================== + + /** + * @brief 检查是否已加载 + * @return 如果已加载返回 true + */ + bool isLoaded() const; + + /** + * @brief 设置加载状态 + * @param loaded 是否已加载 + */ + void setLoaded(bool loaded); + + /** + * @brief 加载完成回调 + */ + virtual void onLoaded(); + + // ==================== 默认资源 ==================== + + /** + * @brief 检查是否为默认资源 + * @return 如果是默认资源返回 true + */ + bool isDefault() const; + + /** + * @brief 初始化默认资源 + * @param uuid 可选的 UUID + */ + virtual void initDefault(const std::string& uuid = ""); + + /** + * @brief 验证资源有效性 + * @return 如果资源有效返回 true + */ + virtual bool validate() const; + + // ==================== 销毁 ==================== + + /** + * @brief 销毁资源 + * @return 销毁成功返回 true + */ + bool destroy() override; + +protected: + /** + * @brief 销毁时的回调 + */ + void onDestroy() override; + + std::string uuid_; + std::string nativeUrl_; + u32 assetRefCount_{0}; + bool loaded_{false}; + bool isDefault_{false}; +}; + +} // namespace extra2d diff --git a/Extra2D/include/extra2d/core/assets/AssetManager.h b/Extra2D/include/extra2d/core/assets/AssetManager.h new file mode 100644 index 0000000..b653e9f --- /dev/null +++ b/Extra2D/include/extra2d/core/assets/AssetManager.h @@ -0,0 +1,180 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace extra2d { + +/** + * @brief 资源加载状态 + */ +enum class AssetLoadState { + NotLoaded, + Loading, + Loaded, + Failed, +}; + +/** + * @brief 资源加载进度回调 + */ +using AssetLoadProgress = std::function; +using AssetLoadComplete = std::function; +using AssetLoadError = std::function; + +/** + * @brief 资源加载选项 + */ +struct AssetLoadOptions { + bool async{false}; + bool cache{true}; + bool preload{false}; +}; + +/** + * @brief 资源管理器 + * + * 管理所有资源的加载、缓存和生命周期。 + */ +class AssetManager { +public: + /** + * @brief 获取单例实例 + * @return 资源管理器实例 + */ + static AssetManager* getInstance(); + + /** + * @brief 初始化资源管理器 + * @param device GFX 设备 + * @return 成功返回 true + */ + bool initialize(gfx::Device* device); + + /** + * @brief 销毁资源管理器 + */ + void destroy(); + + // ==================== 资源加载 ==================== + + /** + * @brief 加载资源 + * @param path 资源路径 + * @param type 资源类型 + * @param options 加载选项 + * @return 资源指针 + */ + Asset* load(const std::string& path, const std::string& type = "", + const AssetLoadOptions& options = AssetLoadOptions{}); + + /** + * @brief 异步加载资源 + * @param path 资源路径 + * @param type 资源类型 + * @param progress 进度回调 + * @param complete 完成回调 + * @param error 错误回调 + * @param options 加载选项 + */ + void loadAsync(const std::string& path, const std::string& type, + AssetLoadProgress progress, AssetLoadComplete complete, + AssetLoadError error, + const AssetLoadOptions& options = AssetLoadOptions{}); + + /** + * @brief 从缓存获取资源 + * @param path 资源路径 + * @return 资源指针, */ + Asset* getAsset(const std::string& path); + + /** + * @brief 从缓存获取资源(类型化) + * @tparam T 资源类型 + * @param path 资源路径 + * @return 资源指针 + */ + template + T* getAsset(const std::string& path); + + /** + * @brief 从 UUID 获取资源 + * @param uuid 资源 UUID + * @return 资源指针 + */ + Asset* getAssetByUuid(const std::string& uuid); + + /** + * @brief 释放资源 + * @param path 资源路径 + */ + void releaseAsset(const std::string& path); + + /** + * @brief 释放所有资源 + */ + void releaseAll(); + + // ==================== 资源缓存 ==================== + + /** + * @brief 添加资源到缓存 + * @param path 资源路径 + * @param asset 资源指针 + */ + void addAsset(const std::string& path, Asset* asset); + + /** + * @brief 移除资源从缓存 + * @param path 资源路径 + */ + void removeAsset(const std::string& path); + + /** + * @brief 检查资源是否存在 + * @param path 资源路径 + * @return 如果存在返回 true + */ + bool hasAsset(const std::string& path); + + /** + * @brief 获取缓存资源数量 + * @return 资源数量 + */ + size_t getAssetCount() const; + + // ==================== 默认资源 ==================== + + /** + * @brief 设置默认纹理 + * @param name 名称 + * @param texture 纹理资源 + */ + void setDefaultTexture(const std::string& name, Texture2D* texture); + + /** + * @brief 获取默认纹理 + * @param name 名称 + * @return 纹理资源 + */ + Texture2D* getDefaultTexture(const std::string& name); + +private: + AssetManager(); + ~AssetManager(); + + AssetManager(const AssetManager&) = delete; + AssetManager& operator=(const AssetManager&) = delete; + + void initDefaultAssets(); + + gfx::Device* device_{nullptr}; + std::unordered_map> assets_; + std::unordered_map> defaultTextures_; +}; + diff --git a/Extra2D/include/extra2d/core/assets/ImageAsset.h b/Extra2D/include/extra2d/core/assets/ImageAsset.h new file mode 100644 index 0000000..596e58f --- /dev/null +++ b/Extra2D/include/extra2d/core/assets/ImageAsset.h @@ -0,0 +1,173 @@ +#pragma once + +#include +#include +#include + +namespace extra2d { + +/** + * @brief 像素格式 + */ +enum class PixelFormat : u32 { + Unknown, + RGB565, + RGB5A1, + RGBA4444, + RGB888, + RGBA8888, + RGB32F, + RGBA32F, + A8, + I8, + AI8, + R8, + RG8, + R16F, + RG16F, + RGBA16F, + R32F, + RG32F, + RGBA32F, + Depth, + DepthStencil, +}; + +/** + * @brief 图像资源类 + * + * 表示内存中的图像数据,继承自 Asset。 + */ +class ImageAsset : public Asset { +public: + /** + * @brief 构造函数 + */ + ImageAsset(); + + /** + * @brief 析构函数 + */ + ~ImageAsset() override; + + // ==================== 数据访问 ==================== + + /** + * @brief 获取图像数据指针 + * @return 数据指针 + */ + const u8* getData() const; + + /** + * @brief 获取数据大小 + * @return 数据大小(字节) + */ + u32 getDataSize() const; + + /** + * @brief 设置图像数据 + * @param data 数据指针 + * @param size 数据大小 + * @param ownsData 是否拥有数据 + */ + void setData(u8* data, u32 size, bool ownsData = true); + + // ==================== 图像属性 ==================== + + /** + * @brief 获取图像宽度 + * @return 宽度(像素) + */ + u32 getWidth() const; + + /** + * @brief 设置图像宽度 + * @param width 宽度 + */ + void setWidth(u32 width); + + /** + * @brief 获取图像高度 + * @return 高度(像素) + */ + u32 getHeight() const; + + /** + * @brief 设置图像高度 + * @param height 高度 + */ + void setHeight(u32 height); + + /** + * @brief 获取像素格式 + * @return 像素格式 + */ + PixelFormat getFormat() const; + + /** + * @brief 设置像素格式 + * @param format 像素格式 + */ + void setFormat(PixelFormat format); + + /** + * @brief 检查是否为压缩格式 + * @return 如果是压缩格式返回 true + */ + bool isCompressed() const; + + /** + * @brief 设置是否压缩 + * @param compressed 是否压缩 + */ + void setCompressed(bool compressed); + + // ==================== Mipmap 信息 ==================== + + /** + * @brief 获取 Mipmap 层级数据大小列表 + * @return 大小列表 + */ + const std::vector& getMipmapLevelDataSize() const; + + /** + * @brief 设置 Mipmap 层级数据大小列表 + * @param sizes 大小列表 + */ + void setMipmapLevelDataSize(const std::vector& sizes); + + // ==================== 工具方法 ==================== + + /** + * @brief 获取每像素字节数 + * @return 字节数 + */ + u32 getBytesPerPixel() const; + + /** + * @brief 获取 GFX 格式 + * @return GFX 格式 + */ + gfx::Format getGFXFormat() const; + + /** + * @brief 验证资源有效性 + * @return 如果资源有效返回 true + */ + bool validate() const override; + +protected: + void onDestroy() override; + +private: + u8* data_{nullptr}; + u32 dataSize_{0}; + u32 width_{0}; + u32 height_{0}; + PixelFormat format_{PixelFormat::RGBA8888}; + bool compressed_{false}; + bool ownsData_{false}; + std::vector mipmapLevelDataSize_; +}; + +} // namespace extra2d diff --git a/Extra2D/include/extra2d/core/assets/Material.h b/Extra2D/include/extra2d/core/assets/Material.h new file mode 100644 index 0000000..16ce610 --- /dev/null +++ b/Extra2D/include/extra2d/core/assets/Material.h @@ -0,0 +1,384 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace extra2d { + +namespace gfx { +class Shader; +class PipelineState; +} + +/** + * @brief 宏定义记录 + */ +using MacroRecord = std::unordered_map; + +/** + * @brief 材质属性变体类型 + */ +using MaterialPropertyVariant = std::variant< + std::monostate, + float, + i32, + Vec2, + Vec3, + Vec4, + Color, + std::vector, + std::vector, + std::vector, + std::vector, + std::vector, + std::vector>; + +/** + * @brief Pass 重载信息 + */ +struct PassOverrides { + gfx::RasterizerState rasterizerState; + gfx::DepthStencilState depthStencilState; + gfx::BlendState blendState; + + void reset() { + rasterizerState = gfx::RasterizerState{}; + depthStencilState = gfx::DepthStencilState{}; + blendState = gfx::BlendState{}; + } +}; + +/** + * @brief 材质初始化信息 + */ +struct MaterialInfo { + std::string effectName; + u32 technique{0}; + MacroRecord defines; + PassOverrides states; +}; + +/** + * @brief 渲染 Pass + */ +class Pass { +public: + Pass(); + ~Pass(); + + /** + * @brief 初始化 Pass + * @param shader 着色器 + * @return 成功返回 true + */ + bool initialize(gfx::Shader* shader); + + /** + * @brief 销毁 Pass + */ + void destroy(); + + /** + * @brief 设置 uniform 值 + * @param name 名称 + * @param value 值 + */ + void setUniform(const std::string& name, const MaterialPropertyVariant& value); + + /** + * @brief 获取 uniform 值 + * @param name 名称 + * @return 值 + */ + const MaterialPropertyVariant* getUniform(const std::string& name) const; + + /** + * @brief 设置管线状态 + * @param state 管线状态 + */ + void setPipelineState(gfx::PipelineState* state); + + /** + * @brief 获取管线状态 + * @return 管线状态 + */ + gfx::PipelineState* getPipelineState() const; + + /** + * @brief 获取着色器 + * @return 着色器 + */ + gfx::Shader* getShader() const; + + /** + * @brief 设置混合状态 + * @param state 混合状态 + */ + void setBlendState(const gfx::BlendState& state); + + /** + * @brief 获取混合状态 + * @return 混合状态 + */ + const gfx::BlendState& getBlendState() const; + + /** + * @brief 设置深度模板状态 + * @param state 深度模板状态 + */ + void setDepthStencilState(const gfx::DepthStencilState& state); + + /** + * @brief 获取深度模板状态 + * @return 深度模板状态 + */ + const gfx::DepthStencilState& getDepthStencilState() const; + + /** + * @brief 设置光栅化状态 + * @param state 光栅化状态 + */ + void setRasterizerState(const gfx::RasterizerState& state); + + /** + * @brief 获取光栅化状态 + * @return 光栅化状态 + */ + const gfx::RasterizerState& getRasterizerState() const; + + /** + * @brief 获取哈希值 + * @return 哈希值 + */ + size_t getHash() const; + +private: + gfx::Shader* shader_{nullptr}; + gfx::PipelineState* pipelineState_{nullptr}; + + gfx::BlendState blendState_; + gfx::DepthStencilState depthStencilState_; + gfx::RasterizerState rasterizerState_; + + std::unordered_map uniforms_; + size_t hash_{0}; +}; + +/** + * @brief 材质类 + * + * 管理渲染材质,包含着色器和渲染状态。 + * 参考 Cocos Creator 的 Material 设计。 + */ +class Material : public Asset { +public: + /** + * @brief 创建材质 + * @return 材质引用 + */ + static Ref create(); + + /** + * @brief 构造函数 + */ + Material(); + + /** + * @brief 析构函数 + */ + ~Material() override; + + /** + * @brief 初始化材质 + * @param info 材质信息 + * @return 成功返回 true + */ + bool initialize(const MaterialInfo& info); + + /** + * @brief 重置材质 + * @param info 材质信息 + */ + void reset(const MaterialInfo& info); + + /** + * @brief 销毁材质 + * @return 成功返回 true + */ + bool destroy() override; + + // ==================== 属性设置 ==================== + + /** + * @brief 设置属性值 + * @param name 属性名 + * @param value 值 + * @param passIdx Pass 索引(默认所有 Pass) + */ + void setProperty(const std::string& name, const MaterialPropertyVariant& value, i32 passIdx = -1); + + /** + * @brief 设置浮点属性 + * @param name 属性名 + * @param value 值 + * @param passIdx Pass 索引 + */ + void setPropertyFloat(const std::string& name, float value, i32 passIdx = -1); + + /** + * @brief 设置整数属性 + * @param name 属性名 + * @param value 值 + * @param passIdx Pass 索引 + */ + void setPropertyInt(const std::string& name, i32 value, i32 passIdx = -1); + + /** + * @brief 设置 Vec2 属性 + * @param name 属性名 + * @param value 值 + * @param passIdx Pass 索引 + */ + void setPropertyVec2(const std::string& name, const Vec2& value, i32 passIdx = -1); + + /** + * @brief 设置 Vec3 属性 + * @param name 属性名 + * @param value 值 + * @param passIdx Pass 索引 + */ + void setPropertyVec3(const std::string& name, const Vec3& value, i32 passIdx = -1); + + /** + * @brief 设置 Vec4 属性 + * @param name 属性名 + * @param value 值 + * @param passIdx Pass 索引 + */ + void setPropertyVec4(const std::string& name, const Vec4& value, i32 passIdx = -1); + + /** + * @brief 设置颜色属性 + * @param name 属性名 + * @param value 值 + * @param passIdx Pass 索引 + */ + void setPropertyColor(const std::string& name, const Color& value, i32 passIdx = -1); + + /** + * @brief 获取属性值 + * @param name 属性名 + * @param passIdx Pass 索引 + * @return 属性值 + */ + const MaterialPropertyVariant* getProperty(const std::string& name, i32 passIdx = -1) const; + + // ==================== Pass 管理 ==================== + + /** + * @brief 获取 Pass 数量 + * @return Pass 数量 + */ + size_t getPassCount() const; + + /** + * @brief 获取 Pass + * @param index 索引 + * @return Pass 指针 + */ + Pass* getPass(size_t index); + + /** + * @brief 获取所有 Pass + * @return Pass 列表 + */ + const std::vector>& getPasses() const; + + /** + * @brief 添加 Pass + * @param pass Pass + */ + void addPass(Ref pass); + + // ==================== 状态重载 ==================== + + /** + * @brief 重载管线状态 + * @param overrides 重载信息 + * @param passIdx Pass 索引 + */ + void overridePipelineStates(const PassOverrides& overrides, i32 passIdx = -1); + + /** + * @brief 重编译着色器 + * @param defines 宏定义 + * @param passIdx Pass 索引 + */ + void recompileShaders(const MacroRecord& defines, i32 passIdx = -1); + + // ==================== 属性访问器 ==================== + + /** + * @brief 设置 Effect 名称 + * @param name 名称 + */ + void setEffectName(const std::string& name); + + /** + * @brief 获取 Effect 名称 + * @return 名称 + */ + const std::string& getEffectName() const; + + /** + * @brief 设置 Technique 索引 + * @param index 索引 + */ + void setTechniqueIndex(u32 index); + + /** + * @brief 获取 Technique 索引 + * @return 索引 + */ + u32 getTechniqueIndex() const; + + /** + * @brief 获取哈希值 + * @return 哈希值 + */ + size_t getHash() const; + + /** + * @brief 复制材质 + * @param other 源材质 + */ + void copy(const Material* other); + + /** + * @brief 重置 uniform 为默认值 + */ + void resetUniforms(); + +protected: + /** + * @brief 更新哈希值 + */ + void updateHash(); + +private: + std::string effectName_; + u32 techniqueIndex_{0}; + + std::vector> passes_; + std::vector defines_; + std::vector states_; + + std::unordered_map properties_; + size_t hash_{0}; +}; + +} // namespace extra2d diff --git a/Extra2D/include/extra2d/core/assets/Texture2D.h b/Extra2D/include/extra2d/core/assets/Texture2D.h new file mode 100644 index 0000000..099a576 --- /dev/null +++ b/Extra2D/include/extra2d/core/assets/Texture2D.h @@ -0,0 +1,238 @@ +#pragma once + +#include +#include +#include +#include + +namespace extra2d { + +namespace gfx { + class Device; + class Texture; +} + +/** + * @brief 纹理过滤模式 + */ +enum class Filter : u32 { + None, + Nearest, + Linear, +}; + +/** + * @brief 纹理寻址模式 + */ +enum class WrapMode : u32 { + Repeat, + ClampToEdge, + MirroredRepeat, + ClampToBorder, +}; + +/** + * @brief 纹理创建信息 + */ +struct Texture2DCreateInfo { + u32 width{0}; + u32 height{0}; + PixelFormat format{PixelFormat::RGBA8888}; + u32 mipmapLevel{1}; + u32 baseLevel{0}; + u32 maxLevel{1000}; +}; + +/** + * @brief 2D 纹理资源类 + * + * 表示 GPU 纹理资源,继承自 Asset。 + */ +class Texture2D : public Asset { +public: + /** + * @brief 构造函数 + */ + Texture2D(); + + /** + * @brief 析构函数 + */ + ~Texture2D() override; + + // ==================== 初始化 ==================== + + /** + * @brief 初始化纹理 + * @param device GFX 设备 + * @param info 创建信息 + * @return 成功返回 true + */ + bool initialize(gfx::Device* device, const Texture2DCreateInfo& info); + + /** + * @brief 重置纹理 + * @param info 创建信息 + */ + void reset(const Texture2DCreateInfo& info); + + // ==================== 图像数据 ==================== + + /** + * @brief 获取 Mipmap 图像列表 + * @return 图像列表 + */ + const std::vector>& getMipmaps() const; + + /** + * @brief 设置 Mipmap 图像列表 + * @param mipmaps 图像列表 + */ + void setMipmaps(const std::vector>& mipmaps); + + /** + * @brief 获取 0 级 Mipmap 图像 + * @return 图像资源 + */ + ImageAsset* getImage() const; + + /** + * @brief 设置 0 级 Mipmap 图像 + * @param image 图像资源 + */ + void setImage(ImageAsset* image); + + // ==================== 纹理属性 ==================== + + /** + * @brief 获取宽度 + * @return 宽度(像素) + */ + u32 getWidth() const; + + /** + * @brief 获取高度 + * @return 高度(像素) + */ + u32 getHeight() const; + + /** + * @brief 获取像素格式 + * @return 像素格式 + */ + PixelFormat getFormat() const; + + /** + * @brief 获取 Mipmap 层级数 + * @return 层级数 + */ + u32 getMipmapLevel() const; + + // ==================== 采样参数 ==================== + + /** + * @brief 获取最小过滤模式 + * @return 过滤模式 + */ + Filter getMinFilter() const; + + /** + * @brief 设置最小过滤模式 + * @param filter 过滤模式 + */ + void setMinFilter(Filter filter); + + /** + * @brief 获取最大过滤模式 + * @return 过滤模式 + */ + Filter getMagFilter() const; + + /** + * @brief 设置最大过滤模式 + * @param filter 过滤模式 + */ + void setMagFilter(Filter filter); + + /** + * @brief 获取 S 轴寻址模式 + * @return 寻址模式 + */ + WrapMode getWrapModeS() const; + + /** + * @brief 设置 S 轴寻址模式 + * @param mode 寻址模式 + */ + void setWrapModeS(WrapMode mode); + + /** + * @brief 获取 T 轴寻址模式 + * @return 寻址模式 + */ + WrapMode getWrapModeT() const; + + /** + * @brief 设置 T 轴寻址模式 + * @param mode 寻址模式 + */ + void setWrapModeT(WrapMode mode); + + // ==================== GPU 资源 ==================== + + /** + * @brief 获取 GFX 纹理 + * @return GFX 纹理指针 + */ + gfx::Texture* getGFXTexture() const; + + /** + * @brief 更新图像数据到 GPU + */ + void updateImage(); + + /** + * @brief 上传数据到 GPU + * @param data 数据指针 + * @param level Mipmap 层级 + */ + void uploadData(const u8* data, u32 level = 0); + + // ==================== 生命周期 ==================== + + /** + * @brief 加载完成回调 + */ + void onLoaded() override; + + /** + * @brief 验证资源有效性 + * @return 如果资源有效返回 true + */ + bool validate() const override; + +protected: + void onDestroy() override; + +private: + void createGFXTexture(); + + gfx::Device* device_{nullptr}; + gfx::Texture* gfxTexture_{nullptr}; + + std::vector> mipmaps_; + + u32 width_{0}; + u32 height_{0}; + PixelFormat format_{PixelFormat::RGBA8888}; + u32 mipmapLevel_{1}; + u32 baseLevel_{0}; + u32 maxLevel_{1000}; + + Filter minFilter_{Filter::Linear}; + Filter magFilter_{Filter::Linear}; + WrapMode wrapModeS_{WrapMode::ClampToEdge}; + WrapMode wrapModeT_{WrapMode::ClampToEdge}; +}; + +} // namespace extra2d diff --git a/Extra2D/include/extra2d/core/event/EventBus.h b/Extra2D/include/extra2d/core/event/EventBus.h new file mode 100644 index 0000000..1d548ad --- /dev/null +++ b/Extra2D/include/extra2d/core/event/EventBus.h @@ -0,0 +1,343 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace extra2d { + +/** + * @brief 事件监听器 ID + */ +struct EventListenerID { + u32 id{0}; + + EventListenerID() = default; + explicit EventListenerID(u32 value) : id(value) {} + + bool isValid() const { return id != 0; } + void invalidate() { id = 0; } + + bool operator==(const EventListenerID& other) const { return id == other.id; } + bool operator!=(const EventListenerID& other) const { return id != other.id; } +}; + +/** + * @brief 键盘事件动作 + */ +enum class KeyAction { + Press, + Release, + Repeat +}; + +/** + * @brief 键盘事件 + */ +struct KeyboardEvent { + i32 key{-1}; + i32 scancode{-1}; + KeyAction action{KeyAction::Press}; + bool shift{false}; + bool ctrl{false}; + bool alt{false}; + bool gui{false}; +}; + +/** + * @brief 鼠标事件类型 + */ +enum class MouseEventType { + Down, + Up, + Move, + Wheel +}; + +/** + * @brief 鼠标事件 + */ +struct MouseEvent { + MouseEventType type{MouseEventType::Move}; + float x{0}; + float y{0}; + float xDelta{0}; + float yDelta{0}; + u16 button{0}; +}; + +/** + * @brief 触摸事件类型 + */ +enum class TouchEventType { + Began, + Moved, + Ended, + Cancelled +}; + +/** + * @brief 触摸点信息 + */ +struct TouchPoint { + float x{0}; + float y{0}; + float dx{0}; + float dy{0}; + float pressure{1.0f}; + i64 id{-1}; +}; + +/** + * @brief 触摸事件 + */ +struct TouchEvent { + TouchEventType type{TouchEventType::Began}; + std::vector touches; + i64 touchDeviceId{-1}; +}; + +/** + * @brief 手势事件类型 + */ +enum class GestureEventType { + Pinch, + Rotate, + Swipe, + Tap, + LongPress +}; + +/** + * @brief 手势事件 + */ +struct GestureEvent { + GestureEventType type{GestureEventType::Pinch}; + float scale{1.0f}; + Vec2 center{0, 0}; + float rotation{0}; + Vec2 direction{0, 0}; + float velocity{0}; + Vec2 position{0, 0}; + i32 tapCount{1}; +}; + +/** + * @brief 手柄事件类型 + */ +enum class ControllerEventType { + Connected, + Disconnected, + ButtonDown, + ButtonUp, + AxisMotion +}; + +/** + * @brief 手柄事件 + */ +struct ControllerEvent { + ControllerEventType type{ControllerEventType::Connected}; + i32 controllerId{-1}; + i32 button{-1}; + i32 axis{-1}; + float value{0}; +}; + +/** + * @brief 窗口事件类型 + */ +enum class WindowEventType { + Quit, + Resize, + FocusGained, + FocusLost, + Close, + Show, + Hide, + Minimize, + Maximize, + Restore +}; + +/** + * @brief 窗口事件 + */ +struct WindowEvent { + WindowEventType type{WindowEventType::Quit}; + i32 width{0}; + i32 height{0}; +}; + +/** + * @brief 应用事件类型 + */ +enum class AppEventType { + WillEnterBackground, + DidEnterBackground, + WillEnterForeground, + DidEnterForeground, + MemoryWarning +}; + +/** + * @brief 应用事件 + */ +struct AppEvent { + AppEventType type{AppEventType::MemoryWarning}; +}; + +/** + * @brief 事件监听器基类 + */ +class EventListenerBase { +public: + virtual ~EventListenerBase() = default; + virtual void invoke(const std::any& event) = 0; +}; + +/** + * @brief 类型化事件监听器 + */ +template +class EventListener : public EventListenerBase { +public: + using Callback = std::function; + + explicit EventListener(const Callback& callback) : callback_(callback) {} + + void invoke(const std::any& event) override { + try { + callback_(std::any_cast(event)); + } catch (const std::bad_any_cast&) { + } + } + +private: + Callback callback_; +}; + +/** + * @brief 事件总线 + * + * 全局事件分发系统,支持类型安全的事件订阅和发布。 + * 参考 Cocos Creator 的 EventBus 设计。 + */ +class EventBus { +public: + /** + * @brief 获取单例实例 + * @return 事件总线实例 + */ + static EventBus* getInstance(); + + /** + * @brief 订阅事件 + * @tparam EventT 事件类型 + * @param callback 回调函数 + * @return 监听器 ID,用于取消订阅 + */ + template + EventListenerID on(std::function callback) { + auto typeIndex = std::type_index(typeid(EventT)); + auto listener = new EventListener(callback); + + u32 id = nextId_++; + ListenerEntry entry; + entry.id = id; + entry.listener = listener; + + listeners_[typeIndex].push_back(entry); + return EventListenerID(id); + } + + /** + * @brief 取消订阅事件 + * @tparam EventT 事件类型 + * @param listenerId 监听器 ID + */ + template + void off(EventListenerID listenerId) { + if (!listenerId.isValid()) return; + + auto typeIndex = std::type_index(typeid(EventT)); + auto it = listeners_.find(typeIndex); + if (it == listeners_.end()) return; + + auto& entries = it->second; + for (auto entryIt = entries.begin(); entryIt != entries.end(); ++entryIt) { + if (entryIt->id == listenerId.id) { + delete entryIt->listener; + entries.erase(entryIt); + break; + } + } + } + + /** + * @brief 发布事件 + * @tparam EventT 事件类型 + * @param event 事件数据 + */ + template + void emit(const EventT& event) { + auto typeIndex = std::type_index(typeid(EventT)); + auto it = listeners_.find(typeIndex); + if (it == listeners_.end()) return; + + std::any eventAny = event; + for (const auto& entry : it->second) { + entry.listener->invoke(eventAny); + } + } + + /** + * @brief 移除所有监听器 + */ + void clear(); + + /** + * @brief 移除指定类型的所有监听器 + * @tparam EventT 事件类型 + */ + template + void clear() { + auto typeIndex = std::type_index(typeid(EventT)); + auto it = listeners_.find(typeIndex); + if (it != listeners_.end()) { + for (auto& entry : it->second) { + delete entry.listener; + } + listeners_.erase(it); + } + } + +private: + EventBus(); + ~EventBus(); + + EventBus(const EventBus&) = delete; + EventBus& operator=(const EventBus&) = delete; + + struct ListenerEntry { + u32 id; + EventListenerBase* listener; + }; + + static EventBus* instance_; + std::unordered_map> listeners_; + u32 nextId_{1}; +}; + +/** + * @brief 全局事件便捷访问命名空间 + */ +namespace events { + inline EventBus* bus() { return EventBus::getInstance(); } +} + +} // namespace extra2d diff --git a/Extra2D/include/extra2d/core/event/EventTarget.h b/Extra2D/include/extra2d/core/event/EventTarget.h new file mode 100644 index 0000000..7e11054 --- /dev/null +++ b/Extra2D/include/extra2d/core/event/EventTarget.h @@ -0,0 +1,113 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace extra2d { + +/** + * @brief 事件目标 + * + * 对象级事件系统,支持对象自身的事件订阅和发布。 + * 参考 Cocos Creator 的 EventTarget 设计。 + */ +class EventTarget { +public: + EventTarget(); + ~EventTarget(); + + /** + * @brief 订阅事件 + * @tparam EventT 事件类型 + * @param callback 回调函数 + * @return 监听器 ID + */ + template + EventListenerID on(Fn&& callback) { + auto typeIndex = std::type_index(typeid(EventT)); + u32 id = nextId_++; + + ListenerEntry entry; + entry.id = id; + entry.callback = [cb = std::forward(callback)](const std::any& e) { + try { + cb(std::any_cast(e)); + } catch (const std::bad_any_cast&) { + } + }; + + listeners_[typeIndex].push_back(entry); + return EventListenerID(id); + } + + /** + * @brief 取消订阅事件 + * @tparam EventT 事件类型 + * @param listenerId 监听器 ID + */ + template + void off(EventListenerID listenerId) { + if (!listenerId.isValid()) return; + + auto typeIndex = std::type_index(typeid(EventT)); + auto it = listeners_.find(typeIndex); + if (it == listeners_.end()) return; + + auto& entries = it->second; + for (auto entryIt = entries.begin(); entryIt != entries.end(); ++entryIt) { + if (entryIt->id == listenerId.id) { + entries.erase(entryIt); + break; + } + } + } + + /** + * @brief 发布事件 + * @tparam EventT 事件类型 + * @param event 事件数据 + */ + template + void emit(const EventT& event) { + auto typeIndex = std::type_index(typeid(EventT)); + auto it = listeners_.find(typeIndex); + if (it == listeners_.end()) return; + + std::any eventAny = event; + for (const auto& entry : it->second) { + entry.callback(eventAny); + } + } + + /** + * @brief 移除所有监听器 + */ + void offAll(); + + /** + * @brief 检查是否有指定类型的监听器 + * @tparam EventT 事件类型 + * @return 如果有监听器返回 true + */ + template + bool hasListener() const { + auto typeIndex = std::type_index(typeid(EventT)); + auto it = listeners_.find(typeIndex); + return it != listeners_.end() && !it->second.empty(); + } + +private: + struct ListenerEntry { + u32 id; + std::function callback; + }; + + std::unordered_map> listeners_; + u32 nextId_{1}; +}; + +} // namespace extra2d diff --git a/Extra2D/include/extra2d/core/module.h b/Extra2D/include/extra2d/core/module.h deleted file mode 100644 index 8eb5b17..0000000 --- a/Extra2D/include/extra2d/core/module.h +++ /dev/null @@ -1,82 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -namespace extra2d { - -class Application; - -/** - * @brief 模块基类 - * 所有模块必须继承此类 - */ -class Module { -public: - virtual ~Module() = default; - - /** - * @brief 初始化模块 - * @return 初始化成功返回 true - */ - virtual bool init() = 0; - - /** - * @brief 关闭模块 - */ - virtual void shutdown() = 0; - - /** - * @brief 检查模块是否已初始化 - * @return 已初始化返回 true - */ - virtual bool ok() const = 0; - - /** - * @brief 获取模块名称 - * @return 模块名称 - */ - virtual const char* name() const = 0; - - /** - * @brief 获取模块优先级(数值越小越优先) - * @return 优先级值 - */ - virtual int priority() const { return 100; } - - /** - * @brief 获取模块依赖列表 - * @return 依赖模块类型列表 - */ - virtual std::vector deps() const { return {}; } - - /** - * @brief 检查模块是否支持并行初始化 - * @return 支持并行初始化返回 true - */ - virtual bool parallel() const { return true; } - - /** - * @brief 设置所属Application - * @param app Application指针 - */ - void setApp(class Application* app) { app_ = app; } - - /** - * @brief 获取Application - * @return Application指针 - */ - class Application* app() const { return app_; } - -protected: - class Application* app_ = nullptr; -}; - -/** - * @brief 模块工厂函数类型 - */ -using ModuleFactory = std::function()>; - -} // namespace extra2d diff --git a/Extra2D/include/extra2d/core/object_pool.h b/Extra2D/include/extra2d/core/object_pool.h deleted file mode 100644 index ea6305f..0000000 --- a/Extra2D/include/extra2d/core/object_pool.h +++ /dev/null @@ -1,63 +0,0 @@ -#pragma once - -#include -#include -#include - -namespace extra2d { - -/** - * @brief 固定大小对象池 - * @tparam T 对象类型 - * @tparam Size 池大小 - */ -template class ObjectPool { -public: - ObjectPool() { - for (size_t i = 0; i < Size; ++i) { - available_.push(&pool_[i]); - } - } - - /** - * @brief 获取对象 - * @return 对象指针,池耗尽返回nullptr - */ - T *acquire() { - if (available_.empty()) { - return nullptr; - } - T *obj = available_.front(); - available_.pop(); - return obj; - } - - /** - * @brief 释放对象回池 - * @param obj 对象指针 - */ - void release(T *obj) { - if (obj >= pool_ && obj < pool_ + Size) { - obj->~T(); - new (obj) T(); - available_.push(obj); - } - } - - /** - * @brief 获取可用对象数量 - */ - size_t available() const { return available_.size(); } - - /** - * @brief 获取池总大小 - */ - static constexpr size_t capacity() { return Size; } - -private: - alignas(alignof(T)) std::array storage_; - T *pool_ = reinterpret_cast(storage_.data()); - std::queue available_; -}; - -} // namespace extra2d diff --git a/Extra2D/include/extra2d/core/registry.h b/Extra2D/include/extra2d/core/registry.h deleted file mode 100644 index dd19253..0000000 --- a/Extra2D/include/extra2d/core/registry.h +++ /dev/null @@ -1,170 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include - -namespace extra2d { - -class Application; - -/** - * @brief 编译期类型ID生成器 - */ -using TypeId = size_t; - -namespace detail { - inline TypeId nextTypeId() { - static TypeId id = 0; - return ++id; - } - - template - inline TypeId getTypeId() { - static TypeId id = nextTypeId(); - return id; - } -} - -/** - * @brief 模块注册表 - * 管理模块的注册、拓扑排序和生命周期 - */ -class Registry { -public: - static constexpr size_t MAX_MODULES = 64; - - static Registry& instance(); - - Registry(const Registry&) = delete; - Registry& operator=(const Registry&) = delete; - - /** - * @brief 注册模块 - * @tparam T 模块类型 - * @tparam Args 构造函数参数类型 - * @param args 构造函数参数 - * @return 模块指针 - */ - template - T* use(Args&&... args) { - static_assert(std::is_base_of_v, "T must derive from Module"); - - TypeId typeId = detail::getTypeId(); - - // 数组查找,O(n) 但 n 很小,缓存友好 - for (size_t i = 0; i < moduleCount_; ++i) { - if (modules_[i].id == typeId) { - return static_cast(modules_[i].module.get()); - } - } - - // 添加新模块 - if (moduleCount_ >= MAX_MODULES) { - return nullptr; // 模块数量超过上限 - } - - auto module = ptr::makeUnique(std::forward(args)...); - T* ptr = module.get(); - module->setApp(app_); - - modules_[moduleCount_].id = typeId; - modules_[moduleCount_].module = std::move(module); - modules_[moduleCount_].valid = true; - ++moduleCount_; - - return ptr; - } - - /** - * @brief 获取模块 - * @tparam T 模块类型 - * @return 模块指针,不存在返回 nullptr - */ - template - T* get() const { - TypeId typeId = detail::getTypeId(); - - for (size_t i = 0; i < moduleCount_; ++i) { - if (modules_[i].id == typeId && modules_[i].valid) { - return static_cast(modules_[i].module.get()); - } - } - return nullptr; - } - - /** - * @brief 获取模块(基类版本) - * @param typeIdx 类型索引 - * @return 模块指针 - */ - Module* get(std::type_index typeIdx) const { - // 这里仍然使用type_index作为后备方案 - for (size_t i = 0; i < moduleCount_; ++i) { - if (modules_[i].valid && - std::type_index(typeid(*modules_[i].module)) == typeIdx) { - return modules_[i].module.get(); - } - } - return nullptr; - } - - /** - * @brief 设置Application - */ - void setApp(Application* app) { app_ = app; } - - /** - * @brief 初始化所有模块(按优先级拓扑排序,支持并行初始化) - * @return 初始化成功返回 true - */ - bool init(); - - /** - * @brief 关闭所有模块 - */ - void shutdown(); - - /** - * @brief 清空所有模块 - */ - void clear(); - - /** - * @brief 获取模块数量 - */ - size_t size() const { return moduleCount_; } - -private: - Registry() = default; - ~Registry() = default; - - struct ModuleEntry { - TypeId id = 0; - Unique module; - bool valid = false; - }; - - /** - * @brief 拓扑排序模块 - * @return 排序后的模块列表 - */ - std::vector sort(); - - /** - * @brief 按层级对模块进行分组 - * 同一层级的模块没有相互依赖,可以并行初始化 - * @return 按层级分组的模块列表 - */ - std::vector> group(); - - std::array modules_; - size_t moduleCount_ = 0; - Application* app_ = nullptr; -}; - -} // namespace extra2d diff --git a/Extra2D/include/extra2d/core/result.h b/Extra2D/include/extra2d/core/result.h deleted file mode 100644 index 44385d6..0000000 --- a/Extra2D/include/extra2d/core/result.h +++ /dev/null @@ -1,398 +0,0 @@ -#pragma once - -#include -#include -#include - -namespace extra2d { - -/** - * @brief 错误码枚举 - */ -enum class ErrorCode { - None = 0, - Unknown = 1, - InvalidArgument = 2, - OutOfMemory = 3, - FileNotFound = 4, - PermissionDenied = 5, - NotImplemented = 6, - AlreadyExists = 7, - NotInitialized = 8, - AlreadyInitialized = 9, - OperationFailed = 10, - Timeout = 11, - Cancelled = 12, - InvalidState = 13, - ResourceExhausted = 14, - Unavailable = 15, - DataLoss = 16, - Unauthenticated = 17, - PermissionDenied2 = 18, - ResourceNotFound = 19, - Aborted = 20, - OutOfRange = 21, - Unimplemented = 22, - Internal = 23, - DataCorrupted = 24, - RequestTooLarge = 25, - ResourceBusy = 26, - QuotaExceeded = 27, - DeadlineExceeded = 28, - LoadBalancing = 29, - NetworkError = 30, - ProtocolError = 31, - ServiceUnavailable = 32, - GatewayError = 33, - RateLimited = 34, - BadRequest = 35, - Unauthorized = 36, - Forbidden = 37, - NotFound = 38, - MethodNotAllowed = 39, - Conflict = 40, - Gone = 41, - LengthRequired = 42, - PreconditionFailed = 43, - PayloadTooLarge = 44, - UriTooLong = 45, - UnsupportedMediaType = 46, - RangeNotSatisfiable = 47, - ExpectationFailed = 48, - ImATeapot = 49, - MisdirectedRequest = 50, - UnprocessableEntity = 51, - Locked = 52, - FailedDependency = 53, - TooEarly = 54, - UpgradeRequired = 55, - PreconditionRequired = 56, - TooManyRequests = 57, - RequestHeaderFieldsTooLarge = 58, - UnavailableForLegalReasons = 59 -}; - -/** - * @brief 错误信息结构 - */ -struct Error { - ErrorCode code = ErrorCode::None; - std::string message; - std::string file; - int line = 0; - - Error() = default; - Error(ErrorCode c, const std::string& msg) : code(c), message(msg) {} - Error(ErrorCode c, const std::string& msg, const std::string& f, int l) - : code(c), message(msg), file(f), line(l) {} - - bool ok() const { return code == ErrorCode::None; } - - static Error none() { return Error(); } - static Error unknown(const std::string& msg) { return Error(ErrorCode::Unknown, msg); } - static Error invalidArgument(const std::string& msg) { return Error(ErrorCode::InvalidArgument, msg); } - static Error outOfMemory(const std::string& msg) { return Error(ErrorCode::OutOfMemory, msg); } - static Error fileNotFound(const std::string& msg) { return Error(ErrorCode::FileNotFound, msg); } - static Error permissionDenied(const std::string& msg) { return Error(ErrorCode::PermissionDenied, msg); } - static Error notImplemented(const std::string& msg) { return Error(ErrorCode::NotImplemented, msg); } - static Error alreadyExists(const std::string& msg) { return Error(ErrorCode::AlreadyExists, msg); } - static Error notInitialized(const std::string& msg) { return Error(ErrorCode::NotInitialized, msg); } - static Error alreadyInitialized(const std::string& msg) { return Error(ErrorCode::AlreadyInitialized, msg); } - static Error operationFailed(const std::string& msg) { return Error(ErrorCode::OperationFailed, msg); } - static Error timeout(const std::string& msg) { return Error(ErrorCode::Timeout, msg); } - static Error cancelled(const std::string& msg) { return Error(ErrorCode::Cancelled, msg); } - static Error invalidState(const std::string& msg) { return Error(ErrorCode::InvalidState, msg); } - static Error resourceExhausted(const std::string& msg) { return Error(ErrorCode::ResourceExhausted, msg); } - static Error unavailable(const std::string& msg) { return Error(ErrorCode::Unavailable, msg); } - static Error dataLoss(const std::string& msg) { return Error(ErrorCode::DataLoss, msg); } - static Error unauthenticated(const std::string& msg) { return Error(ErrorCode::Unauthenticated, msg); } - static Error permissionDenied2(const std::string& msg) { return Error(ErrorCode::PermissionDenied2, msg); } - static Error resourceNotFound(const std::string& msg) { return Error(ErrorCode::ResourceNotFound, msg); } - static Error aborted(const std::string& msg) { return Error(ErrorCode::Aborted, msg); } - static Error outOfRange(const std::string& msg) { return Error(ErrorCode::OutOfRange, msg); } - static Error unimplemented(const std::string& msg) { return Error(ErrorCode::Unimplemented, msg); } - static Error internal(const std::string& msg) { return Error(ErrorCode::Internal, msg); } - static Error dataCorrupted(const std::string& msg) { return Error(ErrorCode::DataCorrupted, msg); } - static Error requestTooLarge(const std::string& msg) { return Error(ErrorCode::RequestTooLarge, msg); } - static Error resourceBusy(const std::string& msg) { return Error(ErrorCode::ResourceBusy, msg); } - static Error quotaExceeded(const std::string& msg) { return Error(ErrorCode::QuotaExceeded, msg); } - static Error deadlineExceeded(const std::string& msg) { return Error(ErrorCode::DeadlineExceeded, msg); } - static Error loadBalancing(const std::string& msg) { return Error(ErrorCode::LoadBalancing, msg); } - static Error networkError(const std::string& msg) { return Error(ErrorCode::NetworkError, msg); } - static Error protocolError(const std::string& msg) { return Error(ErrorCode::ProtocolError, msg); } - static Error serviceUnavailable(const std::string& msg) { return Error(ErrorCode::ServiceUnavailable, msg); } - static Error gatewayError(const std::string& msg) { return Error(ErrorCode::GatewayError, msg); } - static Error rateLimited(const std::string& msg) { return Error(ErrorCode::RateLimited, msg); } - static Error badRequest(const std::string& msg) { return Error(ErrorCode::BadRequest, msg); } - static Error unauthorized(const std::string& msg) { return Error(ErrorCode::Unauthorized, msg); } - static Error forbidden(const std::string& msg) { return Error(ErrorCode::Forbidden, msg); } - static Error notFound(const std::string& msg) { return Error(ErrorCode::NotFound, msg); } - static Error methodNotAllowed(const std::string& msg) { return Error(ErrorCode::MethodNotAllowed, msg); } - static Error conflict(const std::string& msg) { return Error(ErrorCode::Conflict, msg); } - static Error gone(const std::string& msg) { return Error(ErrorCode::Gone, msg); } - static Error lengthRequired(const std::string& msg) { return Error(ErrorCode::LengthRequired, msg); } - static Error preconditionFailed(const std::string& msg) { return Error(ErrorCode::PreconditionFailed, msg); } - static Error payloadTooLarge(const std::string& msg) { return Error(ErrorCode::PayloadTooLarge, msg); } - static Error uriTooLong(const std::string& msg) { return Error(ErrorCode::UriTooLong, msg); } - static Error unsupportedMediaType(const std::string& msg) { return Error(ErrorCode::UnsupportedMediaType, msg); } - static Error rangeNotSatisfiable(const std::string& msg) { return Error(ErrorCode::RangeNotSatisfiable, msg); } - static Error expectationFailed(const std::string& msg) { return Error(ErrorCode::ExpectationFailed, msg); } - static Error imATeapot(const std::string& msg) { return Error(ErrorCode::ImATeapot, msg); } - static Error misdirectedRequest(const std::string& msg) { return Error(ErrorCode::MisdirectedRequest, msg); } - static Error unprocessableEntity(const std::string& msg) { return Error(ErrorCode::UnprocessableEntity, msg); } - static Error locked(const std::string& msg) { return Error(ErrorCode::Locked, msg); } - static Error failedDependency(const std::string& msg) { return Error(ErrorCode::FailedDependency, msg); } - static Error tooEarly(const std::string& msg) { return Error(ErrorCode::TooEarly, msg); } - static Error upgradeRequired(const std::string& msg) { return Error(ErrorCode::UpgradeRequired, msg); } - static Error preconditionRequired(const std::string& msg) { return Error(ErrorCode::PreconditionRequired, msg); } - static Error tooManyRequests(const std::string& msg) { return Error(ErrorCode::TooManyRequests, msg); } - static Error requestHeaderFieldsTooLarge(const std::string& msg) { return Error(ErrorCode::RequestHeaderFieldsTooLarge, msg); } - static Error unavailableForLegalReasons(const std::string& msg) { return Error(ErrorCode::UnavailableForLegalReasons, msg); } -}; - -/** - * @brief Result类型,用于错误处理 - * @tparam T 成功时的值类型 - * @tparam E 错误类型,默认为Error - */ -template -class Result { -public: - Result() : hasValue_(false) { - new (&storage_.error) E(); - } - - ~Result() { - if (hasValue_) { - storage_.value.~T(); - } else { - storage_.error.~E(); - } - } - - Result(const Result& other) : hasValue_(other.hasValue_) { - if (hasValue_) { - new (&storage_.value) T(other.storage_.value); - } else { - new (&storage_.error) E(other.storage_.error); - } - } - - Result(Result&& other) noexcept : hasValue_(other.hasValue_) { - if (hasValue_) { - new (&storage_.value) T(std::move(other.storage_.value)); - } else { - new (&storage_.error) E(std::move(other.storage_.error)); - } - } - - Result& operator=(const Result& other) { - if (this != &other) { - this->~Result(); - hasValue_ = other.hasValue_; - if (hasValue_) { - new (&storage_.value) T(other.storage_.value); - } else { - new (&storage_.error) E(other.storage_.error); - } - } - return *this; - } - - Result& operator=(Result&& other) noexcept { - if (this != &other) { - this->~Result(); - hasValue_ = other.hasValue_; - if (hasValue_) { - new (&storage_.value) T(std::move(other.storage_.value)); - } else { - new (&storage_.error) E(std::move(other.storage_.error)); - } - } - return *this; - } - - static Result ok(T value) { - Result result; - result.hasValue_ = true; - new (&result.storage_.value) T(std::move(value)); - return result; - } - - static Result err(E error) { - Result result; - result.hasValue_ = false; - new (&result.storage_.error) E(std::move(error)); - return result; - } - - bool ok() const { return hasValue_; } - bool isOk() const { return hasValue_; } - bool isErr() const { return !hasValue_; } - - T& value() & { - return storage_.value; - } - - const T& value() const & { - return storage_.value; - } - - T&& value() && { - return std::move(storage_.value); - } - - E& error() & { - return storage_.error; - } - - const E& error() const & { - return storage_.error; - } - - E&& error() && { - return std::move(storage_.error); - } - - T valueOr(T defaultValue) const { - return hasValue_ ? storage_.value : std::move(defaultValue); - } - - template - Result map(F&& f) { - if (hasValue_) { - return Result::ok(f(storage_.value)); - } - return *this; - } - - template - Result mapErr(F&& f) { - if (!hasValue_) { - return Result::err(f(storage_.error)); - } - return *this; - } - - template - auto andThen(F&& f) -> decltype(f(std::declval())) { - if (hasValue_) { - return f(storage_.value); - } - return Result()))::ValueType, E>::err(storage_.error); - } - - template - Result orElse(F&& f) { - if (!hasValue_) { - return f(storage_.error); - } - return *this; - } - -private: - union Storage { - T value; - E error; - - Storage() {} - ~Storage() {} - } storage_; - - bool hasValue_; -}; - -// 特化void类型 -template -class Result { -public: - Result() : hasValue_(true) {} - - ~Result() { - if (!hasValue_) { - storage_.error.~E(); - } - } - - Result(const Result& other) : hasValue_(other.hasValue_) { - if (!hasValue_) { - new (&storage_.error) E(other.storage_.error); - } - } - - Result(Result&& other) noexcept : hasValue_(other.hasValue_) { - if (!hasValue_) { - new (&storage_.error) E(std::move(other.storage_.error)); - } - } - - Result& operator=(const Result& other) { - if (this != &other) { - this->~Result(); - hasValue_ = other.hasValue_; - if (!hasValue_) { - new (&storage_.error) E(other.storage_.error); - } - } - return *this; - } - - Result& operator=(Result&& other) noexcept { - if (this != &other) { - this->~Result(); - hasValue_ = other.hasValue_; - if (!hasValue_) { - new (&storage_.error) E(std::move(other.storage_.error)); - } - } - return *this; - } - - static Result ok() { - return Result(); - } - - static Result err(E error) { - Result result; - result.hasValue_ = false; - new (&result.storage_.error) E(std::move(error)); - return result; - } - - bool ok() const { return hasValue_; } - bool isOk() const { return hasValue_; } - bool isErr() const { return !hasValue_; } - - E& error() & { - return storage_.error; - } - - const E& error() const & { - return storage_.error; - } - - E&& error() && { - return std::move(storage_.error); - } - -private: - union Storage { - E error; - - Storage() {} - ~Storage() {} - } storage_; - - bool hasValue_; -}; - -// 便捷宏 -#define E2D_TRY(result) \ - do { \ - auto _res = (result); \ - if (!_res.ok()) { \ - return Result::err(_res.error()); \ - } \ - } while(0) - -} // namespace extra2d diff --git a/Extra2D/include/extra2d/core/ring_buffer.h b/Extra2D/include/extra2d/core/ring_buffer.h deleted file mode 100644 index 15ede7a..0000000 --- a/Extra2D/include/extra2d/core/ring_buffer.h +++ /dev/null @@ -1,103 +0,0 @@ -#pragma once - -#include -#include -#include - -namespace extra2d { - -/** - * @brief 无锁环形缓冲区(单生产者单消费者) - * @tparam T 元素类型 - * @tparam Size 缓冲区大小(必须是2的幂) - */ -template -class RingBuffer { - static_assert((Size & (Size - 1)) == 0, "Size must be a power of 2"); - -public: - RingBuffer() = default; - - /** - * @brief 入队 - * @param item 元素 - * @return 成功返回true,缓冲区满返回false - */ - bool push(const T& item) { - const size_t currentHead = head_.load(std::memory_order_relaxed); - const size_t currentTail = tail_.load(std::memory_order_acquire); - - if ((currentHead - currentTail) >= Size) { - return false; // 缓冲区满 - } - - buffer_[currentHead & mask_] = item; - head_.store(currentHead + 1, std::memory_order_release); - return true; - } - - /** - * @brief 入队(移动语义) - * @param item 元素 - * @return 成功返回true,缓冲区满返回false - */ - bool push(T&& item) { - const size_t currentHead = head_.load(std::memory_order_relaxed); - const size_t currentTail = tail_.load(std::memory_order_acquire); - - if ((currentHead - currentTail) >= Size) { - return false; // 缓冲区满 - } - - buffer_[currentHead & mask_] = std::move(item); - head_.store(currentHead + 1, std::memory_order_release); - return true; - } - - /** - * @brief 出队 - * @param item 输出元素 - * @return 成功返回true,缓冲区空返回false - */ - bool pop(T& item) { - const size_t currentTail = tail_.load(std::memory_order_relaxed); - const size_t currentHead = head_.load(std::memory_order_acquire); - - if (currentTail == currentHead) { - return false; // 缓冲区空 - } - - item = std::move(buffer_[currentTail & mask_]); - tail_.store(currentTail + 1, std::memory_order_release); - return true; - } - - /** - * @brief 检查是否为空 - */ - bool empty() const { - return head_.load(std::memory_order_acquire) == - tail_.load(std::memory_order_acquire); - } - - /** - * @brief 获取当前大小 - */ - size_t size() const { - return head_.load(std::memory_order_acquire) - - tail_.load(std::memory_order_acquire); - } - - /** - * @brief 获取容量 - */ - static constexpr size_t capacity() { return Size; } - -private: - static constexpr size_t mask_ = Size - 1; - alignas(64) std::array buffer_; - alignas(64) std::atomic head_{0}; - alignas(64) std::atomic tail_{0}; -}; - -} // namespace extra2d diff --git a/Extra2D/include/extra2d/core/scene-graph/Camera.h b/Extra2D/include/extra2d/core/scene-graph/Camera.h new file mode 100644 index 0000000..aacfff5 --- /dev/null +++ b/Extra2D/include/extra2d/core/scene-graph/Camera.h @@ -0,0 +1,383 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace extra2d { + +namespace gfx { +class Device; +} + +class Node; +class RenderScene; + +/** + * @brief 相机投影类型 + */ +enum class CameraProjection { + Ortho, + Perspective, +}; + +/** + * @brief 相机信息结构 + */ +struct CameraInfo { + std::string name; + Node* node{nullptr}; + CameraProjection projection{CameraProjection::Ortho}; + u32 priority{0}; + u32 targetDisplay{0}; +}; + +/** + * @brief 2D 相机类 + * + * 管理 2D 场景的视图和投影变换。 + * 参考 Cocos Creator 的 Camera 设计。 + */ +class Camera : public RefCounted { +public: + /** + * @brief 创建相机 + * @param device GFX 设备 + * @return 相机引用 + */ + static Ref create(gfx::Device* device); + + /** + * @brief 构造函数 + * @param device GFX 设备 + */ + explicit Camera(gfx::Device* device); + + /** + * @brief 析构函数 + */ + ~Camera() override; + + /** + * @brief 初始化相机 + * @param info 相机信息 + * @return 成功返回 true + */ + bool initialize(const CameraInfo& info); + + /** + * @brief 销毁相机 + */ + void destroy(); + + /** + * @brief 更新相机 + * @param forceUpdate 是否强制更新 + */ + void update(bool forceUpdate = false); + + /** + * @brief 调整相机大小 + * @param width 宽度 + * @param height 高度 + */ + void resize(u32 width, u32 height); + + // ==================== 变换方法 ==================== + + /** + * @brief 将屏幕坐标转换为世界坐标 + * @param screenPos 屏幕坐标 + * @return 世界坐标 + */ + Vec2 screenToWorld(const Vec2& screenPos); + + /** + * @brief 将世界坐标转换为屏幕坐标 + * @param worldPos 世界坐标 + * @return 屏幕坐标 + */ + Vec2 worldToScreen(const Vec2& worldPos); + + /** + * @brief 将屏幕点转换为射线(用于拾取) + * @param x 屏幕 X 坐标 + * @param y 屏幕 Y 坐标 + * @return 射线原点(2D 使用) + */ + Vec2 screenPointToRay(float x, float y); + + // ==================== 属性访问器 ==================== + + /** + * @brief 设置关联节点 + * @param node 节点指针 + */ + void setNode(Node* node); + + /** + * @brief 获取关联节点 + * @return 节点指针 + */ + Node* getNode() const; + + /** + * @brief 设置是否启用 + * @param enabled 是否启用 + */ + void setEnabled(bool enabled); + + /** + * @brief 检查是否启用 + * @return 如果启用返回 true + */ + bool isEnabled() const; + + /** + * @brief 设置正交高度 + * @param height 正交高度 + */ + void setOrthoHeight(float height); + + /** + * @brief 获取正交高度 + * @return 正交高度 + */ + float getOrthoHeight() const; + + /** + * @brief 设置投影类型 + * @param projection 投影类型 + */ + void setProjectionType(CameraProjection projection); + + /** + * @brief 获取投影类型 + * @return 投影类型 + */ + CameraProjection getProjectionType() const; + + /** + * @brief 设置视场角 + * @param fov 视场角(弧度) + */ + void setFov(float fov); + + /** + * @brief 获取视场角 + * @return 视场角(弧度) + */ + float getFov() const; + + /** + * @brief 设置近裁剪面 + * @param near 近裁剪面距离 + */ + void setNearClip(float near); + + /** + * @brief 获取近裁剪面 + * @return 近裁剪面距离 + */ + float getNearClip() const; + + /** + * @brief 设置远裁剪面 + * @param far 远裁剪面距离 + */ + void setFarClip(float far); + + /** + * @brief 获取远裁剪面 + * @return 远裁剪面距离 + */ + float getFarClip() const; + + /** + * @brief 设置清除颜色 + * @param color 清除颜色 + */ + void setClearColor(const gfx::Color& color); + + /** + * @brief 获取清除颜色 + * @return 清除颜色 + */ + const gfx::Color& getClearColor() const; + + /** + * @brief 设置清除标志 + * @param flag 清除标志 + */ + void setClearFlag(gfx::ClearFlagBit flag); + + /** + * @brief 获取清除标志 + * @return 清除标志 + */ + gfx::ClearFlagBit getClearFlag() const; + + /** + * @brief 设置优先级 + * @param priority 优先级 + */ + void setPriority(u32 priority); + + /** + * @brief 获取优先级 + * @return 优先级 + */ + u32 getPriority() const; + + /** + * @brief 获取宽度 + * @return 宽度 + */ + u32 getWidth() const; + + /** + * @brief 获取高度 + * @return 高度 + */ + u32 getHeight() const; + + /** + * @brief 获取宽高比 + * @return 宽高比 + */ + float getAspect() const; + + /** + * @brief 获取视图矩阵 + * @return 视图矩阵 + */ + const glm::mat4& getMatView() const; + + /** + * @brief 获取投影矩阵 + * @return 投影矩阵 + */ + const glm::mat4& getMatProj() const; + + /** + * @brief 获取视图投影矩阵 + * @return 视图投影矩阵 + */ + const glm::mat4& getMatViewProj() const; + + /** + * @brief 获取相机名称 + * @return 相机名称 + */ + const std::string& getName() const; + + /** + * @brief 获取所属渲染场景 + * @return 渲染场景指针 + */ + RenderScene* getScene() const; + + /** + * @brief 附加到渲染场景 + * @param scene 渲染场景 + */ + void attachToScene(RenderScene* scene); + + /** + * @brief 从渲染场景分离 + */ + void detachFromScene(); + + /** + * @brief 设置可见性掩码 + * @param visibility 可见性掩码 + */ + void setVisibility(u32 visibility); + + /** + * @brief 获取可见性掩码 + * @return 可见性掩码 + */ + u32 getVisibility() const; + + /** + * @brief 设置相机位置 + * @param pos 位置 + */ + void setPosition(const Vec2& pos); + + /** + * @brief 获取相机位置 + * @return 位置 + */ + const Vec2& getPosition() const; + + /** + * @brief 设置相机前向 + * @param forward 前向向量 + */ + void setForward(const Vec2& forward); + + /** + * @brief 获取相机前向 + * @return 前向向量 + */ + const Vec2& getForward() const; + + /** + * @brief 获取相机 ID + * @return 相机 ID + */ + u32 getCameraId() const; + +protected: + /** + * @brief 更新投影矩阵 + */ + void updateProjection(); + + /** + * @brief 更新视图矩阵 + */ + void updateView(); + +private: + gfx::Device* device_{nullptr}; + RenderScene* scene_{nullptr}; + Node* node_{nullptr}; + + std::string name_; + u32 cameraId_{0}; + bool enabled_{false}; + + CameraProjection projection_{CameraProjection::Ortho}; + float aspect_{1.0f}; + float orthoHeight_{10.0f}; + float fov_{0.785398f}; + float nearClip_{1.0f}; + float farClip_{1000.0f}; + + gfx::Color clearColor_{0.2f, 0.2f, 0.2f, 1.0f}; + gfx::ClearFlagBit clearFlag_{gfx::ClearFlagBit::ALL}; + + u32 width_{0}; + u32 height_{0}; + u32 priority_{0}; + u32 visibility_{0xFFFFFFFF}; + + Vec2 position_{0, 0}; + Vec2 forward_{0, -1}; + + glm::mat4 matView_{1.0f}; + glm::mat4 matProj_{1.0f}; + glm::mat4 matViewProj_{1.0f}; + + bool isProjDirty_{true}; + bool isViewDirty_{true}; + + static u32 s_cameraIdCounter; +}; + +} // namespace extra2d diff --git a/Extra2D/include/extra2d/core/scene-graph/Component.h b/Extra2D/include/extra2d/core/scene-graph/Component.h new file mode 100644 index 0000000..26ea5b2 --- /dev/null +++ b/Extra2D/include/extra2d/core/scene-graph/Component.h @@ -0,0 +1,110 @@ +#pragma once + +#include +#include + +namespace extra2d { + +class Node; + +/** + * @brief 组件基类 + * + * 所有组件的基类,提供生命周期回调。 + * 参考 Cocos Creator 的 Component 设计。 + */ +class Component : public Object { +public: + /** + * @brief 构造函数 + * @param name 组件名称 + */ + explicit Component(const std::string& name = ""); + + /** + * @brief 析构函数 + */ + ~Component() override; + + /** + * @brief 获取所属节点 + * @return 节点指针 + */ + Node* getNode() const; + + /** + * @brief 设置所属节点 + * @param node 节点指针 + */ + void setNode(Node* node); + + /** + * @brief 检查组件是否启用 + * @return 如果启用返回 true + */ + bool isEnabled() const; + + /** + * @brief 设置组件是否启用 + * @param enabled 是否启用 + */ + void setEnabled(bool enabled); + + // ==================== 生命周期回调 ==================== + + /** + * @brief 组件添加到节点时调用 + */ + virtual void onAdd(); + + /** + * @brief 组件从节点移除时调用 + */ + virtual void onRemove(); + + /** + * @brief 组件启用时调用 + */ + virtual void onEnable(); + + /** + * @brief 组件禁用时调用 + */ + virtual void onDisable(); + + /** + * @brief 每帧更新 + * @param dt 帧间隔时间 + */ + virtual void update(f32 dt); + + /** + * @brief 每帧晚期更新 + * @param dt 帧间隔时间 + */ + virtual void lateUpdate(f32 dt); + + /** + * @brief 渲染 + */ + virtual void render(); + + /** + * @brief 销毁组件 + * @return 销毁成功返回 true + */ + bool destroy() override; + +protected: + /** + * @brief 销毁时的回调 + */ + void onDestroy() override; + + Node* node_{nullptr}; + bool enabled_{true}; + + friend class Node; +}; + +} // namespace extra2d diff --git a/Extra2D/include/extra2d/core/scene-graph/Layers.h b/Extra2D/include/extra2d/core/scene-graph/Layers.h new file mode 100644 index 0000000..276cf67 --- /dev/null +++ b/Extra2D/include/extra2d/core/scene-graph/Layers.h @@ -0,0 +1,128 @@ +#pragma once + +#include +#include +#include +#include + +namespace extra2d { + +/** + * @brief 节点层管理器 + * + * 层数据是以掩码方式存储在 Node.layer 中,用于射线检测、物理碰撞和用户自定义逻辑。 + * 每个节点可属于一个或多个层,可通过包含式或排除式检测器进行层检测。 + */ +class Layers final { +public: + Layers() = delete; + ~Layers() = delete; + + /** + * @brief 内置层列表 + */ + enum class LayerList : u32 { + None = 0, + IgnoreRaycast = (1 << 20), + Gizmos = (1 << 21), + Editor = (1 << 22), + UI3D = (1 << 23), + SceneGizmo = (1 << 24), + UI2D = (1 << 25), + Profiler = (1 << 28), + Default = (1 << 30), + All = 0xFFFFFFFF, + }; + + using Enum = LayerList; + + /** + * @brief 创建包含式层检测器 + * @param includes 包含的层列表 + * @return 掩码值 + */ + static u32 makeMaskInclude(const std::vector& includes) { + u32 mask = 0; + for (u32 inc : includes) { + mask |= inc; + } + return mask; + } + + /** + * @brief 创建排除式层检测器 + * @param excludes 排除的层列表 + * @return 掩码值 + */ + static u32 makeMaskExclude(const std::vector& excludes) { + return ~makeMaskInclude(excludes); + } + + /** + * @brief 添加一个新层 + * @param name 层名称 + * @param bitNum 层位位置(0-19 为用户自定义层) + */ + static void addLayer(const std::string& name, u32 bitNum); + + /** + * @brief 移除一个层 + * @param bitNum 层位位置 + */ + static void deleteLayer(u32 bitNum); + + /** + * @brief 根据层名称获取层索引 + * @param name 层名称 + * @return 层索引 + */ + static u32 nameToLayer(const std::string& name); + + /** + * @brief 根据层索引获取层名称 + * @param bitNum 层位位置 + * @return 层名称 + */ + static std::string layerToName(u32 bitNum); + + /** + * @brief 检查层是否包含指定层 + * @param layer 层值 + * @param mask 掩码 + * @return 如果包含返回 true + */ + static bool contains(u32 layer, u32 mask) { + return (layer & mask) != 0; + } + +private: + static std::unordered_map s_nameToLayer; + static std::unordered_map s_layerToName; +}; + +/** + * @brief 层枚举位运算操作符 + */ +inline Layers::LayerList operator|(Layers::LayerList a, Layers::LayerList b) { + return static_cast(static_cast(a) | static_cast(b)); +} + +inline Layers::LayerList operator&(Layers::LayerList a, Layers::LayerList b) { + return static_cast(static_cast(a) & static_cast(b)); +} + +inline Layers::LayerList operator~(Layers::LayerList a) { + return static_cast(~static_cast(a)); +} + +inline Layers::LayerList& operator|=(Layers::LayerList& a, Layers::LayerList b) { + a = a | b; + return a; +} + +inline Layers::LayerList& operator&=(Layers::LayerList& a, Layers::LayerList b) { + a = a & b; + return a; +} + +} // namespace extra2d diff --git a/Extra2D/include/extra2d/core/scene-graph/Model.h b/Extra2D/include/extra2d/core/scene-graph/Model.h new file mode 100644 index 0000000..e4fb2a0 --- /dev/null +++ b/Extra2D/include/extra2d/core/scene-graph/Model.h @@ -0,0 +1,241 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace extra2d { + +class Node; +class RenderScene; + +/** + * @brief 模型信息 + */ +struct ModelInfo { + Node* node{nullptr}; + u32 priority{0}; +}; + +/** + * @brief 子模型 + */ +class SubModel { +public: + SubModel(); + ~SubModel(); + + /** + * @brief 初始化子模型 + * @param vertexCount 顶点数 + * @param indexCount 索引数 + * @return 成功返回 true + */ + bool initialize(u32 vertexCount, u32 indexCount); + + /** + * @brief 销毁子模型 + */ + void destroy(); + + /** + * @brief 设置材质 + * @param material 材质 + */ + void setMaterial(Material* material); + + /** + * @brief 获取材质 + * @return 材质 + */ + Material* getMaterial() const; + + /** + * @brief 设置输入装配器 + * @param ia 输入装配器 + */ + void setInputAssembler(gfx::InputAssembler* ia); + + /** + * @brief 获取输入装配器 + * @return 输入装配器 + */ + gfx::InputAssembler* getInputAssembler() const; + +private: + Material* material_{nullptr}; + gfx::InputAssembler* inputAssembler_{nullptr}; +}; + +/** + * @brief 模型类 + * + * 管理 2D/3D 渲染模型。 + * 参考 Cocos Creator 的 Model 设计。 + */ +class Model : public RefCounted { +public: + /** + * @brief 创建模型 + * @return 模型引用 + */ + static Ref create(); + + /** + * @brief 构造函数 + */ + Model(); + + /** + * @brief 析构函数 + */ + ~Model() override; + + /** + * @brief 初始化模型 + * @param info 模型信息 + * @return 成功返回 true + */ + bool initialize(const ModelInfo& info); + + /** + * @brief 销毁模型 + */ + void destroy(); + + /** + * @brief 更新模型 + * @param stamp 时间戳 + */ + void update(u32 stamp); + + // ==================== 子模型管理 ==================== + + /** + * @brief 添加子模型 + * @param subModel 子模型 + */ + void addSubModel(SubModel* subModel); + + /** + * @brief 获取子模型数量 + * @return 子模型数量 + */ + size_t getSubModelCount() const; + + /** + * @brief 获取子模型 + * @param index 索引 + * @return 子模型 + */ + SubModel* getSubModel(size_t index) const; + + /** + * @brief 获取所有子模型 + * @return 子模型列表 + */ + const std::vector& getSubModels() const; + + // ==================== 属性访问器 ==================== + + /** + * @brief 设置关联节点 + * @param node 节点 + */ + void setNode(Node* node); + + /** + * @brief 获取关联节点 + * @return 节点 + */ + Node* getNode() const; + + /** + * @brief 设置优先级 + * @param priority 优先级 + */ + void setPriority(u32 priority); + + /** + * @brief 获取优先级 + * @return 优先级 + */ + u32 getPriority() const; + + /** + * @brief 设置可见性 + * @param visible 是否可见 + */ + void setVisible(bool visible); + + /** + * @brief 检查是否可见 + * @return 如果可见返回 true + */ + bool isVisible() const; + + /** + * @brief 设置世界矩阵 + * @param matrix 世界矩阵 + */ + void setWorldMatrix(const Transform2D& matrix); + + /** + * @brief 获取世界矩阵 + * @return 世界矩阵 + */ + const Transform2D& getWorldMatrix() const; + + /** + * @brief 设置包围盒 + * @param aabb 包围盒 + */ + void setAABB(const Rect& aabb); + + /** + * @brief 获取包围盒 + * @return 包围盒 + */ + const Rect& getAABB() const; + + /** + * @brief 设置所属渲染场景 + * @param scene 渲染场景 + */ + void setScene(RenderScene* scene); + + /** + * @brief 获取所属渲染场景 + * @return 渲染场景 + */ + RenderScene* getScene() const; + + /** + * @brief 获取模型 ID + * @return 模型 ID + */ + u64 getModelId() const; + + /** + * @brief 设置模型 ID + * @param id 模型 ID + */ + void setModelId(u64 id); + +private: + Node* node_{nullptr}; + RenderScene* scene_{nullptr}; + + std::vector subModels_; + + u32 priority_{0}; + bool visible_{true}; + u64 modelId_{0}; + + Transform2D worldMatrix_; + Rect aabb_; +}; + +} // namespace extra2d diff --git a/Extra2D/include/extra2d/core/scene-graph/Node.h b/Extra2D/include/extra2d/core/scene-graph/Node.h new file mode 100644 index 0000000..2ab78ca --- /dev/null +++ b/Extra2D/include/extra2d/core/scene-graph/Node.h @@ -0,0 +1,598 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace extra2d { + +class Node; +class Component; +class Scene; + +/** + * @brief 变换脏标记位 + */ +enum class TransformBit : u32 { + None = 0, + Position = 1 << 0, + Rotation = 1 << 1, + Scale = 1 << 2, + All = Position | Rotation | Scale, +}; + +/** + * @brief 实现枚举标志位的位运算 + */ +inline TransformBit operator|(TransformBit a, TransformBit b) { + return static_cast(static_cast(a) | static_cast(b)); +} + +inline TransformBit operator&(TransformBit a, TransformBit b) { + return static_cast(static_cast(a) & static_cast(b)); +} + +inline TransformBit operator~(TransformBit a) { + return static_cast(~static_cast(a)); +} + +inline TransformBit& operator|=(TransformBit& a, TransformBit b) { + a = a | b; + return a; +} + +inline bool hasTransformBit(TransformBit flags, TransformBit bit) { + return (static_cast(flags) & static_cast(bit)) != 0; +} + +/** + * @brief 节点遍历回调类型 + */ +using NodeWalkCallback = std::function; + +/** + * @brief 场景节点类 + * + * 场景图的核心类,管理变换、层级关系和组件。 + * 参考 Cocos Creator 的 Node 设计。 + */ +class Node : public Object { +public: + /** + * @brief 创建节点 + * @return 节点引用 + */ + static Ref create(); + + /** + * @brief 通过路径查找节点 + * @param path 路径字符串(如 "Parent/Child/Grandchild") + * @param reference 参考节点(默认从根节点开始) + * @return 找到的节点,如果未找到返回 nullptr + */ + static Node* find(const std::string& path, Node* reference = nullptr); + + /** + * @brief 构造函数 + * @param name 节点名称 + */ + explicit Node(const std::string& name = ""); + + /** + * @brief 析构函数 + */ + ~Node() override; + + // ==================== 变换属性 ==================== + + /** + * @brief 设置本地位置 + * @param pos 位置向量 + */ + void setPosition(const Vec2& pos); + + /** + * @brief 设置本地位置 + * @param x X 坐标 + * @param y Y 坐标 + */ + void setPosition(float x, float y); + + /** + * @brief 获取本地位置 + * @return 本地位置 + */ + const Vec2& getPosition() const; + + /** + * @brief 设置本地旋转角度 + * @param degrees 角度(度数) + */ + void setRotation(float degrees); + + /** + * @brief 获取本地旋转角度 + * @return 角度(度数) + */ + float getRotation() const; + + /** + * @brief 设置本地缩放 + * @param scale 缩放向量 + */ + void setScale(const Vec2& scale); + + /** + * @brief 设置本地缩放 + * @param sx X 轴缩放 + * @param sy Y 轴缩放 + */ + void setScale(float sx, float sy); + + /** + * @brief 设置统一缩放 + * @param s 缩放值 + */ + void setScale(float s); + + /** + * @brief 获取本地缩放 + * @return 本地缩放 + */ + const Vec2& getScale() const; + + // ==================== 世界变换 ==================== + + /** + * @brief 获取世界位置 + * @return 世界位置 + */ + Vec2 getWorldPosition(); + + /** + * @brief 获取世界旋转角度 + * @return 世界旋转角度(度数) + */ + float getWorldRotation(); + + /** + * @brief 获取世界缩放 + * @return 世界缩放 + */ + Vec2 getWorldScale(); + + /** + * @brief 获取世界变换矩阵 + * @return 世界变换矩阵 + */ + const Transform2D& getWorldMatrix(); + + /** + * @brief 将本地坐标转换为世界坐标 + * @param localPoint 本地坐标点 + * @return 世界坐标点 + */ + Vec2 localToWorld(const Vec2& localPoint); + + /** + * @brief 将世界坐标转换为本地坐标 + * @param worldPoint 世界坐标点 + * @return 本地坐标点 + */ + Vec2 worldToLocal(const Vec2& worldPoint); + + // ==================== 层级管理 ==================== + + /** + * @brief 添加子节点 + * @param child 子节点 + */ + void addChild(Ref child); + + /** + * @brief 移除子节点 + * @param child 子节点 + */ + void removeChild(Node* child); + + /** + * @brief 从父节点移除自身 + */ + void removeFromParent(); + + /** + * @brief 移除所有子节点 + */ + void removeAllChildren(); + + /** + * @brief 获取父节点 + * @return 父节点指针 + */ + Node* getParent(); + + /** + * @brief 获取父节点(const 版本) + * @return 父节点指针 + */ + const Node* getParent() const; + + /** + * @brief 获取所有子节点 + * @return 子节点列表 + */ + const std::vector>& getChildren() const; + + /** + * @brief 获取子节点数量 + * @return 子节点数量 + */ + size_t getChildCount() const; + + // ==================== 遍历和查找 ==================== + + /** + * @brief 遍历节点树(前序遍历) + * @param preFunc 进入节点时的回调 + */ + void walk(const NodeWalkCallback& preFunc); + + /** + * @brief 遍历节点树(前序和后序) + * @param preFunc 进入节点时的回调 + * @param postFunc 离开节点时的回调 + */ + void walk(const NodeWalkCallback& preFunc, const NodeWalkCallback& postFunc); + + /** + * @brief 通过名称查找子节点 + * @param name 节点名称 + * @return 找到的节点,如果未找到返回 nullptr + */ + Node* getChildByName(const std::string& name); + + /** + * @brief 通过标签查找子节点 + * @param tag 标签值 + * @return 找到的节点,如果未找到返回 nullptr + */ + Node* getChildByTag(i32 tag); + + /** + * @brief 通过路径查找子节点 + * @param path 路径字符串 + * @return 找到的节点,如果未找到返回 nullptr + */ + Node* getChildByPath(const std::string& path); + + // ==================== 组件系统 ==================== + + /** + * @brief 添加组件 + * @param component 组件 + */ + void addComponent(Ref component); + + /** + * @brief 移除组件 + * @param component 组件 + */ + void removeComponent(Component* component); + + /** + * @brief 获取指定类型的组件 + * @return 组件指针,如果未找到返回 nullptr + */ + template + T* getComponent(); + + /** + * @brief 获取子节点中的指定类型组件 + * @return 组件指针,如果未找到返回 nullptr + */ + template + T* getComponentInChildren(); + + /** + * @brief 获取所有指定类型的组件 + * @return 组件列表 + */ + template + std::vector getComponents(); + + // ==================== 标识和属性 ==================== + + /** + * @brief 设置标签 + * @param tag 标签值 + */ + void setTag(i32 tag); + + /** + * @brief 获取标签 + * @return 标签值 + */ + i32 getTag() const; + + /** + * @brief 设置层级(用于渲染排序) + * @param layer 层级值 + */ + void setLayer(u32 layer); + + /** + * @brief 获取层级 + * @return 层级值 + */ + u32 getLayer() const; + + // ==================== 激活和可见性 ==================== + + /** + * @brief 设置节点是否激活 + * @param active 是否激活 + */ + void setActive(bool active); + + /** + * @brief 检查节点是否激活 + * @return 如果激活返回 true + */ + bool isActive() const; + + /** + * @brief 检查节点在层级中是否激活 + * @return 如果节点及其所有父节点都激活返回 true + */ + bool isActiveInHierarchy() const; + + /** + * @brief 设置节点是否可见 + * @param visible 是否可见 + */ + void setVisible(bool visible); + + /** + * @brief 检查节点是否可见 + * @return 如果可见返回 true + */ + bool isVisible() const; + + // ==================== 渲染顺序 ==================== + + /** + * @brief 设置兄弟索引 + * @param index 索引值 + */ + void setSiblingIndex(i32 index); + + /** + * @brief 获取兄弟索引 + * @return 索引值 + */ + i32 getSiblingIndex() const; + + // ==================== 更新和渲染 ==================== + + /** + * @brief 更新节点 + * @param dt 帧间隔时间 + */ + virtual void update(f32 dt); + + /** + * @brief 渲染节点 + */ + virtual void render(); + + /** + * @brief 晚期更新 + * @param dt 帧间隔时间 + */ + virtual void lateUpdate(f32 dt); + + // ==================== 生命周期回调 ==================== + + /** + * @brief 进入场景时调用 + */ + virtual void onEnter(); + + /** + * @brief 退出场景时调用 + */ + virtual void onExit(); + + /** + * @brief 激活状态改变时调用 + * @param active 是否激活 + */ + virtual void onActiveChanged(bool active); + + // ==================== 事件系统 ==================== + + /** + * @brief 订阅事件 + * @tparam EventT 事件类型 + * @param callback 回调函数 + * @return 监听器 ID + */ + template + EventListenerID on(Fn&& callback) { + return eventTarget_.on(std::forward(callback)); + } + + /** + * @brief 取消订阅事件 + * @tparam EventT 事件类型 + * @param id 监听器 ID + */ + template + void off(EventListenerID id) { + eventTarget_.off(id); + } + + /** + * @brief 发布事件 + * @tparam EventT 事件类型 + * @param event 事件数据 + */ + template + void emit(const EventT& event) { + eventTarget_.emit(event); + } + + /** + * @brief 移除所有事件监听器 + */ + void clearAllEventListeners(); + + // ==================== 场景关联 ==================== + + /** + * @brief 获取所属场景 + * @return 场景指针 + */ + Scene* getScene(); + + /** + * @brief 设置所属场景 + * @param scene 场景指针 + */ + void setScene(Scene* scene); + + // ==================== 销毁 ==================== + + /** + * @brief 销毁节点 + * @return 销毁成功返回 true + */ + bool destroy() override; + +protected: + /** + * @brief 销毁时的回调 + */ + void onDestroy() override; + + /** + * @brief 更新世界变换 + */ + void updateWorldTransform(); + + /** + * @brief 使变换失效 + * @param dirtyBit 脏标记位 + */ + void invalidateTransform(TransformBit dirtyBit); + + /** + * @brief 设置父节点 + * @param parent 新父节点 + */ + void setParent(Node* parent); + + /** + * @brief 父节点改变时的回调 + * @param oldParent 旧父节点 + */ + virtual void onSetParent(Node* oldParent); + + /** + * @brief 子节点添加时的回调 + * @param child 添加的子节点 + */ + virtual void onChildAdded(Node* child); + + /** + * @brief 子节点移除时的回调 + * @param child 移除的子节点 + */ + virtual void onChildRemoved(Node* child); + + /** + * @brief 更新激活状态 + */ + void updateActiveInHierarchy(); + + // 本地变换 + Vec2 localPosition_{0, 0}; + float localRotation_{0}; + Vec2 localScale_{1, 1}; + + // 世界变换缓存 + mutable Vec2 worldPosition_{0, 0}; + mutable float worldRotation_{0}; + mutable Vec2 worldScale_{1, 1}; + mutable Transform2D worldMatrix_; + mutable u32 transformFlags_{static_cast(TransformBit::All)}; + + // 层级关系 + Node* parent_{nullptr}; + std::vector> children_; + i32 siblingIndex_{0}; + + // 组件 + std::vector> components_; + + // 属性 + i32 tag_{0}; + u32 layer_{0}; + bool active_{true}; + bool activeInHierarchy_{false}; + bool visible_{true}; + + // 事件目标 + EventTarget eventTarget_; + + // 场景关联 + Scene* scene_{nullptr}; + + friend class Scene; + friend class Component; +}; + +// ==================== 组件获取模板实现 ==================== + +template +T* Node::getComponent() { + for (auto& comp : components_) { + T* result = dynamic_cast(comp.get()); + if (result) { + return result; + } + } + return nullptr; +} + +template +T* Node::getComponentInChildren() { + T* result = getComponent(); + if (result) { + return result; + } + + for (auto& child : children_) { + result = child->getComponentInChildren(); + if (result) { + return result; + } + } + return nullptr; +} + +template +std::vector Node::getComponents() { + std::vector results; + for (auto& comp : components_) { + T* result = dynamic_cast(comp.get()); + if (result) { + results.push_back(result); + } + } + return results; +} + +} // namespace extra2d diff --git a/Extra2D/include/extra2d/core/scene-graph/RenderScene.h b/Extra2D/include/extra2d/core/scene-graph/RenderScene.h new file mode 100644 index 0000000..dac6a46 --- /dev/null +++ b/Extra2D/include/extra2d/core/scene-graph/RenderScene.h @@ -0,0 +1,209 @@ +#pragma once + +#include +#include +#include +#include + +namespace extra2d { + +class Node; +class Camera; +class Model; +struct DrawBatch2D; + +/** + * @brief 射线检测结果 + */ +struct RaycastResult { + Node* node{nullptr}; + float distance{0.0f}; +}; + +/** + * @brief 渲染场景信息 + */ +struct RenderSceneInfo { + std::string name; +}; + +/** + * @brief 渲染场景类 + * + * 管理渲染场景中的相机、模型和绘制批次。 + * 参考 Cocos Creator 的 RenderScene 设计。 + */ +class RenderScene : public RefCounted { +public: + /** + * @brief 创建渲染场景 + * @return 渲染场景引用 + */ + static Ref create(); + + /** + * @brief 构造函数 + */ + RenderScene(); + + /** + * @brief 析构函数 + */ + ~RenderScene() override; + + /** + * @brief 初始化渲染场景 + * @param info 场景信息 + * @return 成功返回 true + */ + bool initialize(const RenderSceneInfo& info); + + /** + * @brief 销毁渲染场景 + */ + void destroy(); + + /** + * @brief 激活渲染场景 + */ + void activate(); + + /** + * @brief 更新渲染场景 + * @param stamp 时间戳 + */ + void update(u32 stamp); + + // ==================== 相机管理 ==================== + + /** + * @brief 添加相机 + * @param camera 相机 + */ + void addCamera(Camera* camera); + + /** + * @brief 移除相机 + * @param camera 相机 + */ + void removeCamera(Camera* camera); + + /** + * @brief 移除所有相机 + */ + void removeCameras(); + + /** + * @brief 获取所有相机 + * @return 相机列表 + */ + const std::vector>& getCameras() const; + + /** + * @brief 获取主相机 + * @return 主相机指针 + */ + Camera* getMainCamera() const; + + /** + * @brief 设置主相机 + * @param camera 相机 + */ + void setMainCamera(Camera* camera); + + // ==================== 模型管理 ==================== + + /** + * @brief 添加模型 + * @param model 模型 + */ + void addModel(Model* model); + + /** + * @brief 移除模型 + * @param model 模型 + */ + void removeModel(Model* model); + + /** + * @brief 移除所有模型 + */ + void removeModels(); + + /** + * @brief 获取所有模型 + * @return 模型列表 + */ + const std::vector>& getModels() const; + + /** + * @brief 生成模型 ID + * @return 模型 ID + */ + u64 generateModelId(); + + // ==================== 批次管理 ==================== + + /** + * @brief 添加绘制批次 + * @param batch 批次 + */ + void addBatch(DrawBatch2D* batch); + + /** + * @brief 移除绘制批次 + * @param batch 批次 + */ + void removeBatch(DrawBatch2D* batch); + + /** + * @brief 移除所有批次 + */ + void removeBatches(); + + /** + * @brief 获取所有批次 + * @return 批次列表 + */ + const std::vector& getBatches() const; + + // ==================== 属性访问器 ==================== + + /** + * @brief 获取场景名称 + * @return 场景名称 + */ + const std::string& getName() const; + + /** + * @brief 设置根节点 + * @param node 根节点 + */ + void setRootNode(Node* node); + + /** + * @brief 获取根节点 + * @return 根节点 + */ + Node* getRootNode() const; + + // ==================== 全局状态 ==================== + + /** + * @brief 全局管线状态改变回调 + */ + void onGlobalPipelineStateChanged(); + +private: + std::string name_; + u64 modelId_{0}; + + std::vector> cameras_; + std::vector> models_; + std::vector batches_; + + Camera* mainCamera_{nullptr}; + Node* rootNode_{nullptr}; +}; + +} // namespace extra2d diff --git a/Extra2D/include/extra2d/core/scene-graph/Scene.h b/Extra2D/include/extra2d/core/scene-graph/Scene.h new file mode 100644 index 0000000..2d347c1 --- /dev/null +++ b/Extra2D/include/extra2d/core/scene-graph/Scene.h @@ -0,0 +1,120 @@ +#pragma once + +#include +#include +#include + +namespace extra2d { + +/** + * @brief 场景类 + * + * 管理场景图的根节点和场景生命周期。 + * 参考 Cocos Creator 的 Scene 设计。 + */ +class Scene : public Object { +public: + /** + * @brief 创建场景 + * @param name 场景名称 + * @return 场景引用 + */ + static Ref create(const std::string& name = "Scene"); + + /** + * @brief 构造函数 + * @param name 场景名称 + */ + explicit Scene(const std::string& name = "Scene"); + + /** + * @brief 析构函数 + */ + ~Scene() override; + + // ==================== 根节点管理 ==================== + + /** + * @brief 获取根节点 + * @return 根节点指针 + */ + Node* getRoot(); + + /** + * @brief 获取根节点(const 版本) + * @return 根节点指针 + */ + const Node* getRoot() const; + + // ==================== 节点查找 ==================== + + /** + * @brief 通过名称查找节点 + * @param name 节点名称 + * @return 找到的节点,如果未找到返回 nullptr + */ + Node* getNodeByName(const std::string& name); + + /** + * @brief 通过标签查找节点 + * @param tag 标签值 + * @return 找到的节点,如果未找到返回 nullptr + */ + Node* getNodeByTag(i32 tag); + + /** + * @brief 通过路径查找节点 + * @param path 路径字符串 + * @return 找到的节点,如果未找到返回 nullptr + */ + Node* getNodeByPath(const std::string& path); + + // ==================== 更新和渲染 ==================== + + /** + * @brief 更新场景 + * @param dt 帧间隔时间 + */ + void update(f32 dt); + + /** + * @brief 渲染场景 + */ + void render(); + + /** + * @brief 晚期更新 + * @param dt 帧间隔时间 + */ + void lateUpdate(f32 dt); + + // ==================== 生命周期 ==================== + + /** + * @brief 场景激活时调用 + */ + void onActivate(); + + /** + * @brief 场景停用时调用 + */ + void onDeactivate(); + + // ==================== 销毁 ==================== + + /** + * @brief 销毁场景 + * @return 销毁成功返回 true + */ + bool destroy() override; + +protected: + /** + * @brief 销毁时的回调 + */ + void onDestroy() override; + + Ref root_; +}; + +} // namespace extra2d diff --git a/Extra2D/include/extra2d/core/service_interface.h b/Extra2D/include/extra2d/core/service_interface.h deleted file mode 100644 index dcc3416..0000000 --- a/Extra2D/include/extra2d/core/service_interface.h +++ /dev/null @@ -1,144 +0,0 @@ -#pragma once - -#include -#include - -namespace extra2d { - -/** - * @brief 服务优先级枚举 - * 定义服务的初始化顺序,数值越小越先初始化 - */ -enum class ServicePriority : i32 { - Core = 0, - Event = 100, - Timer = 200, - Scene = 300, - Camera = 400, - Resource = 500, - Audio = 600, - User = 1000 -}; - -/** - * @brief 服务状态枚举 - */ -enum class ServiceState { - Uninitialized, - Initializing, - Running, - Paused, - Stopping, - Stopped -}; - -/** - * @brief 服务信息结构体 - */ -struct ServiceInfo { - std::string name; - ServicePriority priority = ServicePriority::User; - ServiceState state = ServiceState::Uninitialized; - bool enabled = true; -}; - -/** - * @brief 服务接口基类 - * 所有服务必须实现此接口,支持依赖注入和生命周期管理 - */ -class IService { - friend class ServiceLocator; - -public: - virtual ~IService() = default; - - /** - * @brief 获取服务信息 - * @return 服务信息结构体 - */ - virtual ServiceInfo info() const = 0; - - /** - * @brief 初始化服务 - * @return 初始化成功返回 true - */ - virtual bool init() = 0; - - /** - * @brief 关闭服务 - */ - virtual void shutdown() = 0; - - /** - * @brief 暂停服务 - */ - virtual void pause() { - info_.state = ServiceState::Paused; - } - - /** - * @brief 恢复服务 - */ - virtual void resume() { - if (info_.state == ServiceState::Paused) { - info_.state = ServiceState::Running; - } - } - - /** - * @brief 更新服务 - * @param dt 帧间隔时间 - */ - virtual void update(f32 dt) { } - - /** - * @brief 检查服务是否已初始化 - * @return 已初始化返回 true - */ - virtual bool initialized() const { - return info_.state == ServiceState::Running || - info_.state == ServiceState::Paused; - } - - /** - * @brief 获取服务状态 - * @return 当前服务状态 - */ - ServiceState state() const { return info_.state; } - - /** - * @brief 获取服务名称 - * @return 服务名称 - */ - const std::string& name() const { return info_.name; } - -protected: - ServiceInfo info_; - - /** - * @brief 设置服务状态 - * @param state 新状态 - */ - void setState(ServiceState state) { info_.state = state; } -}; - -/** - * @brief 类型ID生成器 - * 用于为每种服务类型生成唯一ID - */ -using ServiceTypeId = size_t; - -namespace detail { - inline ServiceTypeId nextServiceTypeId() { - static ServiceTypeId id = 0; - return ++id; - } - - template - ServiceTypeId getServiceTypeId() { - static ServiceTypeId id = nextServiceTypeId(); - return id; - } -} - -} diff --git a/Extra2D/include/extra2d/core/service_locator.h b/Extra2D/include/extra2d/core/service_locator.h deleted file mode 100644 index 565627e..0000000 --- a/Extra2D/include/extra2d/core/service_locator.h +++ /dev/null @@ -1,307 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace extra2d { - -/** - * @brief 服务工厂函数类型 - */ -template using ServiceFactory = Fn()>; - -/** - * @brief 服务定位器 - * 实现依赖注入和服务发现模式,解耦模块间依赖 - * - * 特性: - * - 类型安全的服务注册和获取 - * - 支持服务工厂延迟创建 - * - 支持服务依赖声明 - * - 线程安全(读写锁) - * - 支持 Mock 测试 - */ -class ServiceLocator { -public: - /** - * @brief 获取单例实例 - * @return 服务定位器实例引用 - */ - static ServiceLocator &instance(); - - ServiceLocator(const ServiceLocator &) = delete; - ServiceLocator &operator=(const ServiceLocator &) = delete; - - /** - * @brief 注册服务实例 - * @tparam T 服务接口类型 - * @param svc 服务实例 - */ - template void add(Ref svc) { - static_assert(std::is_base_of_v, - "T must derive from IService"); - - std::unique_lock lock(mutex_); - auto typeId = std::type_index(typeid(T)); - services_[typeId] = std::static_pointer_cast(svc); - orderedServices_.push_back(svc); - sort(); - } - - /** - * @brief 注册服务工厂 - * @tparam T 服务接口类型 - * @param fn 服务工厂函数 - */ - template void setFactory(ServiceFactory fn) { - static_assert(std::is_base_of_v, - "T must derive from IService"); - - std::unique_lock lock(mutex_); - auto typeId = std::type_index(typeid(T)); - factories_[typeId] = [fn]() -> Ref { - return std::static_pointer_cast(fn()); - }; - - // 立即创建服务实例并添加到有序列表 - auto svc = factories_[typeId](); - services_[typeId] = svc; - orderedServices_.push_back(svc); - sort(); - } - - /** - * @brief 获取服务实例 - * @tparam T 服务接口类型 - * @return 服务实例,不存在返回 nullptr - */ - template Ref get() const { - static_assert(std::is_base_of_v, - "T must derive from IService"); - - auto typeId = std::type_index(typeid(T)); - - // 读锁查询 - std::shared_lock lock(mutex_); - - auto it = services_.find(typeId); - if (it != services_.end()) { - return std::static_pointer_cast(it->second); - } - - auto factoryIt = factories_.find(typeId); - if (factoryIt != factories_.end()) { - auto svc = factoryIt->second(); - services_[typeId] = svc; - return std::static_pointer_cast(svc); - } - - return nullptr; - } - - /** - * @brief 尝试获取服务实例(不创建) - * @tparam T 服务接口类型 - * @return 服务实例,不存在返回 nullptr - */ - template Ref tryGet() const { - static_assert(std::is_base_of_v, - "T must derive from IService"); - - auto typeId = std::type_index(typeid(T)); - - // 读锁查询 - std::shared_lock lock(mutex_); - auto it = services_.find(typeId); - if (it != services_.end()) { - return std::static_pointer_cast(it->second); - } - return nullptr; - } - - /** - * @brief 检查服务是否已注册 - * @tparam T 服务接口类型 - * @return 已注册返回 true - */ - template bool has() const { - std::shared_lock lock(mutex_); - auto typeId = std::type_index(typeid(T)); - return services_.find(typeId) != services_.end() || - factories_.find(typeId) != factories_.end(); - } - - /** - * @brief 注销服务 - * @tparam T 服务接口类型 - */ - template void remove() { - std::unique_lock lock(mutex_); - auto typeId = std::type_index(typeid(T)); - - auto it = services_.find(typeId); - if (it != services_.end()) { - auto svc = it->second; - services_.erase(it); - - auto orderIt = - std::find(orderedServices_.begin(), orderedServices_.end(), svc); - if (orderIt != orderedServices_.end()) { - orderedServices_.erase(orderIt); - } - } - - factories_.erase(typeId); - } - - /** - * @brief 初始化所有已注册的服务 - * @return 所有服务初始化成功返回 true - */ - bool init(); - - /** - * @brief 关闭所有服务 - */ - void shutdown(); - - /** - * @brief 更新所有服务 - * @param dt 帧间隔时间 - */ - void update(f32 dt); - - /** - * @brief 暂停所有服务 - */ - void pause(); - - /** - * @brief 恢复所有服务 - */ - void resume(); - - /** - * @brief 获取所有服务(按优先级排序) - * @return 服务列表 - */ - std::vector> all() const; - - /** - * @brief 清空所有服务和工厂 - */ - void clear(); - - /** - * @brief 获取已注册服务数量 - * @return 服务数量 - */ - size_t size() const { return services_.size(); } - -private: - ServiceLocator() = default; - ~ServiceLocator() = default; - - /** - * @brief 按优先级排序服务 - */ - void sort(); - - mutable std::unordered_map> services_; - std::unordered_map()>> - factories_; - std::vector> orderedServices_; - mutable std::shared_mutex mutex_; -}; - -/** - * @brief 服务注册器 - * 用于静态注册服务 - */ -template class ServiceRegistrar { -public: - explicit ServiceRegistrar(ServiceFactory fn = nullptr) { - if (fn) { - ServiceLocator::instance().setFactory(fn); - } else { - ServiceLocator::instance().setFactory( - []() -> Ref { - return ptr::make(); - }); - } - } -}; - -/** - * @brief 服务注册元数据模板 - * 使用模板元编程实现编译期服务注册 - * 通过静态成员变量的初始化触发注册 - */ -template struct ServiceAutoReg { - /** - * @brief 注册标记,访问此变量时触发服务注册 - */ - static const bool registered; - - /** - * @brief 执行实际的服务注册 - * @return true 表示注册成功 - */ - static bool doRegister() { - ::extra2d::ServiceLocator::instance().setFactory( - []() -> ::extra2d::Ref { - return ::extra2d::ptr::make(); - }); - return true; - } -}; - -// 静态成员定义,在此处触发注册 -template -const bool ServiceAutoReg::registered = - ServiceAutoReg::doRegister(); - -/** - * @brief 服务注册元数据(带自定义工厂) - */ -template struct ServiceAutoRegFactory { - template struct Impl { - static const bool registered; - - static bool doRegister(Factory fn) { - ::extra2d::ServiceLocator::instance().setFactory(fn); - return true; - } - }; -}; - -template -template -const bool ServiceAutoRegFactory::Impl::registered = - ServiceAutoRegFactory::Impl::doRegister(Factory{}); - -/** - * @brief 自动注册服务宏(元数据驱动) - * 在服务实现类中使用,通过模板元编程实现自动注册 - * 比静态对象更可靠,不易被编译器优化 - */ -#define E2D_AUTO_REGISTER_SERVICE(Interface, Implementation) \ - static inline const bool E2D_CONCAT(_service_reg_, __LINE__) = \ - ServiceAutoReg::registered - -/** - * @brief 带自定义工厂的自动注册服务宏(元数据驱动) - */ -#define E2D_AUTO_REGISTER_SERVICE_FACTORY(Interface, Factory) \ - static inline const bool E2D_CONCAT(_service_factory_reg_, __LINE__) = \ - ServiceAutoRegFactory::Impl::registered - -} // namespace extra2d diff --git a/Extra2D/include/extra2d/core/service_registry.h b/Extra2D/include/extra2d/core/service_registry.h deleted file mode 100644 index 3a855f2..0000000 --- a/Extra2D/include/extra2d/core/service_registry.h +++ /dev/null @@ -1,137 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -namespace extra2d { - -/** - * @brief 服务注册信息 - */ -struct ServiceRegistration { - std::string name; - ServicePriority priority; - std::function()> factory; - bool enabled = true; -}; - -/** - * @brief 服务注册表 - * 管理服务的注册信息,支持延迟创建和配置 - */ -class ServiceRegistry { -public: - /** - * @brief 获取单例实例 - * @return 服务注册表实例引用 - */ - static ServiceRegistry& instance(); - - ServiceRegistry(const ServiceRegistry&) = delete; - ServiceRegistry& operator=(const ServiceRegistry&) = delete; - - /** - * @brief 注册服务 - * @tparam T 服务接口类型 - * @tparam Impl 服务实现类型 - * @param name 服务名称 - * @param priority 服务优先级 - */ - template - void add(const std::string& name, ServicePriority priority) { - static_assert(std::is_base_of_v, - "T must derive from IService"); - static_assert(std::is_base_of_v, - "Impl must derive from T"); - - ServiceRegistration reg; - reg.name = name; - reg.priority = priority; - reg.factory = []() -> Ref { - return std::static_pointer_cast(ptr::make()); - }; - registrations_.push_back(reg); - } - - /** - * @brief 注册服务(带工厂函数) - * @tparam T 服务接口类型 - * @param name 服务名称 - * @param priority 服务优先级 - * @param factory 工厂函数 - */ - template - void addWithFactory( - const std::string& name, - ServicePriority priority, - std::function()> factory) { - static_assert(std::is_base_of_v, - "T must derive from IService"); - - ServiceRegistration reg; - reg.name = name; - reg.priority = priority; - reg.factory = [factory]() -> Ref { - return std::static_pointer_cast(factory()); - }; - registrations_.push_back(reg); - } - - /** - * @brief 启用/禁用服务 - * @param name 服务名称 - * @param enabled 是否启用 - */ - void setEnabled(const std::string& name, bool enabled); - - /** - * @brief 创建所有已注册的服务 - * 并注册到 ServiceLocator - */ - void createAll(); - - /** - * @brief 获取所有注册信息 - * @return 注册信息列表 - */ - const std::vector& all() const { - return registrations_; - } - - /** - * @brief 清空所有注册 - */ - void clear() { - registrations_.clear(); - } - -private: - ServiceRegistry() = default; - ~ServiceRegistry() = default; - - std::vector registrations_; -}; - -/** - * @brief 自动服务注册器 - * 在全局作用域使用,自动注册服务 - */ -template -class AutoServiceRegistrar { -public: - AutoServiceRegistrar(const std::string& name, ServicePriority priority) { - ServiceRegistry::instance().add( - name, priority); - } -}; - -} - -#define E2D_REGISTER_SERVICE_AUTO(Interface, Implementation, Name, Priority) \ - namespace { \ - static ::extra2d::AutoServiceRegistrar \ - E2D_CONCAT(auto_service_registrar_, __LINE__)(Name, Priority); \ - } diff --git a/Extra2D/include/extra2d/engine/Engine.h b/Extra2D/include/extra2d/engine/Engine.h new file mode 100644 index 0000000..c50ab58 --- /dev/null +++ b/Extra2D/include/extra2d/engine/Engine.h @@ -0,0 +1,172 @@ +#pragma once + +#include +#include +#include + +namespace extra2d { + +class EventBus; + +/** + * @brief 引擎状态 + */ +enum class EngineStatus { + Start, + Pause, + Resume, + Close +}; + +/** + * @brief 引擎配置 + */ +struct EngineConfig { + i32 fps{60}; + bool fixedTimeStep{true}; +}; + +/** + * @brief 引擎核心类 + * + * 管理主循环、时间系统和调度器。 + * 参考 Cocos Creator 的 Engine 设计。 + */ +class Engine { +public: + /** + * @brief 获取单例实例 + * @return 引擎实例 + */ + static Engine* getInstance(); + + /** + * @brief 初始化引擎 + * @return 成功返回 0 + */ + i32 init(); + + /** + * @brief 关闭引擎 + */ + void close(); + + /** + * @brief 执行一帧 + */ + void tick(); + + /** + * @brief 暂停引擎 + */ + void pause(); + + /** + * @brief 恢复引擎 + */ + void resume(); + + /** + * @brief 检查是否已初始化 + * @return 如果已初始化返回 true + */ + bool isInited() const; + + /** + * @brief 检查是否暂停 + * @return 如果暂停返回 true + */ + bool isPaused() const; + + /** + * @brief 设置目标帧率 + * @param fps 目标帧率 + */ + void setPreferredFramesPerSecond(i32 fps); + + /** + * @brief 获取目标帧率 + * @return 目标帧率 + */ + i32 getPreferredFramesPerSecond() const; + + /** + * @brief 获取总帧数 + * @return 总帧数 + */ + u32 getTotalFrames() const; + + /** + * @brief 获取帧间隔时间 + * @return 帧间隔时间(秒) + */ + f32 getDeltaTime() const; + + /** + * @brief 获取总运行时间 + * @return 总运行时间(秒) + */ + f32 getTotalTime() const; + + /** + * @brief 获取帧开始时间 + * @return 帧开始时间(秒) + */ + f32 getFrameStartTime() const; + + /** + * @brief 获取调度器 + * @return 调度器指针 + */ + Scheduler* getScheduler(); + + /** + * @brief 获取事件总线 + * @return 事件总线指针 + */ + EventBus* getEventBus(); + + /** + * @brief 设置时间缩放 + * @param scale 时间缩放因子 + */ + void setTimeScale(f32 scale); + + /** + * @brief 获取时间缩放 + * @return 时间缩放因子 + */ + f32 getTimeScale() const; + + /** + * @brief 计算帧间隔时间 + * @return 帧间隔时间(秒) + */ + f32 calculateDeltaTime(); + +private: + Engine(); + ~Engine(); + + Engine(const Engine&) = delete; + Engine& operator=(const Engine&) = delete; + + static Engine* instance_; + + Unique scheduler_; + EventBus* eventBus_{nullptr}; + + i64 preferredNanosecondsPerFrame_{16666667L}; + u32 totalFrames_{0}; + f32 deltaTime_{0.0f}; + f32 totalTime_{0.0f}; + f32 frameStartTime_{0.0f}; + + std::chrono::high_resolution_clock::time_point lastFrameTime_; + std::chrono::high_resolution_clock::time_point startTime_; + + bool inited_{false}; + bool paused_{false}; +}; + +} // namespace extra2d diff --git a/Extra2D/include/extra2d/event/event.h b/Extra2D/include/extra2d/event/event.h deleted file mode 100644 index 92e00a6..0000000 --- a/Extra2D/include/extra2d/event/event.h +++ /dev/null @@ -1,172 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -namespace extra2d { - -// ============================================================================ -// 事件类型枚举 -// ============================================================================ -enum class EventType { - None = 0, - - // 窗口事件 - WindowClose, - WindowResize, - WindowFocus, - WindowLostFocus, - WindowMoved, - - // 键盘事件 - KeyPressed, - KeyReleased, - KeyRepeat, - - // 鼠标事件 - MouseButtonPressed, - MouseButtonReleased, - MouseMoved, - MouseScrolled, - - // UI 事件 - UIHoverEnter, - UIHoverExit, - UIPressed, - UIReleased, - UIClicked, - - // 游戏手柄事件 - GamepadConnected, - GamepadDisconnected, - GamepadButtonPressed, - GamepadButtonReleased, - GamepadAxisMoved, - - // 触摸事件 (移动端) - TouchBegan, - TouchMoved, - TouchEnded, - TouchCancelled, - - // 自定义事件 - Custom -}; - -// ============================================================================ -// 键盘事件数据 -// ============================================================================ -struct KeyEvent { - i32 key; - i32 scancode; - i32 mods; // 修饰键 (Shift, Ctrl, Alt, etc.) -}; - -// ============================================================================ -// 鼠标事件数据 -// ============================================================================ -struct MouseButtonEvent { - i32 button; - i32 mods; - Vec2 pos; -}; - -struct MouseMoveEvent { - Vec2 pos; - Vec2 delta; -}; - -struct MouseScrollEvent { - Vec2 offset; - Vec2 pos; -}; - -// ============================================================================ -// 窗口事件数据 -// ============================================================================ -struct WindowResizeEvent { - i32 w; - i32 h; -}; - -struct WindowMoveEvent { - i32 x; - i32 y; -}; - -// ============================================================================ -// 游戏手柄事件数据 -// ============================================================================ -struct GamepadButtonEvent { - i32 gamepadId; - i32 button; -}; - -struct GamepadAxisEvent { - i32 gamepadId; - i32 axis; - f32 value; -}; - -// ============================================================================ -// 触摸事件数据 -// ============================================================================ -struct TouchEvent { - i32 touchId; - Vec2 pos; -}; - -// ============================================================================ -// 自定义事件数据 -// ============================================================================ -struct CustomEvent { - u32 id; - void *data; -}; - -// ============================================================================ -// 事件结构 -// ============================================================================ -struct Event { - EventType type = EventType::None; - f64 timestamp = 0.0; - bool handled = false; - - // 事件数据联合体 - std::variant - data; - - // 便捷访问方法 - bool window() const { - return type == EventType::WindowClose || type == EventType::WindowResize || - type == EventType::WindowFocus || - type == EventType::WindowLostFocus || type == EventType::WindowMoved; - } - - bool keyboard() const { - return type == EventType::KeyPressed || type == EventType::KeyReleased || - type == EventType::KeyRepeat; - } - - bool mouse() const { - return type == EventType::MouseButtonPressed || - type == EventType::MouseButtonReleased || - type == EventType::MouseMoved || type == EventType::MouseScrolled; - } - - // 静态工厂方法 - static Event windowResize(i32 w, i32 h); - static Event windowClose(); - static Event keyPress(i32 key, i32 scancode, i32 mods); - static Event keyRelease(i32 key, i32 scancode, i32 mods); - static Event mousePress(i32 btn, i32 mods, Vec2 pos); - static Event mouseRelease(i32 btn, i32 mods, Vec2 pos); - static Event mouseMove(Vec2 pos, Vec2 delta); - static Event mouseScroll(Vec2 offset, Vec2 pos); -}; - -} // namespace extra2d diff --git a/Extra2D/include/extra2d/event/event_dispatcher.h b/Extra2D/include/extra2d/event/event_dispatcher.h deleted file mode 100644 index 4d01f25..0000000 --- a/Extra2D/include/extra2d/event/event_dispatcher.h +++ /dev/null @@ -1,56 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -namespace extra2d { - -// ============================================================================ -// 事件监听器 ID -// ============================================================================ -using ListenerID = u64; - -// ============================================================================ -// 事件分发器 -// ============================================================================ -class EventDispatcher { -public: - using EventFn = Fn; - - EventDispatcher(); - ~EventDispatcher() = default; - - // 添加监听器 - ListenerID on(EventType type, EventFn fn); - - // 移除监听器 - void off(ListenerID id); - void offAll(EventType type); - void offAll(); - - // 分发事件 - void dispatch(Event &event); - void dispatch(const Event &event); - - // 处理事件队列 - void process(class EventQueue &queue); - - // 统计 - size_t listenerCount(EventType type) const; - size_t totalListeners() const; - -private: - struct Listener { - ListenerID id; - EventType type; - EventFn fn; - }; - - std::unordered_map> listeners_; - ListenerID nextId_; -}; - -} // namespace extra2d diff --git a/Extra2D/include/extra2d/event/event_queue.h b/Extra2D/include/extra2d/event/event_queue.h deleted file mode 100644 index 2c01fab..0000000 --- a/Extra2D/include/extra2d/event/event_queue.h +++ /dev/null @@ -1,43 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -namespace extra2d { - -// ============================================================================ -// 事件队列 - 线程安全的事件队列 -// ============================================================================ -class EventQueue { -public: - static constexpr size_t DEFAULT_CAPACITY = 1024; - - EventQueue(); - ~EventQueue() = default; - - // 添加事件到队列 - bool push(const Event &event); - bool push(Event &&event); - - // 从队列取出事件 - bool poll(Event &event); - - // 查看队列头部事件(不移除) - bool peek(Event &event) const; - - // 清空队列 - void clear(); - - // 队列状态 - bool empty() const; - size_t size() const; - size_t capacity() const { return buffer_.capacity(); } - -private: - RingBuffer buffer_; - mutable std::mutex mutex_; // 用于peek和clear的互斥 -}; - -} // namespace extra2d diff --git a/Extra2D/include/extra2d/extra2d.h b/Extra2D/include/extra2d/extra2d.h index f384383..b5f6d89 100644 --- a/Extra2D/include/extra2d/extra2d.h +++ b/Extra2D/include/extra2d/extra2d.h @@ -3,45 +3,79 @@ // Extra2D - 统一入口头文件 // 包含所有公共 API +// Base +#include +#include + // Core #include #include -#include -#include #include +#include + +// Core - Event +#include +#include + +// Core - Scene Graph +#include +#include +#include +#include +#include +#include + +// Core - Assets +#include +#include +#include +#include +#include // Platform -#include -#include -#include +#include +#include +#include -// Event -#include -#include -#include +// Engine +#include + +// Application +#include + +// GFX - Base +#include +#include +#include +#include +#include +#include +#include +#include + +// GFX - OpenGL Backend +#include +#include +#include +#include +#include +#include + +// 2D Renderer +#include +#include +#include +#include +#include + +// 2D Components +#include +#include // Utils #include #include -// Services -#include -#include -#include -#include - -// Asset -#include -#include -#include -#include -#include -#include -#include - -// Application -#include - #ifdef __SWITCH__ #include #endif diff --git a/Extra2D/include/extra2d/gfx/GFXBuffer.h b/Extra2D/include/extra2d/gfx/GFXBuffer.h new file mode 100644 index 0000000..0250cf6 --- /dev/null +++ b/Extra2D/include/extra2d/gfx/GFXBuffer.h @@ -0,0 +1,170 @@ +#pragma once + +#include +#include + +namespace extra2d { +namespace gfx { + +/** + * @brief 缓冲区创建信息 + */ +struct BufferInfo { + BufferUsage usage{BufferUsage::None}; + MemoryUsage memUsage{MemoryUsage::Device}; + u32 size{0}; + u32 stride{0}; + BufferFlag flags{BufferFlag::None}; + const void* initData{nullptr}; +}; + +/** + * @brief 缓冲区视图信息 + */ +struct BufferViewInfo { + class Buffer* buffer{nullptr}; + u32 offset{0}; + u32 range{0}; +}; + +/** + * @brief 缓冲区抽象基类 + * + * GPU 缓冲区的抽象接口,支持顶点缓冲、索引缓冲、Uniform 缓冲等。 + */ +class Buffer : public GFXObject { +public: + /** + * @brief 构造函数 + */ + Buffer(); + + /** + * @brief 析构函数 + */ + ~Buffer() override; + + /** + * @brief 初始化缓冲区 + * @param info 缓冲区信息 + * @return 成功返回 true + */ + bool initialize(const BufferInfo& info); + + /** + * @brief 初始化缓冲区视图 + * @param info 视图信息 + * @return 成功返回 true + */ + bool initialize(const BufferViewInfo& info); + + /** + * @brief 销毁缓冲区 + */ + void destroy(); + + /** + * @brief 调整缓冲区大小 + * @param size 新大小 + */ + void resize(u32 size); + + /** + * @brief 更新缓冲区数据 + * @param data 数据指针 + * @param size 数据大小 + */ + virtual void update(const void* data, u32 size) = 0; + + /** + * @brief 写入数据到指定偏移 + * @param data 数据指针 + * @param offset 偏移量 + * @param size 数据大小 + */ + void write(const void* data, u32 offset, u32 size); + + /** + * @brief 获取用途 + * @return 用途标志 + */ + BufferUsage getUsage() const; + + /** + * @brief 获取内存用途 + * @return 内存用途 + */ + MemoryUsage getMemUsage() const; + + /** + * @brief 获取缓冲区大小 + * @return 大小(字节) + */ + u32 getSize() const; + + /** + * @brief 获取步长 + * @return 步长(字节) + */ + u32 getStride() const; + + /** + * @brief 获取元素数量 + * @return 元素数量 + */ + u32 getCount() const; + + /** + * @brief 获取标志 + * @return 标志 + */ + BufferFlag getFlags() const; + + /** + * @brief 检查是否为视图 + * @return 如果是视图返回 true + */ + bool isBufferView() const; + + /** + * @brief 获取类型名称 + * @return "Buffer" + */ + const char* getTypeName() const override { return "Buffer"; } + +protected: + /** + * @brief 子类实现初始化 + */ + virtual bool doInit(const BufferInfo& info) = 0; + + /** + * @brief 子类实现视图初始化 + */ + virtual bool doInit(const BufferViewInfo& info) = 0; + + /** + * @brief 子类实现销毁 + */ + virtual void doDestroy() = 0; + + /** + * @brief 子类实现调整大小 + */ + virtual void doResize(u32 size) = 0; + + BufferUsage usage_{BufferUsage::None}; + MemoryUsage memUsage_{MemoryUsage::Device}; + u32 size_{0}; + u32 stride_{0}; + u32 count_{0}; + BufferFlag flags_{BufferFlag::None}; + bool isView_{false}; + + Buffer* sourceBuffer_{nullptr}; + u32 viewOffset_{0}; + u32 viewRange_{0}; +}; + +} // namespace gfx +} // namespace extra2d diff --git a/Extra2D/include/extra2d/gfx/GFXCommandBuffer.h b/Extra2D/include/extra2d/gfx/GFXCommandBuffer.h new file mode 100644 index 0000000..bd93225 --- /dev/null +++ b/Extra2D/include/extra2d/gfx/GFXCommandBuffer.h @@ -0,0 +1,206 @@ +#pragma once + +#include +#include +#include +#include + +namespace extra2d { +namespace gfx { + +class Buffer; +class Texture; +class RenderPass; +class Framebuffer; +class PipelineState; +class InputAssembler; +class DescriptorSet; + +/** + * @brief 绘制区域信息 + */ +struct Viewport { + float left{0.0f}; + float top{1.0f}; + float width{1.0f}; + float height{1.0f}; + float minDepth{0.0f}; + float maxDepth{1.0f}; +}; + +/** + * @brief 矩形区域 + */ +struct Rect { + float x{0.0f}; + float y{0.0f}; + float width{1.0f}; + float height{0.0f}; +}; + +/** + * @brief 清除标志位运算 + */ +inline ClearFlagBit operator|(ClearFlagBit a, ClearFlagBit b) { + return static_cast(static_cast(a) | static_cast(b)); +} + +inline ClearFlagBit& operator|=(ClearFlagBit& a, ClearFlagBit b) { + a = a | b; + return a; +} + +/** + * @brief 命令缓冲区类 + * + * 记录和执行渲染命令。 + * 参考 Cocos Creator 的 CommandBuffer 设计。 + */ +class CommandBuffer : public Object { +public: + /** + * @brief 析构函数 + */ + ~CommandBuffer() override; + + // ==================== 绑定命令 ==================== + + /** + * @brief 绑定管线状态 + * @param pipelineState 管线状态 + */ + virtual void bindPipelineState(PipelineState* pipelineState) = 0; + + /** + * @brief 绑定输入装配器 + * @param inputAssembler 输入装配器 + */ + virtual void bindInputAssembler(InputAssembler* inputAssembler) = 0; + + /** + * @brief 绑定描述符集 + * @param descriptorSet 描述符集 + * @param setIndex 集合索引 + */ + virtual void bindDescriptorSet(DescriptorSet* descriptorSet, u32 setIndex) = 0; + + /** + * @brief 绑定帧缓冲 + * @param framebuffer 帧缓冲 + * @param renderPass 渲染通道 + */ + virtual void bindFramebuffer(Framebuffer* framebuffer, RenderPass* renderPass) = 0; + + // ==================== 设置命令 ==================== + + /** + * @brief 设置视口 + * @param viewport 视口 + */ + virtual void setViewport(const Viewport& viewport) = 0; + + /** + * @brief 设置矩形区域 + * @param rect 矩形区域 + */ + virtual void setScissor(const Rect& rect) = 0; + + /** + * @brief 设置混合常量 + * @param constants 常量数组 + */ + virtual void setBlendConstants(const float* constants) = 0; + + /** + * @brief 设置深度偏移 + * @param bias 偏移值 + */ + virtual void setDepthBias(float bias, float slope, float clamp) = 0; + + /** + * @brief 设置模板参考值 + * @param front 前面参考值 + * @param back 后面参考值 + */ + virtual void setStencilReference(u32 front, u32 back) = 0; + + // ==================== 绘制命令 ==================== + + /** + * @brief 清除颜色 + * @param color 颜色值 + */ + virtual void clearColor(const Color& color) = 0; + + /** + * @brief 清除深度模板 + * @param depth 深度值 + * @param stencil 模板值 + */ + virtual void clearDepthStencil(float depth, u32 stencil) = 0; + + /** + * @brief 绘制 + * @param vertexCount 顶点数量 + * @param firstVertex 起始顶点 + * @param vertexCount 顶点数量 + */ + virtual void drawArrays(u32 vertexCount, u32 firstVertex = 0, u32 instanceCount = 1, u32 firstInstance = 0) = 0; + + /** + * @brief 绘制索引 + * @param indexCount 索引数量 + * @param firstIndex 起始索引 + * @param instanceCount 实例数量 + * @param firstInstance 起始实例 + */ + virtual void drawIndexed(u32 indexCount, u32 firstIndex = 0, u32 instanceCount = 1, u32 firstInstance = 0) = 0; + + /** + * @brief 绘制实例化 + * @param inputAssembler 输入装配器 + * @param instanceCount 实例数量 + */ + virtual void drawInstanced(InputAssembler* inputAssembler, u32 instanceCount) = 0; + + // ==================== 更新命令 ==================== + + /** + * @brief 更新缓冲区 + * @param buffer 缓冲区 + * @param offset 偏移量 + * @param data 数据指针 + * @param size 数据大小 + */ + virtual void updateBuffer(Buffer* buffer, u32 offset, const void* data, u32 size) = 0; + + /** + * @brief 复制缓冲区 + * @param src 源缓冲区 + * @param srcOffset 源偏移量 + * @param dst 目标缓冲区 + * @param dstOffset 目标偏移量 + * @param size 大小 + */ + virtual void copyBuffer(Buffer* src, u32 srcOffset, Buffer* dst, u32 dstOffset, u32 size) = 0; + + /** + * @brief 复制纹理 + * @param src 源纹理 + * @param dst 目标纹理 + */ + virtual void copyTexture(Texture* src, Texture* dst) = 0; + + /** + * @brief 执行渲染通道 + * @param renderPass 渲染通道 + * @param callback 回调函数 + */ + virtual void executeRenderPass(RenderPass* renderPass, const std::function& callback) = 0; + +protected: + CommandBuffer() = default; +}; + +} // namespace gfx +} // namespace extra2d diff --git a/Extra2D/include/extra2d/gfx/GFXDef.h b/Extra2D/include/extra2d/gfx/GFXDef.h new file mode 100644 index 0000000..28f1b54 --- /dev/null +++ b/Extra2D/include/extra2d/gfx/GFXDef.h @@ -0,0 +1,450 @@ +#pragma once + +#include +#include + +namespace extra2d { +namespace gfx { + +/** + * @brief 图形 API 类型 + */ +enum class API : u32 { + Unknown, + GLES2, + GLES3, + Metal, + Vulkan, + OpenGL, + WebGL, + WebGL2, +}; + +/** + * @brief 设备特性 + */ +enum class Feature : u32 { + ElementIndexUint, + InstancedArrays, + MultipleRenderTargets, + BlendMinmax, + ComputeShader, + InputAttachmentBenefit, + SubpassColorInput, + SubpassDepthStencilInput, + MultiSampleResolveDepthStencil, + Count, +}; + +/** + * @brief 格式类型 + */ +enum class Format : u32 { + Unknown, + + // 8-bit formats + R8, + R8SN, + R8UI, + R8I, + + // 16-bit formats + R16UI, + R16I, + R16F, + RG8, + RG8SN, + RG8UI, + RG8I, + + // 32-bit formats + R32UI, + R32I, + R32F, + RG16UI, + RG16I, + RG16F, + RGBA8, + RGBA8SN, + RGBA8UI, + RGBA8I, + BGRA8, + + // Packed formats + RGB10A2, + RGB10A2UI, + R11G11B10F, + + // 64-bit formats + RG32UI, + RG32I, + RG32F, + RGBA16UI, + RGBA16I, + RGBA16F, + + // 128-bit formats + RGBA32UI, + RGBA32I, + RGBA32F, + + // Depth formats + Depth, + DepthStencil, + Depth24Stencil8, + + // Compressed formats + BC1, + BC1Alpha, + BC2, + BC3, + BC4, + BC4SN, + BC5, + BC5SN, + BC6H, + BC6HUF, + BC7, + + Count, +}; + +/** + * @brief 格式特性标志 + */ +enum class FormatFeature : u32 { + None = 0, + Sampled = 1 << 0, + Storage = 1 << 1, + ColorAttachment = 1 << 2, + DepthStencilAttachment = 1 << 3, + Blend = 1 << 4, +}; + +/** + * @brief 缓冲区用途标志 + */ +enum class BufferUsage : u32 { + None = 0, + TransferSrc = 1 << 0, + TransferDst = 1 << 1, + Index = 1 << 2, + Vertex = 1 << 3, + Uniform = 1 << 4, + Storage = 1 << 5, + Indirect = 1 << 6, +}; + +/** + * @brief 缓冲区标志 + */ +enum class BufferFlag : u32 { + None = 0, + EnableStagingWrite = 1 << 0, +}; + +/** + * @brief 内存用途 + */ +enum class MemoryUsage : u32 { + None = 0, + Device = 1 << 0, + Host = 1 << 1, +}; + +/** + * @brief 纹理类型 + */ +enum class TextureType : u32 { + Tex1D, + Tex2D, + Tex3D, + Cube, + Tex1DArray, + Tex2DArray, +}; + +/** + * @brief 纹理用途标志 + */ +enum class TextureUsage : u32 { + None = 0, + TransferSrc = 1 << 0, + TransferDst = 1 << 1, + Sampled = 1 << 2, + Storage = 1 << 3, + ColorAttachment = 1 << 4, + DepthStencilAttachment = 1 << 5, + InputAttachment = 1 << 6, +}; + +/** + * @brief 纹理标志 + */ +enum class TextureFlag : u32 { + None = 0, + GenMipmaps = 1 << 0, + ExternalOES = 1 << 1, + LazilyAllocated = 1 << 2, +}; + +/** + * @brief 采样计数 + */ +enum class SampleCount : u32 { + X1 = 1, + X2 = 2, + X4 = 4, + X8 = 8, + X16 = 16, +}; + +/** + * @brief 着色器阶段标志 + */ +enum class ShaderStage : u32 { + None = 0, + Vertex = 1 << 0, + Geometry = 1 << 1, + Fragment = 1 << 2, + Compute = 1 << 3, + All = Vertex | Geometry | Fragment | Compute, +}; + +/** + * @brief 图元模式 + */ +enum class PrimitiveMode : u32 { + PointList, + LineList, + LineStrip, + LineLoop, + TriangleList, + TriangleStrip, + TriangleFan, +}; + +/** + * @brief 多边形模式 + */ +enum class PolygonMode : u32 { + Fill, + Point, + Line, +}; + +/** + * @brief 剔除模式 + */ +enum class CullMode : u32 { + None, + Front, + Back, +}; + +/** + * @brief 深度比较函数 + */ +enum class ComparisonFunc : u32 { + Never, + Less, + Equal, + LessEqual, + Greater, + NotEqual, + GreaterEqual, + Always, +}; + +/** + * @brief 模板操作 + */ +enum class StencilOp : u32 { + Zero, + Keep, + Replace, + Increment, + Decrement, + Invert, + IncrementWrap, + DecrementWrap, +}; + +/** + * @brief 混合因子 + */ +enum class BlendFactor : u32 { + Zero, + One, + SrcAlpha, + DstAlpha, + OneMinusSrcAlpha, + OneMinusDstAlpha, + SrcColor, + DstColor, + OneMinusSrcColor, + OneMinusDstColor, + SrcAlphaSaturate, + ConstantColor, + OneMinusConstantColor, + ConstantAlpha, + OneMinusConstantAlpha, +}; + +/** + * @brief 混合操作 + */ +enum class BlendOp : u32 { + Add, + Subtract, + ReverseSubtract, + Min, + Max, +}; + +/** + * @brief 管线绑定点 + */ +enum class PipelineBindPoint : u32 { + Graphics, + Compute, +}; + +/** + * @brief 动态状态标志 + */ +enum class DynamicStateFlag : u32 { + None = 0, + Viewport = 1 << 0, + Scissor = 1 << 1, + LineWidth = 1 << 2, + DepthBias = 1 << 3, + BlendConstants = 1 << 4, + DepthBounds = 1 << 5, + StencilCompareMask = 1 << 6, + StencilWriteMask = 1 << 7, + StencilReference = 1 << 8, +}; + +/** + * @brief 过滤模式 + */ +enum class Filter : u32 { + None, + Point, + Linear, +}; + +/** + * @brief 寻址模式 + */ +enum class Address : u32 { + Wrap, + Mirror, + Clamp, + Border, + MirrorOnce, +}; + +/** + * @brief 设备能力信息 + */ +struct DeviceCaps { + API api{API::Unknown}; + u32 maxVertexAttributes{16}; + u32 maxVertexUniformVectors{256}; + u32 maxFragmentUniformVectors{256}; + u32 maxTextureUnits{16}; + u32 maxVertexTextureUnits{8}; + u32 maxDrawBuffers{8}; + u32 maxColorAttachments{8}; + u32 maxTextureSize{4096}; + u32 maxCubeMapSize{4096}; + u32 maxArrayLayers{256}; + u32 maxSamples{4}; + float maxAnisotropy{16.0f}; + bool instancedArrays{false}; + bool standardDerivatives{false}; + bool elementIndexUint{false}; + bool depth24{false}; + bool depth32{false}; + bool depth24Stencil8{false}; +}; + +/** + * @brief 格式特性标志位运算 + */ +inline FormatFeature operator|(FormatFeature a, FormatFeature b) { + return static_cast(static_cast(a) | static_cast(b)); +} + +inline FormatFeature operator&(FormatFeature a, FormatFeature b) { + return static_cast(static_cast(a) & static_cast(b)); +} + +inline bool hasFeature(FormatFeature flags, FormatFeature flag) { + return (static_cast(flags) & static_cast(flag)) != 0; +} + +/** + * @brief 缓冲区用途标志位运算 + */ +inline BufferUsage operator|(BufferUsage a, BufferUsage b) { + return static_cast(static_cast(a) | static_cast(b)); +} + +inline BufferUsage operator&(BufferUsage a, BufferUsage b) { + return static_cast(static_cast(a) & static_cast(b)); +} + +inline bool hasUsage(BufferUsage flags, BufferUsage flag) { + return (static_cast(flags) & static_cast(flag)) != 0; +} + +/** + * @brief 纹理用途标志位运算 + */ +inline TextureUsage operator|(TextureUsage a, TextureUsage b) { + return static_cast(static_cast(a) | static_cast(b)); +} + +inline TextureUsage operator&(TextureUsage a, TextureUsage b) { + return static_cast(static_cast(a) & static_cast(b)); +} + +inline bool hasUsage(TextureUsage flags, TextureUsage flag) { + return (static_cast(flags) & static_cast(flag)) != 0; +} + +/** + * @brief 着色器阶段标志位运算 + */ +inline ShaderStage operator|(ShaderStage a, ShaderStage b) { + return static_cast(static_cast(a) | static_cast(b)); +} + +inline ShaderStage operator&(ShaderStage a, ShaderStage b) { + return static_cast(static_cast(a) & static_cast(b)); +} + +inline bool hasStage(ShaderStage flags, ShaderStage flag) { + return (static_cast(flags) & static_cast(flag)) != 0; +} + +/** + * @brief 动态状态标志位运算 + */ +inline DynamicStateFlag operator|(DynamicStateFlag a, DynamicStateFlag b) { + return static_cast(static_cast(a) | static_cast(b)); +} + +inline DynamicStateFlag operator&(DynamicStateFlag a, DynamicStateFlag b) { + return static_cast(static_cast(a) & static_cast(b)); +} + +inline bool hasDynamicState(DynamicStateFlag flags, DynamicStateFlag flag) { + return (static_cast(flags) & static_cast(flag)) != 0; +} + +} // namespace gfx +} // namespace extra2d diff --git a/Extra2D/include/extra2d/gfx/GFXDevice.h b/Extra2D/include/extra2d/gfx/GFXDevice.h new file mode 100644 index 0000000..4465daa --- /dev/null +++ b/Extra2D/include/extra2d/gfx/GFXDevice.h @@ -0,0 +1,276 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace extra2d { +namespace gfx { + +class Shader; +class PipelineState; +class PipelineLayout; +class RenderPass; +class Framebuffer; +class InputAssembler; +class CommandBuffer; +class DescriptorSet; +class DescriptorSetLayout; + +/** + * @brief 设备创建信息 + */ +struct DeviceInfo { + std::string windowHandle; + u32 width{1280}; + u32 height{720}; + bool debug{false}; +}; + +/** + * @brief GFX 设备抽象基类 + * + * 图形设备的抽象接口,管理所有 GPU 资源的创建和生命周期。 + * 参考 Cocos Creator 的 GFXDevice 设计。 + */ +class Device : public GFXObject { +public: + /** + * @brief 获取单例实例 + * @return 设备实例 + */ + static Device* getInstance(); + + /** + * @brief 构造函数 + */ + Device(); + + /** + * @brief 析构函数 + */ + ~Device() override; + + /** + * @brief 初始化设备 + * @param info 设备信息 + * @return 成功返回 true + */ + bool initialize(const DeviceInfo& info); + + /** + * @brief 销毁设备 + */ + void destroy(); + + /** + * @brief 帧同步 + * + * 等待 GPU 完成当前帧的所有工作 + */ + virtual void frameSync() = 0; + + /** + * @brief 呈现帧 + */ + virtual void present() = 0; + + // ==================== 资源创建工厂方法 ==================== + + /** + * @brief 创建缓冲区 + * @param info 缓冲区信息 + * @return 缓冲区指针 + */ + virtual Buffer* createBuffer(const BufferInfo& info) = 0; + + /** + * @brief 创建纹理 + * @param info 纹理信息 + * @return 纹理指针 + */ + virtual Texture* createTexture(const TextureInfo& info) = 0; + + /** + * @brief 创建着色器 + * @param name 着色器名称 + * @param vertexSource 顶点着色器源码 + * @param fragmentSource 片段着色器源码 + * @return 着色器指针 + */ + virtual Shader* createShader(const std::string& name, + const std::string& vertexSource, + const std::string& fragmentSource) = 0; + + /** + * @brief 创建管线状态 + * @param info 管线状态信息 + * @return 管线状态指针 + */ + virtual PipelineState* createPipelineState(const PipelineStateInfo& info) = 0; + + /** + * @brief 创建输入装配器 + * @param info 输入装配器信息 + * @return 输入装配器指针 + */ + virtual InputAssembler* createInputAssembler(const InputAssemblerInfo& info) = 0; + + // ==================== 数据传输 ==================== + + /** + * @brief 复制缓冲区数据到纹理 + * @param buffers 缓冲区数组 + * @param dst 目标纹理 + * @param regions 复制区域 + * @param count 区域数量 + */ + virtual void copyBuffersToTexture(const u8* const* buffers, Texture* dst, + const BufferTextureCopy* regions, u32 count) = 0; + + // ==================== 设备能力查询 ==================== + + /** + * @brief 获取设备能力 + * @return 设备能力信息 + */ + const DeviceCaps& getCapabilities() const; + + /** + * @brief 获取图形 API 类型 + * @return API 类型 + */ + API getGfxAPI() const; + + /** + * @brief 检查是否支持指定特性 + * @param feature 特性 + * @return 如果支持返回 true + */ + bool hasFeature(Feature feature) const; + + /** + * @brief 获取格式特性 + * @param format 格式 + * @return 格式特性标志 + */ + FormatFeature getFormatFeatures(Format format) const; + + /** + * @brief 获取类型名称 + * @return "Device" + */ + const char* getTypeName() const override { return "Device"; } + + // ==================== 状态管理 ==================== + + /** + * @brief 设置视口 + * @param x X 坐标 + * @param y Y 坐标 + * @param width 宽度 + * @param height 高度 + */ + virtual void setViewport(float x, float y, float width, float height) = 0; + + /** + * @brief 设置裁剪区域 + * @param x X 坐标 + * @param y Y 坐标 + * @param width 宽度 + * @param height 高度 + */ + virtual void setScissor(i32 x, i32 y, u32 width, u32 height) = 0; + + /** + * @brief 清除颜色缓冲区 + * @param r 红色分量 + * @param g 绿色分量 + * @param b 蓝色分量 + * @param a 透明度 + */ + virtual void clearColor(float r, float g, float b, float a) = 0; + + /** + * @brief 清除深度和模板缓冲区 + * @param depth 深度值 + * @param stencil 模板值 + */ + virtual void clearDepthStencil(float depth, u8 stencil) = 0; + + /** + * @brief 开始渲染通道 + */ + virtual void beginRenderPass() = 0; + + /** + * @brief 结束渲染通道 + */ + virtual void endRenderPass() = 0; + + /** + * @brief 绑定管线状态 + * @param pipeline 管线状态 + */ + virtual void bindPipelineState(PipelineState* pipeline) = 0; + + /** + * @brief 绑定输入装配器 + * @param ia 输入装配器 + */ + virtual void bindInputAssembler(InputAssembler* ia) = 0; + + /** + * @brief 绘制 + * @param firstVertex 起始顶点 + * @param vertexCount 顶点数量 + */ + virtual void draw(u32 firstVertex, u32 vertexCount) = 0; + + /** + * @brief 绘制索引 + * @param firstIndex 起始索引 + * @param indexCount 索引数量 + * @param vertexOffset 顶点偏移 + */ + virtual void drawIndexed(u32 firstIndex, u32 indexCount, i32 vertexOffset) = 0; + + /** + * @brief 绘制实例 + * @param firstVertex 起始顶点 + * @param vertexCount 顶点数量 + * @param instanceCount 实例数量 + */ + virtual void drawInstanced(u32 firstVertex, u32 vertexCount, u32 instanceCount) = 0; + + /** + * @brief 绘制索引实例 + * @param firstIndex 起始索引 + * @param indexCount 索引数量 + * @param instanceCount 实例数量 + * @param vertexOffset 顶点偏移 + */ + virtual void drawIndexedInstanced(u32 firstIndex, u32 indexCount, + u32 instanceCount, i32 vertexOffset) = 0; + +protected: + /** + * @brief 子类实现初始化 + */ + virtual bool doInit(const DeviceInfo& info) = 0; + + /** + * @brief 子类实现销毁 + */ + virtual void doDestroy() = 0; + + static Device* instance_; + DeviceCaps caps_; + bool initialized_{false}; +}; + +} // namespace gfx +} // namespace extra2d diff --git a/Extra2D/include/extra2d/gfx/GFXInputAssembler.h b/Extra2D/include/extra2d/gfx/GFXInputAssembler.h new file mode 100644 index 0000000..7fd9271 --- /dev/null +++ b/Extra2D/include/extra2d/gfx/GFXInputAssembler.h @@ -0,0 +1,139 @@ +#pragma once + +#include +#include +#include + +namespace extra2d { +namespace gfx { + +/** + * @brief 顶点流信息 + */ +struct VertexStream { + Buffer* buffer{nullptr}; + u32 offset{0}; +}; + +/** + * @brief 输入装配器信息 + */ +struct InputAssemblerInfo { + std::vector vertexStreams; + Buffer* indexBuffer{nullptr}; + u32 indexOffset{0}; + u32 firstIndex{0}; + u32 indexCount{0}; + u32 vertexCount{0}; + u32 instanceCount{1}; + u32 firstInstance{0}; + u32 vertexOffset{0}; +}; + +/** + * @brief 输入装配器抽象基类 + * + * 管理顶点缓冲区和索引缓冲区的绑定。 + */ +class InputAssembler : public GFXObject { +public: + /** + * @brief 构造函数 + */ + InputAssembler(); + + /** + * @brief 析构函数 + */ + ~InputAssembler() override; + + /** + * @brief 初始化输入装配器 + * @param info 输入装配器信息 + * @return 成功返回 true + */ + bool initialize(const InputAssemblerInfo& info); + + /** + * @brief 销毁输入装配器 + */ + void destroy(); + + /** + * @brief 获取顶点流列表 + * @return 顶点流列表 + */ + const std::vector& getVertexStreams() const; + + /** + * @brief 获取索引缓冲区 + * @return 索引缓冲区指针 + */ + Buffer* getIndexBuffer() const; + + /** + * @brief 获取索引偏移 + * @return 索引偏移 + */ + u32 getIndexOffset() const; + + /** + * @brief 获取起始索引 + * @return 起始索引 + */ + u32 getFirstIndex() const; + + /** + * @brief 获取索引数量 + * @return 索引数量 + */ + u32 getIndexCount() const; + + /** + * @brief 获取顶点数量 + * @return 顶点数量 + */ + u32 getVertexCount() const; + + /** + * @brief 获取实例数量 + * @return 实例数量 + */ + u32 getInstanceCount() const; + + /** + * @brief 设置实例数量 + * @param count 实例数量 + */ + void setInstanceCount(u32 count); + + /** + * @brief 获取类型名称 + * @return "InputAssembler" + */ + const char* getTypeName() const override { return "InputAssembler"; } + +protected: + /** + * @brief 子类实现初始化 + */ + virtual bool doInit(const InputAssemblerInfo& info) = 0; + + /** + * @brief 子类实现销毁 + */ + virtual void doDestroy() = 0; + + std::vector vertexStreams_; + Buffer* indexBuffer_{nullptr}; + u32 indexOffset_{0}; + u32 firstIndex_{0}; + u32 indexCount_{0}; + u32 vertexCount_{0}; + u32 instanceCount_{1}; + u32 firstInstance_{0}; + u32 vertexOffset_{0}; +}; + +} // namespace gfx +} // namespace extra2d diff --git a/Extra2D/include/extra2d/gfx/GFXObject.h b/Extra2D/include/extra2d/gfx/GFXObject.h new file mode 100644 index 0000000..8218f93 --- /dev/null +++ b/Extra2D/include/extra2d/gfx/GFXObject.h @@ -0,0 +1,50 @@ +#pragma once + +#include +#include +#include + +namespace extra2d { +namespace gfx { + +/** + * @brief GFX 对象基类 + * + * 所有 GFX 资源的基类,提供类型标识和调试名称。 + */ +class GFXObject : public RefCounted { +public: + /** + * @brief 构造函数 + */ + GFXObject(); + + /** + * @brief 析构函数 + */ + ~GFXObject() override; + + /** + * @brief 获取对象类型名称 + * @return 类型名称 + */ + virtual const char* getTypeName() const = 0; + + /** + * @brief 获取调试名称 + * @return 调试名称 + */ + const std::string& getDebugName() const; + + /** + * @brief 设置调试名称 + * @param name 调试名称 + */ + void setDebugName(const std::string& name); + +protected: + std::string debugName_; +}; + +} // namespace gfx +} // namespace extra2d diff --git a/Extra2D/include/extra2d/gfx/GFXPipeline.h b/Extra2D/include/extra2d/gfx/GFXPipeline.h new file mode 100644 index 0000000..97e5930 --- /dev/null +++ b/Extra2D/include/extra2d/gfx/GFXPipeline.h @@ -0,0 +1,197 @@ +#pragma once + +#include +#include +#include +#include + +namespace extra2d { +namespace gfx { + +class Shader; + +/** + * @brief 混合目标状态 + */ +struct BlendTarget { + bool blend{false}; + BlendFactor srcColor{BlendFactor::SrcAlpha}; + BlendFactor dstColor{BlendFactor::OneMinusSrcAlpha}; + BlendOp blendColorOp{BlendOp::Add}; + BlendFactor srcAlpha{BlendFactor::One}; + BlendFactor dstAlpha{BlendFactor::OneMinusSrcAlpha}; + BlendOp blendAlphaOp{BlendOp::Add}; + u32 colorWriteMask{0xF}; +}; + +/** + * @brief 混合状态 + */ +struct BlendState { + bool alphaToCoverage{false}; + bool independentBlend{false}; + float blendColor[4]{0, 0, 0, 0}; + std::vector targets; + + BlendState() { + targets.push_back(BlendTarget{}); + } +}; + +/** + * @brief 光栅化状态 + */ +struct RasterizerState { + bool discard{false}; + PolygonMode polygonMode{PolygonMode::Fill}; + CullMode cullMode{CullMode::Back}; + bool frontFaceCCW{true}; + bool depthBiasEnabled{false}; + float depthBias{0.0f}; + float depthBiasClamp{0.0f}; + float depthBiasSlop{0.0f}; + bool depthClip{true}; + bool multisample{false}; + float lineWidth{1.0f}; +}; + +/** + * @brief 深度模板状态 + */ +struct DepthStencilState { + bool depthTest{true}; + bool depthWrite{true}; + ComparisonFunc depthFunc{ComparisonFunc::Less}; + + bool stencilTestFront{false}; + ComparisonFunc stencilFuncFront{ComparisonFunc::Always}; + u8 stencilReadMaskFront{0xFF}; + u8 stencilWriteMaskFront{0xFF}; + u8 stencilRefFront{0}; + StencilOp stencilFailOpFront{StencilOp::Keep}; + StencilOp stencilDepthFailOpFront{StencilOp::Keep}; + StencilOp stencilPassOpFront{StencilOp::Keep}; + + bool stencilTestBack{false}; + ComparisonFunc stencilFuncBack{ComparisonFunc::Always}; + u8 stencilReadMaskBack{0xFF}; + u8 stencilWriteMaskBack{0xFF}; + u8 stencilRefBack{0}; + StencilOp stencilFailOpBack{StencilOp::Keep}; + StencilOp stencilDepthFailOpBack{StencilOp::Keep}; + StencilOp stencilPassOpBack{StencilOp::Keep}; +}; + +/** + * @brief 输入状态 + */ +struct InputState { + std::vector attributes; +}; + +/** + * @brief 管线状态信息 + */ +struct PipelineStateInfo { + Shader* shader{nullptr}; + PipelineBindPoint bindPoint{PipelineBindPoint::Graphics}; + PrimitiveMode primitive{PrimitiveMode::TriangleList}; + DynamicStateFlag dynamicStates{DynamicStateFlag::None}; + InputState inputState; + RasterizerState rasterizerState; + DepthStencilState depthStencilState; + BlendState blendState; +}; + +/** + * @brief 管线状态抽象基类 + */ +class PipelineState : public GFXObject { +public: + /** + * @brief 构造函数 + */ + PipelineState(); + + /** + * @brief 析构函数 + */ + ~PipelineState() override; + + /** + * @brief 初始化管线状态 + * @param info 管线状态信息 + * @return 成功返回 true + */ + bool initialize(const PipelineStateInfo& info); + + /** + * @brief 销毁管线状态 + */ + void destroy(); + + /** + * @brief 获取着色器 + * @return 着色器指针 + */ + Shader* getShader() const; + + /** + * @brief 获取绑定点 + * @return 绑定点 + */ + PipelineBindPoint getBindPoint() const; + + /** + * @brief 获取图元模式 + * @return 图元模式 + */ + PrimitiveMode getPrimitive() const; + + /** + * @brief 获取光栅化状态 + * @return 光栅化状态 + */ + const RasterizerState& getRasterizerState() const; + + /** + * @brief 获取深度模板状态 + * @return 深度模板状态 + */ + const DepthStencilState& getDepthStencilState() const; + + /** + * @brief 获取混合状态 + * @return 混合状态 + */ + const BlendState& getBlendState() const; + + /** + * @brief 获取类型名称 + * @return "PipelineState" + */ + const char* getTypeName() const override { return "PipelineState"; } + +protected: + /** + * @brief 子类实现初始化 + */ + virtual bool doInit(const PipelineStateInfo& info) = 0; + + /** + * @brief 子类实现销毁 + */ + virtual void doDestroy() = 0; + + Shader* shader_{nullptr}; + PipelineBindPoint bindPoint_{PipelineBindPoint::Graphics}; + PrimitiveMode primitive_{PrimitiveMode::TriangleList}; + DynamicStateFlag dynamicStates_{DynamicStateFlag::None}; + InputState inputState_; + RasterizerState rasterizerState_; + DepthStencilState depthStencilState_; + BlendState blendState_; +}; + +} // namespace gfx +} // namespace extra2d diff --git a/Extra2D/include/extra2d/gfx/GFXShader.h b/Extra2D/include/extra2d/gfx/GFXShader.h new file mode 100644 index 0000000..3be5141 --- /dev/null +++ b/Extra2D/include/extra2d/gfx/GFXShader.h @@ -0,0 +1,145 @@ +#pragma once + +#include +#include +#include +#include + +namespace extra2d { +namespace gfx { + +/** + * @brief 顶点属性 + */ +struct Attribute { + std::string name; + Format format{Format::RGBA8}; + bool normalized{false}; + u32 stream{0}; + bool instanced{false}; + u32 location{0}; +}; + +/** + * @brief Uniform 成员 + */ +struct Uniform { + std::string name; + Format format{Format::Unknown}; + u32 count{1}; + u32 offset{0}; +}; + +/** + * @brief Uniform 块 + */ +struct UniformBlock { + u32 set{0}; + u32 binding{0}; + std::string name; + std::vector members; + u32 count{1}; + u32 size{0}; +}; + +/** + * @brief 采样器纹理 + */ +struct UniformSamplerTexture { + u32 set{0}; + u32 binding{0}; + std::string name; + TextureType type{TextureType::Tex2D}; + u32 count{1}; +}; + +/** + * @brief 着色器创建信息 + */ +struct ShaderInfo { + std::string name; + std::string vertexSource; + std::string fragmentSource; + std::vector attributes; + std::vector blocks; + std::vector samplerTextures; +}; + +/** + * @brief 着色器抽象基类 + */ +class Shader : public GFXObject { +public: + /** + * @brief 构造函数 + */ + Shader(); + + /** + * @brief 析构函数 + */ + ~Shader() override; + + /** + * @brief 初始化着色器 + * @param info 着色器信息 + * @return 成功返回 true + */ + bool initialize(const ShaderInfo& info); + + /** + * @brief 销毁着色器 + */ + void destroy(); + + /** + * @brief 获取着色器名称 + * @return 名称 + */ + const std::string& getName() const; + + /** + * @brief 获取顶点属性列表 + * @return 属性列表 + */ + const std::vector& getAttributes() const; + + /** + * @brief 获取 Uniform 块列表 + * @return Uniform 块列表 + */ + const std::vector& getBlocks() const; + + /** + * @brief 获取采样器纹理列表 + * @return 采样器纹理列表 + */ + const std::vector& getSamplerTextures() const; + + /** + * @brief 获取类型名称 + * @return "Shader" + */ + const char* getTypeName() const override { return "Shader"; } + +protected: + /** + * @brief 子类实现初始化 + */ + virtual bool doInit(const ShaderInfo& info) = 0; + + /** + * @brief 子类实现销毁 + */ + virtual void doDestroy() = 0; + + std::string name_; + std::string vertexSource_; + std::string fragmentSource_; + std::vector attributes_; + std::vector blocks_; + std::vector samplerTextures_; +}; + +} // namespace gfx +} // namespace extra2d diff --git a/Extra2D/include/extra2d/gfx/GFXTexture.h b/Extra2D/include/extra2d/gfx/GFXTexture.h new file mode 100644 index 0000000..80a16d9 --- /dev/null +++ b/Extra2D/include/extra2d/gfx/GFXTexture.h @@ -0,0 +1,183 @@ +#pragma once + +#include +#include +#include + +namespace extra2d { +namespace gfx { + +class Buffer; + +/** + * @brief 纹理创建信息 + */ +struct TextureInfo { + TextureType type{TextureType::Tex2D}; + TextureUsage usage{TextureUsage::None}; + Format format{Format::RGBA8}; + u32 width{1}; + u32 height{1}; + u32 depth{1}; + u32 arrayLength{1}; + u32 mipLevels{1}; + SampleCount samples{SampleCount::X1}; + TextureFlag flags{TextureFlag::None}; +}; + +/** + * @brief 纹理视图信息 + */ +struct TextureViewInfo { + class Texture* texture{nullptr}; + TextureType type{TextureType::Tex2D}; + Format format{Format::Unknown}; + u32 baseLevel{0}; + u32 levelCount{1}; + u32 baseLayer{0}; + u32 layerCount{1}; +}; + +/** + * @brief 缓冲区到纹理复制区域 + */ +struct BufferTextureCopy { + u32 bufferOffset{0}; + u32 bufferStride{0}; + u32 bufferTexStride{0}; + u32 texOffsetX{0}; + u32 texOffsetY{0}; + u32 texOffsetZ{0}; + u32 texExtentX{0}; + u32 texExtentY{0}; + u32 texExtentZ{0}; + u32 texBaseLevel{0}; + u32 texLevelCount{1}; +}; + +/** + * @brief 纹理抽象基类 + * + * GPU 纹理资源的抽象接口。 + */ +class Texture : public GFXObject { +public: + /** + * @brief 构造函数 + */ + Texture(); + + /** + * @brief 析构函数 + */ + ~Texture() override; + + /** + * @brief 初始化纹理 + * @param info 纹理信息 + * @return 成功返回 true + */ + bool initialize(const TextureInfo& info); + + /** + * @brief 初始化纹理视图 + * @param info 视图信息 + * @return 成功返回 true + */ + bool initialize(const TextureViewInfo& info); + + /** + * @brief 销毁纹理 + */ + void destroy(); + + /** + * @brief 调整纹理大小 + * @param width 新宽度 + * @param height 新高度 + */ + void resize(u32 width, u32 height); + + /** + * @brief 获取纹理信息 + * @return 纹理信息 + */ + const TextureInfo& getInfo() const; + + /** + * @brief 获取视图信息 + * @return 视图信息 + */ + const TextureViewInfo& getViewInfo() const; + + /** + * @brief 检查是否为视图 + * @return 如果是视图返回 true + */ + bool isTextureView() const; + + /** + * @brief 获取格式 + * @return 格式 + */ + Format getFormat() const; + + /** + * @brief 获取宽度 + * @return 宽度 + */ + u32 getWidth() const; + + /** + * @brief 获取高度 + * @return 高度 + */ + u32 getHeight() const; + + /** + * @brief 获取深度 + * @return 深度 + */ + u32 getDepth() const; + + /** + * @brief 获取类型名称 + * @return "Texture" + */ + const char* getTypeName() const override { return "Texture"; } + + /** + * @brief 获取原始纹理(视图使用) + * @return 原始纹理指针 + */ + virtual const Texture* getRaw() const { return this; } + +protected: + /** + * @brief 子类实现初始化 + */ + virtual bool doInit(const TextureInfo& info) = 0; + + /** + * @brief 子类实现视图初始化 + */ + virtual bool doInit(const TextureViewInfo& info) = 0; + + /** + * @brief 子类实现销毁 + */ + virtual void doDestroy() = 0; + + /** + * @brief 子类实现调整大小 + */ + virtual void doResize(u32 width, u32 height) = 0; + + TextureInfo info_; + TextureViewInfo viewInfo_; + bool isView_{false}; + Texture* sourceTexture_{nullptr}; +}; + +} // namespace gfx +} // namespace extra2d diff --git a/Extra2D/include/extra2d/gfx/opengl/GLBuffer.h b/Extra2D/include/extra2d/gfx/opengl/GLBuffer.h new file mode 100644 index 0000000..92cf404 --- /dev/null +++ b/Extra2D/include/extra2d/gfx/opengl/GLBuffer.h @@ -0,0 +1,64 @@ +#pragma once + +#include + +namespace extra2d { +namespace gfx { + +/** + * @brief OpenGL 缓冲区实现 + */ +class GLBuffer : public Buffer { +public: + /** + * @brief 构造函数 + */ + GLBuffer(); + + /** + * @brief 析构函数 + */ + ~GLBuffer() override; + + /** + * @brief 更新缓冲区数据 + * @param data 数据指针 + * @param size 数据大小 + */ + void update(const void* data, u32 size) override; + + /** + * @brief 获取 OpenGL 缓冲区句柄 + * @return 缓冲区句柄 + */ + u32 getGLHandle() const; + +protected: + /** + * @brief 实现初始化 + */ + bool doInit(const BufferInfo& info) override; + + /** + * @brief 实现视图初始化 + */ + bool doInit(const BufferViewInfo& info) override; + + /** + * @brief 实现销毁 + */ + void doDestroy() override; + + /** + * @brief 实现调整大小 + */ + void doResize(u32 size) override; + +private: + u32 glHandle_{0}; + u32 glTarget_{0}; + void* mappedData_{nullptr}; +}; + +} // namespace gfx +} // namespace extra2d diff --git a/Extra2D/include/extra2d/gfx/opengl/GLCommandBuffer.h b/Extra2D/include/extra2d/gfx/opengl/GLCommandBuffer.h new file mode 100644 index 0000000..5e52d73 --- /dev/null +++ b/Extra2D/include/extra2d/gfx/opengl/GLCommandBuffer.h @@ -0,0 +1,180 @@ +#include + +namespace extra2d { +namespace gfx { + +/** + * @brief OpenGL 命令缓冲区实现 + */ +class GLCommandBuffer : public CommandBuffer { +public: + /** + * @brief 构造函数 + */ + GLCommandBuffer(); + + /** + * @brief 析构函数 + */ + ~GLCommandBuffer() override; + + // ==================== 生命周期 ==================== + + /** + * @brief 初始化命令缓冲区 + * @return 成功返回 true + */ + bool initialize(); + + /** + * @brief 销毁命令缓冲区 + */ + void destroy(); + + // ==================== 开始/结束 ==================== + + /** + * @brief 开始录制命令 + */ + void begin() override; + + /** + * @brief 结束录制命令 + */ + void end() override; + + // ==================== 渲染通道 ==================== + + /** + * @brief 开始渲染通道 + * @param renderPass 渲染通道 + * @param framebuffer 帧缓冲 + * @param viewport 视口 + * @param clearFlags 清除标志 + * @param clearColor 清除颜色 + * @param clearDepth 清除深度 + * @param clearStencil 清除模板 + */ + void beginRenderPass(RenderPass* renderPass, Framebuffer* framebuffer, + const Viewport& viewport, + ClearFlagBit clearFlags, + const Color& clearColor, + float clearDepth, u32 clearStencil) override; + + /** + * @brief 结束渲染通道 + */ + void endRenderPass() override; + + // ==================== 绑定命令 ==================== + + /** + * @brief 绑定管线状态 + * @param pipelineState 管线状态 + */ + void bindPipelineState(PipelineState* pipelineState) override; + + /** + * @brief 绑定输入装配器 + * @param inputAssembler 输入装配器 + */ + void bindInputAssembler(InputAssembler* inputAssembler) override; + + /** + * @brief 绑定描述符集 + * @param descriptorSet 描述符集 + * @param setIndex 集合索引 + */ + void bindDescriptorSet(DescriptorSet* descriptorSet, u32 setIndex) override; + + // ==================== 状态设置 ==================== + + /** + * @brief 设置视口 + * @param viewport 视口 + */ + void setViewport(const Viewport& viewport) override; + + /** + * @brief 设置矩形区域 + * @param rect 矩形区域 + */ + void setScissor(const Rect& rect) override; + + /** + * @brief 设置混合常量 + * @param constants 常量数组 + */ + void setBlendConstants(const float* constants) override; + + /** + * @brief 设置深度偏移 + * @param bias 偏移值 + */ + void setDepthBias(float bias, float slope, float clamp) override; + + /** + * @brief 设置模板参考值 + * @param front 前面参考值 + * @param back 后面参考值 + */ + void setStencilReference(u32 front, u32 back) override; + + // ==================== 绘制命令 ==================== + + /** + * @brief 绘制数组 + * @param vertexCount 顶点数量 + * @param firstVertex 起始顶点 + * @param instanceCount 实例数量 + * @param firstInstance 起始实例 + */ + void drawArrays(u32 vertexCount, u32 firstVertex = 0, + u32 instanceCount = 1, u32 firstInstance = 0) override; + + /** + * @brief 绘制索引 + * @param indexCount 索引数量 + * @param firstIndex 起始索引 + * @param instanceCount 实例数量 + * @param firstInstance 起始实例 + */ + void drawIndexed(u32 indexCount, u32 firstIndex = 0, + u32 instanceCount = 1, u32 firstInstance = 0) override; + + // ==================== 更新命令 ==================== + + /** + * @brief 更新缓冲区 + * @param buffer 缓冲区 + * @param offset 偏移量 + * @param data 数据指针 + * @param size 数据大小 + */ + void updateBuffer(Buffer* buffer, u32 offset, const void* data, u32 size) override; + + /** + * @brief 复制缓冲区 + * @param src 源缓冲区 + * @param srcOffset 源偏移量 + * @param dst 目标缓冲区 + * @param dstOffset 目标偏移量 + * @param size 大小 + */ + void copyBuffer(Buffer* src, u32 srcOffset, Buffer* dst, u32 dstOffset, u32 size) override; + + /** + * @brief 复制纹理 + * @param src 源纹理 + * @param dst 目标纹理 + */ + void copyTexture(Texture* src, Texture* dst) override; + +private: + bool recording_{false}; + RenderPass* currentRenderPass_{nullptr}; + Framebuffer* currentFramebuffer_{nullptr}; +}; + +} // namespace gfx +} // namespace extra2d diff --git a/Extra2D/include/extra2d/gfx/opengl/GLDevice.h b/Extra2D/include/extra2d/gfx/opengl/GLDevice.h new file mode 100644 index 0000000..f0e840b --- /dev/null +++ b/Extra2D/include/extra2d/gfx/opengl/GLDevice.h @@ -0,0 +1,75 @@ +#pragma once + +#include + +namespace extra2d { +namespace gfx { + +/** + * @brief OpenGL 设备实现 + */ +class GLDevice : public Device { +public: + /** + * @brief 构造函数 + */ + GLDevice(); + + /** + * @brief 析构函数 + */ + ~GLDevice() override; + + // ==================== 设备生命周期 ==================== + + void frameSync() override; + void present() override; + + // ==================== 资源创建 ==================== + + Buffer* createBuffer(const BufferInfo& info) override; + Texture* createTexture(const TextureInfo& info) override; + Shader* createShader(const std::string& name, + const std::string& vertexSource, + const std::string& fragmentSource) override; + PipelineState* createPipelineState(const PipelineStateInfo& info) override; + InputAssembler* createInputAssembler(const InputAssemblerInfo& info) override; + + // ==================== 数据传输 ==================== + + void copyBuffersToTexture(const u8* const* buffers, Texture* dst, + const BufferTextureCopy* regions, u32 count) override; + + // ==================== 状态管理 ==================== + + void setViewport(float x, float y, float width, float height) override; + void setScissor(i32 x, i32 y, u32 width, u32 height) override; + void clearColor(float r, float g, float b, float a) override; + void clearDepthStencil(float depth, u8 stencil) override; + + void beginRenderPass() override; + void endRenderPass() override; + + void bindPipelineState(PipelineState* pipeline) override; + void bindInputAssembler(InputAssembler* ia) override; + + void draw(u32 firstVertex, u32 vertexCount) override; + void drawIndexed(u32 firstIndex, u32 indexCount, i32 vertexOffset) override; + void drawInstanced(u32 firstVertex, u32 vertexCount, u32 instanceCount) override; + void drawIndexedInstanced(u32 firstIndex, u32 indexCount, + u32 instanceCount, i32 vertexOffset) override; + +protected: + bool doInit(const DeviceInfo& info) override; + void doDestroy() override; + +private: + void initCapabilities(); + + class GLPipelineState* currentPipeline_{nullptr}; + class GLInputAssembler* currentIA_{nullptr}; + class GLShader* currentShader_{nullptr}; +}; + +} // namespace gfx +} // namespace extra2d diff --git a/Extra2D/include/extra2d/gfx/opengl/GLInputAssembler.h b/Extra2D/include/extra2d/gfx/opengl/GLInputAssembler.h new file mode 100644 index 0000000..25d6198 --- /dev/null +++ b/Extra2D/include/extra2d/gfx/opengl/GLInputAssembler.h @@ -0,0 +1,43 @@ +#pragma once + +#include +#include + +namespace extra2d { +namespace gfx { + +/** + * @brief OpenGL 输入装配器实现 + */ +class GLInputAssembler : public InputAssembler { +public: + /** + * @brief 构造函数 + */ + GLInputAssembler(); + + /** + * @brief 析构函数 + */ + ~GLInputAssembler() override; + + /** + * @brief 绑定顶点属性 + * @param shader 着色器 + */ + void bindAttributes(class GLShader* shader); + +protected: + /** + * @brief 实现初始化 + */ + bool doInit(const InputAssemblerInfo& info) override; + + /** + * @brief 实现销毁 + */ + void doDestroy() override; +}; + +} // namespace gfx +} // namespace extra2d diff --git a/Extra2D/include/extra2d/gfx/opengl/GLPipelineState.h b/Extra2D/include/extra2d/gfx/opengl/GLPipelineState.h new file mode 100644 index 0000000..8867425 --- /dev/null +++ b/Extra2D/include/extra2d/gfx/opengl/GLPipelineState.h @@ -0,0 +1,46 @@ +#pragma once + +#include + +namespace extra2d { +namespace gfx { + +/** + * @brief OpenGL 管线状态实现 + */ +class GLPipelineState : public PipelineState { +public: + /** + * @brief 构造函数 + */ + GLPipelineState(); + + /** + * @brief 析构函数 + */ + ~GLPipelineState() override; + + /** + * @brief 应用管线状态 + */ + void apply(); + +protected: + /** + * @brief 实现初始化 + */ + bool doInit(const PipelineStateInfo& info) override; + + /** + * @brief 实现销毁 + */ + void doDestroy() override; + +private: + void applyRasterizerState(); + void applyDepthStencilState(); + void applyBlendState(); +}; + +} // namespace gfx +} // namespace extra2d diff --git a/Extra2D/include/extra2d/gfx/opengl/GLShader.h b/Extra2D/include/extra2d/gfx/opengl/GLShader.h new file mode 100644 index 0000000..83f37b5 --- /dev/null +++ b/Extra2D/include/extra2d/gfx/opengl/GLShader.h @@ -0,0 +1,80 @@ +#pragma once + +#include + +namespace extra2d { +namespace gfx { + +/** + * @brief OpenGL 着色器实现 + */ +class GLShader : public Shader { +public: + /** + * @brief 构造函数 + */ + GLShader(); + + /** + * @brief 析构函数 + */ + ~GLShader() override; + + /** + * @brief 获取 OpenGL 程序句柄 + * @return 程序句柄 + */ + u32 getGLProgram() const; + + /** + * @brief 激活着色器 + */ + void bind(); + + /** + * @brief 获取 Uniform 位置 + * @param name Uniform 名称 + * @return 位置 + */ + i32 getUniformLocation(const std::string& name); + + /** + * @brief 获取属性位置 + * @param name 属性名称 + * @return 位置 + */ + i32 getAttributeLocation(const std::string& name); + +protected: + /** + * @brief 实现初始化 + */ + bool doInit(const ShaderInfo& info) override; + + /** + * @brief 实现销毁 + */ + void doDestroy() override; + +private: + /** + * @brief 编译着色器 + * @param source 源码 + * @param type 着色器类型 + * @return 着色器句柄 + */ + u32 compileShader(const std::string& source, u32 type); + + /** + * @brief 链接程序 + * @param vertexShader 顶点着色器 + * @param fragmentShader 片段着色器 + * @return 是否成功 + */ + bool linkProgram(u32 vertexShader, u32 fragmentShader); + + u32 glProgram_{0}; +}; + +} // namespace gfx +} // namespace extra2d diff --git a/Extra2D/include/extra2d/gfx/opengl/GLTexture.h b/Extra2D/include/extra2d/gfx/opengl/GLTexture.h new file mode 100644 index 0000000..76dcac6 --- /dev/null +++ b/Extra2D/include/extra2d/gfx/opengl/GLTexture.h @@ -0,0 +1,77 @@ +#pragma once + +#include + +namespace extra2d { +namespace gfx { + +/** + * @brief OpenGL 纹理实现 + */ +class GLTexture : public Texture { +public: + /** + * @brief 构造函数 + */ + GLTexture(); + + /** + * @brief 析构函数 + */ + ~GLTexture() override; + + /** + * @brief 获取 OpenGL 纹理句柄 + * @return 纹理句柄 + */ + u32 getGLHandle() const; + + /** + * @brief 获取 OpenGL 纹理目标 + * @return 纹理目标 + */ + u32 getGLTarget() const; + + /** + * @brief 上传纹理数据 + * @param data 数据指针 + * @param level Mipmap 级别 + */ + void uploadData(const void* data, u32 level = 0); + + /** + * @brief 生成 Mipmap + */ + void generateMipmaps(); + +protected: + /** + * @brief 实现初始化 + */ + bool doInit(const TextureInfo& info) override; + + /** + * @brief 实现视图初始化 + */ + bool doInit(const TextureViewInfo& info) override; + + /** + * @brief 实现销毁 + */ + void doDestroy() override; + + /** + * @brief 实现调整大小 + */ + void doResize(u32 width, u32 height) override; + +private: + u32 glHandle_{0}; + u32 glTarget_{0}; + u32 glInternalFormat_{0}; + u32 glFormat_{0}; + u32 glType_{0}; +}; + +} // namespace gfx +} // namespace extra2d diff --git a/Extra2D/include/extra2d/platform/Input.h b/Extra2D/include/extra2d/platform/Input.h new file mode 100644 index 0000000..65c9c41 --- /dev/null +++ b/Extra2D/include/extra2d/platform/Input.h @@ -0,0 +1,542 @@ +#pragma once + +#include +#include +#include +#include + +struct SDL_GameController; + +namespace extra2d { + +/** + * @brief 触摸点信息 + */ +struct Touch { + i64 id{-1}; + Vec2 position{0, 0}; + Vec2 startPosition{0, 0}; + Vec2 delta{0, 0}; + float pressure{1.0f}; + float timestamp{0}; +}; + +/** + * @brief 按键码常量 + */ +namespace KeyCode { + constexpr i32 A = 4; + constexpr i32 B = 5; + constexpr i32 C = 6; + constexpr i32 D = 7; + constexpr i32 E = 8; + constexpr i32 F = 9; + constexpr i32 G = 10; + constexpr i32 H = 11; + constexpr i32 I = 12; + constexpr i32 J = 13; + constexpr i32 K = 14; + constexpr i32 L = 15; + constexpr i32 M = 16; + constexpr i32 N = 17; + constexpr i32 O = 18; + constexpr i32 P = 19; + constexpr i32 Q = 20; + constexpr i32 R = 21; + constexpr i32 S = 22; + constexpr i32 T = 23; + constexpr i32 U = 24; + constexpr i32 V = 25; + constexpr i32 W = 26; + constexpr i32 X = 27; + constexpr i32 Y = 28; + constexpr i32 Z = 29; + + constexpr i32 Num1 = 30; + constexpr i32 Num2 = 31; + constexpr i32 Num3 = 32; + constexpr i32 Num4 = 33; + constexpr i32 Num5 = 34; + constexpr i32 Num6 = 35; + constexpr i32 Num7 = 36; + constexpr i32 Num8 = 37; + constexpr i32 Num9 = 38; + constexpr i32 Num0 = 39; + + constexpr i32 Return = 40; + constexpr i32 Escape = 41; + constexpr i32 Backspace = 42; + constexpr i32 Tab = 43; + constexpr i32 Space = 44; + + constexpr i32 Minus = 45; + constexpr i32 Equals = 46; + constexpr i32 LeftBracket = 47; + constexpr i32 RightBracket = 48; + constexpr i32 Backslash = 49; + constexpr i32 Semicolon = 51; + constexpr i32 Quote = 52; + constexpr i32 Grave = 53; + constexpr i32 Comma = 54; + constexpr i32 Period = 55; + constexpr i32 Slash = 56; + + constexpr i32 CapsLock = 57; + + constexpr i32 F1 = 58; + constexpr i32 F2 = 59; + constexpr i32 F3 = 60; + constexpr i32 F4 = 61; + constexpr i32 F5 = 62; + constexpr i32 F6 = 63; + constexpr i32 F7 = 64; + constexpr i32 F8 = 65; + constexpr i32 F9 = 66; + constexpr i32 F10 = 67; + constexpr i32 F11 = 68; + constexpr i32 F12 = 69; + + constexpr i32 PrintScreen = 70; + constexpr i32 ScrollLock = 71; + constexpr i32 Pause = 72; + constexpr i32 Insert = 73; + constexpr i32 Home = 74; + constexpr i32 PageUp = 75; + constexpr i32 Delete = 76; + constexpr i32 End = 77; + constexpr i32 PageDown = 78; + + constexpr i32 Right = 79; + constexpr i32 Left = 80; + constexpr i32 Down = 81; + constexpr i32 Up = 82; + + constexpr i32 NumLockClear = 83; + constexpr i32 KPDivide = 84; + constexpr i32 KPMultiply = 85; + constexpr i32 KPMinus = 86; + constexpr i32 KPPlus = 87; + constexpr i32 KPEnter = 88; + constexpr i32 KP1 = 89; + constexpr i32 KP2 = 90; + constexpr i32 KP3 = 91; + constexpr i32 KP4 = 92; + constexpr i32 KP5 = 93; + constexpr i32 KP6 = 94; + constexpr i32 KP7 = 95; + constexpr i32 KP8 = 96; + constexpr i32 KP9 = 97; + constexpr i32 KP0 = 98; + constexpr i32 KPPeriod = 99; + + constexpr i32 LCtrl = 224; + constexpr i32 LShift = 225; + constexpr i32 LAlt = 226; + constexpr i32 LGUI = 227; + constexpr i32 RCtrl = 228; + constexpr i32 RShift = 229; + constexpr i32 RAlt = 230; + constexpr i32 RGUI = 231; +} + +/** + * @brief 鼠标按钮常量 + */ +namespace MouseButton { + constexpr i32 Left = 1; + constexpr i32 Middle = 2; + constexpr i32 Right = 3; + constexpr i32 X1 = 4; + constexpr i32 X2 = 5; +} + +/** + * @brief 手柄按钮常量 + */ +namespace GamepadButton { + constexpr i32 A = 0; + constexpr i32 B = 1; + constexpr i32 X = 2; + constexpr i32 Y = 3; + constexpr i32 Back = 4; + constexpr i32 Guide = 5; + constexpr i32 Start = 6; + constexpr i32 LeftStick = 7; + constexpr i32 RightStick = 8; + constexpr i32 LeftShoulder = 9; + constexpr i32 RightShoulder = 10; + constexpr i32 DPadUp = 11; + constexpr i32 DPadDown = 12; + constexpr i32 DPadLeft = 13; + constexpr i32 DPadRight = 14; +} + +/** + * @brief 手柄轴常量 + */ +namespace GamepadAxis { + constexpr i32 LeftX = 0; + constexpr i32 LeftY = 1; + constexpr i32 RightX = 2; + constexpr i32 RightY = 3; + constexpr i32 LeftTrigger = 4; + constexpr i32 RightTrigger = 5; +} + +/** + * @brief 输入管理类 + * + * 管理键盘、鼠标、触摸和手柄输入状态。 + */ +class Input { +public: + /** + * @brief 获取单例实例 + * @return 输入实例 + */ + static Input* getInstance(); + + /** + * @brief 每帧更新 + */ + void update(); + + /** + * @brief 重置帧状态 + */ + void resetFrameState(); + + // ==================== 键盘输入 ==================== + + /** + * @brief 检查按键是否刚按下 + * @param keycode 按键码 + * @return 如果刚按下返回 true + */ + bool isKeyDown(i32 keycode) const; + + /** + * @brief 检查按键是否刚释放 + * @param keycode 按键码 + * @return 如果刚释放返回 true + */ + bool isKeyUp(i32 keycode) const; + + /** + * @brief 检查按键是否按住 + * @param keycode 按键码 + * @return 如果按住返回 true + */ + bool isKeyPressed(i32 keycode) const; + + /** + * @brief 获取任意按键是否按下 + * @return 如果有任意按键按下返回 true + */ + bool isAnyKeyPressed() const; + + // ==================== 鼠标输入 ==================== + + /** + * @brief 获取鼠标位置 + * @return 鼠标位置 + */ + Vec2 getMousePosition() const; + + /** + * @brief 获取鼠标移动增量 + * @return 鼠标移动增量 + */ + Vec2 getMouseDelta() const; + + /** + * @brief 检查鼠标按钮是否刚按下 + * @param button 鼠标按钮 + * @return 如果刚按下返回 true + */ + bool isMouseButtonDown(i32 button) const; + + /** + * @brief 检查鼠标按钮是否刚释放 + * @param button 鼠标按钮 + * @return 如果刚释放返回 true + */ + bool isMouseButtonUp(i32 button) const; + + /** + * @brief 检查鼠标按钮是否按住 + * @param button 鼠标按钮 + * @return 如果按住返回 true + */ + bool isMouseButtonPressed(i32 button) const; + + /** + * @brief 获取鼠标滚轮增量 + * @return 鼠标滚轮增量 + */ + Vec2 getMouseWheelDelta() const; + + /** + * @brief 设置鼠标锁定模式 + * @param locked 是否锁定 + */ + void setMouseLocked(bool locked); + + /** + * @brief 检查鼠标是否锁定 + * @return 如果锁定返回 true + */ + bool isMouseLocked() const; + + // ==================== 触摸输入 ==================== + + /** + * @brief 检查是否有触摸输入 + * @return 如果有触摸返回 true + */ + bool hasTouch() const; + + /** + * @brief 获取当前触摸点数量 + * @return 触摸点数量 + */ + i32 getTouchCount() const; + + /** + * @brief 获取指定 ID 的触摸点 + * @param id 触摸点 ID + * @return 触摸点指针,如果不存在返回 nullptr + */ + const Touch* getTouch(i64 id) const; + + /** + * @brief 按索引获取触摸点 + * @param index 索引 + * @return 触摸点指针 + */ + const Touch* getTouchByIndex(i32 index) const; + + /** + * @brief 获取所有触摸点 + * @return 触摸点列表 + */ + const std::vector& getTouches() const; + + /** + * @brief 检查触摸点是否刚按下 + * @param id 触摸点 ID + * @return 如果刚按下返回 true + */ + bool isTouchDown(i64 id) const; + + /** + * @brief 检查触摸点是否刚抬起 + * @param id 触摸点 ID + * @return 如果刚抬起返回 true + */ + bool isTouchUp(i64 id) const; + + /** + * @brief 检查触摸点是否移动中 + * @param id 触摸点 ID + * @return 如果移动中返回 true + */ + bool isTouchMoving(i64 id) const; + + // ==================== 多点触控手势 ==================== + + /** + * @brief 检查是否正在双指缩放 + * @return 如果正在缩放返回 true + */ + bool isPinching() const; + + /** + * @brief 获取缩放比例 + * @return 缩放比例 + */ + float getPinchScale() const; + + /** + * @brief 获取缩放中心点 + * @return 缩放中心点 + */ + Vec2 getPinchCenter() const; + + /** + * @brief 检查是否正在双指旋转 + * @return 如果正在旋转返回 true + */ + bool isRotating() const; + + /** + * @brief 获取旋转角度 + * @return 旋转角度(度数) + */ + float getRotationAngle() const; + + // ==================== 手柄输入 ==================== + + /** + * @brief 获取已连接手柄数量 + * @return 手柄数量 + */ + i32 getConnectedGamepadCount() const; + + /** + * @brief 检查手柄是否已连接 + * @param id 手柄 ID + * @return 如果已连接返回 true + */ + bool isGamepadConnected(i32 id) const; + + /** + * @brief 获取手柄名称 + * @param id 手柄 ID + * @return 手柄名称 + */ + std::string getGamepadName(i32 id) const; + + /** + * @brief 检查手柄按钮是否按下 + * @param id 手柄 ID + * @param button 按钮索引 + * @return 如果按下返回 true + */ + bool isGamepadButtonDown(i32 id, i32 button) const; + + /** + * @brief 获取手柄轴值 + * @param id 手柄 ID + * @param axis 轴索引 + * @return 轴值 (-1.0 到 1.0) + */ + float getGamepadAxis(i32 id, i32 axis) const; + + /** + * @brief 设置手柄震动 + * @param id 手柄 ID + * @param lowFreq 低频震动强度 (0.0 到 1.0) + * @param highFreq 高频震动强度 (0.0 到 1.0) + * @param durationMs 震动持续时间(毫秒) + */ + void setGamepadRumble(i32 id, float lowFreq, float highFreq, u32 durationMs); + + // ==================== 内部方法 ==================== + + /** + * @brief 处理键盘按下事件 + */ + void handleKeyDown(i32 keycode); + + /** + * @brief 处理键盘释放事件 + */ + void handleKeyUp(i32 keycode); + + /** + * @brief 处理鼠标移动事件 + */ + void handleMouseMove(float x, float y, float dx, float dy); + + /** + * @brief 处理鼠标按钮按下事件 + */ + void handleMouseButtonDown(i32 button); + + /** + * @brief 处理鼠标按钮释放事件 + */ + void handleMouseButtonUp(i32 button); + + /** + * @brief 处理鼠标滚轮事件 + */ + void handleMouseWheel(float dx, float dy); + + /** + * @brief 处理触摸开始事件 + */ + void handleTouchDown(i64 id, float x, float y, float pressure); + + /** + * @brief 处理触摸移动事件 + */ + void handleTouchMove(i64 id, float x, float y, float dx, float dy, float pressure); + + /** + * @brief 处理触摸结束事件 + */ + void handleTouchUp(i64 id); + + /** + * @brief 处理手柄连接事件 + */ + void handleGamepadConnected(i32 id); + + /** + * @brief 处理手柄断开事件 + */ + void handleGamepadDisconnected(i32 id); + + /** + * @brief 处理手柄按钮事件 + */ + void handleGamepadButton(i32 id, i32 button, bool pressed); + + /** + * @brief 处理手柄轴事件 + */ + void handleGamepadAxis(i32 id, i32 axis, float value); + +private: + Input(); + ~Input(); + + Input(const Input&) = delete; + Input& operator=(const Input&) = delete; + + void updateGestures(); + + static Input* instance_; + + static constexpr size_t KEY_COUNT = 512; + static constexpr size_t MOUSE_BUTTON_COUNT = 8; + static constexpr size_t GAMEPAD_COUNT = 4; + static constexpr size_t GAMEPAD_BUTTON_COUNT = 32; + static constexpr size_t GAMEPAD_AXIS_COUNT = 8; + + std::array currentKeyState_{}; + std::array previousKeyState_{}; + + Vec2 mousePosition_{0, 0}; + Vec2 mouseDelta_{0, 0}; + Vec2 mouseWheelDelta_{0, 0}; + std::array currentMouseState_{}; + std::array previousMouseState_{}; + bool mouseLocked_{false}; + + std::vector activeTouches_; + std::vector beganTouchIds_; + std::vector endedTouchIds_; + + float lastPinchDistance_{0}; + float pinchScale_{1.0f}; + Vec2 pinchCenter_{0, 0}; + bool pinching_{false}; + + float lastRotationAngle_{0}; + float rotationAngle_{0}; + bool rotating_{false}; + + struct GamepadState { + SDL_GameController* controller{nullptr}; + std::array buttonState_{}; + std::array axisState_{}; + bool connected{false}; + std::string name; + }; + std::array gamepads_; +}; + +} // namespace extra2d diff --git a/Extra2D/include/extra2d/platform/SDLHelper.h b/Extra2D/include/extra2d/platform/SDLHelper.h new file mode 100644 index 0000000..203d5ea --- /dev/null +++ b/Extra2D/include/extra2d/platform/SDLHelper.h @@ -0,0 +1,193 @@ +#pragma once + +#include +#include +#include + +struct SDL_Window; +struct SDL_Event; +struct SDL_KeyboardEvent; +struct SDL_MouseMotionEvent; +struct SDL_MouseButtonEvent; +struct SDL_MouseWheelEvent; +struct SDL_ControllerDeviceEvent; +struct SDL_ControllerButtonEvent; +struct SDL_ControllerAxisEvent; +struct SDL_TouchFingerEvent; +struct SDL_MultiGestureEvent; +struct SDL_DollarGestureEvent; +struct SDL_WindowEvent; + +namespace extra2d { + +/** + * @brief SDL2 辅助类 + * + * 封装 SDL2 的初始化、窗口创建和事件分发功能。 + * 参考 Cocos Creator 的 SDLHelper 设计。 + */ +class SDLHelper { +public: + /** + * @brief 初始化 SDL2 + * @return 成功返回 0,失败返回错误码 + */ + static i32 init(); + + /** + * @brief 关闭 SDL2 + */ + static void quit(); + + /** + * @brief 检查是否已初始化 + * @return 如果已初始化返回 true + */ + static bool isInitialized(); + + /** + * @brief 创建窗口 + * @param title 窗口标题 + * @param w 窗口宽度 + * @param h 窗口高度 + * @param flags 窗口标志 + * @return SDL 窗口指针 + */ + static SDL_Window* createWindow(const char* title, i32 w, i32 h, u32 flags); + + /** + * @brief 创建窗口(指定位置) + * @param title 窗口标题 + * @param x 窗口 X 坐标 + * @param y 窗口 Y 坐标 + * @param w 窗口宽度 + * @param h 窗口高度 + * @param flags 窗口标志 + * @return SDL 窗口指针 + */ + static SDL_Window* createWindow(const char* title, i32 x, i32 y, i32 w, i32 h, u32 flags); + + /** + * @brief 销毁窗口 + * @param window SDL 窗口指针 + */ + static void destroyWindow(SDL_Window* window); + + /** + * @brief 获取窗口原生句柄 + * @param window SDL 窗口指针 + * @return 原生窗口句柄 + */ + static void* getWindowHandle(SDL_Window* window); + + /** + * @brief 获取窗口位置 + * @param window SDL 窗口指针 + * @return 窗口位置 + */ + static Vec2 getWindowPosition(SDL_Window* window); + + /** + * @brief 获取窗口大小 + * @param window SDL 窗口指针 + * @return 窗口大小 + */ + static Vec2 getWindowSize(SDL_Window* window); + + /** + * @brief 检查窗口是否最小化 + * @param window SDL 窗口指针 + * @return 如果最小化返回 true + */ + static bool isWindowMinimized(SDL_Window* window); + + /** + * @brief 检查窗口是否获得焦点 + * @param window SDL 窗口指针 + * @return 如果获得焦点返回 true + */ + static bool isWindowFocused(SDL_Window* window); + + /** + * @brief 设置光标可见性 + * @param enabled 是否可见 + */ + static void setCursorEnabled(bool enabled); + + /** + * @brief 检查光标是否可见 + * @return 如果可见返回 true + */ + static bool isCursorEnabled(); + + /** + * @brief 设置光标锁定模式 + * @param locked 是否锁定 + */ + static void setCursorLocked(bool locked); + + /** + * @brief 检查光标是否锁定 + * @return 如果锁定返回 true + */ + static bool isCursorLocked(); + + /** + * @brief 开始文本输入 + */ + static void startTextInput(); + + /** + * @brief 停止文本输入 + */ + static void stopTextInput(); + + /** + * @brief 检查是否在文本输入模式 + * @return 如果在文本输入模式返回 true + */ + static bool isTextInputActive(); + + /** + * @brief 分发 SDL 事件 + * @param event SDL 事件 + */ + static void dispatchSDLEvent(const SDL_Event& event); + + /** + * @brief 轮询所有 SDL 事件并分发 + */ + static void pollAndDispatchEvents(); + + /** + * @brief 获取触摸设备 ID + * @return 触摸设备 ID,如果没有返回 -1 + */ + static i64 getTouchDeviceId(); + + /** + * @brief 检查是否有触摸设备 + * @return 如果有触摸设备返回 true + */ + static bool hasTouchDevice(); + +private: + static void dispatchWindowEvent(const SDL_WindowEvent& event); + static void dispatchKeyboardEvent(const SDL_KeyboardEvent& event); + static void dispatchMouseMotionEvent(const SDL_MouseMotionEvent& event); + static void dispatchMouseButtonEvent(const SDL_MouseButtonEvent& event); + static void dispatchMouseWheelEvent(const SDL_MouseWheelEvent& event); + static void dispatchControllerDeviceEvent(const SDL_ControllerDeviceEvent& event); + static void dispatchControllerButtonEvent(const SDL_ControllerButtonEvent& event); + static void dispatchControllerAxisEvent(const SDL_ControllerAxisEvent& event); + static void dispatchTouchFingerEvent(const SDL_TouchFingerEvent& event); + static void dispatchMultiGestureEvent(const SDL_MultiGestureEvent& event); + static void dispatchDollarGestureEvent(const SDL_DollarGestureEvent& event); + + static bool initialized_; + static bool cursorVisible_; + static bool cursorLocked_; + static i64 touchDeviceId_; +}; + +} // namespace extra2d diff --git a/Extra2D/include/extra2d/platform/Window.h b/Extra2D/include/extra2d/platform/Window.h new file mode 100644 index 0000000..f4e9ec7 --- /dev/null +++ b/Extra2D/include/extra2d/platform/Window.h @@ -0,0 +1,233 @@ +#pragma once + +#include +#include +#include + +struct SDL_Window; +struct SDL_GLContext; +typedef void* SDL_GLContext; + +namespace extra2d { + +/** + * @brief 窗口配置 + */ +struct WindowConfig { + std::string title = "Extra2D"; + i32 width = 1280; + i32 height = 720; + bool fullscreen = false; + bool resizable = true; + bool vsync = true; + bool borderless = false; + bool highDPI = true; +}; + +/** + * @brief 窗口类 + * + * 封装 SDL2 窗口和 OpenGL 上下文管理。 + */ +class Window { +public: + /** + * @brief 获取单例实例 + * @return 窗口实例 + */ + static Window* getInstance(); + + /** + * @brief 初始化窗口 + * @param config 窗口配置 + * @return 成功返回 0,失败返回错误码 + */ + i32 initialize(const WindowConfig& config); + + /** + * @brief 关闭窗口 + */ + void shutdown(); + + /** + * @brief 检查是否已初始化 + * @return 如果已初始化返回 true + */ + bool isInitialized() const; + + /** + * @brief 获取窗口大小 + * @return 窗口大小 (宽度, 高度) + */ + Vec2 getSize() const; + + /** + * @brief 设置窗口大小 + * @param width 宽度 + * @param height 高度 + */ + void setSize(i32 width, i32 height); + + /** + * @brief 获取窗口位置 + * @return 窗口位置 + */ + Vec2 getPosition() const; + + /** + * @brief 设置窗口位置 + * @param x X 坐标 + * @param y Y 坐标 + */ + void setPosition(i32 x, i32 y); + + /** + * @brief 获取窗口标题 + * @return 窗口标题 + */ + std::string getTitle() const; + + /** + * @brief 设置窗口标题 + * @param title 标题 + */ + void setTitle(const std::string& title); + + /** + * @brief 检查是否全屏 + * @return 如果全屏返回 true + */ + bool isFullscreen() const; + + /** + * @brief 设置全屏模式 + * @param fullscreen 是否全屏 + */ + void setFullscreen(bool fullscreen); + + /** + * @brief 设置无边框窗口模式 + * @param borderless 是否无边框 + */ + void setBorderless(bool borderless); + + /** + * @brief 检查是否可调整大小 + * @return 如果可调整大小返回 true + */ + bool isResizable() const; + + /** + * @brief 设置是否可调整大小 + * @param resizable 是否可调整大小 + */ + void setResizable(bool resizable); + + /** + * @brief 检查窗口是否获得焦点 + * @return 如果获得焦点返回 true + */ + bool hasFocus() const; + + /** + * @brief 检查窗口是否最小化 + * @return 如果最小化返回 true + */ + bool isMinimized() const; + + /** + * @brief 检查窗口是否最大化 + * @return 如果最大化返回 true + */ + bool isMaximized() const; + + /** + * @brief 设置垂直同步 + * @param enabled 是否启用 + */ + void setVSync(bool enabled); + + /** + * @brief 检查是否启用垂直同步 + * @return 如果启用返回 true + */ + bool isVSync() const; + + /** + * @brief 显示窗口 + */ + void show(); + + /** + * @brief 隐藏窗口 + */ + void hide(); + + /** + * @brief 最小化窗口 + */ + void minimize(); + + /** + * @brief 最大化窗口 + */ + void maximize(); + + /** + * @brief 还原窗口 + */ + void restore(); + + /** + * @brief 交换缓冲区 + */ + void swapBuffers(); + + /** + * @brief 获取 SDL 窗口指针 + * @return SDL 窗口指针 + */ + SDL_Window* getSDLWindow(); + + /** + * @brief 获取 OpenGL 上下文 + * @return OpenGL 上下文 + */ + SDL_GLContext getGLContext(); + + /** + * @brief 获取帧缓冲大小(可能在高 DPI 显示器上与窗口大小不同) + * @return 帧缓冲大小 + */ + Vec2 getFramebufferSize() const; + + /** + * @brief 获取显示缩放因子 + * @return 缩放因子 + */ + float getContentScale() const; + + /** + * @brief 获取原生窗口句柄 + * @return 原生窗口句柄 + */ + void* getNativeHandle(); + +private: + Window(); + ~Window(); + + Window(const Window&) = delete; + Window& operator=(const Window&) = delete; + + static Window* instance_; + + SDL_Window* sdlWindow_{nullptr}; + SDL_GLContext glContext_{nullptr}; + bool initialized_{false}; + bool vsync_{true}; + bool fullscreen_{false}; + bool focused_{true}; +}; + +} // namespace extra2d diff --git a/Extra2D/include/extra2d/platform/glfw/glfw_window.h b/Extra2D/include/extra2d/platform/glfw/glfw_window.h deleted file mode 100644 index 9843118..0000000 --- a/Extra2D/include/extra2d/platform/glfw/glfw_window.h +++ /dev/null @@ -1,226 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -namespace extra2d { - -/** - * @brief 光标形状 - */ -enum class Cursor { - Arrow, - IBeam, - Crosshair, - Hand, - HResize, - VResize, - Hidden -}; - -/** - * @brief GLFW 窗口实现 - */ -class GLFWWindow { -public: - using ResizeFn = std::function; - using CloseFn = std::function; - using FocusFn = std::function; - - GLFWWindow(); - ~GLFWWindow(); - - /** - * @brief 创建窗口 - * @param title 窗口标题 - * @param w 窗口宽度 - * @param h 窗口高度 - * @param vsync 是否启用垂直同步 - * @return 创建是否成功 - */ - bool create(const std::string& title, int w, int h, bool vsync = true); - - /** - * @brief 销毁窗口 - */ - void destroy(); - - /** - * @brief 轮询事件 - */ - void poll(); - - /** - * @brief 交换缓冲区 - */ - void swap(); - - /** - * @brief 窗口是否应该关闭 - */ - bool shouldClose() const; - - /** - * @brief 设置窗口关闭标志 - */ - void close(); - - /** - * @brief 设置窗口标题 - */ - void title(const std::string& t); - - /** - * @brief 设置窗口大小 - */ - void size(int w, int h); - - /** - * @brief 设置窗口位置 - */ - void pos(int x, int y); - - /** - * @brief 设置全屏模式 - */ - void fullscreen(bool fs); - - /** - * @brief 设置垂直同步 - */ - void vsync(bool v); - - /** - * @brief 设置窗口可见性 - */ - void visible(bool v); - - /** - * @brief 获取窗口宽度 - */ - int w() const { return w_; } - - /** - * @brief 获取窗口高度 - */ - int h() const { return h_; } - - /** - * @brief 获取窗口大小 - */ - Size size() const; - - /** - * @brief 获取窗口位置 - */ - Vec2 pos() const; - - /** - * @brief 是否全屏 - */ - bool fullscreen() const { return fullscreen_; } - - /** - * @brief 是否启用垂直同步 - */ - bool vsync() const { return vsync_; } - - /** - * @brief 窗口是否获得焦点 - */ - bool focused() const { return focused_; } - - /** - * @brief 窗口是否最小化 - */ - bool minimized() const { return minimized_; } - - /** - * @brief 获取内容缩放X - */ - f32 scaleX() const { return scaleX_; } - - /** - * @brief 获取内容缩放Y - */ - f32 scaleY() const { return scaleY_; } - - /** - * @brief 设置光标形状 - */ - void cursor(Cursor c); - - /** - * @brief 显示/隐藏光标 - */ - void showCursor(bool show); - - /** - * @brief 锁定/解锁光标 - */ - void lockCursor(bool lock); - - /** - * @brief 设置大小改变回调 - */ - void onResize(ResizeFn fn) { resizeFn_ = fn; } - - /** - * @brief 设置关闭回调 - */ - void onClose(CloseFn fn) { closeFn_ = fn; } - - /** - * @brief 设置焦点改变回调 - */ - void onFocus(FocusFn fn) { focusFn_ = fn; } - - /** - * @brief 获取原生窗口句柄 - */ - void* native() const; - - /** - * @brief 获取 GLFW 窗口句柄 - */ - GLFWwindow* handle() const { return handle_; } - -private: - bool initGLFW(); - void deinitGLFW(); - void updateContentScale(); - - // GLFW 回调函数(静态) - static void framebufferSizeCallback(GLFWwindow* window, int w, int h); - static void windowCloseCallback(GLFWwindow* window); - static void windowFocusCallback(GLFWwindow* window, int focused); - static void windowIconifyCallback(GLFWwindow* window, int iconified); - static void cursorPosCallback(GLFWwindow* window, double x, double y); - static void mouseButtonCallback(GLFWwindow* window, int btn, int action, int mods); - static void scrollCallback(GLFWwindow* window, double x, double y); - static void keyCallback(GLFWwindow* window, int key, int scancode, int action, int mods); - static void joystickCallback(int jid, int event); - - GLFWwindow* handle_ = nullptr; - - int w_ = 1280; - int h_ = 720; - bool fullscreen_ = false; - bool vsync_ = true; - bool focused_ = true; - bool minimized_ = false; - bool shouldClose_ = false; - f32 scaleX_ = 1.0f; - f32 scaleY_ = 1.0f; - bool cursorVisible_ = true; - bool cursorLocked_ = false; - - ResizeFn resizeFn_; - CloseFn closeFn_; - FocusFn focusFn_; -}; - -} // namespace extra2d diff --git a/Extra2D/include/extra2d/platform/keys.h b/Extra2D/include/extra2d/platform/keys.h deleted file mode 100644 index 7fafae1..0000000 --- a/Extra2D/include/extra2d/platform/keys.h +++ /dev/null @@ -1,72 +0,0 @@ -#pragma once - -namespace extra2d { - -/** - * @brief 键盘按键码 - */ -enum class Key : int { - None = 0, - A, B, C, D, E, F, G, H, I, J, K, L, M, - N, O, P, Q, R, S, T, U, V, W, X, Y, Z, - Num0, Num1, Num2, Num3, Num4, - Num5, Num6, Num7, Num8, Num9, - F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, - Space, Enter, Escape, Tab, Backspace, - Insert, Delete, Home, End, PageUp, PageDown, - Up, Down, Left, Right, - LShift, RShift, LCtrl, RCtrl, LAlt, RAlt, - CapsLock, NumLock, ScrollLock, - Count -}; - -/** - * @brief 鼠标按钮 - */ -enum class Mouse : int { - Left = 0, - Right, - Middle, - X1, - X2, - Count -}; - -/** - * @brief 游戏手柄按钮 - */ -enum class Gamepad : int { - A = 0, - B, - X, - Y, - LB, - RB, - LT, - RT, - Back, - Start, - Guide, - LStick, - RStick, - DUp, - DDown, - DLeft, - DRight, - Count -}; - -/** - * @brief 手柄轴 - */ -enum class GamepadAxis : int { - LeftX = 0, - LeftY, - RightX, - RightY, - LeftTrigger, - RightTrigger, - Count -}; - -} // namespace extra2d diff --git a/Extra2D/include/extra2d/platform/window_module.h b/Extra2D/include/extra2d/platform/window_module.h deleted file mode 100644 index e9b5dd9..0000000 --- a/Extra2D/include/extra2d/platform/window_module.h +++ /dev/null @@ -1,59 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -namespace extra2d { - -/** - * @brief 窗口模块配置结构 - */ -struct WindowCfg { - std::string title; - int w; - int h; - bool vsync; - int priority; - - WindowCfg() - : title("Extra2D"), w(1280), h(720), vsync(true), priority(0) {} -}; - -/** - * @brief 窗口模块 - * 管理窗口创建和生命周期 - */ -class WindowModule : public Module { -public: - /** - * @brief 构造函数(Lambda 配置) - * @param configFn 配置函数 - */ - explicit WindowModule(std::function configFn); - - /** - * @brief 析构函数 - */ - ~WindowModule() override; - - bool init() override; - void shutdown() override; - bool ok() const override { return initialized_; } - const char *name() const override { return "window"; } - int priority() const override { return cfg_.priority; } - - /** - * @brief 获取窗口 - * @return 窗口指针 - */ - GLFWWindow *win() const { return win_.get(); } - -private: - WindowCfg cfg_; - Unique win_; - bool initialized_ = false; -}; - -} // namespace extra2d diff --git a/Extra2D/include/extra2d/services/asset_service.h b/Extra2D/include/extra2d/services/asset_service.h deleted file mode 100644 index e4e4982..0000000 --- a/Extra2D/include/extra2d/services/asset_service.h +++ /dev/null @@ -1,334 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace extra2d { - -// --------------------------------------------------------------------------- -// IAssetService - 资源服务接口 -// --------------------------------------------------------------------------- - -/** - * @brief 资源服务接口 - * - * 提供资源加载、缓存、异步加载等功能。 - * 使用模板方法支持类型安全的资源加载。 - */ -class IAssetService : public IService { -public: - virtual ~IAssetService() = default; - - /** - * @brief 同步加载资源 - * @tparam T 资源类型 - * @param path 资源路径 - * @return 资源句柄 - */ - template AssetHandle load(const std::string &path) { - static_assert(std::is_base_of_v, "T must derive from Asset"); - return AssetHandle(loadImpl(AssetID(path), typeid(T))); - } - - /** - * @brief 异步加载资源 - * @tparam T 资源类型 - * @param path 资源路径 - * @param callback 加载完成回调 - */ - template - void loadAsync(const std::string &path, AssetLoadCallback callback) { - static_assert(std::is_base_of_v, "T must derive from Asset"); - loadAsyncImpl(AssetID(path), typeid(T), - [cb = std::move(callback)](AssetHandleBase handle) { - cb(AssetHandle(handle)); - }); - } - - /** - * @brief 获取已缓存的资源 - * @tparam T 资源类型 - * @param path 资源路径 - * @return 资源句柄,不存在返回空句柄 - */ - template AssetHandle get(const std::string &path) { - static_assert(std::is_base_of_v, "T must derive from Asset"); - return AssetHandle(getImpl(AssetID(path), typeid(T))); - } - - /** - * @brief 预加载资源(后台加载,不返回句柄) - * @tparam T 资源类型 - * @param path 资源路径 - */ - template void preload(const std::string &path) { - static_assert(std::is_base_of_v, "T must derive from Asset"); - preloadImpl(AssetID(path), typeid(T)); - } - - /** - * @brief 检查资源是否已加载 - * @param path 资源路径 - * @return 已加载返回 true - */ - virtual bool isLoaded(const std::string &path) const = 0; - - /** - * @brief 检查资源是否正在加载 - * @param path 资源路径 - * @return 正在加载返回 true - */ - virtual bool isLoading(const std::string &path) const = 0; - - /** - * @brief 卸载资源 - * @param path 资源路径 - */ - virtual void unload(const std::string &path) = 0; - - /** - * @brief 设置缓存上限 - * @param maxBytes 最大字节数 - */ - virtual void setLimit(size_t maxBytes) = 0; - - /** - * @brief 获取当前缓存大小 - * @return 缓存字节数 - */ - virtual size_t size() const = 0; - - /** - * @brief 清理无引用资源 - */ - virtual void purge() = 0; - - /** - * @brief 清空所有缓存 - */ - virtual void clear() = 0; - - /** - * @brief 获取缓存统计信息 - * @return 缓存统计 - */ - virtual CacheStats stats() const = 0; - - /** - * @brief 注册加载器 - * @tparam T 资源类型 - * @param loader 加载器实例 - */ - template void registerLoader(Unique> loader) { - static_assert(std::is_base_of_v, "T must derive from Asset"); - registerLoaderImpl(typeid(T), std::move(loader)); - } - - /** - * @brief 挂载资源包 - * @param path 资源包路径 - * @return 成功返回 true - */ - virtual bool mount(const std::string &path) = 0; - - /** - * @brief 卸载资源包 - * @param path 资源包路径 - */ - virtual void unmount(const std::string &path) = 0; - - /** - * @brief 设置数据处理管道 - * @param pipe 处理管道 - */ - virtual void setPipe(DataPipe pipe) = 0; - - /** - * @brief 设置资源根目录 - * @param path 根目录路径 - */ - virtual void setRoot(const std::string &path) = 0; - - /** - * @brief 获取资源根目录 - * @return 根目录路径 - */ - virtual std::string root() const = 0; - - /** - * @brief 处理异步加载完成回调(在主线程调用) - */ - virtual void process() = 0; - -protected: - /** - * @brief 同步加载实现 - */ - virtual AssetHandleBase loadImpl(const AssetID &id, std::type_index type) = 0; - - /** - * @brief 异步加载实现 - */ - virtual void loadAsyncImpl(const AssetID &id, std::type_index type, - std::function callback) = 0; - - /** - * @brief 获取资源实现 - */ - virtual AssetHandleBase getImpl(const AssetID &id, std::type_index type) = 0; - - /** - * @brief 预加载实现 - */ - virtual void preloadImpl(const AssetID &id, std::type_index type) = 0; - - /** - * @brief 注册加载器实现 - */ - virtual void registerLoaderImpl(std::type_index type, - Unique loader) = 0; -}; - -// --------------------------------------------------------------------------- -// AssetService - 资源服务实现 -// --------------------------------------------------------------------------- - -/** - * @brief 资源服务实现 - * - * 实现资源加载、缓存、异步加载等功能。 - * 使用线程池处理异步加载任务。 - */ -class AssetService : public IAssetService { -public: - AssetService(); - ~AssetService() override; - - ServiceInfo info() const override { - ServiceInfo i; - i.name = "AssetService"; - i.priority = ServicePriority::Resource; - i.enabled = true; - return i; - } - - bool init() override; - void shutdown() override; - - bool isLoaded(const std::string &path) const override; - bool isLoading(const std::string &path) const override; - void unload(const std::string &path) override; - - void setLimit(size_t maxBytes) override; - size_t size() const override; - void purge() override; - void clear() override; - CacheStats stats() const override; - - bool mount(const std::string &path) override; - void unmount(const std::string &path) override; - void setPipe(DataPipe pipe) override; - void setRoot(const std::string &path) override; - std::string root() const override; - - void process() override; - -protected: - AssetHandleBase loadImpl(const AssetID &id, std::type_index type) override; - void loadAsyncImpl(const AssetID &id, std::type_index type, - std::function callback) override; - AssetHandleBase getImpl(const AssetID &id, std::type_index type) override; - void preloadImpl(const AssetID &id, std::type_index type) override; - void registerLoaderImpl(std::type_index type, - Unique loader) override; - -private: - struct LoadTask { - AssetID id; - std::type_index type = typeid(void); - std::function callback; - }; - - struct LoadedAsset { - Ref asset; - std::type_index type = typeid(void); - }; - - std::string root_; - Unique cache_; - PackManager packManager_; - DataPipe pipe_; - - mutable std::shared_mutex mutex_; - std::unordered_map assets_; - std::unordered_map states_; - std::unordered_map> loaders_; - - std::thread workerThread_; - std::queue taskQueue_; - std::mutex taskMutex_; - std::condition_variable taskCv_; - std::atomic running_{false}; - - std::queue> callbackQueue_; - std::mutex callbackMutex_; - - /** - * @brief 工作线程函数 - */ - void workerFunc(); - - /** - * @brief 从文件系统加载资源 - * @param id 资源ID - * @param type 资源类型 - * @return 加载的资源 - */ - Ref loadFromFile(const AssetID &id, std::type_index type); - - /** - * @brief 从资源包加载资源 - * @param id 资源ID - * @param type 资源类型 - * @return 加载的资源 - */ - Ref loadFromPack(const AssetID &id, std::type_index type); - - /** - * @brief 获取加载器 - * @param type 资源类型 - * @return 加载器指针 - */ - AssetLoaderBase *getLoader(std::type_index type); - - /** - * @brief 根据路径推断资源类型 - * @param path 资源路径 - * @return 资源类型索引 - */ - std::type_index inferType(const std::string &path); - - E2D_AUTO_REGISTER_SERVICE(IAssetService, AssetService); -}; - -} // namespace extra2d diff --git a/Extra2D/include/extra2d/services/event_service.h b/Extra2D/include/extra2d/services/event_service.h deleted file mode 100644 index ae60d64..0000000 --- a/Extra2D/include/extra2d/services/event_service.h +++ /dev/null @@ -1,77 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -namespace extra2d { - -/** - * @brief 事件服务接口 - */ -class IEventService : public IService { -public: - virtual ~IEventService() = default; - - virtual void push(const Event& event) = 0; - virtual void push(Event&& event) = 0; - virtual bool poll(Event& event) = 0; - - virtual ListenerID on(EventType type, EventDispatcher::EventFn fn) = 0; - virtual void off(ListenerID id) = 0; - virtual void offAll(EventType type) = 0; - virtual void offAll() = 0; - - virtual void dispatch(Event& event) = 0; - virtual void process() = 0; - - virtual size_t listenerCount(EventType type) const = 0; - virtual size_t totalListeners() const = 0; - virtual size_t queueSize() const = 0; -}; - -/** - * @brief 事件服务实现 - */ -class EventService : public IEventService { -public: - EventService(); - ~EventService() override = default; - - ServiceInfo info() const override; - - bool init() override; - void shutdown() override; - void update(f32 dt) override; - - void push(const Event& event) override; - void push(Event&& event) override; - bool poll(Event& event) override; - - ListenerID on(EventType type, EventDispatcher::EventFn fn) override; - void off(ListenerID id) override; - void offAll(EventType type) override; - void offAll() override; - - void dispatch(Event& event) override; - void process() override; - - size_t listenerCount(EventType type) const override; - size_t totalListeners() const override; - size_t queueSize() const override; - - EventQueue& queue() { return queue_; } - const EventQueue& queue() const { return queue_; } - EventDispatcher& dispatcher() { return dispatcher_; } - const EventDispatcher& dispatcher() const { return dispatcher_; } - -private: - EventQueue queue_; - EventDispatcher dispatcher_; - - // 服务注册元数据 - E2D_AUTO_REGISTER_SERVICE(IEventService, EventService); -}; - -} diff --git a/Extra2D/include/extra2d/services/logger_service.h b/Extra2D/include/extra2d/services/logger_service.h deleted file mode 100644 index a3333ad..0000000 --- a/Extra2D/include/extra2d/services/logger_service.h +++ /dev/null @@ -1,310 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include - -namespace extra2d { - -/** - * @brief 日志颜色结构 - */ -struct LogColor { - u8 r, g, b; - - constexpr LogColor() : r(255), g(255), b(255) {} - constexpr LogColor(u8 r, u8 g, u8 b) : r(r), g(g), b(b) {} - - static constexpr LogColor White() { return LogColor(255, 255, 255); } - static constexpr LogColor Gray() { return LogColor(128, 128, 128); } - static constexpr LogColor Red() { return LogColor(255, 85, 85); } - static constexpr LogColor Green() { return LogColor(85, 255, 85); } - static constexpr LogColor Yellow() { return LogColor(255, 255, 85); } - static constexpr LogColor Blue() { return LogColor(85, 85, 255); } - static constexpr LogColor Magenta() { return LogColor(255, 85, 255); } - static constexpr LogColor Cyan() { return LogColor(85, 255, 255); } - static constexpr LogColor Orange() { return LogColor(255, 165, 0); } - - static constexpr LogColor Slate() { return LogColor(100, 116, 139); } - static constexpr LogColor SlateLight() { return LogColor(148, 163, 184); } - static constexpr LogColor Sky() { return LogColor(14, 165, 233); } - static constexpr LogColor SkyLight() { return LogColor(125, 211, 252); } - static constexpr LogColor Emerald() { return LogColor(16, 185, 129); } - static constexpr LogColor EmeraldLight() { return LogColor(110, 231, 183); } - static constexpr LogColor Amber() { return LogColor(245, 158, 11); } - static constexpr LogColor AmberLight() { return LogColor(252, 211, 77); } - static constexpr LogColor Rose() { return LogColor(244, 63, 94); } - static constexpr LogColor RoseLight() { return LogColor(253, 164, 175); } - static constexpr LogColor Violet() { return LogColor(139, 92, 246); } - static constexpr LogColor VioletLight() { return LogColor(196, 181, 253); } - static constexpr LogColor Indigo() { return LogColor(99, 102, 241); } - static constexpr LogColor IndigoLight() { return LogColor(165, 180, 252); } -}; - -/** - * @brief 日志级别枚举 - */ -enum class LogLevel { - Trace = 0, - Debug = 1, - Info = 2, - Registry = 3, - Warn = 4, - Error = 5, - Fatal = 6, - Off = 7 -}; - -/** - * @brief 日志服务接口 - */ -class ILogger : public IService { -public: - virtual ~ILogger() = default; - - /** - * @brief 设置日志级别 - */ - virtual void level(LogLevel lvl) = 0; - - /** - * @brief 获取日志级别 - */ - virtual LogLevel level() const = 0; - - /** - * @brief 检查日志级别是否启用 - */ - virtual bool enabled(LogLevel lvl) const = 0; - - /** - * @brief 记录日志(格式化) - */ - virtual void log(LogLevel lvl, const char *fmt, ...) = 0; - - /** - * @brief 记录日志(字符串) - */ - virtual void log(LogLevel lvl, const std::string &msg) = 0; - - /** - * @brief Trace级别日志 - */ - virtual void trace(const char *fmt, ...) = 0; - - /** - * @brief Debug级别日志 - */ - virtual void debug(const char *fmt, ...) = 0; - - /** - * @brief Info级别日志 - */ - virtual void info(const char *fmt, ...) = 0; - - /** - * @brief Registry级别日志(用于模块/服务注册显示) - */ - virtual void registry(const char *fmt, ...) = 0; - - /** - * @brief Warn级别日志 - */ - virtual void warn(const char *fmt, ...) = 0; - - /** - * @brief Error级别日志 - */ - virtual void error(const char *fmt, ...) = 0; - - /** - * @brief Fatal级别日志 - */ - virtual void fatal(const char *fmt, ...) = 0; - - /** - * @brief 设置日志级别颜色 - * @param lvl 日志级别 - * @param c 颜色 - */ - virtual void levelColor(LogLevel lvl, const LogColor &c) = 0; - - /** - * @brief 获取日志级别颜色 - * @param lvl 日志级别 - * @return 颜色 - */ - virtual LogColor levelColor(LogLevel lvl) const = 0; - - /** - * @brief 启用/禁用颜色输出 - * @param on 是否启用 - */ - virtual void colors(bool on) = 0; - - /** - * @brief 是否启用颜色输出 - */ - virtual bool colors() const = 0; - - ServiceInfo info() const override { - ServiceInfo i; - i.name = "Logger"; - i.priority = ServicePriority::Core; - i.enabled = true; - return i; - } -}; - -/** - * @brief 控制台日志服务实现 - */ -class ConsoleLogger : public ILogger { -public: - ConsoleLogger(); - ~ConsoleLogger() override; - - bool init() override; - void shutdown() override; - - void level(LogLevel lvl) override; - LogLevel level() const override; - bool enabled(LogLevel lvl) const override; - - void log(LogLevel lvl, const char *fmt, ...) override; - void log(LogLevel lvl, const std::string &msg) override; - - void trace(const char *fmt, ...) override; - void debug(const char *fmt, ...) override; - void info(const char *fmt, ...) override; - void registry(const char *fmt, ...) override; - void warn(const char *fmt, ...) override; - void error(const char *fmt, ...) override; - void fatal(const char *fmt, ...) override; - - void levelColor(LogLevel lvl, const LogColor &c) override; - LogColor levelColor(LogLevel lvl) const override; - void colors(bool on) override; - bool colors() const override; - -private: - void output(LogLevel lvl, const char *msg); - const char *levelString(LogLevel lvl); - std::string ansiColor(LogLevel lvl); - - LogLevel level_; - bool colors_; - LogColor levelColors_[7]; - class Impl; - Unique impl_; - - // 服务注册元数据 - E2D_AUTO_REGISTER_SERVICE(ILogger, ConsoleLogger); -}; - -} // namespace extra2d - -// 格式化辅助函数 - 将参数转换为字符串 -namespace extra2d { -namespace detail { -template std::string to_string(T &&value) { - using Decayed = std::decay_t; - if constexpr (std::is_same_v) { - return value; - } else if constexpr (std::is_same_v) { - return value ? value : "(null)"; - } else if constexpr (std::is_arithmetic_v) { - if constexpr (std::is_same_v) { - return value ? "true" : "false"; - } else if constexpr (std::is_floating_point_v) { - return std::to_string(value); - } else { - return std::to_string(value); - } - } else { - return ""; - } -} - -inline void format_impl(std::string &result, const char *fmt) { result += fmt; } - -template -void format_impl(std::string &result, const char *fmt, T &&value, - Args &&...args) { - const char *p = fmt; - while (*p) { - if (*p == '{' && *(p + 1) == '}') { - result += to_string(std::forward(value)); - format_impl(result, p + 2, std::forward(args)...); - return; - } - result += *p++; - } - result += " "; - result += to_string(std::forward(value)); - format_impl(result, p, std::forward(args)...); -} -} // namespace detail - -template -std::string format_str(const char *fmt, Args &&...args) { - if constexpr (sizeof...(args) == 0) { - return std::string(fmt); - } else { - std::string result; - detail::format_impl(result, fmt, std::forward(args)...); - return result; - } -} -} // namespace extra2d - -// 便捷宏 - 自动获取日志服务 -#define E2D_LOG(lvl, ...) \ - do { \ - if (auto logService = ::extra2d::ServiceLocator::instance() \ - .tryGet<::extra2d::ILogger>()) { \ - if (logService->enabled(lvl)) { \ - logService->log(lvl, ::extra2d::format_str(__VA_ARGS__)); \ - } \ - } \ - } while (0) - -#define E2D_TRACE(...) E2D_LOG(::extra2d::LogLevel::Trace, __VA_ARGS__) -#define E2D_DEBUG(...) E2D_LOG(::extra2d::LogLevel::Debug, __VA_ARGS__) -#define E2D_INFO(...) E2D_LOG(::extra2d::LogLevel::Info, __VA_ARGS__) -#define E2D_REGISTRY(...) E2D_LOG(::extra2d::LogLevel::Registry, __VA_ARGS__) -#define E2D_WARN(...) E2D_LOG(::extra2d::LogLevel::Warn, __VA_ARGS__) -#define E2D_ERROR(...) E2D_LOG(::extra2d::LogLevel::Error, __VA_ARGS__) -#define E2D_FATAL(...) E2D_LOG(::extra2d::LogLevel::Fatal, __VA_ARGS__) - -// 带颜色参数的日志宏 -#define E2D_LOG_COLOR(lvl, c, ...) \ - do { \ - if (auto logService = ::extra2d::ServiceLocator::instance() \ - .tryGet<::extra2d::ILogger>()) { \ - if (logService->enabled(lvl)) { \ - auto prevColor = logService->levelColor(lvl); \ - logService->levelColor(lvl, c); \ - logService->log(lvl, ::extra2d::format_str(__VA_ARGS__)); \ - logService->levelColor(lvl, prevColor); \ - } \ - } \ - } while (0) - -#define E2D_TRACE_COLOR(c, ...) \ - E2D_LOG_COLOR(::extra2d::LogLevel::Trace, c, __VA_ARGS__) -#define E2D_DEBUG_COLOR(c, ...) \ - E2D_LOG_COLOR(::extra2d::LogLevel::Debug, c, __VA_ARGS__) -#define E2D_INFO_COLOR(c, ...) \ - E2D_LOG_COLOR(::extra2d::LogLevel::Info, c, __VA_ARGS__) -#define E2D_REGISTRY_COLOR(c, ...) \ - E2D_LOG_COLOR(::extra2d::LogLevel::Registry, c, __VA_ARGS__) -#define E2D_WARN_COLOR(c, ...) \ - E2D_LOG_COLOR(::extra2d::LogLevel::Warn, c, __VA_ARGS__) -#define E2D_ERROR_COLOR(c, ...) \ - E2D_LOG_COLOR(::extra2d::LogLevel::Error, c, __VA_ARGS__) -#define E2D_FATAL_COLOR(c, ...) \ - E2D_LOG_COLOR(::extra2d::LogLevel::Fatal, c, __VA_ARGS__) diff --git a/Extra2D/include/extra2d/services/timer_service.h b/Extra2D/include/extra2d/services/timer_service.h deleted file mode 100644 index c7b112b..0000000 --- a/Extra2D/include/extra2d/services/timer_service.h +++ /dev/null @@ -1,57 +0,0 @@ -#pragma once - -#include -#include -#include - -namespace extra2d { - -/** - * @brief 计时器服务接口 - */ -class ITimerService : public IService { -public: - virtual ~ITimerService() = default; - - virtual u32 add(f32 delay, Timer::Fn fn) = 0; - virtual u32 addRepeat(f32 interval, Timer::Fn fn) = 0; - virtual void cancel(u32 timerId) = 0; - virtual void pauseTimer(u32 timerId) = 0; - virtual void resumeTimer(u32 timerId) = 0; - virtual void clear() = 0; - virtual size_t count() const = 0; -}; - -/** - * @brief 计时器服务实现 - */ -class TimerService : public ITimerService { -public: - TimerService(); - ~TimerService() override = default; - - ServiceInfo info() const override; - - bool init() override; - void shutdown() override; - void update(f32 dt) override; - - u32 add(f32 delay, Timer::Fn fn) override; - u32 addRepeat(f32 interval, Timer::Fn fn) override; - void cancel(u32 timerId) override; - void pauseTimer(u32 timerId) override; - void resumeTimer(u32 timerId) override; - void clear() override; - size_t count() const override; - - TimerManager& mgr() { return mgr_; } - const TimerManager& mgr() const { return mgr_; } - -private: - TimerManager mgr_; - - // 服务注册元数据 - E2D_AUTO_REGISTER_SERVICE(ITimerService, TimerService); -}; - -} diff --git a/Extra2D/src/2d/components/Sprite.cpp b/Extra2D/src/2d/components/Sprite.cpp new file mode 100644 index 0000000..aa0f33a --- /dev/null +++ b/Extra2D/src/2d/components/Sprite.cpp @@ -0,0 +1,169 @@ +#include +#include +#include + +namespace extra2d { + +Sprite::Sprite() : Component("Sprite") { +} + +Sprite::~Sprite() { + if (drawInfo_) { + delete drawInfo_; + drawInfo_ = nullptr; + } + if (renderEntity_) { + delete renderEntity_; + renderEntity_ = nullptr; + } +} + +SpriteFrame* Sprite::getSpriteFrame() const { + return spriteFrame_; +} + +void Sprite::setSpriteFrame(SpriteFrame* frame) { + if (spriteFrame_ != frame) { + spriteFrame_ = frame; + dirty_ = true; + + if (frame && !sizeSetByUser_) { + size_ = Vec2{frame->getOriginalSize().x, frame->getOriginalSize().y}; + } + } +} + +Vec4 Sprite::getColor() const { + return color_; +} + +void Sprite::setColor(const Vec4& color) { + color_ = color; + dirty_ = true; +} + +void Sprite::setColor(float r, float g, float b) { + setColor(r, g, b, color_.a); +} + +void Sprite::setColor(float r, float g, float b, float a) { + color_ = Vec4{r, g, b, a}; + dirty_ = true; +} + +float Sprite::getWidth() const { + return size_.x; +} + +void Sprite::setWidth(float width) { + size_.x = width; + sizeSetByUser_ = true; + dirty_ = true; +} + +float Sprite::getHeight() const { + return size_.y; +} + +void Sprite::setHeight(float height) { + size_.y = height; + sizeSetByUser_ = true; + dirty_ = true; +} + +Vec2 Sprite::getSize() const { + return size_; +} + +void Sprite::setSize(const Vec2& size) { + size_ = size; + sizeSetByUser_ = true; + dirty_ = true; +} + +bool Sprite::isDirty() const { + return dirty_; +} + +void Sprite::setDirty(bool dirty) { + dirty_ = dirty; +} + +void Sprite::markForUpdate() { + dirty_ = true; +} + +void Sprite::onAdd() { + renderEntity_ = new RenderEntity(); + renderEntity_->setNode(getNode()); + + drawInfo_ = new RenderDrawInfo(); + drawInfo_->setDrawInfoType(RenderDrawInfoType::Component); + renderEntity_->addDynamicRenderDrawInfo(drawInfo_); + + dirty_ = true; +} + +void Sprite::onRemove() { + if (drawInfo_) { + delete drawInfo_; + drawInfo_ = nullptr; + } + if (renderEntity_) { + delete renderEntity_; + renderEntity_ = nullptr; + } +} + +void Sprite::update(f32 dt) { + if (dirty_) { + updateGeometry(); + dirty_ = false; + } +} + +void Sprite::render() { +} + +void Sprite::updateGeometry() { + if (!spriteFrame_ || !drawInfo_) return; + + updateRenderEntity(); + updateVertexData(); +} + +void Sprite::updateRenderEntity() { + if (!renderEntity_) return; + + renderEntity_->setColor( + static_cast(color_.r * 255), + static_cast(color_.g * 255), + static_cast(color_.b * 255), + static_cast(color_.a * 255) + ); + + if (getNode()) { + renderEntity_->setPriority(static_cast(getNode()->getLayer())); + } +} + +void Sprite::updateVertexData() { + if (!spriteFrame_ || !drawInfo_) return; + + auto* texture = spriteFrame_->getTexture(); + if (!texture) return; + + drawInfo_->setTexture(texture->getGFXTexture()); + + float width = size_.x; + float height = size_.y; + + float halfW = width * 0.5f; + float halfH = height * 0.5f; + + drawInfo_->setVbCount(4); + drawInfo_->setIbCount(6); + drawInfo_->setVertDirty(true); +} + +} // namespace extra2d diff --git a/Extra2D/src/2d/components/SpriteFrame.cpp b/Extra2D/src/2d/components/SpriteFrame.cpp new file mode 100644 index 0000000..6786205 --- /dev/null +++ b/Extra2D/src/2d/components/SpriteFrame.cpp @@ -0,0 +1,104 @@ +#include +#include +#include + +namespace extra2d { + +SpriteFrame::SpriteFrame() : Asset("SpriteFrame") { +} + +SpriteFrame::~SpriteFrame() { +} + +Texture2D* SpriteFrame::getTexture() const { + return texture_; +} + +void SpriteFrame::setTexture(Texture2D* texture) { + texture_ = texture; + if (texture && rect_.width == 0 && rect_.height == 0) { + rect_.x = 0; + rect_.y = 0; + rect_.width = static_cast(texture->getWidth()); + rect_.height = static_cast(texture->getHeight()); + calculateUV(); + } +} + +Rect SpriteFrame::getRect() const { + return rect_; +} + +void SpriteFrame::setRect(const Rect& rect) { + rect_ = rect; + calculateUV(); +} + +Rect SpriteFrame::getUnrotatedRect() const { + return unrotatedRect_; +} + +void SpriteFrame::setUnrotatedRect(const Rect& rect) { + unrotatedRect_ = rect; +} + +bool SpriteFrame::isRotated() const { + return rotated_; +} + +void SpriteFrame::setRotated(bool rotated) { + rotated_ = rotated; + calculateUV(); +} + +Vec2 SpriteFrame::getOffset() const { + return offset_; +} + +void SpriteFrame::setOffset(const Vec2& offset) { + offset_ = offset; +} + +Vec2 SpriteFrame::getOriginalSize() const { + return originalSize_; +} + +void SpriteFrame::setOriginalSize(const Vec2& size) { + originalSize_ = size; +} + +std::array SpriteFrame::getUVs() const { + return uvs_; +} + +void SpriteFrame::calculateUV() { + if (!texture_ || rect_.width == 0 || rect_.height == 0) { + uvs_[0] = Vec2{0, 0}; + uvs_[1] = Vec2{1, 0}; + uvs_[2] = Vec2{1, 1}; + uvs_[3] = Vec2{0, 1}; + return; + } + + float texWidth = static_cast(texture_->getWidth()); + float texHeight = static_cast(texture_->getHeight()); + + float left = rect_.x / texWidth; + float right = (rect_.x + rect_.width) / texWidth; + float top = rect_.y / texHeight; + float bottom = (rect_.y + rect_.height) / texHeight; + + if (rotated_) { + uvs_[0] = Vec2{left, top}; + uvs_[1] = Vec2{right, top}; + uvs_[2] = Vec2{right, bottom}; + uvs_[3] = Vec2{left, bottom}; + } else { + uvs_[0] = Vec2{left, bottom}; + uvs_[1] = Vec2{right, bottom}; + uvs_[2] = Vec2{right, top}; + uvs_[3] = Vec2{left, top}; + } +} + +} // namespace extra2d diff --git a/Extra2D/src/2d/renderer/Batcher2d.cpp b/Extra2D/src/2d/renderer/Batcher2d.cpp new file mode 100644 index 0000000..bf82d0f --- /dev/null +++ b/Extra2D/src/2d/renderer/Batcher2d.cpp @@ -0,0 +1,228 @@ +#include +#include +#include +#include +#include +#include + +namespace extra2d { + +Batcher2d* Batcher2d::instance_ = nullptr; + +Batcher2d::Batcher2d() { + initDefaultAttributes(); +} + +Batcher2d::~Batcher2d() { + destroy(); +} + +Batcher2d* Batcher2d::getInstance() { + if (!instance_) { + instance_ = new Batcher2d(); + } + return instance_; +} + +bool Batcher2d::initialize(gfx::Device* device) { + device_ = device; + return device_ != nullptr; +} + +void Batcher2d::destroy() { + reset(); + + for (auto* batch : batches_) { + delete batch; + } + batches_.clear(); + + for (auto& pair : meshBuffersMap_) { + for (auto* buffer : pair.second) { + delete buffer; + } + } + meshBuffersMap_.clear(); + + rootNodes_.clear(); + device_ = nullptr; +} + +void Batcher2d::update() { + fillBuffersAndMergeBatches(); + uploadBuffers(); +} + +void Batcher2d::uploadBuffers() { + for (auto& pair : meshBuffersMap_) { + for (auto* buffer : pair.second) { + if (buffer && buffer->isDirty()) { + buffer->uploadBuffers(); + } + } + } +} + +void Batcher2d::reset() { + for (auto* batch : batches_) { + delete batch; + } + batches_.clear(); + + for (auto& pair : meshBuffersMap_) { + for (auto* buffer : pair.second) { + if (buffer) { + buffer->reset(); + } + } + } + + currEntity_ = nullptr; + currDrawInfo_ = nullptr; + currMeshBuffer_ = nullptr; + currStencilStage_ = 0; + currStencilValue_ = 0; +} + +void Batcher2d::fillBuffersAndMergeBatches() { + reset(); + + for (auto* rootNode : rootNodes_) { + if (rootNode && rootNode->isActiveInHierarchy()) { + walk(rootNode, 1.0f, false); + } + } +} + +void Batcher2d::walk(Node* node, float parentOpacity, bool parentColorDirty) { + if (!node || !node->isActive()) return; + + float opacity = parentOpacity; + bool colorDirty = parentColorDirty; + + auto* renderEntity = static_cast(node->getUserData()); + if (renderEntity) { + currEntity_ = renderEntity; + + u32 drawInfoCount = renderEntity->getRenderDrawInfoCount(); + for (u32 i = 0; i < drawInfoCount; ++i) { + auto* drawInfo = renderEntity->getRenderDrawInfoAt(i); + if (drawInfo) { + handleDrawInfo(renderEntity, drawInfo, node); + } + } + } + + const auto& children = node->getChildren(); + for (auto& child : children) { + walk(child.get(), opacity, colorDirty); + } +} + +void Batcher2d::handleDrawInfo(RenderEntity* entity, RenderDrawInfo* drawInfo, Node* node) { + currDrawInfo_ = drawInfo; + + auto type = drawInfo->getDrawInfoType(); + switch (type) { + case RenderDrawInfoType::Component: + case RenderDrawInfoType::Model: + generateBatch(entity, drawInfo); + break; + case RenderDrawInfoType::Middleware: + break; + case RenderDrawInfoType::SubNode: + break; + } +} + +void Batcher2d::generateBatch(RenderEntity* entity, RenderDrawInfo* drawInfo) { + if (!drawInfo) return; + + auto* meshBuffer = drawInfo->getMeshBuffer(); + if (!meshBuffer) { + u16 accId = drawInfo->getAccId(); + u16 bufferId = drawInfo->getBufferId(); + meshBuffer = getMeshBuffer(accId, bufferId); + drawInfo->setMeshBuffer(meshBuffer); + } + + if (!meshBuffer) return; + + currMeshBuffer_ = meshBuffer; + + DrawBatch2D* batch = new DrawBatch2D(); + batch->inputAssembler = meshBuffer->getInputAssembler(); + batch->vertexOffset = drawInfo->getVertexOffset(); + batch->indexOffset = drawInfo->getIndexOffset(); + batch->vbCount = drawInfo->getVbCount(); + batch->ibCount = drawInfo->getIbCount(); + batch->texture = drawInfo->getTexture(); + batch->priority = entity->getPriority(); + batch->stencilStage = currStencilStage_; + batch->stencilValue = currStencilValue_; + + auto* material = drawInfo->getMaterial(); + if (material) { + } + + batches_.push_back(batch); +} + +void Batcher2d::renderBatches() { + if (!device_) return; + + device_->beginRenderPass(); + + for (auto* batch : batches_) { + if (!batch || !batch->inputAssembler) continue; + + updateBatch(batch); + + device_->bindInputAssembler(batch->inputAssembler); + device_->drawIndexed(batch->indexOffset, batch->ibCount, 0); + } + + device_->endRenderPass(); +} + +void Batcher2d::updateBatch(DrawBatch2D* batch) { + (void)batch; +} + +UIMeshBuffer* Batcher2d::getMeshBuffer(u16 accId, u16 bufferId) { + auto it = meshBuffersMap_.find(accId); + if (it == meshBuffersMap_.end()) { + return nullptr; + } + + const auto& buffers = it->second; + if (bufferId >= buffers.size()) { + return nullptr; + } + + return buffers[bufferId]; +} + +void Batcher2d::syncMeshBuffers(u16 accId, std::vector&& buffers) { + meshBuffersMap_[accId] = std::move(buffers); +} + +void Batcher2d::syncRootNodes(std::vector&& rootNodes) { + rootNodes_ = std::move(rootNodes); +} + +gfx::Device* Batcher2d::getDevice() { + return device_; +} + +const std::vector& Batcher2d::getDefaultAttributes() const { + return defaultAttributes_; +} + +void Batcher2d::initDefaultAttributes() { + defaultAttributes_.push_back({gfx::ATTR_NAME_POSITION, gfx::Format::RGB32F}); + defaultAttributes_.push_back({gfx::ATTR_NAME_TEX_COORD, gfx::Format::RG32F}); + defaultAttributes_.push_back({gfx::ATTR_NAME_COLOR, gfx::Format::RGBA32F}); +} + +} // namespace extra2d diff --git a/Extra2D/src/2d/renderer/RenderDrawInfo.cpp b/Extra2D/src/2d/renderer/RenderDrawInfo.cpp new file mode 100644 index 0000000..bc9f868 --- /dev/null +++ b/Extra2D/src/2d/renderer/RenderDrawInfo.cpp @@ -0,0 +1,142 @@ +#include +#include +#include + +namespace extra2d { + +RenderDrawInfo::RenderDrawInfo() { +} + +RenderDrawInfo::~RenderDrawInfo() { + if (inputAssembler_) { + inputAssembler_->release(); + inputAssembler_ = nullptr; + } +} + +RenderDrawInfoType RenderDrawInfo::getDrawInfoType() const { + return attrs_.type; +} + +void RenderDrawInfo::setDrawInfoType(RenderDrawInfoType type) { + attrs_.type = type; +} + +u16 RenderDrawInfo::getAccId() const { + return attrs_.accId; +} + +void RenderDrawInfo::setAccId(u16 accId) { + attrs_.accId = accId; +} + +u16 RenderDrawInfo::getBufferId() const { + return attrs_.bufferId; +} + +void RenderDrawInfo::setBufferId(u16 bufferId) { + attrs_.bufferId = bufferId; +} + +u32 RenderDrawInfo::getVertexOffset() const { + return attrs_.vertexOffset; +} + +void RenderDrawInfo::setVertexOffset(u32 offset) { + attrs_.vertexOffset = offset; +} + +u32 RenderDrawInfo::getIndexOffset() const { + return attrs_.indexOffset; +} + +void RenderDrawInfo::setIndexOffset(u32 offset) { + attrs_.indexOffset = offset; +} + +u32 RenderDrawInfo::getVbCount() const { + return attrs_.vbCount; +} + +void RenderDrawInfo::setVbCount(u32 count) { + attrs_.vbCount = count; +} + +u32 RenderDrawInfo::getIbCount() const { + return attrs_.ibCount; +} + +void RenderDrawInfo::setIbCount(u32 count) { + attrs_.ibCount = count; +} + +Material* RenderDrawInfo::getMaterial() const { + return material_; +} + +void RenderDrawInfo::setMaterial(Material* material) { + material_ = material; +} + +UIMeshBuffer* RenderDrawInfo::getMeshBuffer() const { + return meshBuffer_; +} + +void RenderDrawInfo::setMeshBuffer(UIMeshBuffer* buffer) { + meshBuffer_ = buffer; +} + +gfx::Texture* RenderDrawInfo::getTexture() const { + return texture_; +} + +void RenderDrawInfo::setTexture(gfx::Texture* texture) { + texture_ = texture; +} + +float* RenderDrawInfo::getVDataBuffer() const { + return vData_; +} + +void RenderDrawInfo::setVDataBuffer(float* data) { + vData_ = data; +} + +u16* RenderDrawInfo::getIDataBuffer() const { + return iData_; +} + +void RenderDrawInfo::setIDataBuffer(u16* data) { + iData_ = data; +} + +gfx::InputAssembler* RenderDrawInfo::requestIA(gfx::Device* device) { + if (!inputAssembler_ && meshBuffer_) { + inputAssembler_ = meshBuffer_->requireFreeIA(device); + } + return inputAssembler_; +} + +void RenderDrawInfo::uploadBuffers() { + if (meshBuffer_) { + meshBuffer_->uploadBuffers(); + } +} + +bool RenderDrawInfo::isVertDirty() const { + return attrs_.vertDirty; +} + +void RenderDrawInfo::setVertDirty(bool dirty) { + attrs_.vertDirty = dirty; +} + +u64 RenderDrawInfo::getDataHash() const { + return attrs_.dataHash; +} + +void RenderDrawInfo::setDataHash(u64 hash) { + attrs_.dataHash = hash; +} + +} // namespace extra2d diff --git a/Extra2D/src/2d/renderer/RenderEntity.cpp b/Extra2D/src/2d/renderer/RenderEntity.cpp new file mode 100644 index 0000000..1bfb809 --- /dev/null +++ b/Extra2D/src/2d/renderer/RenderEntity.cpp @@ -0,0 +1,160 @@ +#include +#include + +namespace extra2d { + +RenderEntity::RenderEntity() { + attr_.enabledIndex = 0; + attr_.useLocal = 1; + attr_.fillColorType = FillColorType::Color; +} + +RenderEntity::~RenderEntity() { + clearDynamicRenderDrawInfos(); + clearStaticRenderDrawInfos(); +} + +void RenderEntity::addDynamicRenderDrawInfo(RenderDrawInfo* drawInfo) { + if (entityType_ == RenderEntityType::Static) { + clearStaticRenderDrawInfos(); + entityType_ = RenderEntityType::Dynamic; + } + dynamicDrawInfos_.push_back(drawInfo); + drawInfoCount_ = static_cast(dynamicDrawInfos_.size()); +} + +void RenderEntity::setDynamicRenderDrawInfo(RenderDrawInfo* drawInfo, u32 index) { + if (entityType_ == RenderEntityType::Static) { + clearStaticRenderDrawInfos(); + entityType_ = RenderEntityType::Dynamic; + } + if (index >= dynamicDrawInfos_.size()) { + dynamicDrawInfos_.resize(index + 1, nullptr); + } + dynamicDrawInfos_[index] = drawInfo; + drawInfoCount_ = static_cast(dynamicDrawInfos_.size()); +} + +void RenderEntity::removeDynamicRenderDrawInfo() { + if (entityType_ == RenderEntityType::Dynamic && !dynamicDrawInfos_.empty()) { + dynamicDrawInfos_.pop_back(); + drawInfoCount_ = static_cast(dynamicDrawInfos_.size()); + } +} + +void RenderEntity::clearDynamicRenderDrawInfos() { + for (auto* info : dynamicDrawInfos_) { + if (info) { + delete info; + } + } + dynamicDrawInfos_.clear(); + drawInfoCount_ = 0; +} + +void RenderEntity::clearStaticRenderDrawInfos() { + drawInfoCount_ = 0; +} + +RenderDrawInfo* RenderEntity::getRenderDrawInfoAt(u32 index) { + if (index >= drawInfoCount_) { + return nullptr; + } + if (entityType_ == RenderEntityType::Dynamic) { + return dynamicDrawInfos_[index]; + } + return nullptr; +} + +u32 RenderEntity::getRenderDrawInfoCount() const { + return drawInfoCount_; +} + +Node* RenderEntity::getNode() const { + return node_; +} + +void RenderEntity::setNode(Node* node) { + node_ = node; +} + +RenderEntityType RenderEntity::getRenderEntityType() const { + return entityType_; +} + +void RenderEntity::setRenderEntityType(RenderEntityType type) { + entityType_ = type; +} + +u32 RenderEntity::getPriority() const { + return attr_.priority; +} + +void RenderEntity::setPriority(u32 priority) { + attr_.priority = priority; +} + +MaskMode RenderEntity::getMaskMode() const { + return static_cast(attr_.maskMode); +} + +void RenderEntity::setMaskMode(MaskMode mode) { + attr_.maskMode = static_cast(mode); +} + +bool RenderEntity::isMask() const { + return attr_.maskMode == static_cast(MaskMode::Mask) || + attr_.maskMode == static_cast(MaskMode::MaskInverted); +} + +bool RenderEntity::isSubMask() const { + return attr_.maskMode == static_cast(MaskMode::MaskNode) || + attr_.maskMode == static_cast(MaskMode::MaskNodeInverted); +} + +bool RenderEntity::isMaskInverted() const { + return attr_.maskMode == static_cast(MaskMode::MaskInverted) || + attr_.maskMode == static_cast(MaskMode::MaskNodeInverted); +} + +FillColorType RenderEntity::getFillColorType() const { + return attr_.fillColorType; +} + +void RenderEntity::setFillColorType(FillColorType type) { + attr_.fillColorType = type; +} + +Vec4 RenderEntity::getColor() const { + return Vec4{ + static_cast(attr_.colorR) / 255.0f, + static_cast(attr_.colorG) / 255.0f, + static_cast(attr_.colorB) / 255.0f, + static_cast(attr_.colorA) / 255.0f + }; +} + +void RenderEntity::setColor(u8 r, u8 g, u8 b, u8 a) { + attr_.colorR = r; + attr_.colorG = g; + attr_.colorB = b; + attr_.colorA = a; +} + +u32 RenderEntity::getStencilStage() const { + return stencilStage_; +} + +void RenderEntity::setStencilStage(u32 stage) { + stencilStage_ = stage; +} + +bool RenderEntity::useLocal() const { + return attr_.useLocal != 0; +} + +void RenderEntity::setUseLocal(bool useLocal) { + attr_.useLocal = useLocal ? 1 : 0; +} + +} // namespace extra2d diff --git a/Extra2D/src/2d/renderer/StencilManager.cpp b/Extra2D/src/2d/renderer/StencilManager.cpp new file mode 100644 index 0000000..5c9c3a9 --- /dev/null +++ b/Extra2D/src/2d/renderer/StencilManager.cpp @@ -0,0 +1,171 @@ +#include +#include + +namespace extra2d { + +StencilManager* StencilManager::instance_ = nullptr; + +StencilManager* StencilManager::getInstance() { + if (!instance_) { + instance_ = new StencilManager(); + } + return instance_; +} + +StencilManager::~StencilManager() { + for (auto& pair : cacheStateMap_) { + delete pair.second; + } + cacheStateMap_.clear(); + + if (instance_ == this) { + instance_ = nullptr; + } +} + +gfx::DepthStencilState* StencilManager::getDepthStencilState(StencilStage stage) { + u32 hash = getStencilHash(stage); + + auto it = cacheStateMap_.find(hash); + if (it != cacheStateMap_.end()) { + return it->second; + } + + gfx::DepthStencilState* state = new gfx::DepthStencilState(); + + switch (stage) { + case StencilStage::Disabled: + state->depthTest = false; + state->depthWrite = false; + state->stencilTest = false; + break; + + case StencilStage::Clear: + state->depthTest = false; + state->depthWrite = false; + state->stencilTest = true; + state->stencilFuncFront = gfx::ComparisonFunc::Always; + state->stencilFuncBack = gfx::ComparisonFunc::Always; + state->stencilFailOpFront = gfx::StencilOp::Zero; + state->stencilFailOpBack = gfx::StencilOp::Zero; + state->stencilZFailOpFront = gfx::StencilOp::Zero; + state->stencilZFailOpBack = gfx::StencilOp::Zero; + state->stencilPassOpFront = gfx::StencilOp::Zero; + state->stencilPassOpBack = gfx::StencilOp::Zero; + state->stencilReadMask = 0xFF; + state->stencilWriteMask = getWriteMask(); + state->stencilRef = 0; + break; + + case StencilStage::EnterLevel: + state->depthTest = false; + state->depthWrite = false; + state->stencilTest = true; + state->stencilFuncFront = gfx::ComparisonFunc::Equal; + state->stencilFuncBack = gfx::ComparisonFunc::Equal; + state->stencilFailOpFront = gfx::StencilOp::Keep; + state->stencilFailOpBack = gfx::StencilOp::Keep; + state->stencilZFailOpFront = gfx::StencilOp::Keep; + state->stencilZFailOpBack = gfx::StencilOp::Keep; + state->stencilPassOpFront = gfx::StencilOp::Incr; + state->stencilPassOpBack = gfx::StencilOp::Incr; + state->stencilReadMask = getWriteMask(); + state->stencilWriteMask = getWriteMask(); + state->stencilRef = getStencilRef(); + break; + + case StencilStage::Enabled: + state->depthTest = false; + state->depthWrite = false; + state->stencilTest = true; + state->stencilFuncFront = gfx::ComparisonFunc::Equal; + state->stencilFuncBack = gfx::ComparisonFunc::Equal; + state->stencilFailOpFront = gfx::StencilOp::Keep; + state->stencilFailOpBack = gfx::StencilOp::Keep; + state->stencilZFailOpFront = gfx::StencilOp::Keep; + state->stencilZFailOpBack = gfx::StencilOp::Keep; + state->stencilPassOpFront = gfx::StencilOp::Keep; + state->stencilPassOpBack = gfx::StencilOp::Keep; + state->stencilReadMask = getWriteMask(); + state->stencilWriteMask = 0; + state->stencilRef = getStencilRef(); + break; + + case StencilStage::ExitLevel: + state->depthTest = false; + state->depthWrite = false; + state->stencilTest = true; + state->stencilFuncFront = gfx::ComparisonFunc::Equal; + state->stencilFuncBack = gfx::ComparisonFunc::Equal; + state->stencilFailOpFront = gfx::StencilOp::Keep; + state->stencilFailOpBack = gfx::StencilOp::Keep; + state->stencilZFailOpFront = gfx::StencilOp::Keep; + state->stencilZFailOpBack = gfx::StencilOp::Keep; + state->stencilPassOpFront = gfx::StencilOp::Decr; + state->stencilPassOpBack = gfx::StencilOp::Decr; + state->stencilReadMask = getExitWriteMask(); + state->stencilWriteMask = getExitWriteMask(); + state->stencilRef = getStencilRef(); + break; + + case StencilStage::ClearInverted: + state->depthTest = false; + state->depthWrite = false; + state->stencilTest = true; + state->stencilFuncFront = gfx::ComparisonFunc::NotEqual; + state->stencilFuncBack = gfx::ComparisonFunc::NotEqual; + state->stencilFailOpFront = gfx::StencilOp::Zero; + state->stencilFailOpBack = gfx::StencilOp::Zero; + state->stencilZFailOpFront = gfx::StencilOp::Zero; + state->stencilZFailOpBack = gfx::StencilOp::Zero; + state->stencilPassOpFront = gfx::StencilOp::Zero; + state->stencilPassOpBack = gfx::StencilOp::Zero; + state->stencilReadMask = 0xFF; + state->stencilWriteMask = getWriteMask(); + state->stencilRef = 0; + break; + + case StencilStage::EnterLevelInverted: + state->depthTest = false; + state->depthWrite = false; + state->stencilTest = true; + state->stencilFuncFront = gfx::ComparisonFunc::Equal; + state->stencilFuncBack = gfx::ComparisonFunc::Equal; + state->stencilFailOpFront = gfx::StencilOp::Keep; + state->stencilFailOpBack = gfx::StencilOp::Keep; + state->stencilZFailOpFront = gfx::StencilOp::Keep; + state->stencilZFailOpBack = gfx::StencilOp::Keep; + state->stencilPassOpFront = gfx::StencilOp::Decr; + state->stencilPassOpBack = gfx::StencilOp::Decr; + state->stencilReadMask = getWriteMask(); + state->stencilWriteMask = getWriteMask(); + state->stencilRef = getStencilRef(); + break; + } + + cacheStateMap_[hash] = state; + return state; +} + +void StencilManager::setDepthStencilStateFromStage(StencilStage stage) { + stage_ = stage; +} + +StencilStage StencilManager::clear(RenderEntity* entity) { + if (maskStackSize_ == 0) { + return StencilStage::Disabled; + } + + stage_ = StencilStage::Clear; + return stage_; +} + +void StencilManager::enterLevel(RenderEntity* entity) { + stage_ = StencilStage::EnterLevel; +} + +void StencilManager::setStencilStage(u32 stageIndex) { + stage_ = static_cast(stageIndex); +} + +} // namespace extra2d diff --git a/Extra2D/src/2d/renderer/UIMeshBuffer.cpp b/Extra2D/src/2d/renderer/UIMeshBuffer.cpp new file mode 100644 index 0000000..2f3477b --- /dev/null +++ b/Extra2D/src/2d/renderer/UIMeshBuffer.cpp @@ -0,0 +1,240 @@ +#include +#include +#include + +namespace extra2d { + +UIMeshBuffer::UIMeshBuffer() { +} + +UIMeshBuffer::~UIMeshBuffer() { + destroy(); +} + +bool UIMeshBuffer::initialize(gfx::Device* device, + const std::vector& attrs, + u32 vertexCapacity, + u32 indexCapacity) { + device_ = device; + attributes_ = attrs; + vertexCapacity_ = vertexCapacity; + indexCapacity_ = indexCapacity; + + vertexFormatBytes_ = 0; + for (const auto& attr : attrs) { + switch (attr.format) { + case gfx::Format::R32F: + vertexFormatBytes_ += 4; + break; + case gfx::Format::RG32F: + vertexFormatBytes_ += 8; + break; + case gfx::Format::RGB32F: + vertexFormatBytes_ += 12; + break; + case gfx::Format::RGBA32F: + vertexFormatBytes_ += 16; + break; + default: + break; + } + } + + vData_ = new float[vertexCapacity_ * vertexFormatBytes_ / sizeof(float)]; + iData_ = new u16[indexCapacity_]; + ownsData_ = true; + + memset(vData_, 0, vertexCapacity_ * vertexFormatBytes_); + memset(iData_, 0, indexCapacity_ * sizeof(u16)); + + return createBuffers(); +} + +void UIMeshBuffer::reset() { + byteOffset_ = 0; + vertexOffset_ = 0; + indexOffset_ = 0; + dirty_ = false; + + if (vData_) { + memset(vData_, 0, vertexCapacity_ * vertexFormatBytes_); + } + if (iData_) { + memset(iData_, 0, indexCapacity_ * sizeof(u16)); + } +} + +void UIMeshBuffer::destroy() { + if (inputAssembler_) { + inputAssembler_->release(); + inputAssembler_ = nullptr; + } + + if (vertexBuffer_) { + vertexBuffer_->release(); + vertexBuffer_ = nullptr; + } + + if (indexBuffer_) { + indexBuffer_->release(); + indexBuffer_ = nullptr; + } + + if (ownsData_) { + delete[] vData_; + delete[] iData_; + } + vData_ = nullptr; + iData_ = nullptr; + ownsData_ = false; +} + +float* UIMeshBuffer::getVData() const { + return vData_; +} + +void UIMeshBuffer::setVData(float* vData) { + if (ownsData_ && vData_) { + delete[] vData_; + } + vData_ = vData; + ownsData_ = false; +} + +u16* UIMeshBuffer::getIData() const { + return iData_; +} + +void UIMeshBuffer::setIData(u16* iData) { + if (ownsData_ && iData_) { + delete[] iData_; + } + iData_ = iData; + ownsData_ = false; +} + +u32 UIMeshBuffer::getByteOffset() const { + return byteOffset_; +} + +void UIMeshBuffer::setByteOffset(u32 offset) { + byteOffset_ = offset; +} + +u32 UIMeshBuffer::getVertexOffset() const { + return vertexOffset_; +} + +void UIMeshBuffer::setVertexOffset(u32 offset) { + vertexOffset_ = offset; +} + +u32 UIMeshBuffer::getIndexOffset() const { + return indexOffset_; +} + +void UIMeshBuffer::setIndexOffset(u32 offset) { + indexOffset_ = offset; +} + +bool UIMeshBuffer::isDirty() const { + return dirty_; +} + +void UIMeshBuffer::setDirty(bool dirty) { + dirty_ = dirty; +} + +u32 UIMeshBuffer::getVertexCapacity() const { + return vertexCapacity_; +} + +u32 UIMeshBuffer::getIndexCapacity() const { + return indexCapacity_; +} + +u32 UIMeshBuffer::getVertexFormatBytes() const { + return vertexFormatBytes_; +} + +gfx::Buffer* UIMeshBuffer::getVertexBuffer() const { + return vertexBuffer_; +} + +gfx::Buffer* UIMeshBuffer::getIndexBuffer() const { + return indexBuffer_; +} + +gfx::InputAssembler* UIMeshBuffer::getInputAssembler() const { + return inputAssembler_; +} + +gfx::InputAssembler* UIMeshBuffer::requireFreeIA(gfx::Device* device) { + if (!inputAssembler_) { + createInputAssembler(); + } + return inputAssembler_; +} + +void UIMeshBuffer::uploadBuffers() { + if (!dirty_ || !vData_ || !iData_) return; + + if (vertexBuffer_) { + vertexBuffer_->update(vData_, vertexCapacity_ * vertexFormatBytes_); + } + if (indexBuffer_) { + indexBuffer_->update(iData_, indexCapacity_ * sizeof(u16)); + } + + dirty_ = false; +} + +void UIMeshBuffer::resetIA() { + if (inputAssembler_) { + inputAssembler_->release(); + inputAssembler_ = nullptr; + } +} + +bool UIMeshBuffer::createBuffers() { + if (!device_) return false; + + gfx::BufferInfo vbInfo; + vbInfo.usage = gfx::BufferUsage::Vertex | gfx::BufferUsage::TransferDst; + vbInfo.memUsage = gfx::MemoryUsage::Host; + vbInfo.size = vertexCapacity_ * vertexFormatBytes_; + vbInfo.stride = vertexFormatBytes_; + + vertexBuffer_ = device_->createBuffer(vbInfo); + if (!vertexBuffer_) return false; + + gfx::BufferInfo ibInfo; + ibInfo.usage = gfx::BufferUsage::Index | gfx::BufferUsage::TransferDst; + ibInfo.memUsage = gfx::MemoryUsage::Host; + ibInfo.size = indexCapacity_ * sizeof(u16); + ibInfo.stride = sizeof(u16); + + indexBuffer_ = device_->createBuffer(ibInfo); + if (!indexBuffer_) { + vertexBuffer_->release(); + vertexBuffer_ = nullptr; + return false; + } + + return true; +} + +bool UIMeshBuffer::createInputAssembler() { + if (!device_ || !vertexBuffer_ || !indexBuffer_) return false; + + gfx::InputAssemblerInfo iaInfo; + iaInfo.vertexStreams.push_back({vertexBuffer_, 0}); + iaInfo.indexBuffer = indexBuffer_; + iaInfo.vertexCount = vertexCapacity_; + iaInfo.indexCount = indexCapacity_; + + inputAssembler_ = device_->createInputAssembler(iaInfo); + return inputAssembler_ != nullptr; +} + +} // namespace extra2d diff --git a/Extra2D/src/app/application.cpp b/Extra2D/src/app/application.cpp deleted file mode 100644 index 7818fd4..0000000 --- a/Extra2D/src/app/application.cpp +++ /dev/null @@ -1,152 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include - -namespace extra2d { - -static f64 getTimeSeconds() { -#ifdef __SWITCH__ - struct timespec ts; - clock_gettime(CLOCK_MONOTONIC, &ts); - return static_cast(ts.tv_sec) + - static_cast(ts.tv_nsec) / 1000000000.0; -#else - using namespace std::chrono; - auto now = steady_clock::now(); - auto duration = now.time_since_epoch(); - return duration_cast>(duration).count(); -#endif -} - -Application &Application::get() { - static Application instance; - return instance; -} - -Application::Application() { Registry::instance().setApp(this); } - -Application::~Application() { - if (initialized_) { - shutdown(); - } -} - -bool Application::init() { - if (initialized_) { - return true; - } - - // 初始化所有模块(拓扑排序) - // 服务通过 E2D_AUTO_REGISTER_SERVICE 宏自动注册 - if (!Registry::instance().init()) { - return false; - } - - // 初始化所有服务 - ServiceLocator::instance().init(); - - initialized_ = true; - running_ = true; - return true; -} - -void Application::shutdown() { - if (!initialized_) - return; - - ServiceLocator::instance().shutdown(); - ServiceLocator::instance().clear(); - Registry::instance().shutdown(); - Registry::instance().clear(); - - initialized_ = false; - running_ = false; -} - -void Application::run() { - if (!initialized_) - return; - - auto *winMod = get(); - if (!winMod || !winMod->win()) - return; - - lastFrameTime_ = getTimeSeconds(); - - while (running_ && !winMod->win()->shouldClose()) { - mainLoop(); - } -} - -void Application::quit() { - shouldQuit_ = true; - running_ = false; -} - -void Application::pause() { - if (!paused_) { - paused_ = true; - ServiceLocator::instance().pause(); - } -} - -void Application::resume() { - if (paused_) { - paused_ = false; - ServiceLocator::instance().resume(); - lastFrameTime_ = getTimeSeconds(); - } -} - -void Application::mainLoop() { - f64 currentTime = getTimeSeconds(); - dt_ = static_cast(currentTime - lastFrameTime_); - lastFrameTime_ = currentTime; - - totalTime_ += dt_; - - frameCount_++; - fpsTimer_ += dt_; - if (fpsTimer_ >= 1.0f) { - fps_ = frameCount_; - frameCount_ = 0; - fpsTimer_ -= 1.0f; - } - - auto *winMod = get(); - if (winMod && winMod->win()) { - winMod->win()->poll(); - } - - auto eventService = ServiceLocator::instance().get(); - if (eventService) { - eventService->process(); - } - - if (!paused_) { - update(); - } - - render(); -} - -void Application::update() { ServiceLocator::instance().update(dt_); } - -void Application::render() { - auto *winMod = get(); - if (!winMod || !winMod->win()) - return; - - winMod->win()->swap(); -} - -GLFWWindow *Application::window() { - auto *winMod = get(); - return winMod ? winMod->win() : nullptr; -} - -} // namespace extra2d diff --git a/Extra2D/src/application/Application.cpp b/Extra2D/src/application/Application.cpp new file mode 100644 index 0000000..67df24e --- /dev/null +++ b/Extra2D/src/application/Application.cpp @@ -0,0 +1,183 @@ +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace extra2d { + +Application* Application::instance_ = nullptr; + +Application::Application() {} + +Application::~Application() { + close(); +} + +Application* Application::getInstance() { + if (!instance_) { + instance_ = new Application(); + } + return instance_; +} + +i32 Application::init(const AppConfig& config) { + if (initialized_) { + return 0; + } + + if (SDLHelper::init() != 0) { + return -1; + } + + window_ = Window::getInstance(); + WindowConfig windowConfig; + windowConfig.title = config.title; + windowConfig.width = config.width; + windowConfig.height = config.height; + windowConfig.fullscreen = config.fullscreen; + windowConfig.resizable = config.resizable; + windowConfig.vsync = config.vsync; + windowConfig.borderless = config.borderless; + windowConfig.highDPI = config.highDPI; + + if (window_->initialize(windowConfig) != 0) { + SDLHelper::quit(); + return -2; + } + + engine_ = Engine::getInstance(); + if (engine_->init() != 0) { + window_->shutdown(); + SDLHelper::quit(); + return -3; + } + + engine_->setPreferredFramesPerSecond(config.fps); + + input_ = Input::getInstance(); + + initialized_ = true; + return 0; +} + +i32 Application::run() { + if (!initialized_) { + return -1; + } + + running_ = true; + paused_ = false; + + mainLoop(); + + return 0; +} + +void Application::pause() { + if (!running_ || paused_) return; + + paused_ = true; + engine_->pause(); +} + +void Application::resume() { + if (!running_ || !paused_) return; + + paused_ = false; + engine_->resume(); +} + +void Application::close() { + if (!initialized_) return; + + running_ = false; + + if (engine_) { + engine_->close(); + } + + if (window_) { + window_->shutdown(); + } + + SDLHelper::quit(); + + initialized_ = false; +} + +bool Application::isRunning() const { + return running_; +} + +bool Application::isPaused() const { + return paused_; +} + +Engine* Application::getEngine() { + return engine_; +} + +Window* Application::getWindow() { + return window_; +} + +Input* Application::getInput() { + return input_; +} + +const std::vector& Application::getArguments() const { + return arguments_; +} + +void Application::setArguments(i32 argc, char* argv[]) { + arguments_.clear(); + for (i32 i = 0; i < argc; ++i) { + arguments_.push_back(argv[i]); + } +} + +void Application::setFrameCallback(const std::function& callback) { + frameCallback_ = callback; +} + +void Application::mainLoop() { + auto lastTime = std::chrono::high_resolution_clock::now(); + i64 targetFrameTime = 1000000000LL / engine_->getPreferredFramesPerSecond(); + + while (running_) { + processEvents(); + + if (!paused_) { + engine_->tick(); + + if (frameCallback_) { + frameCallback_(engine_->getDeltaTime()); + } + + input_->update(); + + window_->swapBuffers(); + } + + auto currentTime = std::chrono::high_resolution_clock::now(); + auto elapsed = std::chrono::duration_cast(currentTime - lastTime); + + if (elapsed.count() < targetFrameTime) { + auto sleepTime = targetFrameTime - elapsed.count(); + std::this_thread::sleep_for(std::chrono::nanoseconds(sleepTime)); + } + + lastTime = std::chrono::high_resolution_clock::now(); + } +} + +void Application::processEvents() { + SDLHelper::pollAndDispatchEvents(); +} + +} // namespace extra2d diff --git a/Extra2D/src/asset/asset.cpp b/Extra2D/src/asset/asset.cpp deleted file mode 100644 index 79cabf8..0000000 --- a/Extra2D/src/asset/asset.cpp +++ /dev/null @@ -1,62 +0,0 @@ -#include - -#define STB_TRUETYPE_IMPLEMENTATION -#include - -namespace extra2d { - -// --------------------------------------------------------------------------- -// FontAsset::Impl - Pimpl 实现类 -// --------------------------------------------------------------------------- - -class FontAsset::Impl { -public: - stbtt_fontinfo info; - bool initialized = false; -}; - -// --------------------------------------------------------------------------- -// FontAsset 实现 -// --------------------------------------------------------------------------- - -FontAsset::FontAsset() : impl_(ptr::makeUnique()) {} - -FontAsset::~FontAsset() = default; - -bool FontAsset::loaded() const { - return state_.load(std::memory_order_acquire) == AssetState::Loaded && impl_->initialized; -} - -float FontAsset::scaleForPixelHeight(float pixels) const { - if (!impl_->initialized || data_.empty()) { - return 0.0f; - } - return stbtt_ScaleForPixelHeight(&impl_->info, pixels); -} - -bool FontAsset::setData(std::vector data) { - if (data.empty()) { - return false; - } - - data_ = std::move(data); - - if (!stbtt_InitFont(&impl_->info, data_.data(), 0)) { - data_.clear(); - impl_->initialized = false; - setState(AssetState::Failed); - return false; - } - - impl_->initialized = true; - setState(AssetState::Loaded); - return true; -} - -void FontAsset::release() { - data_.clear(); - impl_->initialized = false; - setState(AssetState::Unloaded); -} - -} diff --git a/Extra2D/src/asset/asset_cache.cpp b/Extra2D/src/asset/asset_cache.cpp deleted file mode 100644 index 100f0be..0000000 --- a/Extra2D/src/asset/asset_cache.cpp +++ /dev/null @@ -1,121 +0,0 @@ -#include - -namespace extra2d { - -// --------------------------------------------------------------------------- -// AssetCache 实现 -// --------------------------------------------------------------------------- - -AssetCache::AssetCache(size_t limit) - : limit_(limit) { - stats_.limit = limit; -} - -bool AssetCache::has(const AssetID& id) const { - std::shared_lock lock(mutex_); - return entries_.find(id) != entries_.end(); -} - -bool AssetCache::remove(const AssetID& id) { - std::unique_lock lock(mutex_); - - auto it = entries_.find(id); - if (it == entries_.end()) { - return false; - } - - bytes_ -= it->second.entry->asset->memSize(); - lruList_.erase(it->second.lruIterator); - entries_.erase(it); - --stats_.count; - - return true; -} - -void AssetCache::setLimit(size_t limit) { - std::unique_lock lock(mutex_); - limit_ = limit; - stats_.limit = limit; - - if (limit > 0 && bytes_ > limit) { - evict(); - } -} - -size_t AssetCache::count() const { - std::shared_lock lock(mutex_); - return entries_.size(); -} - -size_t AssetCache::purge() { - std::unique_lock lock(mutex_); - - size_t purged = 0; - auto it = entries_.begin(); - - while (it != entries_.end()) { - if (canEvict(*it->second.entry)) { - bytes_ -= it->second.entry->asset->memSize(); - lruList_.erase(it->second.lruIterator); - it = entries_.erase(it); - ++purged; - --stats_.count; - } else { - ++it; - } - } - - return purged; -} - -void AssetCache::clear() { - std::unique_lock lock(mutex_); - entries_.clear(); - lruList_.clear(); - bytes_ = 0; - stats_.count = 0; - stats_.bytes = 0; -} - -CacheStats AssetCache::stats() const { - std::shared_lock lock(mutex_); - stats_.bytes = bytes_; - return stats_; -} - -void AssetCache::resetStats() { - std::unique_lock lock(mutex_); - stats_.hits = 0; - stats_.misses = 0; -} - -void AssetCache::evict() { - while (!lruList_.empty() && (limit_ > 0 && bytes_ > limit_)) { - AssetID id = lruList_.front(); - - auto it = entries_.find(id); - if (it != entries_.end()) { - if (canEvict(*it->second.entry)) { - bytes_ -= it->second.entry->asset->memSize(); - entries_.erase(it); - lruList_.pop_front(); - --stats_.count; - continue; - } - } - - lruList_.pop_front(); - if (it != entries_.end()) { - it->second.lruIterator = lruList_.insert(lruList_.end(), id); - } - - break; - } -} - -bool AssetCache::canEvict(const CacheEntry& entry) const { - long useCount = entry.asset.use_count(); - return useCount <= 1; -} - -} diff --git a/Extra2D/src/asset/asset_loader.cpp b/Extra2D/src/asset/asset_loader.cpp deleted file mode 100644 index 69e06a4..0000000 --- a/Extra2D/src/asset/asset_loader.cpp +++ /dev/null @@ -1,418 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include - -#define STB_IMAGE_IMPLEMENTATION -#include - -namespace extra2d { - -// --------------------------------------------------------------------------- -// 辅助函数 -// --------------------------------------------------------------------------- - -namespace { - -/** - * @brief 转换为小写 - */ -std::string toLower(const std::string& str) { - std::string result = str; - std::transform(result.begin(), result.end(), result.begin(), - [](unsigned char c) { return std::tolower(c); }); - return result; -} - -/** - * @brief 获取文件扩展名 - */ -std::string getExtension(const std::string& path) { - size_t pos = path.rfind('.'); - if (pos == std::string::npos) { - return ""; - } - return toLower(path.substr(pos)); -} - -/** - * @brief 读取文件内容 - */ -std::vector readFile(const std::string& path) { - std::ifstream file(path, std::ios::binary | std::ios::ate); - if (!file) { - return {}; - } - - size_t size = static_cast(file.tellg()); - file.seekg(0, std::ios::beg); - - std::vector data(size); - if (!file.read(reinterpret_cast(data.data()), size)) { - return {}; - } - - return data; -} - -} - -// --------------------------------------------------------------------------- -// TextureLoader 实现 -// --------------------------------------------------------------------------- - -TextureLoader::TextureLoader() = default; - -TextureLoader::~TextureLoader() = default; - -Ref TextureLoader::load(const std::string& path) { - auto data = readFile(path); - if (data.empty()) { - E2D_ERROR("Failed to read texture file: {}", path); - return nullptr; - } - return loadFromMemory(data.data(), data.size()); -} - -Ref TextureLoader::loadFromMemory(const u8* data, size_t size) { - if (!data || size == 0) { - return nullptr; - } - - int width, height, channels; - u8* pixels = stbi_load_from_memory(data, static_cast(size), - &width, &height, &channels, - desiredChannels_); - - if (!pixels) { - E2D_ERROR("Failed to load texture from memory: {}", stbi_failure_reason()); - return nullptr; - } - - int actualChannels = desiredChannels_ > 0 ? desiredChannels_ : channels; - - auto asset = ptr::make(); - - Unique pixelData(new u8[static_cast(width) * height * actualChannels]); - std::memcpy(pixelData.get(), pixels, - static_cast(width) * height * actualChannels); - - asset->setData(width, height, actualChannels, std::move(pixelData)); - - stbi_image_free(pixels); - - return asset; -} - -bool TextureLoader::canLoad(const std::string& path) const { - std::string ext = getExtension(path); - auto exts = extensions(); - return std::find(exts.begin(), exts.end(), ext) != exts.end(); -} - -std::vector TextureLoader::extensions() const { - return {".png", ".jpg", ".jpeg", ".bmp", ".tga", ".gif", ".psd", ".hdr", ".pic"}; -} - -void TextureLoader::setDesiredChannels(int channels) { - desiredChannels_ = std::clamp(channels, 0, 4); -} - -// --------------------------------------------------------------------------- -// FontLoader 实现 -// --------------------------------------------------------------------------- - -Ref FontLoader::load(const std::string& path) { - auto data = readFile(path); - if (data.empty()) { - E2D_ERROR("Failed to read font file: {}", path); - return nullptr; - } - return loadFromMemory(data.data(), data.size()); -} - -Ref FontLoader::loadFromMemory(const u8* data, size_t size) { - if (!data || size == 0) { - return nullptr; - } - - auto asset = ptr::make(); - - std::vector fontData(data, data + size); - if (!asset->setData(std::move(fontData))) { - E2D_ERROR("Failed to initialize font from memory"); - return nullptr; - } - - return asset; -} - -bool FontLoader::canLoad(const std::string& path) const { - std::string ext = getExtension(path); - auto exts = extensions(); - return std::find(exts.begin(), exts.end(), ext) != exts.end(); -} - -std::vector FontLoader::extensions() const { - return {".ttf", ".otf", ".ttc"}; -} - -// --------------------------------------------------------------------------- -// ShaderLoader 实现 -// --------------------------------------------------------------------------- - -Ref ShaderLoader::load(const std::string& path) { - auto data = readFile(path); - if (data.empty()) { - E2D_ERROR("Failed to read shader file: {}", path); - return nullptr; - } - return loadFromMemory(data.data(), data.size()); -} - -Ref ShaderLoader::loadFromMemory(const u8* data, size_t size) { - if (!data || size == 0) { - return nullptr; - } - - std::string content(reinterpret_cast(data), size); - - std::string vertexSrc, fragmentSrc; - - if (content.find(vertexMarker_) != std::string::npos) { - if (!parseCombined(content, vertexSrc, fragmentSrc)) { - E2D_ERROR("Failed to parse combined shader file"); - return nullptr; - } - } else { - vertexSrc = content; - fragmentSrc = content; - } - - auto asset = ptr::make(); - asset->setSource(std::move(vertexSrc), std::move(fragmentSrc)); - - return asset; -} - -bool ShaderLoader::canLoad(const std::string& path) const { - std::string ext = getExtension(path); - auto exts = extensions(); - return std::find(exts.begin(), exts.end(), ext) != exts.end(); -} - -std::vector ShaderLoader::extensions() const { - return {".vert", ".frag", ".glsl", ".vs", ".fs"}; -} - -bool ShaderLoader::parseCombined(const std::string& content, - std::string& vertex, - std::string& fragment) { - size_t vertexPos = content.find(vertexMarker_); - size_t fragmentPos = content.find(fragmentMarker_); - - if (vertexPos == std::string::npos || fragmentPos == std::string::npos) { - return false; - } - - size_t vertexStart = vertexPos + vertexMarker_.length(); - - if (vertexPos < fragmentPos) { - vertex = content.substr(vertexStart, fragmentPos - vertexStart); - fragment = content.substr(fragmentPos + fragmentMarker_.length()); - } else { - fragment = content.substr(fragmentPos + fragmentMarker_.length(), - vertexPos - fragmentPos - fragmentMarker_.length()); - vertex = content.substr(vertexStart); - } - - auto trim = [](std::string& s) { - size_t start = s.find_first_not_of(" \t\r\n"); - if (start == std::string::npos) { - s.clear(); - return; - } - size_t end = s.find_last_not_of(" \t\r\n"); - s = s.substr(start, end - start + 1); - }; - - trim(vertex); - trim(fragment); - - return true; -} - -// --------------------------------------------------------------------------- -// AudioLoader 实现 -// --------------------------------------------------------------------------- - -Ref AudioLoader::load(const std::string& path) { - auto data = readFile(path); - if (data.empty()) { - E2D_ERROR("Failed to read audio file: {}", path); - return nullptr; - } - return loadFromMemory(data.data(), data.size()); -} - -Ref AudioLoader::loadFromMemory(const u8* data, size_t size) { - if (!data || size == 0) { - return nullptr; - } - - std::string ext = ".wav"; - if (size >= 4) { - if (data[0] == 'R' && data[1] == 'I' && data[2] == 'F' && data[3] == 'F') { - return loadWav(data, size); - } - } - - E2D_ERROR("Unsupported audio format"); - return nullptr; -} - -bool AudioLoader::canLoad(const std::string& path) const { - std::string ext = getExtension(path); - auto exts = extensions(); - return std::find(exts.begin(), exts.end(), ext) != exts.end(); -} - -std::vector AudioLoader::extensions() const { - return {".wav"}; -} - -Ref AudioLoader::loadWav(const u8* data, size_t size) { - if (size < 44) { - E2D_ERROR("WAV file too small"); - return nullptr; - } - - if (data[0] != 'R' || data[1] != 'I' || data[2] != 'F' || data[3] != 'F') { - E2D_ERROR("Invalid WAV file: missing RIFF header"); - return nullptr; - } - - if (data[8] != 'W' || data[9] != 'A' || data[10] != 'V' || data[11] != 'E') { - E2D_ERROR("Invalid WAV file: missing WAVE format"); - return nullptr; - } - - size_t pos = 12; - u16 audioFormat = 0; - u16 numChannels = 0; - u32 sampleRate = 0; - u16 bitsPerSample = 0; - size_t dataSize = 0; - const u8* audioData = nullptr; - - while (pos < size) { - u32 chunkId = *reinterpret_cast(data + pos); - u32 chunkSize = *reinterpret_cast(data + pos + 4); - - if (chunkId == 0x20746D66) { // "fmt " - audioFormat = *reinterpret_cast(data + pos + 8); - numChannels = *reinterpret_cast(data + pos + 10); - sampleRate = *reinterpret_cast(data + pos + 12); - bitsPerSample = *reinterpret_cast(data + pos + 22); - } else if (chunkId == 0x61746164) { // "data" - dataSize = chunkSize; - audioData = data + pos + 8; - break; - } - - pos += 8 + chunkSize; - if (chunkSize % 2 == 1) { - pos++; - } - } - - if (!audioData || dataSize == 0) { - E2D_ERROR("Invalid WAV file: missing data chunk"); - return nullptr; - } - - if (audioFormat != 1) { - E2D_ERROR("Unsupported WAV format: only PCM supported"); - return nullptr; - } - - auto asset = ptr::make(); - - std::vector pcmData(audioData, audioData + dataSize); - asset->setData(AudioFormat::PCM, numChannels, sampleRate, bitsPerSample, - std::move(pcmData)); - - return asset; -} - -// --------------------------------------------------------------------------- -// DataLoader 实现 -// --------------------------------------------------------------------------- - -Ref DataLoader::load(const std::string& path) { - auto data = readFile(path); - if (data.empty()) { - E2D_ERROR("Failed to read data file: {}", path); - return nullptr; - } - return loadFromMemory(data.data(), data.size()); -} - -Ref DataLoader::loadFromMemory(const u8* data, size_t size) { - if (!data || size == 0) { - return nullptr; - } - - auto asset = ptr::make(); - std::vector assetData(data, data + size); - asset->setData(std::move(assetData)); - - return asset; -} - -bool DataLoader::canLoad(const std::string& path) const { - return !path.empty(); -} - -std::vector DataLoader::extensions() const { - return {".bin", ".dat"}; -} - -// --------------------------------------------------------------------------- -// AssetLoaderFactory 实现 -// --------------------------------------------------------------------------- - -AssetType AssetLoaderFactory::getTypeByExtension(const std::string& extension) { - std::string ext = toLower(extension); - - if (ext == ".png" || ext == ".jpg" || ext == ".jpeg" || - ext == ".bmp" || ext == ".tga" || ext == ".gif" || - ext == ".psd" || ext == ".hdr" || ext == ".pic") { - return AssetType::Texture; - } - - if (ext == ".ttf" || ext == ".otf" || ext == ".ttc") { - return AssetType::Font; - } - - if (ext == ".vert" || ext == ".frag" || ext == ".glsl" || - ext == ".vs" || ext == ".fs") { - return AssetType::Shader; - } - - if (ext == ".wav" || ext == ".mp3" || ext == ".ogg") { - return AssetType::Audio; - } - - if (ext == ".bin" || ext == ".dat") { - return AssetType::Data; - } - - return AssetType::Unknown; -} - -} diff --git a/Extra2D/src/asset/asset_pack.cpp b/Extra2D/src/asset/asset_pack.cpp deleted file mode 100644 index 4523159..0000000 --- a/Extra2D/src/asset/asset_pack.cpp +++ /dev/null @@ -1,439 +0,0 @@ -#include "extra2d/asset/asset_pack.h" - -#include -#include -#include - -namespace extra2d { - -// --------------------------------------------------------------------------- -// AssetPack 实现 -// --------------------------------------------------------------------------- - -AssetPack::AssetPack(AssetPack&& other) noexcept - : path_(std::move(other.path_)) - , file_(std::move(other.file_)) - , header_(other.header_) - , entries_(std::move(other.entries_)) - , pipe_(std::move(other.pipe_)) { - other.header_ = AssetPackageHeader{}; -} - -AssetPack& AssetPack::operator=(AssetPack&& other) noexcept { - if (this != &other) { - close(); - path_ = std::move(other.path_); - file_ = std::move(other.file_); - header_ = other.header_; - entries_ = std::move(other.entries_); - pipe_ = std::move(other.pipe_); - other.header_ = AssetPackageHeader{}; - } - return *this; -} - -AssetPack::~AssetPack() { - close(); -} - -bool AssetPack::open(const std::string& path) { - close(); - - path_ = path; - file_.open(path, std::ios::binary); - - if (!file_.is_open()) { - return false; - } - - if (!readHeader()) { - close(); - return false; - } - - if (!readIndex()) { - close(); - return false; - } - - return true; -} - -void AssetPack::close() { - if (file_.is_open()) { - file_.close(); - } - entries_.clear(); - path_.clear(); - header_ = AssetPackageHeader{}; -} - -bool AssetPack::has(const AssetID& id) const { - return entries_.find(id) != entries_.end(); -} - -bool AssetPack::has(const std::string& path) const { - return has(AssetID(path)); -} - -std::vector AssetPack::read(const AssetID& id) { - auto raw = readRaw(id); - if (raw.empty()) { - return {}; - } - - if (!pipe_.empty()) { - return pipe_.process(raw); - } - - return raw; -} - -std::vector AssetPack::read(const std::string& path) { - return read(AssetID(path)); -} - -std::vector AssetPack::readRaw(const AssetID& id) { - auto* entry = getEntry(id); - if (!entry) { - return {}; - } - - return readEntryData(*entry); -} - -std::vector AssetPack::assets() const { - std::vector result; - result.reserve(entries_.size()); - for (const auto& pair : entries_) { - result.push_back(pair.first); - } - return result; -} - -const AssetPackageEntry* AssetPack::getEntry(const AssetID& id) const { - auto it = entries_.find(id); - return it != entries_.end() ? &it->second : nullptr; -} - -bool AssetPack::readHeader() { - file_.seekg(0, std::ios::beg); - file_.read(reinterpret_cast(&header_), sizeof(header_)); - - if (!file_.good()) { - return false; - } - - if (!header_.valid()) { - return false; - } - - return true; -} - -bool AssetPack::readIndex() { - u32 entryCount = 0; - file_.read(reinterpret_cast(&entryCount), sizeof(entryCount)); - - if (!file_.good()) { - return false; - } - - entries_.clear(); - entries_.reserve(entryCount); - - for (u32 i = 0; i < entryCount; ++i) { - u32 pathLen = 0; - file_.read(reinterpret_cast(&pathLen), sizeof(pathLen)); - - if (!file_.good()) { - return false; - } - - std::string path(pathLen, '\0'); - file_.read(path.data(), pathLen); - - AssetPackageEntry entry; - entry.id = AssetID(path); - - file_.read(reinterpret_cast(&entry.offset), sizeof(entry.offset)); - file_.read(reinterpret_cast(&entry.size), sizeof(entry.size)); - file_.read(reinterpret_cast(&entry.originalSize), sizeof(entry.originalSize)); - file_.read(reinterpret_cast(&entry.compression), sizeof(entry.compression)); - file_.read(reinterpret_cast(&entry.flags), sizeof(entry.flags)); - - if (!file_.good()) { - return false; - } - - entries_[entry.id] = entry; - } - - return true; -} - -std::vector AssetPack::readEntryData(const AssetPackageEntry& entry) { - std::vector data(entry.size); - - file_.seekg(entry.offset, std::ios::beg); - file_.read(reinterpret_cast(data.data()), entry.size); - - if (!file_.good()) { - return {}; - } - - return data; -} - -// --------------------------------------------------------------------------- -// PackManager 实现 -// --------------------------------------------------------------------------- - -bool PackManager::mount(const std::string& path) { - auto pack = std::make_unique(); - if (!pack->open(path)) { - return false; - } - - (void)defaultPipe_; - - - packs_.push_back(std::move(pack)); - return true; -} - -void PackManager::unmount(const std::string& path) { - packs_.erase( - std::remove_if(packs_.begin(), packs_.end(), - [&path](const Unique& pack) { - return pack->path() == path; - }), - packs_.end() - ); -} - -void PackManager::unmountAll() { - packs_.clear(); -} - -AssetPack* PackManager::find(const AssetID& id) { - for (auto& pack : packs_) { - if (pack->has(id)) { - return pack.get(); - } - } - return nullptr; -} - -AssetPack* PackManager::find(const std::string& path) { - return find(AssetID(path)); -} - -bool PackManager::has(const AssetID& id) const { - for (const auto& pack : packs_) { - if (pack->has(id)) { - return true; - } - } - return false; -} - -bool PackManager::has(const std::string& path) const { - return has(AssetID(path)); -} - -std::vector PackManager::read(const AssetID& id) { - auto* pack = find(id); - if (pack) { - return pack->read(id); - } - return {}; -} - -std::vector PackManager::read(const std::string& path) { - return read(AssetID(path)); -} - -std::vector PackManager::allAssets() const { - std::vector result; - for (const auto& pack : packs_) { - auto assets = pack->assets(); - result.insert(result.end(), assets.begin(), assets.end()); - } - return result; -} - -std::vector PackManager::mountedPacks() const { - std::vector result; - result.reserve(packs_.size()); - for (const auto& pack : packs_) { - result.push_back(pack->path()); - } - return result; -} - -// --------------------------------------------------------------------------- -// AssetPackBuilder 实现 -// --------------------------------------------------------------------------- - -AssetPackBuilder::AssetPackBuilder(Compression compression, int level) - : compression_(compression) - , level_(level) { -} - -void AssetPackBuilder::add(const std::string& path, const std::vector& data) { - BuilderEntry entry; - entry.id = AssetID(path); - entry.data = data; - entry.compression = static_cast(compression_); - entry.flags = encryptType_ != Decryptor::Type::None ? 1 : 0; - - entry.compressedData = processData(entry.data); - if (entry.compressedData.empty()) { - entry.compressedData = entry.data; - entry.compression = static_cast(Compression::None); - } - - entries_.push_back(std::move(entry)); - - totalOriginalSize_ += data.size(); - totalCompressedSize_ += entry.compressedData.size(); -} - -void AssetPackBuilder::add(const std::string& path, std::vector&& data) { - add(path, data); -} - -bool AssetPackBuilder::addFile(const std::string& filePath, const std::string& packPath) { - std::ifstream file(filePath, std::ios::binary | std::ios::ate); - if (!file.is_open()) { - return false; - } - - auto size = file.tellg(); - file.seekg(0, std::ios::beg); - - std::vector data(size); - file.read(reinterpret_cast(data.data()), size); - - if (!file.good()) { - return false; - } - - std::string ppath = packPath.empty() - ? std::filesystem::path(filePath).filename().string() - : packPath; - - add(ppath, std::move(data)); - return true; -} - -size_t AssetPackBuilder::addDirectory(const std::string& dirPath, const std::string& prefix) { - size_t count = 0; - std::filesystem::path dir(dirPath); - - if (!std::filesystem::exists(dir) || !std::filesystem::is_directory(dir)) { - return 0; - } - - for (const auto& entry : std::filesystem::recursive_directory_iterator(dir)) { - if (entry.is_regular_file()) { - std::string relativePath = std::filesystem::relative(entry.path(), dir).string(); - std::string packPath = prefix.empty() - ? relativePath - : prefix + "/" + relativePath; - - if (addFile(entry.path().string(), packPath)) { - ++count; - } - } - } - - return count; -} - -void AssetPackBuilder::setEncryption(const std::string& key, Decryptor::Type type) { - encryptKey_ = key; - encryptType_ = type; -} - -bool AssetPackBuilder::build(const std::string& outputPath) { - std::ofstream out(outputPath, std::ios::binary); - if (!out.is_open()) { - return false; - } - - AssetPackageHeader header; - header.magic = AssetPackageHeader::MAGIC; - header.version = 1; - header.compressionType = static_cast(compression_); - header.encryptionType = static_cast(encryptType_); - header.originalSize = totalOriginalSize_; - - u64 dataOffset = sizeof(header) + sizeof(u32); - for (const auto& entry : entries_) { - dataOffset += sizeof(u32) + entry.id.path.size(); - dataOffset += sizeof(u64) * 3 + sizeof(u32) * 2; - } - - for (auto& entry : entries_) { - entry.offset = dataOffset; - dataOffset += entry.compressedData.size(); - } - - header.compressedSize = dataOffset - sizeof(header) - sizeof(u32); - for (const auto& entry : entries_) { - header.compressedSize -= sizeof(u32) + entry.id.path.size(); - header.compressedSize -= sizeof(u64) * 3 + sizeof(u32) * 2; - } - - out.write(reinterpret_cast(&header), sizeof(header)); - - u32 entryCount = static_cast(entries_.size()); - out.write(reinterpret_cast(&entryCount), sizeof(entryCount)); - - for (const auto& entry : entries_) { - u32 pathLen = static_cast(entry.id.path.size()); - out.write(reinterpret_cast(&pathLen), sizeof(pathLen)); - out.write(entry.id.path.data(), pathLen); - - u64 offset = entry.offset; - u64 size = entry.compressedData.size(); - u64 originalSize = entry.data.size(); - - out.write(reinterpret_cast(&offset), sizeof(offset)); - out.write(reinterpret_cast(&size), sizeof(size)); - out.write(reinterpret_cast(&originalSize), sizeof(originalSize)); - out.write(reinterpret_cast(&entry.compression), sizeof(entry.compression)); - out.write(reinterpret_cast(&entry.flags), sizeof(entry.flags)); - } - - for (const auto& entry : entries_) { - out.write(reinterpret_cast(entry.compressedData.data()), - entry.compressedData.size()); - } - - return out.good(); -} - -void AssetPackBuilder::clear() { - entries_.clear(); - totalOriginalSize_ = 0; - totalCompressedSize_ = 0; -} - -std::vector AssetPackBuilder::processData(const std::vector& data) { - DataPipe pipe; - - if (compression_ != Compression::None) { - pipe.compress(compression_, level_); - } - - if (encryptType_ != Decryptor::Type::None && !encryptKey_.empty()) { - pipe.encrypt(encryptKey_, encryptType_); - } - - return pipe.process(data); -} - -} diff --git a/Extra2D/src/asset/data_processor.cpp b/Extra2D/src/asset/data_processor.cpp deleted file mode 100644 index 524ee62..0000000 --- a/Extra2D/src/asset/data_processor.cpp +++ /dev/null @@ -1,449 +0,0 @@ -#include "extra2d/asset/data_processor.h" - -#include -#include - -#ifdef E2D_USE_ZSTD -#include -#endif - -#ifdef E2D_USE_LZ4 -#include -#endif - -#ifdef E2D_USE_ZLIB -#include -#endif - -namespace extra2d { - -// --------------------------------------------------------------------------- -// Decryptor 实现 -// --------------------------------------------------------------------------- - -Decryptor::Decryptor(const std::string& key, Type type) - : key_(key), type_(type) { -} - -std::vector Decryptor::process(const std::vector& input) { - if (input.empty() || type_ == Type::None) { - return processNext(input); - } - - std::vector result; - switch (type_) { - case Type::XOR: - result = decryptXOR(input); - break; - case Type::AES256: - result = decryptAES256(input); - break; - default: - result = input; - break; - } - - return processNext(result); -} - -std::vector Decryptor::decryptXOR(const std::vector& input) { - if (key_.empty()) { - return input; - } - - std::vector result(input.size()); - const size_t keyLen = key_.size(); - - for (size_t i = 0; i < input.size(); ++i) { - result[i] = input[i] ^ static_cast(key_[i % keyLen]); - } - - return result; -} - -std::vector Decryptor::decryptAES256(const std::vector& input) { - return decryptXOR(input); -} - -// --------------------------------------------------------------------------- -// Decompressor 实现 -// --------------------------------------------------------------------------- - -Decompressor::Decompressor(Compression algo) - : algo_(algo) { -} - -std::vector Decompressor::process(const std::vector& input) { - if (input.empty() || algo_ == Compression::None) { - return processNext(input); - } - - std::vector result; - switch (algo_) { - case Compression::Zstd: - result = decompressZstd(input); - break; - case Compression::LZ4: - result = decompressLZ4(input); - break; - case Compression::Zlib: - result = decompressZlib(input); - break; - default: - result = input; - break; - } - - return processNext(result); -} - -std::vector Decompressor::decompressZstd(const std::vector& input) { -#ifdef E2D_USE_ZSTD - unsigned long long const decompressedSize = ZSTD_getFrameContentSize(input.data(), input.size()); - - if (decompressedSize == ZSTD_CONTENTSIZE_ERROR || - decompressedSize == ZSTD_CONTENTSIZE_UNKNOWN) { - return {}; - } - - std::vector result(static_cast(decompressedSize)); - - size_t const actualSize = ZSTD_decompress( - result.data(), result.size(), - input.data(), input.size() - ); - - if (ZSTD_isError(actualSize)) { - return {}; - } - - result.resize(actualSize); - return result; -#else - return input; -#endif -} - -std::vector Decompressor::decompressLZ4(const std::vector& input) { -#ifdef E2D_USE_LZ4 - if (input.size() < sizeof(u32)) { - return {}; - } - - u32 originalSize; - std::memcpy(&originalSize, input.data(), sizeof(u32)); - - std::vector result(originalSize); - - int const decompressedSize = LZ4_decompress_safe( - reinterpret_cast(input.data() + sizeof(u32)), - reinterpret_cast(result.data()), - static_cast(input.size() - sizeof(u32)), - static_cast(originalSize) - ); - - if (decompressedSize < 0) { - return {}; - } - - return result; -#else - return input; -#endif -} - -std::vector Decompressor::decompressZlib(const std::vector& input) { -#ifdef E2D_USE_ZLIB - std::vector result; - result.reserve(input.size() * 4); - - const size_t CHUNK = 16384; - u8 out[CHUNK]; - - z_stream strm; - strm.zalloc = Z_NULL; - strm.zfree = Z_NULL; - strm.opaque = Z_NULL; - strm.avail_in = static_cast(input.size()); - strm.next_in = const_cast(input.data()); - - if (inflateInit(&strm) != Z_OK) { - return {}; - } - - int ret; - do { - strm.avail_out = CHUNK; - strm.next_out = out; - - ret = inflate(&strm, Z_NO_FLUSH); - if (ret == Z_STREAM_ERROR || ret == Z_DATA_ERROR || ret == Z_MEM_ERROR) { - inflateEnd(&strm); - return {}; - } - - result.insert(result.end(), out, out + CHUNK - strm.avail_out); - } while (strm.avail_out == 0); - - inflateEnd(&strm); - return result; -#else - return input; -#endif -} - -// --------------------------------------------------------------------------- -// Encryptor 实现 -// --------------------------------------------------------------------------- - -Encryptor::Encryptor(const std::string& key, Decryptor::Type type) - : key_(key), type_(type) { -} - -std::vector Encryptor::process(const std::vector& input) { - if (input.empty() || type_ == Decryptor::Type::None) { - return processNext(input); - } - - std::vector result; - switch (type_) { - case Decryptor::Type::XOR: - result = encryptXOR(input); - break; - case Decryptor::Type::AES256: - result = encryptAES256(input); - break; - default: - result = input; - break; - } - - return processNext(result); -} - -std::vector Encryptor::encryptXOR(const std::vector& input) { - if (key_.empty()) { - return input; - } - - std::vector result(input.size()); - const size_t keyLen = key_.size(); - - for (size_t i = 0; i < input.size(); ++i) { - result[i] = input[i] ^ static_cast(key_[i % keyLen]); - } - - return result; -} - -std::vector Encryptor::encryptAES256(const std::vector& input) { - return encryptXOR(input); -} - -// --------------------------------------------------------------------------- -// Compressor 实现 -// --------------------------------------------------------------------------- - -Compressor::Compressor(Compression algo, int level) - : algo_(algo), level_(level) { -} - -std::vector Compressor::process(const std::vector& input) { - if (input.empty() || algo_ == Compression::None) { - return processNext(input); - } - - std::vector result; - switch (algo_) { - case Compression::Zstd: - result = compressZstd(input); - break; - case Compression::LZ4: - result = compressLZ4(input); - break; - case Compression::Zlib: - result = compressZlib(input); - break; - default: - result = input; - break; - } - - return processNext(result); -} - -std::vector Compressor::compressZstd(const std::vector& input) { -#ifdef E2D_USE_ZSTD - size_t const bound = ZSTD_compressBound(input.size()); - std::vector result(bound); - - size_t const compressedSize = ZSTD_compress( - result.data(), result.size(), - input.data(), input.size(), - level_ - ); - - if (ZSTD_isError(compressedSize)) { - return {}; - } - - result.resize(compressedSize); - return result; -#else - return input; -#endif -} - -std::vector Compressor::compressLZ4(const std::vector& input) { -#ifdef E2D_USE_LZ4 - int const bound = LZ4_compressBound(static_cast(input.size())); - std::vector result(sizeof(u32) + bound); - - u32 originalSize = static_cast(input.size()); - std::memcpy(result.data(), &originalSize, sizeof(u32)); - - int const compressedSize = LZ4_compress_default( - reinterpret_cast(input.data()), - reinterpret_cast(result.data() + sizeof(u32)), - static_cast(input.size()), - bound - ); - - if (compressedSize <= 0) { - return {}; - } - - result.resize(sizeof(u32) + compressedSize); - return result; -#else - return input; -#endif -} - -std::vector Compressor::compressZlib(const std::vector& input) { -#ifdef E2D_USE_ZLIB - std::vector result; - result.reserve(input.size() / 2); - - const size_t CHUNK = 16384; - u8 out[CHUNK]; - - z_stream strm; - strm.zalloc = Z_NULL; - strm.zfree = Z_NULL; - strm.opaque = Z_NULL; - - if (deflateInit(&strm, level_) != Z_OK) { - return {}; - } - - strm.avail_in = static_cast(input.size()); - strm.next_in = const_cast(input.data()); - - int ret; - do { - strm.avail_out = CHUNK; - strm.next_out = out; - - ret = deflate(&strm, Z_FINISH); - if (ret == Z_STREAM_ERROR) { - deflateEnd(&strm); - return {}; - } - - result.insert(result.end(), out, out + CHUNK - strm.avail_out); - } while (strm.avail_out == 0); - - deflateEnd(&strm); - return result; -#else - return input; -#endif -} - -// --------------------------------------------------------------------------- -// DataPipe 实现 -// --------------------------------------------------------------------------- - -DataPipe& DataPipe::decrypt(const std::string& key, Decryptor::Type type) { - processors_.push_back(std::make_unique(key, type)); - return *this; -} - -DataPipe& DataPipe::decompress(Compression algo) { - processors_.push_back(std::make_unique(algo)); - return *this; -} - -DataPipe& DataPipe::encrypt(const std::string& key, Decryptor::Type type) { - processors_.push_back(std::make_unique(key, type)); - return *this; -} - -DataPipe& DataPipe::compress(Compression algo, int level) { - processors_.push_back(std::make_unique(algo, level)); - return *this; -} - -DataPipe& DataPipe::add(Unique processor) { - processors_.push_back(std::move(processor)); - return *this; -} - -std::vector DataPipe::process(const std::vector& input) { - if (processors_.empty()) { - return input; - } - - std::vector result = input; - for (auto& processor : processors_) { - result = processor->process(result); - } - - return result; -} - -void DataPipe::clear() { - processors_.clear(); -} - -// --------------------------------------------------------------------------- -// 工具函数实现 -// --------------------------------------------------------------------------- - -std::vector computeChecksum(const std::vector& data) { - std::vector result(32, 0); - - u64 hash1 = 14695981039346656037ULL; - u64 hash2 = 14695981039346656037ULL; - - for (size_t i = 0; i < data.size(); ++i) { - hash1 ^= static_cast(data[i]); - hash1 *= 1099511628211ULL; - - hash2 ^= static_cast(data[i]) << ((i % 8) * 8); - hash2 *= 1099511628211ULL; - } - - for (size_t i = 0; i < 8; ++i) { - result[i] = static_cast((hash1 >> (i * 8)) & 0xFF); - result[i + 8] = static_cast((hash2 >> (i * 8)) & 0xFF); - } - - for (size_t i = 16; i < 32; ++i) { - result[i] = static_cast((hash1 ^ hash2) >> ((i - 16) * 8) & 0xFF); - } - - return result; -} - -bool verifyChecksum(const std::vector& data, const std::vector& checksum) { - if (checksum.size() != 32) { - return false; - } - - auto computed = computeChecksum(data); - return std::equal(computed.begin(), computed.end(), checksum.begin()); -} - -} diff --git a/Extra2D/src/base/AutoreleasePool.cpp b/Extra2D/src/base/AutoreleasePool.cpp new file mode 100644 index 0000000..f841e1d --- /dev/null +++ b/Extra2D/src/base/AutoreleasePool.cpp @@ -0,0 +1,40 @@ +#include +#include + +namespace extra2d { + +AutoreleasePool* AutoreleasePool::instance_ = nullptr; + +AutoreleasePool::AutoreleasePool() {} + +AutoreleasePool::~AutoreleasePool() { + clear(); +} + +AutoreleasePool* AutoreleasePool::getInstance() { + if (!instance_) { + instance_ = new AutoreleasePool(); + } + return instance_; +} + +void AutoreleasePool::addObject(RefCounted* object) { + if (object) { + objects_.push_back(object); + } +} + +void AutoreleasePool::clear() { + for (auto* obj : objects_) { + if (obj) { + obj->release(); + } + } + objects_.clear(); +} + +size_t AutoreleasePool::getObjectCount() const { + return objects_.size(); +} + +} // namespace extra2d diff --git a/Extra2D/src/base/RefCounted.cpp b/Extra2D/src/base/RefCounted.cpp new file mode 100644 index 0000000..b27435a --- /dev/null +++ b/Extra2D/src/base/RefCounted.cpp @@ -0,0 +1,36 @@ +#include +#include + +namespace extra2d { + +RefCounted::RefCounted() : refCount_(1), autorelease_(false) {} + +RefCounted::~RefCounted() {} + +void RefCounted::addRef() { + refCount_.fetch_add(1, std::memory_order_relaxed); +} + +bool RefCounted::release() { + u32 count = refCount_.fetch_sub(1, std::memory_order_acq_rel); + if (count == 1) { + delete this; + return true; + } + return false; +} + +u32 RefCounted::getRefCount() const { + return refCount_.load(std::memory_order_relaxed); +} + +void RefCounted::autorelease() { + autorelease_ = true; + AutoreleasePool::getInstance()->addObject(this); +} + +bool RefCounted::isAutorelease() const { + return autorelease_; +} + +} // namespace extra2d diff --git a/Extra2D/src/base/Scheduler.cpp b/Extra2D/src/base/Scheduler.cpp new file mode 100644 index 0000000..fe15868 --- /dev/null +++ b/Extra2D/src/base/Scheduler.cpp @@ -0,0 +1,185 @@ +#include +#include + +namespace extra2d { + +Scheduler::Scheduler() {} + +Scheduler::~Scheduler() { + unscheduleAll(); +} + +void Scheduler::update(f32 dt) { + dt *= timeScale_; + + runFunctionsToBePerformed(); + + updateLocked_ = true; + + for (auto& pair : targets_) { + auto& entry = pair.second; + if (entry.paused) continue; + + for (auto it = entry.timers.begin(); it != entry.timers.end(); ) { + Timer* timer = *it; + + if (timer->paused) { + ++it; + continue; + } + + if (timer->delay > 0.0f) { + timer->delay -= dt; + ++it; + continue; + } + + timer->accumulator += dt; + + while (timer->accumulator >= timer->interval) { + timer->accumulator -= timer->interval; + + if (timer->callback) { + timer->callback(dt); + } + + if (timer->repeat > 0) { + timer->elapsed++; + if (timer->elapsed >= timer->repeat) { + delete timer; + it = entry.timers.erase(it); + break; + } + } + } + + if (it != entry.timers.end()) { + ++it; + } + } + } + + updateLocked_ = false; +} + +void Scheduler::schedule(const SchedulerCallback& callback, void* target, + f32 interval, u32 repeat, f32 delay, + bool paused, const std::string& key) { + Timer* timer = new Timer(); + timer->callback = callback; + timer->interval = interval; + timer->repeat = repeat; + timer->delay = delay; + timer->paused = paused; + timer->key = key; + timer->target = target; + timer->elapsed = 0; + timer->accumulator = 0.0f; + + auto& entry = targets_[target]; + entry.timers.push_back(timer); +} + +void Scheduler::schedule(const SchedulerCallback& callback, void* target, + f32 interval, bool paused, const std::string& key) { + schedule(callback, target, interval, 0, 0.0f, paused, key); +} + +void Scheduler::scheduleOnce(const SchedulerCallback& callback, void* target, + f32 delay, const std::string& key) { + schedule(callback, target, 0.0f, 1, delay, false, key); +} + +void Scheduler::unschedule(const std::string& key, void* target) { + auto it = targets_.find(target); + if (it == targets_.end()) return; + + auto& timers = it->second.timers; + for (auto timerIt = timers.begin(); timerIt != timers.end(); ++timerIt) { + if ((*timerIt)->key == key) { + delete *timerIt; + timers.erase(timerIt); + break; + } + } +} + +void Scheduler::unscheduleAllForTarget(void* target) { + auto it = targets_.find(target); + if (it == targets_.end()) return; + + for (auto* timer : it->second.timers) { + delete timer; + } + targets_.erase(it); +} + +void Scheduler::unscheduleAll() { + for (auto& pair : targets_) { + for (auto* timer : pair.second.timers) { + delete timer; + } + } + targets_.clear(); +} + +bool Scheduler::isScheduled(const std::string& key, void* target) { + auto it = targets_.find(target); + if (it == targets_.end()) return false; + + for (const auto* timer : it->second.timers) { + if (timer->key == key) { + return true; + } + } + return false; +} + +void Scheduler::pauseTarget(void* target) { + auto it = targets_.find(target); + if (it != targets_.end()) { + it->second.paused = true; + } +} + +void Scheduler::resumeTarget(void* target) { + auto it = targets_.find(target); + if (it != targets_.end()) { + it->second.paused = false; + } +} + +bool Scheduler::isTargetPaused(void* target) { + auto it = targets_.find(target); + if (it != targets_.end()) { + return it->second.paused; + } + return false; +} + +void Scheduler::performFunctionInMainThread(const std::function& func) { + std::lock_guard lock(performMutex_); + functionsToPerform_.push_back(func); +} + +void Scheduler::runFunctionsToBePerformed() { + std::vector> functions; + { + std::lock_guard lock(performMutex_); + functions.swap(functionsToPerform_); + } + + for (const auto& func : functions) { + func(); + } +} + +f32 Scheduler::getTimeScale() const { + return timeScale_; +} + +void Scheduler::setTimeScale(f32 scale) { + timeScale_ = scale; +} + +} // namespace extra2d diff --git a/Extra2D/src/core/Object.cpp b/Extra2D/src/core/Object.cpp new file mode 100644 index 0000000..186a752 --- /dev/null +++ b/Extra2D/src/core/Object.cpp @@ -0,0 +1,75 @@ +#include + +namespace extra2d { + +u64 Object::nextObjectId_ = 1; + +Object::Object(const std::string& name) + : name_(name) + , objectId_(nextObjectId_++) { +} + +Object::~Object() { + if (!hasFlag(ObjectFlags::Destroyed)) { + onDestroy(); + } +} + +const std::string& Object::getName() const { + return name_; +} + +void Object::setName(const std::string& name) { + name_ = name; +} + +bool Object::destroy() { + if (hasFlag(ObjectFlags::Destroyed)) { + return false; + } + + onDestroy(); + flags_ |= ObjectFlags::Destroyed; + return true; +} + +bool Object::isValid() const { + return !hasFlag(ObjectFlags::Destroyed); +} + +bool Object::isDestroyed() const { + return hasFlag(ObjectFlags::Destroyed); +} + +ObjectFlags Object::getFlags() const { + return flags_; +} + +void Object::setFlags(ObjectFlags flags) { + flags_ = flags; +} + +void Object::addFlag(ObjectFlags flag) { + flags_ |= flag; +} + +void Object::removeFlag(ObjectFlags flag) { + flags_ &= ~flag; +} + +bool Object::hasFlag(ObjectFlags flag) const { + return extra2d::hasFlag(flags_, flag); +} + +u64 Object::getObjectId() const { + return objectId_; +} + +std::string Object::toString() const { + return "Object[" + name_ + "]"; +} + +void Object::onDestroy() { +} + +} // namespace extra2d diff --git a/Extra2D/src/core/assets/Asset.cpp b/Extra2D/src/core/assets/Asset.cpp new file mode 100644 index 0000000..3356cc7 --- /dev/null +++ b/Extra2D/src/core/assets/Asset.cpp @@ -0,0 +1,84 @@ +#include + +namespace extra2d { + +Asset::Asset(const std::string& name) : Object(name) { +} + +Asset::~Asset() { +} + +const std::string& Asset::getUuid() const { + return uuid_; +} + +void Asset::setUuid(const std::string& uuid) { + uuid_ = uuid; +} + +const std::string& Asset::getNativeUrl() const { + return nativeUrl_; +} + +void Asset::setNativeUrl(const std::string& url) { + nativeUrl_ = url; +} + +void Asset::addAssetRef() { + ++assetRefCount_; +} + +void Asset::decAssetRef(bool autoRelease) { + if (assetRefCount_ > 0) { + --assetRefCount_; + } + + if (autoRelease && assetRefCount_ == 0 && getRefCount() == 1) { + release(); + } +} + +u32 Asset::getAssetRefCount() const { + return assetRefCount_; +} + +bool Asset::isLoaded() const { + return loaded_; +} + +void Asset::setLoaded(bool loaded) { + loaded_ = loaded; +} + +void Asset::onLoaded() { + loaded_ = true; +} + +bool Asset::isDefault() const { + return isDefault_; +} + +void Asset::initDefault(const std::string& uuid) { + isDefault_ = true; + if (!uuid.empty()) { + uuid_ = uuid; + } +} + +bool Asset::validate() const { + return true; +} + +bool Asset::destroy() { + if (isDestroyed()) { + return false; + } + + return Object::destroy(); +} + +void Asset::onDestroy() { + Object::onDestroy(); +} + +} // namespace extra2d diff --git a/Extra2D/src/core/assets/AssetManager.cpp b/Extra2D/src/core/assets/AssetManager.cpp new file mode 100644 index 0000000..221dcb5 --- /dev/null +++ b/Extra2D/src/core/assets/AssetManager.cpp @@ -0,0 +1,106 @@ +#include +#include +#include +#include +#include +#include +#include + +namespace extra2d { + +AssetManager::AssetManager() { +} + +AssetManager::~AssetManager() { + destroy(); +} + +AssetManager* AssetManager::getInstance() { + static AssetManager instance; + return &instance; +} + +bool AssetManager::initialize(gfx::Device* device) { + device_ = device; + initDefaultAssets(); + return true; +} +void AssetManager::destroy() { + releaseAll(); + assets_.clear(); + defaultTextures_.clear(); +} +Asset* AssetManager::load(const std::string& path, const std::string& type, + const AssetLoadOptions& options) { + auto it = assets_.find(path); + if (it != assets_.end()) { + return it->second.get(); + } + + return nullptr; +} +void AssetManager::loadAsync(const std::string& path, const std::string& type, + AssetLoadProgress progress, AssetLoadComplete complete, + AssetLoadError error, + const AssetLoadOptions& options) { +} +Asset* AssetManager::getAsset(const std::string& path) { + auto it = assets_.find(path); + if (it != assets_.end()) { + return it->second.get(); + } + return nullptr; +} +Asset* AssetManager::getAssetByUuid(const std::string& uuid) { + for (auto& pair : assets_) { + if (pair.second->getUuid() == uuid) { + return pair.second.get(); + } + } + return nullptr; +} +void AssetManager::releaseAsset(const std::string& path) { + auto it = assets_.find(path); + if (it != assets_.end()) { + assets_.erase(it); + } +} +void AssetManager::releaseAll() { + assets_.clear(); +} +void AssetManager::addAsset(const std::string& path, Asset* asset) { + if (asset) { + assets_[path] = asset; + } +} +void AssetManager::removeAsset(const std::string& path) { + assets_.erase(path); +} +bool AssetManager::hasAsset(const std::string& path) { + return assets_.find(path) != assets_.end(); +} +size_t AssetManager::getAssetCount() const { + return assets_.size(); +} +void AssetManager::setDefaultTexture(const std::string& name, Texture2D* texture) { + if (texture) { + defaultTextures_[name] = texture; + assets_[name] = texture; + } +} +Texture2D* AssetManager::getDefaultTexture(const std::string& name) { + auto it = defaultTextures_.find(name); + if (it != defaultTextures_.end()) { + return it->second.get(); + } + return nullptr; +} +std::vector AssetManager::getDefaultTextureNames() const { + std::vector names; + for (const auto& pair : defaultTextures_) { + names.push_back(pair.first); + } + return names; +} +void AssetManager::initDefaultAssets() { +} diff --git a/Extra2D/src/core/assets/ImageAsset.cpp b/Extra2D/src/core/assets/ImageAsset.cpp new file mode 100644 index 0000000..02abe37 --- /dev/null +++ b/Extra2D/src/core/assets/ImageAsset.cpp @@ -0,0 +1,147 @@ +#include +#include + +namespace extra2d { + +ImageAsset::ImageAsset() : Asset("ImageAsset") { +} + +ImageAsset::~ImageAsset() { + if (ownsData_ && data_) { + delete[] data_; + data_ = nullptr; + } +} + +const u8* ImageAsset::getData() const { + return data_; +} + +u32 ImageAsset::getDataSize() const { + return dataSize_; +} + +void ImageAsset::setData(u8* data, u32 size, bool ownsData) { + if (ownsData_ && data_) { + delete[] data_; + } + + data_ = data; + dataSize_ = size; + ownsData_ = ownsData; +} + +u32 ImageAsset::getWidth() const { + return width_; +} + +void ImageAsset::setWidth(u32 width) { + width_ = width; +} + +u32 ImageAsset::getHeight() const { + return height_; +} + +void ImageAsset::setHeight(u32 height) { + height_ = height; +} + +PixelFormat ImageAsset::getFormat() const { + return format_; +} + +void ImageAsset::setFormat(PixelFormat format) { + format_ = format; +} + +bool ImageAsset::isCompressed() const { + return compressed_; +} + +void ImageAsset::setCompressed(bool compressed) { + compressed_ = compressed; +} + +const std::vector& ImageAsset::getMipmapLevelDataSize() const { + return mipmapLevelDataSize_; +} + +void ImageAsset::setMipmapLevelDataSize(const std::vector& sizes) { + mipmapLevelDataSize_ = sizes; +} + +u32 ImageAsset::getBytesPerPixel() const { + switch (format_) { + case PixelFormat::A8: + case PixelFormat::I8: + case PixelFormat::R8: + return 1; + case PixelFormat::RGB565: + case PixelFormat::RGB5A1: + case PixelFormat::RGBA4444: + case PixelFormat::AI8: + case PixelFormat::RG8: + case PixelFormat::R16F: + return 2; + case PixelFormat::RGB888: + return 3; + case PixelFormat::RGBA8888: + case PixelFormat::RG16F: + case PixelFormat::R32F: + return 4; + case PixelFormat::RGB32F: + return 12; + case PixelFormat::RGBA32F: + case PixelFormat::RGBA16F: + return 16; + default: + return 4; + } +} + +gfx::Format ImageAsset::getGFXFormat() const { + switch (format_) { + case PixelFormat::R8: + return gfx::Format::R8; + case PixelFormat::RG8: + return gfx::Format::RG8; + case PixelFormat::RGB888: + return gfx::Format::RGBA8; + case PixelFormat::RGBA8888: + return gfx::Format::RGBA8; + case PixelFormat::R16F: + return gfx::Format::R16F; + case PixelFormat::RG16F: + return gfx::Format::RG16F; + case PixelFormat::RGBA16F: + return gfx::Format::RGBA16F; + case PixelFormat::R32F: + return gfx::Format::R32F; + case PixelFormat::RG32F: + return gfx::Format::RG32F; + case PixelFormat::RGBA32F: + return gfx::Format::RGBA32F; + case PixelFormat::Depth: + return gfx::Format::Depth; + case PixelFormat::DepthStencil: + return gfx::Format::DepthStencil; + default: + return gfx::Format::RGBA8; + } +} + +bool ImageAsset::validate() const { + return data_ != nullptr && width_ > 0 && height_ > 0; +} + +void ImageAsset::onDestroy() { + if (ownsData_ && data_) { + delete[] data_; + data_ = nullptr; + dataSize_ = 0; + } + Asset::onDestroy(); +} + +} // namespace extra2d diff --git a/Extra2D/src/core/assets/Material.cpp b/Extra2D/src/core/assets/Material.cpp new file mode 100644 index 0000000..bf8174a --- /dev/null +++ b/Extra2D/src/core/assets/Material.cpp @@ -0,0 +1,284 @@ +#include +#include +#include +#include + +namespace extra2d { + +// ==================== Pass 实现 ==================== + +Pass::Pass() { +} + +Pass::~Pass() { + destroy(); +} + +bool Pass::initialize(gfx::Shader* shader) { + shader_ = shader; + return true; +} + +void Pass::destroy() { + shader_ = nullptr; + if (pipelineState_) { + delete pipelineState_; + pipelineState_ = nullptr; + } + uniforms_.clear(); +} + +void Pass::setUniform(const std::string& name, const MaterialPropertyVariant& value) { + uniforms_[name] = value; +} + +const MaterialPropertyVariant* Pass::getUniform(const std::string& name) const { + auto it = uniforms_.find(name); + if (it != uniforms_.end()) { + return &it->second; + } + return nullptr; +} + +void Pass::setPipelineState(gfx::PipelineState* state) { + pipelineState_ = state; +} + +gfx::PipelineState* Pass::getPipelineState() const { + return pipelineState_; +} + +gfx::Shader* Pass::getShader() const { + return shader_; +} + +void Pass::setBlendState(const gfx::BlendState& state) { + blendState_ = state; +} + +const gfx::BlendState& Pass::getBlendState() const { + return blendState_; +} + +void Pass::setDepthStencilState(const gfx::DepthStencilState& state) { + depthStencilState_ = state; +} + +const gfx::DepthStencilState& Pass::getDepthStencilState() const { + return depthStencilState_; +} + +void Pass::setRasterizerState(const gfx::RasterizerState& state) { + rasterizerState_ = state; +} + +const gfx::RasterizerState& Pass::getRasterizerState() const { + return rasterizerState_; +} + +size_t Pass::getHash() const { + return hash_; +} + +// ==================== Material 实现 ==================== + +Ref Material::create() { + Ref material = new Material(); + return material; +} + +Material::Material() : Asset() { +} + +Material::~Material() { + destroy(); +} + +bool Material::initialize(const MaterialInfo& info) { + effectName_ = info.effectName; + techniqueIndex_ = info.technique; + defines_.push_back(info.defines); + states_.push_back(info.states); + + updateHash(); + return true; +} + +void Material::reset(const MaterialInfo& info) { + passes_.clear(); + defines_.clear(); + states_.clear(); + properties_.clear(); + + initialize(info); +} + +bool Material::destroy() { + passes_.clear(); + defines_.clear(); + states_.clear(); + properties_.clear(); + return Asset::destroy(); +} + +void Material::setProperty(const std::string& name, const MaterialPropertyVariant& value, i32 passIdx) { + properties_[name] = value; + + if (passIdx >= 0 && static_cast(passIdx) < passes_.size()) { + passes_[passIdx]->setUniform(name, value); + } else { + for (auto& pass : passes_) { + pass->setUniform(name, value); + } + } +} + +void Material::setPropertyFloat(const std::string& name, float value, i32 passIdx) { + setProperty(name, MaterialPropertyVariant(value), passIdx); +} + +void Material::setPropertyInt(const std::string& name, i32 value, i32 passIdx) { + setProperty(name, MaterialPropertyVariant(value), passIdx); +} + +void Material::setPropertyVec2(const std::string& name, const Vec2& value, i32 passIdx) { + setProperty(name, MaterialPropertyVariant(value), passIdx); +} + +void Material::setPropertyVec3(const std::string& name, const Vec3& value, i32 passIdx) { + setProperty(name, MaterialPropertyVariant(value), passIdx); +} + +void Material::setPropertyVec4(const std::string& name, const Vec4& value, i32 passIdx) { + setProperty(name, MaterialPropertyVariant(value), passIdx); +} + +void Material::setPropertyColor(const std::string& name, const Color& value, i32 passIdx) { + setProperty(name, MaterialPropertyVariant(value), passIdx); +} + +const MaterialPropertyVariant* Material::getProperty(const std::string& name, i32 passIdx) const { + auto it = properties_.find(name); + if (it != properties_.end()) { + return &it->second; + } + + if (passIdx >= 0 && static_cast(passIdx) < passes_.size()) { + return passes_[passIdx]->getUniform(name); + } + + return nullptr; +} + +size_t Material::getPassCount() const { + return passes_.size(); +} + +Pass* Material::getPass(size_t index) { + if (index < passes_.size()) { + return passes_[index].get(); + } + return nullptr; +} + +const std::vector>& Material::getPasses() const { + return passes_; +} + +void Material::addPass(Ref pass) { + passes_.push_back(pass); + updateHash(); +} + +void Material::overridePipelineStates(const PassOverrides& overrides, i32 passIdx) { + if (passIdx >= 0 && static_cast(passIdx) < passes_.size()) { + passes_[passIdx]->setBlendState(overrides.blendState); + passes_[passIdx]->setDepthStencilState(overrides.depthStencilState); + passes_[passIdx]->setRasterizerState(overrides.rasterizerState); + } else { + for (auto& pass : passes_) { + pass->setBlendState(overrides.blendState); + pass->setDepthStencilState(overrides.depthStencilState); + pass->setRasterizerState(overrides.rasterizerState); + } + } + updateHash(); +} + +void Material::recompileShaders(const MacroRecord& defines, i32 passIdx) { + if (passIdx >= 0 && static_cast(passIdx) < defines_.size()) { + for (const auto& pair : defines) { + defines_[passIdx][pair.first] = pair.second; + } + } else { + for (auto& def : defines_) { + for (const auto& pair : defines) { + def[pair.first] = pair.second; + } + } + } + updateHash(); +} + +void Material::setEffectName(const std::string& name) { + effectName_ = name; +} + +const std::string& Material::getEffectName() const { + return effectName_; +} + +void Material::setTechniqueIndex(u32 index) { + techniqueIndex_ = index; +} + +u32 Material::getTechniqueIndex() const { + return techniqueIndex_; +} + +size_t Material::getHash() const { + return hash_; +} + +void Material::copy(const Material* other) { + if (!other) return; + + effectName_ = other->effectName_; + techniqueIndex_ = other->techniqueIndex_; + defines_ = other->defines_; + states_ = other->states_; + properties_ = other->properties_; + + passes_.clear(); + for (const auto& pass : other->passes_) { + Ref newPass = new Pass(); + newPass->initialize(pass->getShader()); + newPass->setBlendState(pass->getBlendState()); + newPass->setDepthStencilState(pass->getDepthStencilState()); + newPass->setRasterizerState(pass->getRasterizerState()); + passes_.push_back(newPass); + } + + updateHash(); +} + +void Material::resetUniforms() { + properties_.clear(); + for (auto& pass : passes_) { + pass->setUniform("u_color", MaterialPropertyVariant(Color::White)); + } +} + +void Material::updateHash() { + std::hash stringHash; + std::hash intHash; + + hash_ = stringHash(effectName_); + hash_ ^= intHash(techniqueIndex_) + 0x9e3779b9 + (hash_ << 6) + (hash_ >> 2); + + for (const auto& pass : passes_) { + hash_ ^= pass->getHash() + 0x9e3779b9 + (hash_ << 6) + (hash_ >> 2); + } +} + +} // namespace extra2d diff --git a/Extra2D/src/core/assets/Texture2D.cpp b/Extra2D/src/core/assets/Texture2D.cpp new file mode 100644 index 0000000..c711fc2 --- /dev/null +++ b/Extra2D/src/core/assets/Texture2D.cpp @@ -0,0 +1,184 @@ +#include +#include + +namespace extra2d { + +Texture2D::Texture2D() : Asset("Texture2D") { +} + +Texture2D::~Texture2D() { + if (gfxTexture_) { + gfxTexture_->release(); + gfxTexture_ = nullptr; + } +} + +bool Texture2D::initialize(gfx::Device* device, const Texture2DCreateInfo& info) { + device_ = device; + width_ = info.width; + height_ = info.height; + format_ = info.format; + mipmapLevel_ = info.mipmapLevel; + + createGFXTexture(); + + return gfxTexture_ != nullptr; +} + +void Texture2D::reset(const Texture2DCreateInfo& info) { + width_ = info.width; + height_ = info.height; + format_ = info.format; + mipmapLevel_ = info.mipmapLevel; + + if (gfxTexture_) { + gfxTexture_->resize(width_, height_); + } else { + createGFXTexture(); + } +} + +const std::vector>& Texture2D::getMipmaps() const { + return mipmaps_; +} + +void Texture2D::setMipmaps(const std::vector>& mipmaps) { + mipmaps_ = mipmaps; +} + +ImageAsset* Texture2D::getImage() const { + if (mipmaps_.empty()) return nullptr; + return mipmaps_[0].get(); +} + +void Texture2D::setImage(ImageAsset* image) { + if (mipmaps_.empty()) { + mipmaps_.push_back(Ref(image)); + } else { + mipmaps_[0] = Ref(image); + } + + if (image) { + width_ = image->getWidth(); + height_ = image->getHeight(); + format_ = image->getFormat(); + } +} + +u32 Texture2D::getWidth() const { + return width_; +} + +u32 Texture2D::getHeight() const { + return height_; +} + +PixelFormat Texture2D::getFormat() const { + return format_; +} + +u32 Texture2D::getMipmapLevel() const { + return mipmapLevel_; +} + +Filter Texture2D::getMinFilter() const { + return minFilter_; +} + +void Texture2D::setMinFilter(Filter filter) { + minFilter_ = filter; +} + +Filter Texture2D::getMagFilter() const { + return magFilter_; +} + +void Texture2D::setMagFilter(Filter filter) { + magFilter_ = filter; +} + +WrapMode Texture2D::getWrapModeS() const { + return wrapModeS_; +} + +void Texture2D::setWrapModeS(WrapMode mode) { + wrapModeS_ = mode; +} + +WrapMode Texture2D::getWrapModeT() const { + return wrapModeT_; +} + +void Texture2D::setWrapModeT(WrapMode mode) { + wrapModeT_ = mode; +} + +gfx::Texture* Texture2D::getGFXTexture() const { + return gfxTexture_; +} + +void Texture2D::updateImage() { + if (!gfxTexture_ || mipmaps_.empty()) return; + + auto* image = mipmaps_[0].get(); + if (!image || !image->getData()) return; + + uploadData(image->getData(), 0); +} + +void Texture2D::uploadData(const u8* data, u32 level) { + if (!gfxTexture_ || !data) return; + + gfx::BufferTextureCopy region; + region.texExtentX = width_; + region.texExtentY = height_; + region.texExtentZ = 1; + region.texBaseLevel = level; + region.texLevelCount = 1; + + device_->copyBuffersToTexture(&data, gfxTexture_, ®ion, 1); +} + +void Texture2D::onLoaded() { + Asset::onLoaded(); + + if (!gfxTexture_ && width_ > 0 && height_ > 0) { + createGFXTexture(); + } + + updateImage(); +} + +bool Texture2D::validate() const { + return gfxTexture_ != nullptr && width_ > 0 && height_ > 0; +} + +void Texture2D::onDestroy() { + mipmaps_.clear(); + + if (gfxTexture_) { + gfxTexture_->release(); + gfxTexture_ = nullptr; + } + + Asset::onDestroy(); +} + +void Texture2D::createGFXTexture() { + if (!device_ || width_ == 0 || height_ == 0) return; + + gfx::TextureInfo info; + info.type = gfx::TextureType::Tex2D; + info.usage = gfx::TextureUsage::Sampled | gfx::TextureUsage::TransferDst; + info.width = width_; + info.height = height_; + info.mipLevels = mipmapLevel_; + + ImageAsset tempImage; + tempImage.setFormat(format_); + info.format = tempImage.getGFXFormat(); + + gfxTexture_ = device_->createTexture(info); +} + +} // namespace extra2d diff --git a/Extra2D/src/core/event/EventBus.cpp b/Extra2D/src/core/event/EventBus.cpp new file mode 100644 index 0000000..f67e183 --- /dev/null +++ b/Extra2D/src/core/event/EventBus.cpp @@ -0,0 +1,29 @@ +#include + +namespace extra2d { + +EventBus* EventBus::instance_ = nullptr; + +EventBus::EventBus() {} + +EventBus::~EventBus() { + clear(); +} + +EventBus* EventBus::getInstance() { + if (!instance_) { + instance_ = new EventBus(); + } + return instance_; +} + +void EventBus::clear() { + for (auto& pair : listeners_) { + for (auto& entry : pair.second) { + delete entry.listener; + } + } + listeners_.clear(); +} + +} // namespace extra2d diff --git a/Extra2D/src/core/event/EventTarget.cpp b/Extra2D/src/core/event/EventTarget.cpp new file mode 100644 index 0000000..be1b176 --- /dev/null +++ b/Extra2D/src/core/event/EventTarget.cpp @@ -0,0 +1,15 @@ +#include + +namespace extra2d { + +EventTarget::EventTarget() {} + +EventTarget::~EventTarget() { + offAll(); +} + +void EventTarget::offAll() { + listeners_.clear(); +} + +} // namespace extra2d diff --git a/Extra2D/src/core/registry.cpp b/Extra2D/src/core/registry.cpp deleted file mode 100644 index 9ee33d2..0000000 --- a/Extra2D/src/core/registry.cpp +++ /dev/null @@ -1,223 +0,0 @@ -#include -#include -#include -#include - -namespace extra2d { - -Registry &Registry::instance() { - static Registry instance; - return instance; -} - -bool Registry::init() { - auto levels = group(); - E2D_REGISTRY("正在初始化 {} 个模块,共 {} 个层级...", moduleCount_, - levels.size()); - - for (size_t level = 0; level < levels.size(); ++level) { - auto &modules = levels[level]; - - // 检查当前层级是否有支持并行初始化的模块 - bool hasParallelModules = false; - for (auto *module : modules) { - if (module->parallel()) { - hasParallelModules = true; - break; - } - } - - // 如果只有一个模块或不支持并行,使用串行初始化 - if (modules.size() <= 1 || !hasParallelModules) { - for (auto *module : modules) { - E2D_REGISTRY("正在初始化模块: {} (层级 {})", module->name(), level); - - if (!module->init()) { - E2D_ERROR("初始化模块失败: {}", module->name()); - return false; - } - - E2D_REGISTRY("模块 {} 初始化成功", module->name()); - } - } else { - // 并行初始化当前层级的模块 - E2D_REGISTRY("正在并行初始化 {} 个模块 (层级 {})...", modules.size(), - level); - - std::vector>> futures; - std::vector serialModules; - - // 分离支持并行和不支持并行的模块 - for (auto *module : modules) { - if (module->parallel()) { - futures.push_back(std::async(std::launch::async, [module]() { - return std::make_pair(module, module->init()); - })); - } else { - serialModules.push_back(module); - } - } - - // 等待并行模块完成 - for (auto &future : futures) { - auto [module, success] = future.get(); - if (!success) { - E2D_ERROR("初始化模块失败: {}", module->name()); - return false; - } - E2D_REGISTRY("模块 {} 初始化成功 (并行)", module->name()); - } - - // 串行初始化不支持并行的模块 - for (auto *module : serialModules) { - E2D_REGISTRY("正在初始化模块: {} (串行, 层级 {})", module->name(), - level); - if (!module->init()) { - E2D_ERROR("初始化模块失败: {}", module->name()); - return false; - } - E2D_REGISTRY("模块 {} 初始化成功", module->name()); - } - } - - E2D_REGISTRY("层级 {} 初始化完成", level); - } - - E2D_REGISTRY("所有模块初始化完成"); - return true; -} - -void Registry::shutdown() { - // 从后向前关闭模块 - for (size_t i = moduleCount_; i > 0; --i) { - if (modules_[i - 1].valid && modules_[i - 1].module) { - modules_[i - 1].module->shutdown(); - } - } -} - -void Registry::clear() { - shutdown(); - - // 销毁所有模块 - for (size_t i = 0; i < moduleCount_; ++i) { - modules_[i].module.reset(); - modules_[i].valid = false; - } - moduleCount_ = 0; -} - -std::vector Registry::sort() { - std::vector result; - std::vector inDegree(moduleCount_, 0); - std::vector> adj(moduleCount_); - - // 构建依赖图 - for (size_t i = 0; i < moduleCount_; ++i) { - if (!modules_[i].valid) - continue; - - auto deps = modules_[i].module->deps(); - for (auto &depType : deps) { - // 查找依赖模块的索引 - for (size_t j = 0; j < moduleCount_; ++j) { - if (modules_[j].valid && - std::type_index(typeid(*modules_[j].module)) == depType) { - adj[j].push_back(i); - inDegree[i]++; - break; - } - } - } - } - - // 使用优先队列,按优先级排序 - auto cmp = [this](size_t a, size_t b) { - return modules_[a].module->priority() > modules_[b].module->priority(); - }; - std::priority_queue, decltype(cmp)> pq(cmp); - - for (size_t i = 0; i < moduleCount_; ++i) { - if (inDegree[i] == 0) { - pq.push(i); - } - } - - while (!pq.empty()) { - size_t curr = pq.top(); - pq.pop(); - result.push_back(modules_[curr].module.get()); - - for (size_t next : adj[curr]) { - inDegree[next]--; - if (inDegree[next] == 0) { - pq.push(next); - } - } - } - - return result; -} - -std::vector> Registry::group() { - std::vector> levels; - std::vector inDegree(moduleCount_, 0); - std::vector> adj(moduleCount_); - - // 构建依赖图 - for (size_t i = 0; i < moduleCount_; ++i) { - if (!modules_[i].valid) - continue; - - auto deps = modules_[i].module->deps(); - for (auto &depType : deps) { - for (size_t j = 0; j < moduleCount_; ++j) { - if (modules_[j].valid && - std::type_index(typeid(*modules_[j].module)) == depType) { - adj[j].push_back(i); - inDegree[i]++; - break; - } - } - } - } - - // 使用 BFS 按层级分组 - std::queue q; - std::vector levelMap(moduleCount_, -1); - - // 找到所有入度为 0 的模块(第一层) - for (size_t i = 0; i < moduleCount_; ++i) { - if (inDegree[i] == 0) { - q.push(i); - levelMap[i] = 0; - } - } - - // BFS 遍历 - while (!q.empty()) { - size_t curr = q.front(); - q.pop(); - - int currLevel = levelMap[curr]; - - // 确保当前层级存在 - if (levels.size() <= static_cast(currLevel)) { - levels.resize(currLevel + 1); - } - levels[currLevel].push_back(modules_[curr].module.get()); - - // 处理依赖当前模块的其他模块 - for (size_t next : adj[curr]) { - inDegree[next]--; - if (inDegree[next] == 0) { - q.push(next); - levelMap[next] = currLevel + 1; - } - } - } - - return levels; -} - -} // namespace extra2d diff --git a/Extra2D/src/core/scene-graph/Camera.cpp b/Extra2D/src/core/scene-graph/Camera.cpp new file mode 100644 index 0000000..98bf35d --- /dev/null +++ b/Extra2D/src/core/scene-graph/Camera.cpp @@ -0,0 +1,275 @@ +#include +#include +#include +#include +#include +#include + +namespace extra2d { + +u32 Camera::s_cameraIdCounter = 0; + +Ref Camera::create(gfx::Device* device) { + Ref camera = new Camera(device); + return camera; +} + +Camera::Camera(gfx::Device* device) + : device_(device) + , cameraId_(s_cameraIdCounter++) { +} + +Camera::~Camera() { + destroy(); +} + +bool Camera::initialize(const CameraInfo& info) { + name_ = info.name; + node_ = info.node; + projection_ = info.projection; + priority_ = info.priority; + + isProjDirty_ = true; + isViewDirty_ = true; + + return true; +} + +void Camera::destroy() { + detachFromScene(); + node_ = nullptr; + device_ = nullptr; +} + +void Camera::update(bool forceUpdate) { + if (isProjDirty_ || forceUpdate) { + updateProjection(); + isProjDirty_ = false; + } + + if (isViewDirty_ || forceUpdate) { + updateView(); + isViewDirty_ = false; + } + + matViewProj_ = matProj_ * matView_; +} + +void Camera::resize(u32 width, u32 height) { + width_ = width; + height_ = height; + + if (height > 0) { + aspect_ = static_cast(width) / static_cast(height); + } + + isProjDirty_ = true; +} + +void Camera::updateProjection() { + if (projection_ == CameraProjection::Ortho) { + float halfHeight = orthoHeight_ * 0.5f; + float halfWidth = halfHeight * aspect_; + + matProj_ = glm::ortho(-halfWidth, halfWidth, -halfHeight, halfHeight, nearClip_, farClip_); + } else { + matProj_ = glm::perspective(fov_, aspect_, nearClip_, farClip_); + } +} + +void Camera::updateView() { + glm::vec3 pos(position_.x, position_.y, 0.0f); + glm::vec3 target(position_.x + forward_.x, position_.y + forward_.y, 0.0f); + glm::vec3 up(0.0f, 1.0f, 0.0f); + + matView_ = glm::lookAt(pos, target, up); +} + +Vec2 Camera::screenToWorld(const Vec2& screenPos) { + glm::vec4 viewport(0, 0, static_cast(width_), static_cast(height_)); + + float winX = screenPos.x; + float winY = height_ - screenPos.y; + + glm::vec3 worldPos = glm::unProject( + glm::vec3(winX, winY, 0.0f), + matView_, + matProj_, + viewport); + + return Vec2(worldPos.x, worldPos.y); +} + +Vec2 Camera::worldToScreen(const Vec2& worldPos) { + glm::vec4 viewport(0, 0, static_cast(width_), static_cast(height_)); + + glm::vec3 screenPos = glm::project( + glm::vec3(worldPos.x, worldPos.y, 0.0f), + matView_, + matProj_, + viewport); + + return Vec2(screenPos.x, height_ - screenPos.y); +} + +Vec2 Camera::screenPointToRay(float x, float y) { + return screenToWorld(Vec2(x, y)); +} + +void Camera::setNode(Node* node) { + node_ = node; + isViewDirty_ = true; +} + +Node* Camera::getNode() const { + return node_; +} + +void Camera::setEnabled(bool enabled) { + enabled_ = enabled; +} + +bool Camera::isEnabled() const { + return enabled_; +} + +void Camera::setOrthoHeight(float height) { + orthoHeight_ = height; + isProjDirty_ = true; +} + +float Camera::getOrthoHeight() const { + return orthoHeight_; +} + +void Camera::setProjectionType(CameraProjection projection) { + projection_ = projection; + isProjDirty_ = true; +} + +CameraProjection Camera::getProjectionType() const { + return projection_; +} + +void Camera::setFov(float fov) { + fov_ = fov; + isProjDirty_ = true; +} + +float Camera::getFov() const { + return fov_; +} + +void Camera::setNearClip(float near) { + nearClip_ = near; + isProjDirty_ = true; +} + +float Camera::getNearClip() const { + return nearClip_; +} + +void Camera::setFarClip(float far) { + farClip_ = far; + isProjDirty_ = true; +} + +float Camera::getFarClip() const { + return farClip_; +} + +void Camera::setClearColor(const gfx::Color& color) { + clearColor_ = color; +} + +const gfx::Color& Camera::getClearColor() const { + return clearColor_; +} + +void Camera::setClearFlag(gfx::ClearFlagBit flag) { + clearFlag_ = flag; +} + +gfx::ClearFlagBit Camera::getClearFlag() const { + return clearFlag_; +} + +void Camera::setPriority(u32 priority) { + priority_ = priority; +} + +u32 Camera::getPriority() const { + return priority_; +} + +u32 Camera::getWidth() const { + return width_; +} + +u32 Camera::getHeight() const { + return height_; +} + +float Camera::getAspect() const { + return aspect_; +} + +const glm::mat4& Camera::getMatView() const { + return matView_; +} + +const glm::mat4& Camera::getMatProj() const { + return matProj_; +} + +const glm::mat4& Camera::getMatViewProj() const { + return matViewProj_; +} + +const std::string& Camera::getName() const { + return name_; +} + +RenderScene* Camera::getScene() const { + return scene_; +} + +void Camera::attachToScene(RenderScene* scene) { + scene_ = scene; +} + +void Camera::detachFromScene() { + scene_ = nullptr; +} + +void Camera::setVisibility(u32 visibility) { + visibility_ = visibility; +} + +u32 Camera::getVisibility() const { + return visibility_; +} + +void Camera::setPosition(const Vec2& pos) { + position_ = pos; + isViewDirty_ = true; +} + +const Vec2& Camera::getPosition() const { + return position_; +} + +void Camera::setForward(const Vec2& forward) { + forward_ = forward; + isViewDirty_ = true; +} + +const Vec2& Camera::getForward() const { + return forward_; +} + +u32 Camera::getCameraId() const { + return cameraId_; +} + +} // namespace extra2d diff --git a/Extra2D/src/core/scene-graph/Component.cpp b/Extra2D/src/core/scene-graph/Component.cpp new file mode 100644 index 0000000..d27920b --- /dev/null +++ b/Extra2D/src/core/scene-graph/Component.cpp @@ -0,0 +1,77 @@ +#include +#include + +namespace extra2d { + +Component::Component(const std::string& name) : Object(name) { +} + +Component::~Component() { +} + +Node* Component::getNode() const { + return node_; +} + +void Component::setNode(Node* node) { + node_ = node; +} + +bool Component::isEnabled() const { + return enabled_; +} + +void Component::setEnabled(bool enabled) { + if (enabled_ != enabled) { + enabled_ = enabled; + + if (node_ && node_->isActiveInHierarchy()) { + if (enabled) { + onEnable(); + } else { + onDisable(); + } + } + } +} + +void Component::onAdd() { +} + +void Component::onRemove() { +} + +void Component::onEnable() { +} + +void Component::onDisable() { +} + +void Component::update(f32 dt) { + (void)dt; +} + +void Component::lateUpdate(f32 dt) { + (void)dt; +} + +void Component::render() { +} + +bool Component::destroy() { + if (isDestroyed()) { + return false; + } + + if (node_) { + node_->removeComponent(this); + } + + return Object::destroy(); +} + +void Component::onDestroy() { + Object::onDestroy(); +} + +} // namespace extra2d diff --git a/Extra2D/src/core/scene-graph/Layers.cpp b/Extra2D/src/core/scene-graph/Layers.cpp new file mode 100644 index 0000000..b99eb2d --- /dev/null +++ b/Extra2D/src/core/scene-graph/Layers.cpp @@ -0,0 +1,71 @@ +#include + +namespace extra2d { + +std::unordered_map Layers::s_nameToLayer = { + {"None", static_cast(LayerList::None)}, + {"IgnoreRaycast", static_cast(LayerList::IgnoreRaycast)}, + {"Gizmos", static_cast(LayerList::Gizmos)}, + {"Editor", static_cast(LayerList::Editor)}, + {"UI3D", static_cast(LayerList::UI3D)}, + {"SceneGizmo", static_cast(LayerList::SceneGizmo)}, + {"UI2D", static_cast(LayerList::UI2D)}, + {"Profiler", static_cast(LayerList::Profiler)}, + {"Default", static_cast(LayerList::Default)}, + {"All", static_cast(LayerList::All)}, +}; + +std::unordered_map Layers::s_layerToName = { + {static_cast(LayerList::None), "None"}, + {static_cast(LayerList::IgnoreRaycast), "IgnoreRaycast"}, + {static_cast(LayerList::Gizmos), "Gizmos"}, + {static_cast(LayerList::Editor), "Editor"}, + {static_cast(LayerList::UI3D), "UI3D"}, + {static_cast(LayerList::SceneGizmo), "SceneGizmo"}, + {static_cast(LayerList::UI2D), "UI2D"}, + {static_cast(LayerList::Profiler), "Profiler"}, + {static_cast(LayerList::Default), "Default"}, + {static_cast(LayerList::All), "All"}, +}; + +void Layers::addLayer(const std::string& name, u32 bitNum) { + if (bitNum > 19) { + return; + } + + u32 layerValue = 1 << bitNum; + s_nameToLayer[name] = layerValue; + s_layerToName[layerValue] = name; +} + +void Layers::deleteLayer(u32 bitNum) { + if (bitNum > 19) { + return; + } + + u32 layerValue = 1 << bitNum; + + auto it = s_layerToName.find(layerValue); + if (it != s_layerToName.end()) { + s_nameToLayer.erase(it->second); + s_layerToName.erase(it); + } +} + +u32 Layers::nameToLayer(const std::string& name) { + auto it = s_nameToLayer.find(name); + if (it != s_nameToLayer.end()) { + return it->second; + } + return static_cast(LayerList::None); +} + +std::string Layers::layerToName(u32 bitNum) { + auto it = s_layerToName.find(bitNum); + if (it != s_layerToName.end()) { + return it->second; + } + return ""; +} + +} // namespace extra2d diff --git a/Extra2D/src/core/scene-graph/Model.cpp b/Extra2D/src/core/scene-graph/Model.cpp new file mode 100644 index 0000000..824c0de --- /dev/null +++ b/Extra2D/src/core/scene-graph/Model.cpp @@ -0,0 +1,155 @@ +#include +#include +#include +#include + +namespace extra2d { + +// ==================== SubModel 实现 ==================== + +SubModel::SubModel() { +} + +SubModel::~SubModel() { + destroy(); +} + +bool SubModel::initialize(u32 vertexCount, u32 indexCount) { + (void)vertexCount; + (void)indexCount; + return true; +} + +void SubModel::destroy() { + material_ = nullptr; + inputAssembler_ = nullptr; +} + +void SubModel::setMaterial(Material* material) { + material_ = material; +} + +Material* SubModel::getMaterial() const { + return material_; +} + +void SubModel::setInputAssembler(gfx::InputAssembler* ia) { + inputAssembler_ = ia; +} + +gfx::InputAssembler* SubModel::getInputAssembler() const { + return inputAssembler_; +} + +// ==================== Model 实现 ==================== + +Ref Model::create() { + Ref model = new Model(); + return model; +} + +Model::Model() { +} + +Model::~Model() { + destroy(); +} + +bool Model::initialize(const ModelInfo& info) { + node_ = info.node; + priority_ = info.priority; + return true; +} + +void Model::destroy() { + for (auto* subModel : subModels_) { + delete subModel; + } + subModels_.clear(); + + node_ = nullptr; + scene_ = nullptr; +} + +void Model::update(u32 stamp) { + (void)stamp; +} + +void Model::addSubModel(SubModel* subModel) { + if (subModel) { + subModels_.push_back(subModel); + } +} + +size_t Model::getSubModelCount() const { + return subModels_.size(); +} + +SubModel* Model::getSubModel(size_t index) const { + if (index < subModels_.size()) { + return subModels_[index]; + } + return nullptr; +} + +const std::vector& Model::getSubModels() const { + return subModels_; +} + +void Model::setNode(Node* node) { + node_ = node; +} + +Node* Model::getNode() const { + return node_; +} + +void Model::setPriority(u32 priority) { + priority_ = priority; +} + +u32 Model::getPriority() const { + return priority_; +} + +void Model::setVisible(bool visible) { + visible_ = visible; +} + +bool Model::isVisible() const { + return visible_; +} + +void Model::setWorldMatrix(const Transform2D& matrix) { + worldMatrix_ = matrix; +} + +const Transform2D& Model::getWorldMatrix() const { + return worldMatrix_; +} + +void Model::setAABB(const Rect& aabb) { + aabb_ = aabb; +} + +const Rect& Model::getAABB() const { + return aabb_; +} + +void Model::setScene(RenderScene* scene) { + scene_ = scene; +} + +RenderScene* Model::getScene() const { + return scene_; +} + +u64 Model::getModelId() const { + return modelId_; +} + +void Model::setModelId(u64 id) { + modelId_ = id; +} + +} // namespace extra2d diff --git a/Extra2D/src/core/scene-graph/Node.cpp b/Extra2D/src/core/scene-graph/Node.cpp new file mode 100644 index 0000000..13ac340 --- /dev/null +++ b/Extra2D/src/core/scene-graph/Node.cpp @@ -0,0 +1,526 @@ +#include +#include +#include +#include + +namespace extra2d { + +Ref Node::create() { + return ptr::make(); +} + +Node* Node::find(const std::string& path, Node* reference) { + if (path.empty()) return nullptr; + + Node* current = reference; + + size_t start = 0; + size_t end = path.find('/'); + + while (end != std::string::npos) { + std::string segment = path.substr(start, end - start); + if (segment.empty()) { + start = end + 1; + end = path.find('/', start); + continue; + } + + if (segment == "..") { + if (current) { + current = current->getParent(); + } + } else if (segment != ".") { + if (current) { + current = current->getChildByName(segment); + } else { + return nullptr; + } + } + + if (!current) return nullptr; + + start = end + 1; + end = path.find('/', start); + } + + std::string lastSegment = path.substr(start); + if (!lastSegment.empty() && current) { + if (lastSegment == "..") { + current = current->getParent(); + } else if (lastSegment != ".") { + current = current->getChildByName(lastSegment); + } + } + + return current; +} + +Node::Node(const std::string& name) : Object(name) { +} + +Node::~Node() { + removeAllChildren(); +} + +void Node::setPosition(const Vec2& pos) { + if (localPosition_ != pos) { + localPosition_ = pos; + invalidateTransform(TransformBit::Position); + } +} + +void Node::setPosition(float x, float y) { + setPosition(Vec2{x, y}); +} + +const Vec2& Node::getPosition() const { + return localPosition_; +} + +void Node::setRotation(float degrees) { + if (localRotation_ != degrees) { + localRotation_ = degrees; + invalidateTransform(TransformBit::Rotation); + } +} + +float Node::getRotation() const { + return localRotation_; +} + +void Node::setScale(const Vec2& scale) { + if (localScale_ != scale) { + localScale_ = scale; + invalidateTransform(TransformBit::Scale); + } +} + +void Node::setScale(float sx, float sy) { + setScale(Vec2{sx, sy}); +} + +void Node::setScale(float s) { + setScale(Vec2{s, s}); +} + +const Vec2& Node::getScale() const { + return localScale_; +} + +Vec2 Node::getWorldPosition() { + updateWorldTransform(); + return worldPosition_; +} + +float Node::getWorldRotation() { + updateWorldTransform(); + return worldRotation_; +} + +Vec2 Node::getWorldScale() { + updateWorldTransform(); + return worldScale_; +} + +const Transform2D& Node::getWorldMatrix() { + updateWorldTransform(); + return worldMatrix_; +} + +Vec2 Node::localToWorld(const Vec2& localPoint) { + return getWorldMatrix().transformPoint(localPoint); +} + +Vec2 Node::worldToLocal(const Vec2& worldPoint) { + return getWorldMatrix().inverse().transformPoint(worldPoint); +} + +void Node::addChild(Ref child) { + if (!child || child.get() == this) return; + + if (child->getParent()) { + child->removeFromParent(); + } + + child->setParent(this); + child->siblingIndex_ = static_cast(children_.size()); + children_.push_back(child); + + child->setScene(scene_); + child->updateActiveInHierarchy(); + + onChildAdded(child.get()); +} + +void Node::removeChild(Node* child) { + if (!child || child->getParent() != this) return; + + onChildRemoved(child); + + auto it = std::find_if(children_.begin(), children_.end(), + [child](const Ref& node) { return node.get() == child; }); + + if (it != children_.end()) { + i32 removedIndex = child->siblingIndex_; + child->setParent(nullptr); + child->setScene(nullptr); + children_.erase(it); + + for (size_t i = removedIndex; i < children_.size(); ++i) { + children_[i]->siblingIndex_ = static_cast(i); + } + } +} + +void Node::removeFromParent() { + if (parent_) { + parent_->removeChild(this); + } +} + +void Node::removeAllChildren() { + while (!children_.empty()) { + auto child = children_.back(); + onChildRemoved(child.get()); + child->setParent(nullptr); + child->setScene(nullptr); + children_.pop_back(); + } +} + +Node* Node::getParent() { + return parent_; +} + +const Node* Node::getParent() const { + return parent_; +} + +const std::vector>& Node::getChildren() const { + return children_; +} + +size_t Node::getChildCount() const { + return children_.size(); +} + +void Node::walk(const NodeWalkCallback& preFunc) { + walk(preFunc, nullptr); +} + +void Node::walk(const NodeWalkCallback& preFunc, const NodeWalkCallback& postFunc) { + if (preFunc) { + preFunc(this); + } + + for (auto& child : children_) { + child->walk(preFunc, postFunc); + } + + if (postFunc) { + postFunc(this); + } +} + +Node* Node::getChildByName(const std::string& name) { + for (auto& child : children_) { + if (child->getName() == name) { + return child.get(); + } + } + return nullptr; +} + +Node* Node::getChildByTag(i32 tag) { + for (auto& child : children_) { + if (child->getTag() == tag) { + return child.get(); + } + } + return nullptr; +} + +Node* Node::getChildByPath(const std::string& path) { + return find(path, this); +} + +void Node::addComponent(Ref component) { + if (!component) return; + + component->setNode(this); + components_.push_back(component); + component->onAdd(); +} + +void Node::removeComponent(Component* component) { + if (!component) return; + + auto it = std::find_if(components_.begin(), components_.end(), + [component](const Ref& comp) { return comp.get() == component; }); + + if (it != components_.end()) { + (*it)->onRemove(); + (*it)->setNode(nullptr); + components_.erase(it); + } +} + +void Node::setTag(i32 tag) { + tag_ = tag; +} + +i32 Node::getTag() const { + return tag_; +} + +void Node::setLayer(u32 layer) { + layer_ = layer; +} + +u32 Node::getLayer() const { + return layer_; +} + +void Node::setActive(bool active) { + if (active_ != active) { + active_ = active; + updateActiveInHierarchy(); + onActiveChanged(active); + } +} + +bool Node::isActive() const { + return active_; +} + +bool Node::isActiveInHierarchy() const { + return activeInHierarchy_; +} + +void Node::setVisible(bool visible) { + visible_ = visible; +} + +bool Node::isVisible() const { + return visible_; +} + +void Node::setSiblingIndex(i32 index) { + if (!parent_) return; + + auto& siblings = const_cast>&>(parent_->getChildren()); + i32 oldIndex = siblingIndex_; + + if (index < 0) index = 0; + if (index >= static_cast(siblings.size())) { + index = static_cast(siblings.size()) - 1; + } + + if (oldIndex == index) return; + + auto node = siblings[oldIndex]; + siblings.erase(siblings.begin() + oldIndex); + siblings.insert(siblings.begin() + index, node); + + for (size_t i = 0; i < siblings.size(); ++i) { + siblings[i]->siblingIndex_ = static_cast(i); + } +} + +i32 Node::getSiblingIndex() const { + return siblingIndex_; +} + +void Node::update(f32 dt) { + if (!active_) return; + + for (auto& component : components_) { + if (component->isEnabled()) { + component->update(dt); + } + } + + for (auto& child : children_) { + child->update(dt); + } +} + +void Node::render() { + if (!visible_) return; + + for (auto& component : components_) { + if (component->isEnabled()) { + component->render(); + } + } + + for (auto& child : children_) { + child->render(); + } +} + +void Node::lateUpdate(f32 dt) { + if (!active_) return; + + for (auto& component : components_) { + if (component->isEnabled()) { + component->lateUpdate(dt); + } + } + + for (auto& child : children_) { + child->lateUpdate(dt); + } +} + +void Node::onEnter() { + for (auto& component : components_) { + component->onEnable(); + } +} + +void Node::onExit() { + for (auto& component : components_) { + component->onDisable(); + } +} + +void Node::onActiveChanged(bool active) { + (void)active; +} + +void Node::clearAllEventListeners() { + eventTarget_.offAll(); +} + +Scene* Node::getScene() { + return scene_; +} + +void Node::setScene(Scene* scene) { + scene_ = scene; + for (auto& child : children_) { + child->setScene(scene); + } +} + +bool Node::destroy() { + if (isDestroyed()) { + return false; + } + + removeFromParent(); + removeAllChildren(); + + for (auto& component : components_) { + component->onRemove(); + component->destroy(); + } + components_.clear(); + + return Object::destroy(); +} + +void Node::onDestroy() { + clearAllEventListeners(); + Object::onDestroy(); +} + +void Node::updateWorldTransform() { + if (transformFlags_ == 0) { + return; + } + + if (parent_) { + parent_->updateWorldTransform(); + } + + if (hasTransformBit(static_cast(transformFlags_), TransformBit::Position)) { + if (parent_) { + worldPosition_ = parent_->worldMatrix_.transformPoint(localPosition_); + } else { + worldPosition_ = localPosition_; + } + } + + if (hasTransformBit(static_cast(transformFlags_), TransformBit::Rotation)) { + worldRotation_ = localRotation_; + if (parent_) { + worldRotation_ += parent_->worldRotation_; + } + } + + if (hasTransformBit(static_cast(transformFlags_), TransformBit::Scale)) { + if (parent_) { + worldScale_ = Vec2{ + localScale_.x * parent_->worldScale_.x, + localScale_.y * parent_->worldScale_.y + }; + } else { + worldScale_ = localScale_; + } + } + + Transform2D localTransform = Transform2D::identity(); + localTransform = Transform2D::translation(localPosition_.x, localPosition_.y); + localTransform = localTransform * Transform2D::rotation(localRotation_); + localTransform = localTransform * Transform2D::scaling(localScale_.x, localScale_.y); + + if (parent_) { + worldMatrix_ = parent_->worldMatrix_ * localTransform; + } else { + worldMatrix_ = localTransform; + } + + transformFlags_ = 0; +} + +void Node::invalidateTransform(TransformBit dirtyBit) { + u32 oldFlags = transformFlags_; + transformFlags_ |= static_cast(dirtyBit); + + if (oldFlags == transformFlags_) { + return; + } + + for (auto& child : children_) { + child->invalidateTransform(dirtyBit); + } +} + +void Node::setParent(Node* parent) { + parent_ = parent; +} + +void Node::onSetParent(Node* oldParent) { + (void)oldParent; +} + +void Node::onChildAdded(Node* child) { + (void)child; +} + +void Node::onChildRemoved(Node* child) { + (void)child; +} + +void Node::updateActiveInHierarchy() { + bool newActiveInHierarchy = active_ && + (!parent_ || parent_->isActiveInHierarchy()); + + if (activeInHierarchy_ != newActiveInHierarchy) { + activeInHierarchy_ = newActiveInHierarchy; + + if (activeInHierarchy_) { + onEnter(); + } else { + onExit(); + } + + for (auto& child : children_) { + child->updateActiveInHierarchy(); + } + } +} + +} // namespace extra2d diff --git a/Extra2D/src/core/scene-graph/RenderScene.cpp b/Extra2D/src/core/scene-graph/RenderScene.cpp new file mode 100644 index 0000000..8688200 --- /dev/null +++ b/Extra2D/src/core/scene-graph/RenderScene.cpp @@ -0,0 +1,175 @@ +#include +#include +#include +#include +#include + +namespace extra2d { + +Ref RenderScene::create() { + Ref scene = new RenderScene(); + return scene; +} + +RenderScene::RenderScene() { +} + +RenderScene::~RenderScene() { + destroy(); +} + +bool RenderScene::initialize(const RenderSceneInfo& info) { + name_ = info.name; + return true; +} + +void RenderScene::destroy() { + removeBatches(); + removeModels(); + removeCameras(); + + mainCamera_ = nullptr; + rootNode_ = nullptr; +} + +void RenderScene::activate() { +} + +void RenderScene::update(u32 stamp) { + for (auto& camera : cameras_) { + if (camera && camera->isEnabled()) { + camera->update(); + } + } +} + +void RenderScene::addCamera(Camera* camera) { + if (!camera) return; + + auto it = std::find_if(cameras_.begin(), cameras_.end(), + [camera](const Ref& c) { return c.get() == camera; }); + + if (it == cameras_.end()) { + cameras_.push_back(camera); + camera->attachToScene(this); + + if (!mainCamera_) { + mainCamera_ = camera; + } + } +} + +void RenderScene::removeCamera(Camera* camera) { + if (!camera) return; + + auto it = std::find_if(cameras_.begin(), cameras_.end(), + [camera](const Ref& c) { return c.get() == camera; }); + + if (it != cameras_.end()) { + (*it)->detachFromScene(); + cameras_.erase(it); + + if (mainCamera_ == camera) { + mainCamera_ = cameras_.empty() ? nullptr : cameras_.front().get(); + } + } +} + +void RenderScene::removeCameras() { + for (auto& camera : cameras_) { + if (camera) { + camera->detachFromScene(); + } + } + cameras_.clear(); + mainCamera_ = nullptr; +} + +const std::vector>& RenderScene::getCameras() const { + return cameras_; +} + +Camera* RenderScene::getMainCamera() const { + return mainCamera_; +} + +void RenderScene::setMainCamera(Camera* camera) { + mainCamera_ = camera; +} + +void RenderScene::addModel(Model* model) { + if (!model) return; + + auto it = std::find_if(models_.begin(), models_.end(), + [model](const Ref& m) { return m.get() == model; }); + + if (it == models_.end()) { + models_.push_back(model); + } +} + +void RenderScene::removeModel(Model* model) { + if (!model) return; + + auto it = std::find_if(models_.begin(), models_.end(), + [model](const Ref& m) { return m.get() == model; }); + + if (it != models_.end()) { + models_.erase(it); + } +} + +void RenderScene::removeModels() { + models_.clear(); +} + +const std::vector>& RenderScene::getModels() const { + return models_; +} + +u64 RenderScene::generateModelId() { + return modelId_++; +} + +void RenderScene::addBatch(DrawBatch2D* batch) { + if (!batch) return; + + auto it = std::find(batches_.begin(), batches_.end(), batch); + if (it == batches_.end()) { + batches_.push_back(batch); + } +} + +void RenderScene::removeBatch(DrawBatch2D* batch) { + if (!batch) return; + + auto it = std::find(batches_.begin(), batches_.end(), batch); + if (it != batches_.end()) { + batches_.erase(it); + } +} + +void RenderScene::removeBatches() { + batches_.clear(); +} + +const std::vector& RenderScene::getBatches() const { + return batches_; +} + +const std::string& RenderScene::getName() const { + return name_; +} + +void RenderScene::setRootNode(Node* node) { + rootNode_ = node; +} + +Node* RenderScene::getRootNode() const { + return rootNode_; +} + +void RenderScene::onGlobalPipelineStateChanged() { +} + +} // namespace extra2d diff --git a/Extra2D/src/core/scene-graph/Scene.cpp b/Extra2D/src/core/scene-graph/Scene.cpp new file mode 100644 index 0000000..a195417 --- /dev/null +++ b/Extra2D/src/core/scene-graph/Scene.cpp @@ -0,0 +1,102 @@ +#include + +namespace extra2d { + +Ref Scene::create(const std::string& name) { + return ptr::make(name); +} + +Scene::Scene(const std::string& name) : Object(name) { + root_ = Node::create(); + root_->setName("Root"); + root_->setScene(this); +} + +Scene::~Scene() { + if (root_) { + root_->destroy(); + root_.reset(); + } +} + +Node* Scene::getRoot() { + return root_.get(); +} + +const Node* Scene::getRoot() const { + return root_.get(); +} + +Node* Scene::getNodeByName(const std::string& name) { + if (!root_) return nullptr; + + if (root_->getName() == name) { + return root_.get(); + } + + return root_->getChildByName(name); +} + +Node* Scene::getNodeByTag(i32 tag) { + if (!root_) return nullptr; + + if (root_->getTag() == tag) { + return root_.get(); + } + + return root_->getChildByTag(tag); +} + +Node* Scene::getNodeByPath(const std::string& path) { + if (!root_) return nullptr; + return Node::find(path, root_.get()); +} + +void Scene::update(f32 dt) { + if (root_) { + root_->update(dt); + } +} + +void Scene::render() { + if (root_) { + root_->render(); + } +} + +void Scene::lateUpdate(f32 dt) { + if (root_) { + root_->lateUpdate(dt); + } +} + +void Scene::onActivate() { + if (root_) { + root_->onEnter(); + } +} + +void Scene::onDeactivate() { + if (root_) { + root_->onExit(); + } +} + +bool Scene::destroy() { + if (isDestroyed()) { + return false; + } + + if (root_) { + root_->destroy(); + root_.reset(); + } + + return Object::destroy(); +} + +void Scene::onDestroy() { + Object::onDestroy(); +} + +} // namespace extra2d diff --git a/Extra2D/src/core/service_locator.cpp b/Extra2D/src/core/service_locator.cpp deleted file mode 100644 index 94ca13c..0000000 --- a/Extra2D/src/core/service_locator.cpp +++ /dev/null @@ -1,110 +0,0 @@ -#include -#include - -namespace extra2d { - -ServiceLocator& ServiceLocator::instance() { - static ServiceLocator instance; - return instance; -} - -bool ServiceLocator::init() { - std::shared_lock lock(mutex_); - - for (auto& svc : orderedServices_) { - if (!svc) continue; - - auto info = svc->info(); - if (!info.enabled) continue; - - if (!svc->initialized()) { - svc->setState(ServiceState::Initializing); - if (!svc->init()) { - svc->setState(ServiceState::Stopped); - return false; - } - svc->setState(ServiceState::Running); - } - } - - return true; -} - -void ServiceLocator::shutdown() { - std::shared_lock lock(mutex_); - - for (auto it = orderedServices_.rbegin(); - it != orderedServices_.rend(); ++it) { - if (*it && (*it)->initialized()) { - (*it)->setState(ServiceState::Stopping); - (*it)->shutdown(); - (*it)->setState(ServiceState::Stopped); - } - } -} - -void ServiceLocator::update(f32 dt) { - std::shared_lock lock(mutex_); - - for (auto& svc : orderedServices_) { - if (svc && svc->initialized()) { - auto state = svc->state(); - if (state == ServiceState::Running) { - svc->update(dt); - } - } - } -} - -void ServiceLocator::pause() { - std::shared_lock lock(mutex_); - - for (auto& svc : orderedServices_) { - if (svc && svc->initialized()) { - svc->pause(); - } - } -} - -void ServiceLocator::resume() { - std::shared_lock lock(mutex_); - - for (auto& svc : orderedServices_) { - if (svc && svc->initialized()) { - svc->resume(); - } - } -} - -std::vector> ServiceLocator::all() const { - std::shared_lock lock(mutex_); - return orderedServices_; -} - -void ServiceLocator::clear() { - std::unique_lock lock(mutex_); - - for (auto it = orderedServices_.rbegin(); - it != orderedServices_.rend(); ++it) { - if (*it && (*it)->initialized()) { - (*it)->setState(ServiceState::Stopping); - (*it)->shutdown(); - (*it)->setState(ServiceState::Stopped); - } - } - - services_.clear(); - factories_.clear(); - orderedServices_.clear(); -} - -void ServiceLocator::sort() { - std::stable_sort(orderedServices_.begin(), orderedServices_.end(), - [](const Ref& a, const Ref& b) { - if (!a || !b) return false; - return static_cast(a->info().priority) < - static_cast(b->info().priority); - }); -} - -} diff --git a/Extra2D/src/core/service_registry.cpp b/Extra2D/src/core/service_registry.cpp deleted file mode 100644 index f238a76..0000000 --- a/Extra2D/src/core/service_registry.cpp +++ /dev/null @@ -1,37 +0,0 @@ -#include - -namespace extra2d { - -ServiceRegistry& ServiceRegistry::instance() { - static ServiceRegistry instance; - return instance; -} - -void ServiceRegistry::setEnabled(const std::string& name, bool enabled) { - for (auto& reg : registrations_) { - if (reg.name == name) { - reg.enabled = enabled; - break; - } - } -} - -void ServiceRegistry::createAll() { - std::sort(registrations_.begin(), registrations_.end(), - [](const ServiceRegistration& a, const ServiceRegistration& b) { - return static_cast(a.priority) < static_cast(b.priority); - }); - - for (const auto& reg : registrations_) { - if (!reg.enabled) { - continue; - } - - auto service = reg.factory(); - if (service) { - ServiceLocator::instance().add(service); - } - } -} - -} diff --git a/Extra2D/src/engine/Engine.cpp b/Extra2D/src/engine/Engine.cpp new file mode 100644 index 0000000..6a9817d --- /dev/null +++ b/Extra2D/src/engine/Engine.cpp @@ -0,0 +1,168 @@ +#include +#include +#include + +namespace extra2d { + +Engine* Engine::instance_ = nullptr; + +Engine::Engine() + : scheduler_(ptr::makeUnique()) + , eventBus_(nullptr) + , lastFrameTime_(std::chrono::high_resolution_clock::now()) + , startTime_(std::chrono::high_resolution_clock::now()) { +} + +Engine::~Engine() { + close(); +} + +Engine* Engine::getInstance() { + if (!instance_) { + instance_ = new Engine(); + } + return instance_; +} + +i32 Engine::init() { + if (inited_) { + return 0; + } + + eventBus_ = EventBus::getInstance(); + + startTime_ = std::chrono::high_resolution_clock::now(); + lastFrameTime_ = startTime_; + + inited_ = true; + return 0; +} + +void Engine::close() { + if (!inited_) { + return; + } + + scheduler_.reset(); + eventBus_ = nullptr; + + inited_ = false; +} + +void Engine::tick() { + if (!inited_ || paused_) { + return; + } + + f32 dt = calculateDeltaTime(); + + frameStartTime_ = totalTime_; + totalTime_ += dt; + + if (scheduler_) { + scheduler_->update(dt); + } + + AutoreleasePool::getInstance()->clear(); + + totalFrames_++; +} + +void Engine::pause() { + if (paused_) return; + + paused_ = true; + + if (eventBus_) { + AppEvent event; + event.type = AppEventType::WillEnterBackground; + eventBus_->emit(event); + + event.type = AppEventType::DidEnterBackground; + eventBus_->emit(event); + } +} + +void Engine::resume() { + if (!paused_) return; + + lastFrameTime_ = std::chrono::high_resolution_clock::now(); + paused_ = false; + + if (eventBus_) { + AppEvent event; + event.type = AppEventType::WillEnterForeground; + eventBus_->emit(event); + + event.type = AppEventType::DidEnterForeground; + eventBus_->emit(event); + } +} + +bool Engine::isInited() const { + return inited_; +} + +bool Engine::isPaused() const { + return paused_; +} + +void Engine::setPreferredFramesPerSecond(i32 fps) { + if (fps <= 0) { + fps = 60; + } + preferredNanosecondsPerFrame_ = static_cast(1000000000LL / fps); +} + +i32 Engine::getPreferredFramesPerSecond() const { + return static_cast(1000000000LL / preferredNanosecondsPerFrame_); +} + +u32 Engine::getTotalFrames() const { + return totalFrames_; +} + +f32 Engine::getDeltaTime() const { + return deltaTime_; +} + +f32 Engine::getTotalTime() const { + return totalTime_; +} + +f32 Engine::getFrameStartTime() const { + return frameStartTime_; +} + +Scheduler* Engine::getScheduler() { + return scheduler_.get(); +} + +EventBus* Engine::getEventBus() { + return eventBus_; +} + +void Engine::setTimeScale(f32 scale) { + if (scheduler_) { + scheduler_->setTimeScale(scale); + } +} + +f32 Engine::getTimeScale() const { + if (scheduler_) { + return scheduler_->getTimeScale(); + } + return 1.0f; +} + +f32 Engine::calculateDeltaTime() { + auto now = std::chrono::high_resolution_clock::now(); + auto duration = std::chrono::duration_cast(now - lastFrameTime_); + lastFrameTime_ = now; + + f32 dt = static_cast(duration.count()) / 1000000000.0f; + + return dt; +} + +} // namespace extra2d diff --git a/Extra2D/src/event/event.cpp b/Extra2D/src/event/event.cpp deleted file mode 100644 index 9908f3d..0000000 --- a/Extra2D/src/event/event.cpp +++ /dev/null @@ -1,60 +0,0 @@ -#include - -namespace extra2d { - -Event Event::windowResize(i32 w, i32 h) { - Event event; - event.type = EventType::WindowResize; - event.data = WindowResizeEvent{w, h}; - return event; -} - -Event Event::windowClose() { - Event event; - event.type = EventType::WindowClose; - return event; -} - -Event Event::keyPress(i32 key, i32 scancode, i32 mods) { - Event event; - event.type = EventType::KeyPressed; - event.data = KeyEvent{key, scancode, mods}; - return event; -} - -Event Event::keyRelease(i32 key, i32 scancode, i32 mods) { - Event event; - event.type = EventType::KeyReleased; - event.data = KeyEvent{key, scancode, mods}; - return event; -} - -Event Event::mousePress(i32 btn, i32 mods, Vec2 pos) { - Event event; - event.type = EventType::MouseButtonPressed; - event.data = MouseButtonEvent{btn, mods, pos}; - return event; -} - -Event Event::mouseRelease(i32 btn, i32 mods, Vec2 pos) { - Event event; - event.type = EventType::MouseButtonReleased; - event.data = MouseButtonEvent{btn, mods, pos}; - return event; -} - -Event Event::mouseMove(Vec2 pos, Vec2 delta) { - Event event; - event.type = EventType::MouseMoved; - event.data = MouseMoveEvent{pos, delta}; - return event; -} - -Event Event::mouseScroll(Vec2 offset, Vec2 pos) { - Event event; - event.type = EventType::MouseScrolled; - event.data = MouseScrollEvent{offset, pos}; - return event; -} - -} // namespace extra2d diff --git a/Extra2D/src/event/event_dispatcher.cpp b/Extra2D/src/event/event_dispatcher.cpp deleted file mode 100644 index 7cff7c7..0000000 --- a/Extra2D/src/event/event_dispatcher.cpp +++ /dev/null @@ -1,67 +0,0 @@ -#include -#include - -namespace extra2d { - -EventDispatcher::EventDispatcher() : nextId_(1) {} - -ListenerID EventDispatcher::on(EventType type, EventFn fn) { - ListenerID id = nextId_++; - listeners_[type].push_back({id, type, fn}); - return id; -} - -void EventDispatcher::off(ListenerID id) { - for (auto &[type, listeners] : listeners_) { - auto it = std::remove_if(listeners.begin(), listeners.end(), - [id](const Listener &l) { return l.id == id; }); - if (it != listeners.end()) { - listeners.erase(it, listeners.end()); - return; - } - } -} - -void EventDispatcher::offAll(EventType type) { - listeners_.erase(type); -} - -void EventDispatcher::offAll() { listeners_.clear(); } - -void EventDispatcher::dispatch(Event &event) { - auto it = listeners_.find(event.type); - if (it != listeners_.end()) { - for (auto &listener : it->second) { - if (event.handled) - break; - listener.fn(event); - } - } -} - -void EventDispatcher::dispatch(const Event &event) { - Event mutableEvent = event; - dispatch(mutableEvent); -} - -void EventDispatcher::process(EventQueue &queue) { - Event event; - while (queue.poll(event)) { - dispatch(event); - } -} - -size_t EventDispatcher::listenerCount(EventType type) const { - auto it = listeners_.find(type); - return (it != listeners_.end()) ? it->second.size() : 0; -} - -size_t EventDispatcher::totalListeners() const { - size_t count = 0; - for (const auto &[type, listeners] : listeners_) { - count += listeners.size(); - } - return count; -} - -} // namespace extra2d diff --git a/Extra2D/src/event/event_queue.cpp b/Extra2D/src/event/event_queue.cpp deleted file mode 100644 index 2f7850d..0000000 --- a/Extra2D/src/event/event_queue.cpp +++ /dev/null @@ -1,45 +0,0 @@ -#include - -namespace extra2d { - -EventQueue::EventQueue() = default; - -bool EventQueue::push(const Event &event) { - return buffer_.push(event); -} - -bool EventQueue::push(Event &&event) { - return buffer_.push(std::move(event)); -} - -bool EventQueue::poll(Event &event) { - return buffer_.pop(event); -} - -bool EventQueue::peek(Event &event) const { - std::lock_guard lock(mutex_); - if (buffer_.empty()) { - return false; - } - // 环形缓冲区不支持peek,这里简化处理 - // 实际应用中可能需要双缓冲或其他机制 - return false; -} - -void EventQueue::clear() { - std::lock_guard lock(mutex_); - Event event; - while (buffer_.pop(event)) { - // 持续弹出直到为空 - } -} - -bool EventQueue::empty() const { - return buffer_.empty(); -} - -size_t EventQueue::size() const { - return buffer_.size(); -} - -} // namespace extra2d diff --git a/Extra2D/src/gfx/GFXBuffer.cpp b/Extra2D/src/gfx/GFXBuffer.cpp new file mode 100644 index 0000000..ae03f94 --- /dev/null +++ b/Extra2D/src/gfx/GFXBuffer.cpp @@ -0,0 +1,113 @@ +#include +#include + +namespace extra2d { +namespace gfx { + +Buffer::Buffer() : GFXObject() { +} + +Buffer::~Buffer() { + destroy(); +} + +bool Buffer::initialize(const BufferInfo& info) { + usage_ = info.usage; + memUsage_ = info.memUsage; + size_ = info.size; + stride_ = info.stride; + flags_ = info.flags; + + if (stride_ > 0) { + count_ = size_ / stride_; + } else { + count_ = 1; + } + + isView_ = false; + + return doInit(info); +} + +bool Buffer::initialize(const BufferViewInfo& info) { + if (!info.buffer) return false; + + sourceBuffer_ = info.buffer; + viewOffset_ = info.offset; + viewRange_ = info.range; + isView_ = true; + + usage_ = info.buffer->getUsage(); + memUsage_ = info.buffer->getMemUsage(); + size_ = info.range; + stride_ = info.buffer->getStride(); + + if (stride_ > 0) { + count_ = size_ / stride_; + } else { + count_ = 1; + } + + return doInit(info); +} + +void Buffer::destroy() { + doDestroy(); + + sourceBuffer_ = nullptr; + viewOffset_ = 0; + viewRange_ = 0; + isView_ = false; +} + +void Buffer::resize(u32 size) { + if (isView_) return; + + size_ = size; + if (stride_ > 0) { + count_ = size_ / stride_; + } else { + count_ = 1; + } + + doResize(size); +} + +void Buffer::write(const void* data, u32 offset, u32 size) { + if (isView_ && sourceBuffer_) { + sourceBuffer_->write(data, viewOffset_ + offset, size); + } else { + update(data, size); + } +} + +BufferUsage Buffer::getUsage() const { + return usage_; +} + +MemoryUsage Buffer::getMemUsage() const { + return memUsage_; +} + +u32 Buffer::getSize() const { + return size_; +} + +u32 Buffer::getStride() const { + return stride_; +} + +u32 Buffer::getCount() const { + return count_; +} + +BufferFlag Buffer::getFlags() const { + return flags_; +} + +bool Buffer::isBufferView() const { + return isView_; +} + +} // namespace gfx +} // namespace extra2d diff --git a/Extra2D/src/gfx/GFXDevice.cpp b/Extra2D/src/gfx/GFXDevice.cpp new file mode 100644 index 0000000..0fc13ab --- /dev/null +++ b/Extra2D/src/gfx/GFXDevice.cpp @@ -0,0 +1,76 @@ +#include + +namespace extra2d { +namespace gfx { + +Device* Device::instance_ = nullptr; + +Device::Device() : GFXObject() { +} + +Device::~Device() { + destroy(); +} + +Device* Device::getInstance() { + return instance_; +} + +bool Device::initialize(const DeviceInfo& info) { + if (initialized_) return true; + + if (!doInit(info)) { + return false; + } + + initialized_ = true; + return true; +} + +void Device::destroy() { + if (!initialized_) return; + + doDestroy(); + initialized_ = false; +} + +const DeviceCaps& Device::getCapabilities() const { + return caps_; +} + +API Device::getGfxAPI() const { + return caps_.api; +} + +bool Device::hasFeature(Feature feature) const { + switch (feature) { + case Feature::ElementIndexUint: + return caps_.elementIndexUint; + case Feature::InstancedArrays: + return caps_.instancedArrays; + case Feature::MultipleRenderTargets: + return caps_.maxColorAttachments > 1; + case Feature::BlendMinmax: + return true; + default: + return false; + } +} + +FormatFeature Device::getFormatFeatures(Format format) const { + switch (format) { + case Format::RGBA8: + case Format::BGRA8: + return FormatFeature::Sampled | FormatFeature::ColorAttachment | + FormatFeature::Blend; + case Format::Depth: + case Format::DepthStencil: + case Format::Depth24Stencil8: + return FormatFeature::Sampled | FormatFeature::DepthStencilAttachment; + default: + return FormatFeature::Sampled; + } +} + +} // namespace gfx +} // namespace extra2d diff --git a/Extra2D/src/gfx/GFXInputAssembler.cpp b/Extra2D/src/gfx/GFXInputAssembler.cpp new file mode 100644 index 0000000..82d95a7 --- /dev/null +++ b/Extra2D/src/gfx/GFXInputAssembler.cpp @@ -0,0 +1,67 @@ +#include + +namespace extra2d { +namespace gfx { + +InputAssembler::InputAssembler() : GFXObject() { +} + +InputAssembler::~InputAssembler() { + destroy(); +} + +bool InputAssembler::initialize(const InputAssemblerInfo& info) { + vertexStreams_ = info.vertexStreams; + indexBuffer_ = info.indexBuffer; + indexOffset_ = info.indexOffset; + firstIndex_ = info.firstIndex; + indexCount_ = info.indexCount; + vertexCount_ = info.vertexCount; + instanceCount_ = info.instanceCount; + firstInstance_ = info.firstInstance; + vertexOffset_ = info.vertexOffset; + + return doInit(info); +} + +void InputAssembler::destroy() { + doDestroy(); + + vertexStreams_.clear(); + indexBuffer_ = nullptr; +} + +const std::vector& InputAssembler::getVertexStreams() const { + return vertexStreams_; +} + +Buffer* InputAssembler::getIndexBuffer() const { + return indexBuffer_; +} + +u32 InputAssembler::getIndexOffset() const { + return indexOffset_; +} + +u32 InputAssembler::getFirstIndex() const { + return firstIndex_; +} + +u32 InputAssembler::getIndexCount() const { + return indexCount_; +} + +u32 InputAssembler::getVertexCount() const { + return vertexCount_; +} + +u32 InputAssembler::getInstanceCount() const { + return instanceCount_; +} + +void InputAssembler::setInstanceCount(u32 count) { + instanceCount_ = count; +} + +} // namespace gfx +} // namespace extra2d diff --git a/Extra2D/src/gfx/GFXObject.cpp b/Extra2D/src/gfx/GFXObject.cpp new file mode 100644 index 0000000..15f869a --- /dev/null +++ b/Extra2D/src/gfx/GFXObject.cpp @@ -0,0 +1,21 @@ +#include + +namespace extra2d { +namespace gfx { + +GFXObject::GFXObject() : RefCounted() { +} + +GFXObject::~GFXObject() { +} + +const std::string& GFXObject::getDebugName() const { + return debugName_; +} + +void GFXObject::setDebugName(const std::string& name) { + debugName_ = name; +} + +} // namespace gfx +} // namespace extra2d diff --git a/Extra2D/src/gfx/GFXPipeline.cpp b/Extra2D/src/gfx/GFXPipeline.cpp new file mode 100644 index 0000000..b2448f5 --- /dev/null +++ b/Extra2D/src/gfx/GFXPipeline.cpp @@ -0,0 +1,58 @@ +#include +#include + +namespace extra2d { +namespace gfx { + +PipelineState::PipelineState() : GFXObject() { +} + +PipelineState::~PipelineState() { + destroy(); +} + +bool PipelineState::initialize(const PipelineStateInfo& info) { + shader_ = info.shader; + bindPoint_ = info.bindPoint; + primitive_ = info.primitive; + dynamicStates_ = info.dynamicStates; + inputState_ = info.inputState; + rasterizerState_ = info.rasterizerState; + depthStencilState_ = info.depthStencilState; + blendState_ = info.blendState; + + return doInit(info); +} + +void PipelineState::destroy() { + doDestroy(); + + shader_ = nullptr; +} + +Shader* PipelineState::getShader() const { + return shader_; +} + +PipelineBindPoint PipelineState::getBindPoint() const { + return bindPoint_; +} + +PrimitiveMode PipelineState::getPrimitive() const { + return primitive_; +} + +const RasterizerState& PipelineState::getRasterizerState() const { + return rasterizerState_; +} + +const DepthStencilState& PipelineState::getDepthStencilState() const { + return depthStencilState_; +} + +const BlendState& PipelineState::getBlendState() const { + return blendState_; +} + +} // namespace gfx +} // namespace extra2d diff --git a/Extra2D/src/gfx/GFXShader.cpp b/Extra2D/src/gfx/GFXShader.cpp new file mode 100644 index 0000000..89b862f --- /dev/null +++ b/Extra2D/src/gfx/GFXShader.cpp @@ -0,0 +1,51 @@ +#include + +namespace extra2d { +namespace gfx { + +Shader::Shader() : GFXObject() { +} + +Shader::~Shader() { + destroy(); +} + +bool Shader::initialize(const ShaderInfo& info) { + name_ = info.name; + vertexSource_ = info.vertexSource; + fragmentSource_ = info.fragmentSource; + attributes_ = info.attributes; + blocks_ = info.blocks; + samplerTextures_ = info.samplerTextures; + + return doInit(info); +} + +void Shader::destroy() { + doDestroy(); + + vertexSource_.clear(); + fragmentSource_.clear(); + attributes_.clear(); + blocks_.clear(); + samplerTextures_.clear(); +} + +const std::string& Shader::getName() const { + return name_; +} + +const std::vector& Shader::getAttributes() const { + return attributes_; +} + +const std::vector& Shader::getBlocks() const { + return blocks_; +} + +const std::vector& Shader::getSamplerTextures() const { + return samplerTextures_; +} + +} // namespace gfx +} // namespace extra2d diff --git a/Extra2D/src/gfx/GFXTexture.cpp b/Extra2D/src/gfx/GFXTexture.cpp new file mode 100644 index 0000000..f3a38a9 --- /dev/null +++ b/Extra2D/src/gfx/GFXTexture.cpp @@ -0,0 +1,80 @@ +#include + +namespace extra2d { +namespace gfx { + +Texture::Texture() : GFXObject() { +} + +Texture::~Texture() { + destroy(); +} + +bool Texture::initialize(const TextureInfo& info) { + info_ = info; + isView_ = false; + + return doInit(info); +} + +bool Texture::initialize(const TextureViewInfo& info) { + if (!info.texture) return false; + + viewInfo_ = info; + sourceTexture_ = info.texture; + isView_ = true; + + info_.type = info.type; + info_.format = info.format != Format::Unknown ? info.format : info.texture->getFormat(); + info_.width = info.texture->getWidth(); + info_.height = info.texture->getHeight(); + + return doInit(info); +} + +void Texture::destroy() { + doDestroy(); + + sourceTexture_ = nullptr; + isView_ = false; +} + +void Texture::resize(u32 width, u32 height) { + if (isView_) return; + + info_.width = width; + info_.height = height; + + doResize(width, height); +} + +const TextureInfo& Texture::getInfo() const { + return info_; +} + +const TextureViewInfo& Texture::getViewInfo() const { + return viewInfo_; +} + +bool Texture::isTextureView() const { + return isView_; +} + +Format Texture::getFormat() const { + return info_.format; +} + +u32 Texture::getWidth() const { + return info_.width; +} + +u32 Texture::getHeight() const { + return info_.height; +} + +u32 Texture::getDepth() const { + return info_.depth; +} + +} // namespace gfx +} // namespace extra2d diff --git a/Extra2D/src/gfx/opengl/GLBuffer.cpp b/Extra2D/src/gfx/opengl/GLBuffer.cpp new file mode 100644 index 0000000..f87eab1 --- /dev/null +++ b/Extra2D/src/gfx/opengl/GLBuffer.cpp @@ -0,0 +1,82 @@ +#include +#include +#include + +namespace extra2d { +namespace gfx { + +GLBuffer::GLBuffer() : Buffer(), glHandle_(0), glTarget_(0), mappedData_(nullptr) { +} + +GLBuffer::~GLBuffer() { + destroy(); +} + +bool GLBuffer::doInit(const BufferInfo& info) { + if (isView_) return true; + + glGenBuffers(1, &glHandle_); + + if (hasUsage(usage_, BufferUsage::Index)) { + glTarget_ = GL_ELEMENT_ARRAY_BUFFER; + } else if (hasUsage(usage_, BufferUsage::Vertex)) { + glTarget_ = GL_ARRAY_BUFFER; + } else if (hasUsage(usage_, BufferUsage::Uniform)) { + glTarget_ = GL_UNIFORM_BUFFER; + } else { + glTarget_ = GL_ARRAY_BUFFER; + } + + glBindBuffer(glTarget_, glHandle_); + + u32 glUsage = GL_STATIC_DRAW; + if (hasUsage(memUsage_, MemoryUsage::Host)) { + glUsage = GL_DYNAMIC_DRAW; + } + + glBufferData(glTarget_, size_, info.initData, glUsage); + + glBindBuffer(glTarget_, 0); + + return glHandle_ != 0; +} + +bool GLBuffer::doInit(const BufferViewInfo& info) { + (void)info; + return true; +} + +void GLBuffer::doDestroy() { + if (glHandle_ != 0) { + glDeleteBuffers(1, &glHandle_); + glHandle_ = 0; + } + glTarget_ = 0; + mappedData_ = nullptr; +} + +void GLBuffer::doResize(u32 size) { + if (glHandle_ == 0 || isView_) return; + + glBindBuffer(glTarget_, glHandle_); + glBufferData(glTarget_, size, nullptr, GL_DYNAMIC_DRAW); + glBindBuffer(glTarget_, 0); +} + +void GLBuffer::update(const void* data, u32 size) { + if (glHandle_ == 0 || isView_) return; + + glBindBuffer(glTarget_, glHandle_); + glBufferSubData(glTarget_, 0, size, data); + glBindBuffer(glTarget_, 0); +} + +u32 GLBuffer::getGLHandle() const { + if (isView_ && sourceBuffer_) { + return static_cast(sourceBuffer_)->getGLHandle(); + } + return glHandle_; +} + +} // namespace gfx +} // namespace extra2d diff --git a/Extra2D/src/gfx/opengl/GLCommandBuffer.cpp b/Extra2D/src/gfx/opengl/GLCommandBuffer.cpp new file mode 100644 index 0000000..a7cfbd1 --- /dev/null +++ b/Extra2D/src/gfx/opengl/GLCommandBuffer.cpp @@ -0,0 +1,194 @@ +#include +#include +#include +#include +#include + +#ifdef _WIN32 +#include +#else +#include +#endif + +namespace extra2d { +namespace gfx { + +GLCommandBuffer::GLCommandBuffer() = default; + +GLCommandBuffer::~GLCommandBuffer() { + destroy(); +} + +bool GLCommandBuffer::initialize() { + return true; +} + +void GLCommandBuffer::destroy() { + currentRenderPass_ = nullptr; + currentFramebuffer_ = nullptr; +} + +void GLCommandBuffer::begin() { + recording_ = true; +} + +void GLCommandBuffer::end() { + recording_ = false; +} + +void GLCommandBuffer::beginRenderPass(RenderPass* renderPass, Framebuffer* framebuffer, + const Viewport& viewport, + ClearFlagBit clearFlags, + const Color& clearColor, + float clearDepth, u32 clearStencil) { + currentRenderPass_ = renderPass; + currentFramebuffer_ = framebuffer; + + setViewport(viewport); + + GLbitfield clearMask = 0; + if (hasFlag(clearFlags, ClearFlagBit::Color)) { + clearMask |= GL_COLOR_BUFFER_BIT; + glClearColor(clearColor.r, clearColor.g, clearColor.b, clearColor.a); + } + if (hasFlag(clearFlags, ClearFlagBit::Depth)) { + clearMask |= GL_DEPTH_BUFFER_BIT; + glClearDepthf(clearDepth); + } + if (hasFlag(clearFlags, ClearFlagBit::Stencil)) { + clearMask |= GL_STENCIL_BUFFER_BIT; + glClearStencil(clearStencil); + } + + if (clearMask != 0) { + glClear(clearMask); + } +} + +void GLCommandBuffer::endRenderPass() { + currentRenderPass_ = nullptr; + currentFramebuffer_ = nullptr; +} + +void GLCommandBuffer::bindPipelineState(PipelineState* pipelineState) { + if (!pipelineState) return; + + auto* glPipeline = static_cast(pipelineState); + glPipeline->bind(); +} + +void GLCommandBuffer::bindInputAssembler(InputAssembler* inputAssembler) { + if (!inputAssembler) return; + + auto* glIA = static_cast(inputAssembler); + glIA->bind(); +} + +void GLCommandBuffer::bindDescriptorSet(DescriptorSet* descriptorSet, u32 setIndex) { + // TODO: 实现 OpenGL 描述符集绑定 +} + +void GLCommandBuffer::setViewport(const Viewport& viewport) { + glViewport( + static_cast(viewport.left), + static_cast(viewport.top), + static_cast(viewport.width), + static_cast(viewport.height) + ); + glDepthRangef(viewport.minDepth, viewport.maxDepth); +} + +void GLCommandBuffer::setScissor(const Rect& rect) { + glScissor( + static_cast(rect.x), + static_cast(rect.y), + static_cast(rect.width), + static_cast(rect.height) + ); +} + +void GLCommandBuffer::setBlendConstants(const float* constants) { + glBlendColor(constants[0], constants[1], constants[2], constants[3]); +} + +void GLCommandBuffer::setDepthBias(float bias, float slope, float clamp) { + if (bias != 0.0f || slope != 0.0f) { + glEnable(GL_POLYGON_OFFSET_FILL); + glPolygonOffset(slope, bias); + } else { + glDisable(GL_POLYGON_OFFSET_FILL); + } +} + +void GLCommandBuffer::setStencilReference(u32 front, u32 back) { + glStencilFuncSeparate(GL_FRONT, GL_ALWAYS, front, 0xFF); + glStencilFuncSeparate(GL_BACK, GL_ALWAYS, back, 0xFF); +} + +void GLCommandBuffer::drawArrays(u32 vertexCount, u32 firstVertex, + u32 instanceCount, u32 firstInstance) { + if (instanceCount > 1) { +#ifdef GL_VERSION_3_0 + glDrawArraysInstanced(GL_TRIANGLES, firstVertex, vertexCount, instanceCount); +#else + for (u32 i = 0; i < instanceCount; ++i) { + glDrawArrays(GL_TRIANGLES, firstVertex, vertexCount); + } +#endif + } else { + glDrawArrays(GL_TRIANGLES, firstVertex, vertexCount); + } +} + +void GLCommandBuffer::drawIndexed(u32 indexCount, u32 firstIndex, + u32 instanceCount, u32 firstInstance) { + const void* indices = reinterpret_cast(firstIndex * sizeof(u32)); + + if (instanceCount > 1) { +#ifdef GL_VERSION_3_0 + glDrawElementsInstanced(GL_TRIANGLES, indexCount, GL_UNSIGNED_INT, indices, instanceCount); +#else + for (u32 i = 0; i < instanceCount; ++i) { + glDrawElements(GL_TRIANGLES, indexCount, GL_UNSIGNED_INT, indices); + } +#endif + } else { + glDrawElements(GL_TRIANGLES, indexCount, GL_UNSIGNED_INT, indices); + } +} + +void GLCommandBuffer::updateBuffer(Buffer* buffer, u32 offset, const void* data, u32 size) { + if (!buffer || !data) return; + + auto* glBuffer = static_cast(buffer); + glBuffer->update(offset, data, size); +} + +void GLCommandBuffer::copyBuffer(Buffer* src, u32 srcOffset, Buffer* dst, u32 dstOffset, u32 size) { + if (!src || !dst) return; + + auto* glSrc = static_cast(src); + auto* glDst = static_cast(dst); + + glBindBuffer(GL_COPY_READ_BUFFER, glSrc->getHandle()); + glBindBuffer(GL_COPY_WRITE_BUFFER, glDst->getHandle()); + glCopyBufferSubData(srcOffset, dstOffset, size); + glBindBuffer(GL_COPY_READ_BUFFER, 0); + glBindBuffer(GL_COPY_WRITE_BUFFER, 0); +} + +void GLCommandBuffer::copyTexture(Texture* src, Texture* dst) { + if (!src || !dst) return; + + auto* glSrc = static_cast(src); + auto* glDst = static_cast(dst); + + glCopyImageSubData( + glSrc->getHandle(), GL_TEXTURE_2D, 0, 0, 0, 0, + glDst->getHandle(), GL_TEXTURE_2D, 0, 0, 0, 0, + src->getWidth(), src->getHeight(), 1 + ); +} + +} // namespace gfx +} // namespace extra2d diff --git a/Extra2D/src/gfx/opengl/GLDevice.cpp b/Extra2D/src/gfx/opengl/GLDevice.cpp new file mode 100644 index 0000000..4b52f57 --- /dev/null +++ b/Extra2D/src/gfx/opengl/GLDevice.cpp @@ -0,0 +1,238 @@ +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace extra2d { +namespace gfx { + +GLDevice::GLDevice() : Device(), currentPipeline_(nullptr), currentIA_(nullptr), currentShader_(nullptr) { + instance_ = this; +} + +GLDevice::~GLDevice() { + destroy(); +} + +bool GLDevice::doInit(const DeviceInfo& info) { + (void)info; + + if (!gladLoadGL()) { + return false; + } + + initCapabilities(); + + glEnable(GL_PRIMITIVE_RESTART_FIXED_INDEX); + + return true; +} + +void GLDevice::doDestroy() { + currentPipeline_ = nullptr; + currentIA_ = nullptr; + currentShader_ = nullptr; +} + +void GLDevice::initCapabilities() { + caps_.api = API::OpenGL; + caps_.maxVertexAttributes = 16; + caps_.maxVertexUniformVectors = 256; + caps_.maxFragmentUniformVectors = 256; + caps_.maxTextureUnits = 16; + caps_.maxVertexTextureUnits = 8; + caps_.maxDrawBuffers = 8; + caps_.maxColorAttachments = 8; + caps_.maxTextureSize = 4096; + caps_.maxCubeMapSize = 4096; + caps_.maxArrayLayers = 256; + caps_.maxSamples = 4; + caps_.maxAnisotropy = 16.0f; + + glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, reinterpret_cast(&caps_.maxVertexAttributes)); + glGetIntegerv(GL_MAX_VERTEX_UNIFORM_VECTORS, reinterpret_cast(&caps_.maxVertexUniformVectors)); + glGetIntegerv(GL_MAX_FRAGMENT_UNIFORM_VECTORS, reinterpret_cast(&caps_.maxFragmentUniformVectors)); + glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, reinterpret_cast(&caps_.maxTextureUnits)); + glGetIntegerv(GL_MAX_DRAW_BUFFERS, reinterpret_cast(&caps_.maxDrawBuffers)); + glGetIntegerv(GL_MAX_COLOR_ATTACHMENTS, reinterpret_cast(&caps_.maxColorAttachments)); + glGetIntegerv(GL_MAX_TEXTURE_SIZE, reinterpret_cast(&caps_.maxTextureSize)); + glGetIntegerv(GL_MAX_CUBE_MAP_TEXTURE_SIZE, reinterpret_cast(&caps_.maxCubeMapSize)); + glGetIntegerv(GL_MAX_ARRAY_TEXTURE_LAYERS, reinterpret_cast(&caps_.maxArrayLayers)); + glGetIntegerv(GL_MAX_SAMPLES, reinterpret_cast(&caps_.maxSamples)); + + caps_.instancedArrays = true; + caps_.standardDerivatives = true; + caps_.elementIndexUint = true; + caps_.depth24 = true; + caps_.depth32 = true; + caps_.depth24Stencil8 = true; +} + +void GLDevice::frameSync() { + glFinish(); +} + +void GLDevice::present() { +} + +Buffer* GLDevice::createBuffer(const BufferInfo& info) { + GLBuffer* buffer = new GLBuffer(); + if (!buffer->initialize(info)) { + delete buffer; + return nullptr; + } + return buffer; +} + +Texture* GLDevice::createTexture(const TextureInfo& info) { + GLTexture* texture = new GLTexture(); + if (!texture->initialize(info)) { + delete texture; + return nullptr; + } + return texture; +} + +Shader* GLDevice::createShader(const std::string& name, + const std::string& vertexSource, + const std::string& fragmentSource) { + ShaderInfo info; + info.name = name; + info.vertexSource = vertexSource; + info.fragmentSource = fragmentSource; + + GLShader* shader = new GLShader(); + if (!shader->initialize(info)) { + delete shader; + return nullptr; + } + return shader; +} + +PipelineState* GLDevice::createPipelineState(const PipelineStateInfo& info) { + GLPipelineState* pipeline = new GLPipelineState(); + if (!pipeline->initialize(info)) { + delete pipeline; + return nullptr; + } + return pipeline; +} + +InputAssembler* GLDevice::createInputAssembler(const InputAssemblerInfo& info) { + GLInputAssembler* ia = new GLInputAssembler(); + if (!ia->initialize(info)) { + delete ia; + return nullptr; + } + return ia; +} + +void GLDevice::copyBuffersToTexture(const u8* const* buffers, Texture* dst, + const BufferTextureCopy* regions, u32 count) { + GLTexture* glTexture = static_cast(dst); + if (!glTexture) return; + + glBindTexture(glTexture->getGLTarget(), glTexture->getGLHandle()); + + for (u32 i = 0; i < count; ++i) { + const auto& region = regions[i]; + glTexSubImage2D(glTexture->getGLTarget(), region.texBaseLevel, + region.texOffsetX, region.texOffsetY, + region.texExtentX, region.texExtentY, + GL_RGBA, GL_UNSIGNED_BYTE, buffers[i] + region.bufferOffset); + } + + glBindTexture(glTexture->getGLTarget(), 0); +} + +void GLDevice::setViewport(float x, float y, float width, float height) { + glViewport(static_cast(x), static_cast(y), + static_cast(width), static_cast(height)); +} + +void GLDevice::setScissor(i32 x, i32 y, u32 width, u32 height) { + glScissor(x, y, width, height); +} + +void GLDevice::clearColor(float r, float g, float b, float a) { + glClearColor(r, g, b, a); + glClear(GL_COLOR_BUFFER_BIT); +} + +void GLDevice::clearDepthStencil(float depth, u8 stencil) { + glClearDepth(depth); + glClearStencil(stencil); + glClear(GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); +} + +void GLDevice::beginRenderPass() { +} + +void GLDevice::endRenderPass() { +} + +void GLDevice::bindPipelineState(PipelineState* pipeline) { + currentPipeline_ = static_cast(pipeline); + if (currentPipeline_) { + currentPipeline_->apply(); + currentShader_ = static_cast(currentPipeline_->getShader()); + if (currentShader_) { + currentShader_->bind(); + } + } +} + +void GLDevice::bindInputAssembler(InputAssembler* ia) { + currentIA_ = static_cast(ia); + if (currentIA_ && currentShader_) { + currentIA_->bindAttributes(currentShader_); + } +} + +void GLDevice::draw(u32 firstVertex, u32 vertexCount) { + glDrawArrays(GL_TRIANGLES, firstVertex, vertexCount); +} + +void GLDevice::drawIndexed(u32 firstIndex, u32 indexCount, i32 vertexOffset) { + (void)vertexOffset; + u32 indexType = GL_UNSIGNED_SHORT; + u32 indexSize = 2; + if (currentIA_ && currentIA_->getIndexBuffer()) { + u32 stride = currentIA_->getIndexBuffer()->getStride(); + if (stride == 4) { + indexType = GL_UNSIGNED_INT; + indexSize = 4; + } + } + glDrawElements(GL_TRIANGLES, indexCount, indexType, + reinterpret_cast(static_cast(firstIndex * indexSize))); +} + +void GLDevice::drawInstanced(u32 firstVertex, u32 vertexCount, u32 instanceCount) { + glDrawArraysInstanced(GL_TRIANGLES, firstVertex, vertexCount, instanceCount); +} + +void GLDevice::drawIndexedInstanced(u32 firstIndex, u32 indexCount, + u32 instanceCount, i32 vertexOffset) { + (void)vertexOffset; + u32 indexType = GL_UNSIGNED_SHORT; + u32 indexSize = 2; + if (currentIA_ && currentIA_->getIndexBuffer()) { + u32 stride = currentIA_->getIndexBuffer()->getStride(); + if (stride == 4) { + indexType = GL_UNSIGNED_INT; + indexSize = 4; + } + } + glDrawElementsInstanced(GL_TRIANGLES, indexCount, indexType, + reinterpret_cast(static_cast(firstIndex * indexSize)), + instanceCount); +} + +} // namespace gfx +} // namespace extra2d diff --git a/Extra2D/src/gfx/opengl/GLInputAssembler.cpp b/Extra2D/src/gfx/opengl/GLInputAssembler.cpp new file mode 100644 index 0000000..77bd6d9 --- /dev/null +++ b/Extra2D/src/gfx/opengl/GLInputAssembler.cpp @@ -0,0 +1,90 @@ +#include +#include +#include +#include + +namespace extra2d { +namespace gfx { + +GLInputAssembler::GLInputAssembler() : InputAssembler() { +} + +GLInputAssembler::~GLInputAssembler() { + destroy(); +} + +bool GLInputAssembler::doInit(const InputAssemblerInfo& info) { + (void)info; + return true; +} + +void GLInputAssembler::doDestroy() { +} + +void GLInputAssembler::bindAttributes(GLShader* shader) { + if (!shader) return; + + const auto& attributes = shader->getAttributes(); + u32 program = shader->getGLProgram(); + + for (size_t i = 0; i < vertexStreams_.size(); ++i) { + const auto& stream = vertexStreams_[i]; + if (!stream.buffer) continue; + + GLBuffer* glBuffer = static_cast(stream.buffer); + glBindBuffer(GL_ARRAY_BUFFER, glBuffer->getGLHandle()); + + for (const auto& attr : attributes) { + if (attr.stream != i) continue; + + i32 location = shader->getAttributeLocation(attr.name); + if (location < 0) continue; + + u32 size = 4; + u32 type = GL_FLOAT; + bool normalized = attr.normalized; + + switch (attr.format) { + case Format::R8: + size = 1; type = GL_UNSIGNED_BYTE; break; + case Format::RG8: + size = 2; type = GL_UNSIGNED_BYTE; break; + case Format::RGBA8: + size = 4; type = GL_UNSIGNED_BYTE; break; + case Format::R16F: + size = 1; type = GL_HALF_FLOAT; break; + case Format::RG16F: + size = 2; type = GL_HALF_FLOAT; break; + case Format::RGBA16F: + size = 4; type = GL_HALF_FLOAT; break; + case Format::R32F: + size = 1; type = GL_FLOAT; break; + case Format::RG32F: + size = 2; type = GL_FLOAT; break; + case Format::RGB32F: + size = 3; type = GL_FLOAT; break; + case Format::RGBA32F: + size = 4; type = GL_FLOAT; break; + default: + size = 4; type = GL_FLOAT; break; + } + + u32 stride = stream.buffer->getStride(); + glEnableVertexAttribArray(location); + glVertexAttribPointer(location, size, type, normalized ? GL_TRUE : GL_FALSE, + stride, reinterpret_cast(static_cast(stream.offset))); + + if (attr.instanced) { + glVertexAttribDivisor(location, 1); + } + } + } + + if (indexBuffer_) { + GLBuffer* glIndexBuffer = static_cast(indexBuffer_); + glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, glIndexBuffer->getGLHandle()); + } +} + +} // namespace gfx +} // namespace extra2d diff --git a/Extra2D/src/gfx/opengl/GLPipelineState.cpp b/Extra2D/src/gfx/opengl/GLPipelineState.cpp new file mode 100644 index 0000000..e2fd84b --- /dev/null +++ b/Extra2D/src/gfx/opengl/GLPipelineState.cpp @@ -0,0 +1,147 @@ +#include +#include +#include + +namespace extra2d { +namespace gfx { + +namespace { + +u32 getGLBlendFactor(BlendFactor factor) { + switch (factor) { + case BlendFactor::Zero: return GL_ZERO; + case BlendFactor::One: return GL_ONE; + case BlendFactor::SrcAlpha: return GL_SRC_ALPHA; + case BlendFactor::DstAlpha: return GL_DST_ALPHA; + case BlendFactor::OneMinusSrcAlpha: return GL_ONE_MINUS_SRC_ALPHA; + case BlendFactor::OneMinusDstAlpha: return GL_ONE_MINUS_DST_ALPHA; + case BlendFactor::SrcColor: return GL_SRC_COLOR; + case BlendFactor::DstColor: return GL_DST_COLOR; + case BlendFactor::OneMinusSrcColor: return GL_ONE_MINUS_SRC_COLOR; + case BlendFactor::OneMinusDstColor: return GL_ONE_MINUS_DST_COLOR; + case BlendFactor::SrcAlphaSaturate: return GL_SRC_ALPHA_SATURATE; + case BlendFactor::ConstantColor: return GL_CONSTANT_COLOR; + case BlendFactor::OneMinusConstantColor: return GL_ONE_MINUS_CONSTANT_COLOR; + case BlendFactor::ConstantAlpha: return GL_CONSTANT_ALPHA; + case BlendFactor::OneMinusConstantAlpha: return GL_ONE_MINUS_CONSTANT_ALPHA; + default: return GL_ZERO; + } +} + +u32 getGLBlendOp(BlendOp op) { + switch (op) { + case BlendOp::Add: return GL_FUNC_ADD; + case BlendOp::Subtract: return GL_FUNC_SUBTRACT; + case BlendOp::ReverseSubtract: return GL_FUNC_REVERSE_SUBTRACT; + case BlendOp::Min: return GL_MIN; + case BlendOp::Max: return GL_MAX; + default: return GL_FUNC_ADD; + } +} + +} + +GLPipelineState::GLPipelineState() : PipelineState() { +} + +GLPipelineState::~GLPipelineState() { + destroy(); +} + +bool GLPipelineState::doInit(const PipelineStateInfo& info) { + (void)info; + return true; +} + +void GLPipelineState::doDestroy() { +} + +void GLPipelineState::apply() { + applyRasterizerState(); + applyDepthStencilState(); + applyBlendState(); +} + +void GLPipelineState::applyRasterizerState() { + if (rasterizerState_.cullMode == CullMode::None) { + glDisable(GL_CULL_FACE); + } else { + glEnable(GL_CULL_FACE); + glCullFace(rasterizerState_.cullMode == CullMode::Front ? GL_FRONT : GL_BACK); + } + + glFrontFace(rasterizerState_.frontFaceCCW ? GL_CCW : GL_CW); + + if (rasterizerState_.polygonMode == PolygonMode::Fill) { + glPolygonMode(GL_FRONT_AND_BACK, GL_FILL); + } else if (rasterizerState_.polygonMode == PolygonMode::Line) { + glPolygonMode(GL_FRONT_AND_BACK, GL_LINE); + } else { + glPolygonMode(GL_FRONT_AND_BACK, GL_POINT); + } + + if (rasterizerState_.depthBiasEnabled) { + glEnable(GL_POLYGON_OFFSET_FILL); + glPolygonOffset(rasterizerState_.depthBiasSlop, rasterizerState_.depthBias); + } else { + glDisable(GL_POLYGON_OFFSET_FILL); + } +} + +void GLPipelineState::applyDepthStencilState() { + if (depthStencilState_.depthTest) { + glEnable(GL_DEPTH_TEST); + glDepthMask(depthStencilState_.depthWrite ? GL_TRUE : GL_FALSE); + + u32 glDepthFunc = GL_LESS; + switch (depthStencilState_.depthFunc) { + case ComparisonFunc::Never: glDepthFunc = GL_NEVER; break; + case ComparisonFunc::Less: glDepthFunc = GL_LESS; break; + case ComparisonFunc::Equal: glDepthFunc = GL_EQUAL; break; + case ComparisonFunc::LessEqual: glDepthFunc = GL_LEQUAL; break; + case ComparisonFunc::Greater: glDepthFunc = GL_GREATER; break; + case ComparisonFunc::NotEqual: glDepthFunc = GL_NOTEQUAL; break; + case ComparisonFunc::GreaterEqual: glDepthFunc = GL_GEQUAL; break; + case ComparisonFunc::Always: glDepthFunc = GL_ALWAYS; break; + } + glDepthFunc(glDepthFunc); + } else { + glDisable(GL_DEPTH_TEST); + } + + if (depthStencilState_.stencilTestFront || depthStencilState_.stencilTestBack) { + glEnable(GL_STENCIL_TEST); + } else { + glDisable(GL_STENCIL_TEST); + } +} + +void GLPipelineState::applyBlendState() { + const auto& target = blendState_.targets.empty() ? BlendTarget{} : blendState_.targets[0]; + + if (target.blend) { + glEnable(GL_BLEND); + glBlendFuncSeparate( + getGLBlendFactor(target.srcColor), + getGLBlendFactor(target.dstColor), + getGLBlendFactor(target.srcAlpha), + getGLBlendFactor(target.dstAlpha) + ); + glBlendEquationSeparate( + getGLBlendOp(target.blendColorOp), + getGLBlendOp(target.blendAlphaOp) + ); + } else { + glDisable(GL_BLEND); + } + + glColorMask( + (target.colorWriteMask & 0x1) ? GL_TRUE : GL_FALSE, + (target.colorWriteMask & 0x2) ? GL_TRUE : GL_FALSE, + (target.colorWriteMask & 0x4) ? GL_TRUE : GL_FALSE, + (target.colorWriteMask & 0x8) ? GL_TRUE : GL_FALSE + ); +} + +} // namespace gfx +} // namespace extra2d diff --git a/Extra2D/src/gfx/opengl/GLShader.cpp b/Extra2D/src/gfx/opengl/GLShader.cpp new file mode 100644 index 0000000..cb49ef1 --- /dev/null +++ b/Extra2D/src/gfx/opengl/GLShader.cpp @@ -0,0 +1,102 @@ +#include +#include +#include + +namespace extra2d { +namespace gfx { + +GLShader::GLShader() : Shader(), glProgram_(0) { +} + +GLShader::~GLShader() { + destroy(); +} + +bool GLShader::doInit(const ShaderInfo& info) { + u32 vertexShader = compileShader(info.vertexSource, GL_VERTEX_SHADER); + if (vertexShader == 0) { + return false; + } + + u32 fragmentShader = compileShader(info.fragmentSource, GL_FRAGMENT_SHADER); + if (fragmentShader == 0) { + glDeleteShader(vertexShader); + return false; + } + + if (!linkProgram(vertexShader, fragmentShader)) { + glDeleteShader(vertexShader); + glDeleteShader(fragmentShader); + return false; + } + + glDeleteShader(vertexShader); + glDeleteShader(fragmentShader); + + return glProgram_ != 0; +} + +void GLShader::doDestroy() { + if (glProgram_ != 0) { + glDeleteProgram(glProgram_); + glProgram_ = 0; + } +} + +u32 GLShader::compileShader(const std::string& source, u32 type) { + u32 shader = glCreateShader(type); + const char* src = source.c_str(); + glShaderSource(shader, 1, &src, nullptr); + glCompileShader(shader); + + i32 success; + glGetShaderiv(shader, GL_COMPILE_STATUS, &success); + if (!success) { + char infoLog[512]; + glGetShaderInfoLog(shader, 512, nullptr, infoLog); + std::cerr << "Shader compilation error: " << infoLog << std::endl; + glDeleteShader(shader); + return 0; + } + + return shader; +} + +bool GLShader::linkProgram(u32 vertexShader, u32 fragmentShader) { + glProgram_ = glCreateProgram(); + glAttachShader(glProgram_, vertexShader); + glAttachShader(glProgram_, fragmentShader); + glLinkProgram(glProgram_); + + i32 success; + glGetProgramiv(glProgram_, GL_LINK_STATUS, &success); + if (!success) { + char infoLog[512]; + glGetProgramInfoLog(glProgram_, 512, nullptr, infoLog); + std::cerr << "Shader linking error: " << infoLog << std::endl; + glDeleteProgram(glProgram_); + glProgram_ = 0; + return false; + } + + return true; +} + +u32 GLShader::getGLProgram() const { + return glProgram_; +} + +void GLShader::bind() { + glUseProgram(glProgram_); +} + +i32 GLShader::getUniformLocation(const std::string& name) { + return glGetUniformLocation(glProgram_, name.c_str()); +} + +i32 GLShader::getAttributeLocation(const std::string& name) { + return glGetAttribLocation(glProgram_, name.c_str()); +} + +} // namespace gfx +} // namespace extra2d diff --git a/Extra2D/src/gfx/opengl/GLTexture.cpp b/Extra2D/src/gfx/opengl/GLTexture.cpp new file mode 100644 index 0000000..555509e --- /dev/null +++ b/Extra2D/src/gfx/opengl/GLTexture.cpp @@ -0,0 +1,173 @@ +#include +#include + +namespace extra2d { +namespace gfx { + +GLTexture::GLTexture() : Texture(), glHandle_(0), glTarget_(0), + glInternalFormat_(0), glFormat_(0), glType_(0) { +} + +GLTexture::~GLTexture() { + destroy(); +} + +bool GLTexture::doInit(const TextureInfo& info) { + if (isView_) return true; + + glGenTextures(1, &glHandle_); + + switch (info.type) { + case TextureType::Tex2D: + glTarget_ = GL_TEXTURE_2D; + break; + case TextureType::Cube: + glTarget_ = GL_TEXTURE_CUBE_MAP; + break; + case TextureType::Tex2DArray: + glTarget_ = GL_TEXTURE_2D_ARRAY; + break; + default: + glTarget_ = GL_TEXTURE_2D; + break; + } + + glBindTexture(glTarget_, glHandle_); + + switch (info.format) { + case Format::R8: + glInternalFormat_ = GL_R8; + glFormat_ = GL_RED; + glType_ = GL_UNSIGNED_BYTE; + break; + case Format::RG8: + glInternalFormat_ = GL_RG8; + glFormat_ = GL_RG; + glType_ = GL_UNSIGNED_BYTE; + break; + case Format::RGBA8: + glInternalFormat_ = GL_RGBA8; + glFormat_ = GL_RGBA; + glType_ = GL_UNSIGNED_BYTE; + break; + case Format::BGRA8: + glInternalFormat_ = GL_RGBA8; + glFormat_ = GL_BGRA; + glType_ = GL_UNSIGNED_BYTE; + break; + case Format::R16F: + glInternalFormat_ = GL_R16F; + glFormat_ = GL_RED; + glType_ = GL_HALF_FLOAT; + break; + case Format::RG16F: + glInternalFormat_ = GL_RG16F; + glFormat_ = GL_RG; + glType_ = GL_HALF_FLOAT; + break; + case Format::RGBA16F: + glInternalFormat_ = GL_RGBA16F; + glFormat_ = GL_RGBA; + glType_ = GL_HALF_FLOAT; + break; + case Format::R32F: + glInternalFormat_ = GL_R32F; + glFormat_ = GL_RED; + glType_ = GL_FLOAT; + break; + case Format::RG32F: + glInternalFormat_ = GL_RG32F; + glFormat_ = GL_RG; + glType_ = GL_FLOAT; + break; + case Format::RGBA32F: + glInternalFormat_ = GL_RGBA32F; + glFormat_ = GL_RGBA; + glType_ = GL_FLOAT; + break; + case Format::Depth: + glInternalFormat_ = GL_DEPTH_COMPONENT24; + glFormat_ = GL_DEPTH_COMPONENT; + glType_ = GL_UNSIGNED_INT; + break; + case Format::DepthStencil: + case Format::Depth24Stencil8: + glInternalFormat_ = GL_DEPTH24_STENCIL8; + glFormat_ = GL_DEPTH_STENCIL; + glType_ = GL_UNSIGNED_INT_24_8; + break; + default: + glInternalFormat_ = GL_RGBA8; + glFormat_ = GL_RGBA; + glType_ = GL_UNSIGNED_BYTE; + break; + } + + glTexImage2D(glTarget_, 0, glInternalFormat_, + info.width, info.height, 0, + glFormat_, glType_, nullptr); + + glTexParameteri(glTarget_, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(glTarget_, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexParameteri(glTarget_, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + glTexParameteri(glTarget_, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + + glBindTexture(glTarget_, 0); + + return glHandle_ != 0; +} + +bool GLTexture::doInit(const TextureViewInfo& info) { + (void)info; + return true; +} + +void GLTexture::doDestroy() { + if (glHandle_ != 0) { + glDeleteTextures(1, &glHandle_); + glHandle_ = 0; + } + glTarget_ = 0; +} + +void GLTexture::doResize(u32 width, u32 height) { + if (glHandle_ == 0 || isView_) return; + + glBindTexture(glTarget_, glHandle_); + glTexImage2D(glTarget_, 0, glInternalFormat_, + width, height, 0, + glFormat_, glType_, nullptr); + glBindTexture(glTarget_, 0); +} + +void GLTexture::uploadData(const void* data, u32 level) { + if (glHandle_ == 0 || isView_) return; + + glBindTexture(glTarget_, glHandle_); + glTexSubImage2D(glTarget_, level, 0, 0, + info_.width, info_.height, + glFormat_, glType_, data); + glBindTexture(glTarget_, 0); +} + +void GLTexture::generateMipmaps() { + if (glHandle_ == 0 || isView_) return; + + glBindTexture(glTarget_, glHandle_); + glGenerateMipmap(glTarget_); + glBindTexture(glTarget_, 0); +} + +u32 GLTexture::getGLHandle() const { + if (isView_ && sourceTexture_) { + return static_cast(sourceTexture_)->getGLHandle(); + } + return glHandle_; +} + +u32 GLTexture::getGLTarget() const { + return glTarget_; +} + +} // namespace gfx +} // namespace extra2d diff --git a/Extra2D/src/platform/Input.cpp b/Extra2D/src/platform/Input.cpp new file mode 100644 index 0000000..162a0f3 --- /dev/null +++ b/Extra2D/src/platform/Input.cpp @@ -0,0 +1,380 @@ +#include +#include + +#include +#include +#include + +namespace extra2d { + +Input* Input::instance_ = nullptr; + +Input::Input() { + currentKeyState_.fill(false); + previousKeyState_.fill(false); + currentMouseState_.fill(false); + previousMouseState_.fill(false); + + for (auto& gamepad : gamepads_) { + gamepad.buttonState_.fill(false); + gamepad.axisState_.fill(0.0f); + } +} + +Input::~Input() { + for (auto& gamepad : gamepads_) { + if (gamepad.controller) { + SDL_GameControllerClose(gamepad.controller); + gamepad.controller = nullptr; + } + } +} + +Input* Input::getInstance() { + if (!instance_) { + instance_ = new Input(); + } + return instance_; +} + +void Input::update() { + previousKeyState_ = currentKeyState_; + previousMouseState_ = currentMouseState_; + mouseWheelDelta_ = Vec2{0, 0}; + + beganTouchIds_.clear(); + endedTouchIds_.clear(); + + for (auto& touch : activeTouches_) { + touch.delta = Vec2{0, 0}; + } + + updateGestures(); +} + +void Input::resetFrameState() { + beganTouchIds_.clear(); + endedTouchIds_.clear(); +} + +// ==================== 键盘输入 ==================== + +bool Input::isKeyDown(i32 keycode) const { + if (keycode < 0 || keycode >= static_cast(KEY_COUNT)) return false; + return currentKeyState_[keycode] && !previousKeyState_[keycode]; +} + +bool Input::isKeyUp(i32 keycode) const { + if (keycode < 0 || keycode >= static_cast(KEY_COUNT)) return false; + return !currentKeyState_[keycode] && previousKeyState_[keycode]; +} + +bool Input::isKeyPressed(i32 keycode) const { + if (keycode < 0 || keycode >= static_cast(KEY_COUNT)) return false; + return currentKeyState_[keycode]; +} + +bool Input::isAnyKeyPressed() const { + for (size_t i = 0; i < KEY_COUNT; ++i) { + if (currentKeyState_[i]) return true; + } + return false; +} + +// ==================== 鼠标输入 ==================== + +Vec2 Input::getMousePosition() const { + return mousePosition_; +} + +Vec2 Input::getMouseDelta() const { + return mouseDelta_; +} + +bool Input::isMouseButtonDown(i32 button) const { + if (button < 0 || button >= static_cast(MOUSE_BUTTON_COUNT)) return false; + return currentMouseState_[button] && !previousMouseState_[button]; +} + +bool Input::isMouseButtonUp(i32 button) const { + if (button < 0 || button >= static_cast(MOUSE_BUTTON_COUNT)) return false; + return !currentMouseState_[button] && previousMouseState_[button]; +} + +bool Input::isMouseButtonPressed(i32 button) const { + if (button < 0 || button >= static_cast(MOUSE_BUTTON_COUNT)) return false; + return currentMouseState_[button]; +} + +Vec2 Input::getMouseWheelDelta() const { + return mouseWheelDelta_; +} + +void Input::setMouseLocked(bool locked) { + mouseLocked_ = locked; + SDL_SetRelativeMouseMode(locked ? SDL_TRUE : SDL_FALSE); +} + +bool Input::isMouseLocked() const { + return mouseLocked_; +} + +// ==================== 触摸输入 ==================== + +bool Input::hasTouch() const { + return !activeTouches_.empty(); +} + +i32 Input::getTouchCount() const { + return static_cast(activeTouches_.size()); +} + +const Touch* Input::getTouch(i64 id) const { + for (const auto& touch : activeTouches_) { + if (touch.id == id) { + return &touch; + } + } + return nullptr; +} + +const Touch* Input::getTouchByIndex(i32 index) const { + if (index < 0 || index >= static_cast(activeTouches_.size())) { + return nullptr; + } + return &activeTouches_[index]; +} + +const std::vector& Input::getTouches() const { + return activeTouches_; +} + +bool Input::isTouchDown(i64 id) const { + for (auto touchId : beganTouchIds_) { + if (touchId == id) return true; + } + return false; +} + +bool Input::isTouchUp(i64 id) const { + for (auto touchId : endedTouchIds_) { + if (touchId == id) return true; + } + return false; +} + +bool Input::isTouchMoving(i64 id) const { + const Touch* touch = getTouch(id); + return touch && (touch->delta.x != 0 || touch->delta.y != 0); +} + +// ==================== 多点触控手势 ==================== + +bool Input::isPinching() const { + return pinching_; +} + +float Input::getPinchScale() const { + return pinchScale_; +} + +Vec2 Input::getPinchCenter() const { + return pinchCenter_; +} + +bool Input::isRotating() const { + return rotating_; +} + +float Input::getRotationAngle() const { + return rotationAngle_; +} + +// ==================== 手柄输入 ==================== + +i32 Input::getConnectedGamepadCount() const { + i32 count = 0; + for (const auto& gamepad : gamepads_) { + if (gamepad.connected) ++count; + } + return count; +} + +bool Input::isGamepadConnected(i32 id) const { + if (id < 0 || id >= static_cast(GAMEPAD_COUNT)) return false; + return gamepads_[id].connected; +} + +std::string Input::getGamepadName(i32 id) const { + if (id < 0 || id >= static_cast(GAMEPAD_COUNT)) return ""; + return gamepads_[id].name; +} + +bool Input::isGamepadButtonDown(i32 id, i32 button) const { + if (id < 0 || id >= static_cast(GAMEPAD_COUNT)) return false; + if (button < 0 || button >= static_cast(GAMEPAD_BUTTON_COUNT)) return false; + return gamepads_[id].buttonState_[button]; +} + +float Input::getGamepadAxis(i32 id, i32 axis) const { + if (id < 0 || id >= static_cast(GAMEPAD_COUNT)) return 0.0f; + if (axis < 0 || axis >= static_cast(GAMEPAD_AXIS_COUNT)) return 0.0f; + return gamepads_[id].axisState_[axis]; +} + +void Input::setGamepadRumble(i32 id, float lowFreq, float highFreq, u32 durationMs) { + if (id < 0 || id >= static_cast(GAMEPAD_COUNT)) return; + if (!gamepads_[id].controller) return; + + Uint16 low = static_cast(std::clamp(lowFreq, 0.0f, 1.0f) * 0xFFFF); + Uint16 high = static_cast(std::clamp(highFreq, 0.0f, 1.0f) * 0xFFFF); + + SDL_GameControllerRumble(gamepads_[id].controller, low, high, durationMs); +} + +// ==================== 内部方法 ==================== + +void Input::handleKeyDown(i32 keycode) { + if (keycode >= 0 && keycode < static_cast(KEY_COUNT)) { + currentKeyState_[keycode] = true; + } +} + +void Input::handleKeyUp(i32 keycode) { + if (keycode >= 0 && keycode < static_cast(KEY_COUNT)) { + currentKeyState_[keycode] = false; + } +} + +void Input::handleMouseMove(float x, float y, float dx, float dy) { + mousePosition_ = Vec2{x, y}; + mouseDelta_ = Vec2{dx, dy}; +} + +void Input::handleMouseButtonDown(i32 button) { + if (button >= 0 && button < static_cast(MOUSE_BUTTON_COUNT)) { + currentMouseState_[button] = true; + } +} + +void Input::handleMouseButtonUp(i32 button) { + if (button >= 0 && button < static_cast(MOUSE_BUTTON_COUNT)) { + currentMouseState_[button] = false; + } +} + +void Input::handleMouseWheel(float dx, float dy) { + mouseWheelDelta_ = Vec2{dx, dy}; +} + +void Input::handleTouchDown(i64 id, float x, float y, float pressure) { + Touch touch; + touch.id = id; + touch.position = Vec2{x, y}; + touch.startPosition = Vec2{x, y}; + touch.delta = Vec2{0, 0}; + touch.pressure = pressure; + touch.timestamp = static_cast(SDL_GetTicks()) / 1000.0f; + + activeTouches_.push_back(touch); + beganTouchIds_.push_back(id); +} + +void Input::handleTouchMove(i64 id, float x, float y, float dx, float dy, float pressure) { + for (auto& touch : activeTouches_) { + if (touch.id == id) { + touch.position = Vec2{x, y}; + touch.delta = Vec2{dx, dy}; + touch.pressure = pressure; + break; + } + } +} + +void Input::handleTouchUp(i64 id) { + for (auto it = activeTouches_.begin(); it != activeTouches_.end(); ++it) { + if (it->id == id) { + activeTouches_.erase(it); + break; + } + } + endedTouchIds_.push_back(id); +} + +void Input::handleGamepadConnected(i32 id) { + if (id < 0 || id >= static_cast(GAMEPAD_COUNT)) return; + + if (SDL_IsGameController(id)) { + SDL_GameController* controller = SDL_GameControllerOpen(id); + if (controller) { + gamepads_[id].controller = controller; + gamepads_[id].connected = true; + gamepads_[id].name = SDL_GameControllerName(controller) ? SDL_GameControllerName(controller) : "Unknown"; + } + } +} + +void Input::handleGamepadDisconnected(i32 id) { + if (id < 0 || id >= static_cast(GAMEPAD_COUNT)) return; + + if (gamepads_[id].controller) { + SDL_GameControllerClose(gamepads_[id].controller); + gamepads_[id].controller = nullptr; + } + gamepads_[id].connected = false; + gamepads_[id].name.clear(); + gamepads_[id].buttonState_.fill(false); + gamepads_[id].axisState_.fill(0.0f); +} + +void Input::handleGamepadButton(i32 id, i32 button, bool pressed) { + if (id < 0 || id >= static_cast(GAMEPAD_COUNT)) return; + if (button < 0 || button >= static_cast(GAMEPAD_BUTTON_COUNT)) return; + + gamepads_[id].buttonState_[button] = pressed; +} + +void Input::handleGamepadAxis(i32 id, i32 axis, float value) { + if (id < 0 || id >= static_cast(GAMEPAD_COUNT)) return; + if (axis < 0 || axis >= static_cast(GAMEPAD_AXIS_COUNT)) return; + + gamepads_[id].axisState_[axis] = value; +} + +void Input::updateGestures() { + pinching_ = false; + rotating_ = false; + + if (activeTouches_.size() == 2) { + const Touch& t0 = activeTouches_[0]; + const Touch& t1 = activeTouches_[1]; + + Vec2 diff = t1.position - t0.position; + float distance = diff.length(); + pinchCenter_ = (t0.position + t1.position) * 0.5f; + + if (lastPinchDistance_ > 0) { + pinchScale_ = distance / lastPinchDistance_; + if (std::abs(pinchScale_ - 1.0f) > 0.01f) { + pinching_ = true; + } + } + lastPinchDistance_ = distance; + + float angle = std::atan2(diff.y, diff.x); + if (std::abs(lastRotationAngle_) > 0.001f) { + float angleDiff = angle - lastRotationAngle_; + if (std::abs(angleDiff) > 0.01f) { + rotationAngle_ = angleDiff * 180.0f / 3.14159265f; + rotating_ = true; + } + } + lastRotationAngle_ = angle; + } else { + lastPinchDistance_ = 0; + lastRotationAngle_ = 0; + pinchScale_ = 1.0f; + } +} + +} // namespace extra2d diff --git a/Extra2D/src/platform/SDLHelper.cpp b/Extra2D/src/platform/SDLHelper.cpp new file mode 100644 index 0000000..61293ff --- /dev/null +++ b/Extra2D/src/platform/SDLHelper.cpp @@ -0,0 +1,417 @@ +#include + +#include +#include + +namespace extra2d { + +bool SDLHelper::initialized_ = false; +bool SDLHelper::cursorVisible_ = true; +bool SDLHelper::cursorLocked_ = false; +i64 SDLHelper::touchDeviceId_ = -1; + +i32 SDLHelper::init() { + if (initialized_) { + return 0; + } + + if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_EVENTS | + SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER | SDL_INIT_HAPTIC) != 0) { + return -1; + } + + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3); + SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE); + SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); + SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); + SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8); + + SDL_GameControllerEventState(SDL_ENABLE); + + i32 numTouchDevices = SDL_GetNumTouchDevices(); + if (numTouchDevices > 0) { + touchDeviceId_ = static_cast(SDL_GetTouchDevice(0)); + } + + initialized_ = true; + return 0; +} + +void SDLHelper::quit() { + if (!initialized_) { + return; + } + + SDL_Quit(); + initialized_ = false; +} + +bool SDLHelper::isInitialized() { + return initialized_; +} + +SDL_Window* SDLHelper::createWindow(const char* title, i32 w, i32 h, u32 flags) { + return SDL_CreateWindow(title, + SDL_WINDOWPOS_CENTERED, + SDL_WINDOWPOS_CENTERED, + w, h, flags); +} + +SDL_Window* SDLHelper::createWindow(const char* title, i32 x, i32 y, i32 w, i32 h, u32 flags) { + return SDL_CreateWindow(title, x, y, w, h, flags); +} + +void SDLHelper::destroyWindow(SDL_Window* window) { + if (window) { + SDL_DestroyWindow(window); + } +} + +void* SDLHelper::getWindowHandle(SDL_Window* window) { + if (!window) return nullptr; + + SDL_SysWMinfo wmInfo; + SDL_VERSION(&wmInfo.version); + if (SDL_GetWindowWMInfo(window, &wmInfo)) { +#if defined(_WIN32) + return wmInfo.info.win.window; +#elif defined(__APPLE__) + return wmInfo.info.cocoa.window; +#elif defined(__linux__) + return (void*)(uintptr_t)wmInfo.info.x11.window; +#else + return nullptr; +#endif + } + return nullptr; +} + +Vec2 SDLHelper::getWindowPosition(SDL_Window* window) { + if (!window) return Vec2{0, 0}; + + i32 x, y; + SDL_GetWindowPosition(window, &x, &y); + return Vec2{static_cast(x), static_cast(y)}; +} + +Vec2 SDLHelper::getWindowSize(SDL_Window* window) { + if (!window) return Vec2{0, 0}; + + i32 w, h; + SDL_GetWindowSize(window, &w, &h); + return Vec2{static_cast(w), static_cast(h)}; +} + +bool SDLHelper::isWindowMinimized(SDL_Window* window) { + if (!window) return false; + return (SDL_GetWindowFlags(window) & SDL_WINDOW_MINIMIZED) != 0; +} + +bool SDLHelper::isWindowFocused(SDL_Window* window) { + if (!window) return false; + u32 flags = SDL_GetWindowFlags(window); + return (flags & (SDL_WINDOW_INPUT_FOCUS | SDL_WINDOW_MOUSE_FOCUS)) != 0; +} + +void SDLHelper::setCursorEnabled(bool enabled) { + SDL_ShowCursor(enabled ? SDL_ENABLE : SDL_DISABLE); + cursorVisible_ = enabled; +} + +bool SDLHelper::isCursorEnabled() { + return cursorVisible_; +} + +void SDLHelper::setCursorLocked(bool locked) { + SDL_SetRelativeMouseMode(locked ? SDL_TRUE : SDL_FALSE); + cursorLocked_ = locked; +} + +bool SDLHelper::isCursorLocked() { + return cursorLocked_; +} + +void SDLHelper::startTextInput() { + SDL_StartTextInput(); +} + +void SDLHelper::stopTextInput() { + SDL_StopTextInput(); +} + +bool SDLHelper::isTextInputActive() { + return SDL_IsTextInputActive() == SDL_TRUE; +} + +void SDLHelper::dispatchSDLEvent(const SDL_Event& event) { + switch (event.type) { + case SDL_QUIT: + events::bus()->emit(WindowEvent{WindowEventType::Quit}); + break; + + case SDL_WINDOWEVENT: + dispatchWindowEvent(event.window); + break; + + case SDL_KEYDOWN: + case SDL_KEYUP: + dispatchKeyboardEvent(event.key); + break; + + case SDL_MOUSEMOTION: + dispatchMouseMotionEvent(event.motion); + break; + + case SDL_MOUSEBUTTONDOWN: + case SDL_MOUSEBUTTONUP: + dispatchMouseButtonEvent(event.button); + break; + + case SDL_MOUSEWHEEL: + dispatchMouseWheelEvent(event.wheel); + break; + + case SDL_CONTROLLERDEVICEADDED: + case SDL_CONTROLLERDEVICEREMOVED: + dispatchControllerDeviceEvent(event.cdevice); + break; + + case SDL_CONTROLLERBUTTONDOWN: + case SDL_CONTROLLERBUTTONUP: + dispatchControllerButtonEvent(event.cbutton); + break; + + case SDL_CONTROLLERAXISMOTION: + dispatchControllerAxisEvent(event.caxis); + break; + + case SDL_FINGERDOWN: + case SDL_FINGERUP: + case SDL_FINGERMOTION: + dispatchTouchFingerEvent(event.tfinger); + break; + + case SDL_MULTIGESTURE: + dispatchMultiGestureEvent(event.mgesture); + break; + + case SDL_DOLLARGESTURE: + case SDL_DOLLARRECORD: + dispatchDollarGestureEvent(event.dgesture); + break; + + default: + break; + } +} + +void SDLHelper::pollAndDispatchEvents() { + SDL_Event event; + while (SDL_PollEvent(&event)) { + dispatchSDLEvent(event); + } +} + +i64 SDLHelper::getTouchDeviceId() { + return touchDeviceId_; +} + +bool SDLHelper::hasTouchDevice() { + return touchDeviceId_ >= 0; +} + +void SDLHelper::dispatchWindowEvent(const SDL_WindowEvent& event) { + WindowEvent windowEvent; + + switch (event.event) { + case SDL_WINDOWEVENT_RESIZED: + case SDL_WINDOWEVENT_SIZE_CHANGED: + windowEvent.type = WindowEventType::Resize; + windowEvent.width = static_cast(event.data1); + windowEvent.height = static_cast(event.data2); + break; + case SDL_WINDOWEVENT_FOCUS_GAINED: + windowEvent.type = WindowEventType::FocusGained; + break; + case SDL_WINDOWEVENT_FOCUS_LOST: + windowEvent.type = WindowEventType::FocusLost; + break; + case SDL_WINDOWEVENT_CLOSE: + windowEvent.type = WindowEventType::Close; + break; + case SDL_WINDOWEVENT_SHOWN: + windowEvent.type = WindowEventType::Show; + break; + case SDL_WINDOWEVENT_HIDDEN: + windowEvent.type = WindowEventType::Hide; + break; + case SDL_WINDOWEVENT_MINIMIZED: + windowEvent.type = WindowEventType::Minimize; + break; + case SDL_WINDOWEVENT_MAXIMIZED: + windowEvent.type = WindowEventType::Maximize; + break; + case SDL_WINDOWEVENT_RESTORED: + windowEvent.type = WindowEventType::Restore; + break; + default: + return; + } + + events::bus()->emit(windowEvent); +} + +void SDLHelper::dispatchKeyboardEvent(const SDL_KeyboardEvent& event) { + KeyboardEvent keyEvent; + keyEvent.key = event.keysym.sym; + keyEvent.scancode = event.keysym.scancode; + + switch (event.type) { + case SDL_KEYDOWN: + keyEvent.action = event.repeat ? KeyAction::Repeat : KeyAction::Press; + break; + case SDL_KEYUP: + keyEvent.action = KeyAction::Release; + break; + default: + return; + } + + keyEvent.shift = (event.keysym.mod & KMOD_SHIFT) != 0; + keyEvent.ctrl = (event.keysym.mod & KMOD_CTRL) != 0; + keyEvent.alt = (event.keysym.mod & KMOD_ALT) != 0; + keyEvent.gui = (event.keysym.mod & KMOD_GUI) != 0; + + events::bus()->emit(keyEvent); +} + +void SDLHelper::dispatchMouseMotionEvent(const SDL_MouseMotionEvent& event) { + MouseEvent mouseEvent; + mouseEvent.type = MouseEventType::Move; + mouseEvent.x = static_cast(event.x); + mouseEvent.y = static_cast(event.y); + mouseEvent.xDelta = static_cast(event.xrel); + mouseEvent.yDelta = static_cast(event.yrel); + + events::bus()->emit(mouseEvent); +} + +void SDLHelper::dispatchMouseButtonEvent(const SDL_MouseButtonEvent& event) { + MouseEvent mouseEvent; + mouseEvent.type = (event.type == SDL_MOUSEBUTTONDOWN) ? MouseEventType::Down : MouseEventType::Up; + mouseEvent.x = static_cast(event.x); + mouseEvent.y = static_cast(event.y); + mouseEvent.button = static_cast(event.button); + + events::bus()->emit(mouseEvent); +} + +void SDLHelper::dispatchMouseWheelEvent(const SDL_MouseWheelEvent& event) { + MouseEvent mouseEvent; + mouseEvent.type = MouseEventType::Wheel; + mouseEvent.xDelta = static_cast(event.x); + mouseEvent.yDelta = static_cast(event.y); + + events::bus()->emit(mouseEvent); +} + +void SDLHelper::dispatchControllerDeviceEvent(const SDL_ControllerDeviceEvent& event) { + ControllerEvent controllerEvent; + + switch (event.type) { + case SDL_CONTROLLERDEVICEADDED: + controllerEvent.type = ControllerEventType::Connected; + controllerEvent.controllerId = event.which; + break; + case SDL_CONTROLLERDEVICEREMOVED: + controllerEvent.type = ControllerEventType::Disconnected; + controllerEvent.controllerId = event.which; + break; + default: + return; + } + + events::bus()->emit(controllerEvent); +} + +void SDLHelper::dispatchControllerButtonEvent(const SDL_ControllerButtonEvent& event) { + ControllerEvent controllerEvent; + controllerEvent.type = (event.type == SDL_CONTROLLERBUTTONDOWN) + ? ControllerEventType::ButtonDown + : ControllerEventType::ButtonUp; + controllerEvent.controllerId = event.which; + controllerEvent.button = event.button; + + events::bus()->emit(controllerEvent); +} + +void SDLHelper::dispatchControllerAxisEvent(const SDL_ControllerAxisEvent& event) { + ControllerEvent controllerEvent; + controllerEvent.type = ControllerEventType::AxisMotion; + controllerEvent.controllerId = event.which; + controllerEvent.axis = event.axis; + controllerEvent.value = static_cast(event.value) / 32767.0f; + + events::bus()->emit(controllerEvent); +} + +void SDLHelper::dispatchTouchFingerEvent(const SDL_TouchFingerEvent& event) { + TouchEvent touchEvent; + + switch (event.type) { + case SDL_FINGERDOWN: + touchEvent.type = TouchEventType::Began; + break; + case SDL_FINGERMOTION: + touchEvent.type = TouchEventType::Moved; + break; + case SDL_FINGERUP: + touchEvent.type = TouchEventType::Ended; + break; + default: + return; + } + + TouchPoint touch; + touch.id = static_cast(event.fingerId); + touch.x = event.x; + touch.y = event.y; + touch.dx = event.dx; + touch.dy = event.dy; + touch.pressure = event.pressure; + + touchEvent.touches.push_back(touch); + touchEvent.touchDeviceId = static_cast(event.touchId); + + events::bus()->emit(touchEvent); +} + +void SDLHelper::dispatchMultiGestureEvent(const SDL_MultiGestureEvent& event) { + GestureEvent gestureEvent; + + if (event.numFingers == 2) { + if (std::abs(event.dDist) > 0.001f) { + gestureEvent.type = GestureEventType::Pinch; + gestureEvent.scale = 1.0f + event.dDist; + gestureEvent.center = Vec2{event.x, event.y}; + events::bus()->emit(gestureEvent); + } + + if (std::abs(event.dTheta) > 0.01f) { + gestureEvent.type = GestureEventType::Rotate; + gestureEvent.rotation = event.dTheta * 180.0f / 3.14159265f; + gestureEvent.center = Vec2{event.x, event.y}; + events::bus()->emit(gestureEvent); + } + } +} + +void SDLHelper::dispatchDollarGestureEvent(const SDL_DollarGestureEvent& event) { + GestureEvent gestureEvent; + gestureEvent.center = Vec2{event.x, event.y}; + + events::bus()->emit(gestureEvent); +} + +} // namespace extra2d diff --git a/Extra2D/src/platform/Window.cpp b/Extra2D/src/platform/Window.cpp new file mode 100644 index 0000000..8e5efea --- /dev/null +++ b/Extra2D/src/platform/Window.cpp @@ -0,0 +1,256 @@ +#include +#include + +#include + +namespace extra2d { + +Window* Window::instance_ = nullptr; + +Window::Window() {} + +Window::~Window() { + shutdown(); +} + +Window* Window::getInstance() { + if (!instance_) { + instance_ = new Window(); + } + return instance_; +} + +i32 Window::initialize(const WindowConfig& config) { + if (initialized_) { + return 0; + } + + u32 flags = SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN; + + if (config.resizable) { + flags |= SDL_WINDOW_RESIZABLE; + } + if (config.fullscreen) { + flags |= SDL_WINDOW_FULLSCREEN_DESKTOP; + } + if (config.borderless) { + flags |= SDL_WINDOW_BORDERLESS; + } + if (config.highDPI) { + flags |= SDL_WINDOW_ALLOW_HIGHDPI; + } + + sdlWindow_ = SDLHelper::createWindow( + config.title.c_str(), + config.width, + config.height, + flags + ); + + if (!sdlWindow_) { + return -1; + } + + glContext_ = SDL_GL_CreateContext(sdlWindow_); + if (!glContext_) { + SDLHelper::destroyWindow(sdlWindow_); + sdlWindow_ = nullptr; + return -2; + } + + setVSync(config.vsync); + + fullscreen_ = config.fullscreen; + vsync_ = config.vsync; + initialized_ = true; + + return 0; +} + +void Window::shutdown() { + if (!initialized_) { + return; + } + + if (glContext_) { + SDL_GL_DeleteContext(glContext_); + glContext_ = nullptr; + } + + if (sdlWindow_) { + SDLHelper::destroyWindow(sdlWindow_); + sdlWindow_ = nullptr; + } + + initialized_ = false; +} + +bool Window::isInitialized() const { + return initialized_; +} + +Vec2 Window::getSize() const { + if (!sdlWindow_) return Vec2{0, 0}; + + i32 w, h; + SDL_GetWindowSize(sdlWindow_, &w, &h); + return Vec2{static_cast(w), static_cast(h)}; +} + +void Window::setSize(i32 width, i32 height) { + if (sdlWindow_) { + SDL_SetWindowSize(sdlWindow_, width, height); + } +} + +Vec2 Window::getPosition() const { + if (!sdlWindow_) return Vec2{0, 0}; + + i32 x, y; + SDL_GetWindowPosition(sdlWindow_, &x, &y); + return Vec2{static_cast(x), static_cast(y)}; +} + +void Window::setPosition(i32 x, i32 y) { + if (sdlWindow_) { + SDL_SetWindowPosition(sdlWindow_, x, y); + } +} + +std::string Window::getTitle() const { + if (!sdlWindow_) return ""; + return SDL_GetWindowTitle(sdlWindow_); +} + +void Window::setTitle(const std::string& title) { + if (sdlWindow_) { + SDL_SetWindowTitle(sdlWindow_, title.c_str()); + } +} + +bool Window::isFullscreen() const { + return fullscreen_; +} + +void Window::setFullscreen(bool fullscreen) { + if (!sdlWindow_) return; + + if (fullscreen != fullscreen_) { + SDL_SetWindowFullscreen(sdlWindow_, + fullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0); + fullscreen_ = fullscreen; + } +} + +void Window::setBorderless(bool borderless) { + if (sdlWindow_) { + SDL_SetWindowBordered(sdlWindow_, borderless ? SDL_FALSE : SDL_TRUE); + } +} + +bool Window::isResizable() const { + if (!sdlWindow_) return false; + return (SDL_GetWindowFlags(sdlWindow_) & SDL_WINDOW_RESIZABLE) != 0; +} + +void Window::setResizable(bool resizable) { + if (sdlWindow_) { + SDL_SetWindowResizable(sdlWindow_, resizable ? SDL_TRUE : SDL_FALSE); + } +} + +bool Window::hasFocus() const { + if (!sdlWindow_) return false; + return (SDL_GetWindowFlags(sdlWindow_) & SDL_WINDOW_INPUT_FOCUS) != 0; +} + +bool Window::isMinimized() const { + if (!sdlWindow_) return false; + return (SDL_GetWindowFlags(sdlWindow_) & SDL_WINDOW_MINIMIZED) != 0; +} + +bool Window::isMaximized() const { + if (!sdlWindow_) return false; + return (SDL_GetWindowFlags(sdlWindow_) & SDL_WINDOW_MAXIMIZED) != 0; +} + +void Window::setVSync(bool enabled) { + if (glContext_) { + SDL_GL_SetSwapInterval(enabled ? 1 : 0); + vsync_ = enabled; + } +} + +bool Window::isVSync() const { + return vsync_; +} + +void Window::show() { + if (sdlWindow_) { + SDL_ShowWindow(sdlWindow_); + } +} + +void Window::hide() { + if (sdlWindow_) { + SDL_HideWindow(sdlWindow_); + } +} + +void Window::minimize() { + if (sdlWindow_) { + SDL_MinimizeWindow(sdlWindow_); + } +} + +void Window::maximize() { + if (sdlWindow_) { + SDL_MaximizeWindow(sdlWindow_); + } +} + +void Window::restore() { + if (sdlWindow_) { + SDL_RestoreWindow(sdlWindow_); + } +} + +void Window::swapBuffers() { + if (sdlWindow_ && glContext_) { + SDL_GL_SwapWindow(sdlWindow_); + } +} + +SDL_Window* Window::getSDLWindow() { + return sdlWindow_; +} + +SDL_GLContext Window::getGLContext() { + return glContext_; +} + +Vec2 Window::getFramebufferSize() const { + if (!sdlWindow_) return Vec2{0, 0}; + + i32 w, h; + SDL_GL_GetDrawableSize(sdlWindow_, &w, &h); + return Vec2{static_cast(w), static_cast(h)}; +} + +float Window::getContentScale() const { + if (!sdlWindow_) return 1.0f; + + Vec2 windowSize = getSize(); + Vec2 fbSize = getFramebufferSize(); + + if (windowSize.x > 0) { + return fbSize.x / windowSize.x; + } + return 1.0f; +} + +void* Window::getNativeHandle() { + return SDLHelper::getWindowHandle(sdlWindow_); +} + +} // namespace extra2d diff --git a/Extra2D/src/platform/glfw/glfw_window.cpp b/Extra2D/src/platform/glfw/glfw_window.cpp deleted file mode 100644 index 71b7448..0000000 --- a/Extra2D/src/platform/glfw/glfw_window.cpp +++ /dev/null @@ -1,666 +0,0 @@ -#include - -#include -#include -#include -#include - -namespace extra2d { - -GLFWWindow::GLFWWindow() {} - -GLFWWindow::~GLFWWindow() { destroy(); } - -bool GLFWWindow::create(const std::string &title, int width, int height, - bool vsync) { - if (!initGLFW()) { - return false; - } - - glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); - glfwWindowHint(GLFW_DOUBLEBUFFER, GLFW_TRUE); - -#ifdef __SWITCH__ - glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); - glfwWindowHint(GLFW_DECORATED, GLFW_FALSE); - fullscreen_ = true; -#else - glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); -#endif - - GLFWmonitor *monitor = nullptr; -#ifdef __SWITCH__ - monitor = glfwGetPrimaryMonitor(); -#endif - - handle_ = - glfwCreateWindow(width, height, title.c_str(), monitor, nullptr); - - if (!handle_) { - E2D_ERROR("创建 GLFW 窗口失败"); - deinitGLFW(); - return false; - } - -#ifndef __SWITCH__ - if (!fullscreen_ && !monitor) { - GLFWmonitor *primaryMonitor = glfwGetPrimaryMonitor(); - if (primaryMonitor) { - const GLFWvidmode *mode = glfwGetVideoMode(primaryMonitor); - if (mode) { - int screenWidth = mode->width; - int screenHeight = mode->height; - int windowX = (screenWidth - width) / 2; - int windowY = (screenHeight - height) / 2; - glfwSetWindowPos(handle_, windowX, windowY); - } - } - } -#endif - - glfwMakeContextCurrent(handle_); - glfwSwapInterval(vsync ? 1 : 0); - vsync_ = vsync; - - glfwGetWindowSize(handle_, &w_, &h_); - updateContentScale(); - - glfwSetWindowUserPointer(handle_, this); - glfwSetFramebufferSizeCallback(handle_, framebufferSizeCallback); - glfwSetWindowCloseCallback(handle_, windowCloseCallback); - glfwSetWindowFocusCallback(handle_, windowFocusCallback); - glfwSetWindowIconifyCallback(handle_, windowIconifyCallback); - glfwSetCursorPosCallback(handle_, cursorPosCallback); - glfwSetMouseButtonCallback(handle_, mouseButtonCallback); - glfwSetScrollCallback(handle_, scrollCallback); - glfwSetKeyCallback(handle_, keyCallback); - glfwSetJoystickCallback(joystickCallback); - - E2D_INFO("GLFW 窗口创建成功: {}x{}", w_, h_); - return true; -} - -void GLFWWindow::destroy() { - if (handle_) { - glfwDestroyWindow(handle_); - handle_ = nullptr; - } - - deinitGLFW(); -} - -void GLFWWindow::poll() { - if (!handle_) - return; - - glfwPollEvents(); -} - -void GLFWWindow::swap() { - if (handle_) { - glfwSwapBuffers(handle_); - } -} - -bool GLFWWindow::shouldClose() const { - if (!handle_) - return true; - return shouldClose_ || glfwWindowShouldClose(handle_); -} - -void GLFWWindow::close() { - shouldClose_ = true; - if (handle_) { - glfwSetWindowShouldClose(handle_, GLFW_TRUE); - } -} - -void GLFWWindow::title(const std::string &t) { - if (handle_) { - glfwSetWindowTitle(handle_, t.c_str()); - } -} - -void GLFWWindow::size(int w, int h) { - if (handle_) { - glfwSetWindowSize(handle_, w, h); - w_ = w; - h_ = h; - } -} - -void GLFWWindow::pos(int x, int y) { -#ifndef __SWITCH__ - if (handle_) { - glfwSetWindowPos(handle_, x, y); - } -#else - (void)x; - (void)y; -#endif -} - -void GLFWWindow::fullscreen(bool fs) { -#ifndef __SWITCH__ - if (!handle_) - return; - - if (fs == fullscreen_) - return; - - if (fs) { - GLFWmonitor *monitor = glfwGetPrimaryMonitor(); - const GLFWvidmode *mode = glfwGetVideoMode(monitor); - glfwSetWindowMonitor(handle_, monitor, 0, 0, mode->width, mode->height, - mode->refreshRate); - } else { - glfwSetWindowMonitor(handle_, nullptr, 100, 100, 1280, 720, 0); - } - fullscreen_ = fs; - glfwGetWindowSize(handle_, &w_, &h_); - updateContentScale(); -#else - (void)fs; -#endif -} - -void GLFWWindow::vsync(bool v) { - if (handle_) { - glfwSwapInterval(v ? 1 : 0); - vsync_ = v; - } -} - -void GLFWWindow::visible(bool v) { -#ifndef __SWITCH__ - if (handle_) { - if (v) { - glfwShowWindow(handle_); - } else { - glfwHideWindow(handle_); - } - } -#else - (void)v; -#endif -} - -Size GLFWWindow::size() const { - return Size(static_cast(w_), static_cast(h_)); -} - -Vec2 GLFWWindow::pos() const { - int x = 0, y = 0; -#ifndef __SWITCH__ - if (handle_) { - glfwGetWindowPos(handle_, &x, &y); - } -#endif - return Vec2(static_cast(x), static_cast(y)); -} - -void GLFWWindow::cursor(Cursor c) { -#ifndef __SWITCH__ - if (!handle_) - return; - - if (c == Cursor::Hidden) { - glfwSetInputMode(handle_, GLFW_CURSOR, GLFW_CURSOR_HIDDEN); - return; - } - - glfwSetInputMode(handle_, GLFW_CURSOR, GLFW_CURSOR_NORMAL); - - GLFWcursor *glfwCursor = nullptr; - switch (c) { - case Cursor::Arrow: - glfwCursor = glfwCreateStandardCursor(GLFW_ARROW_CURSOR); - break; - case Cursor::IBeam: - glfwCursor = glfwCreateStandardCursor(GLFW_IBEAM_CURSOR); - break; - case Cursor::Crosshair: - glfwCursor = glfwCreateStandardCursor(GLFW_CROSSHAIR_CURSOR); - break; - case Cursor::Hand: - glfwCursor = glfwCreateStandardCursor(GLFW_HAND_CURSOR); - break; - case Cursor::HResize: - glfwCursor = glfwCreateStandardCursor(GLFW_HRESIZE_CURSOR); - break; - case Cursor::VResize: - glfwCursor = glfwCreateStandardCursor(GLFW_VRESIZE_CURSOR); - break; - default: - glfwCursor = glfwCreateStandardCursor(GLFW_ARROW_CURSOR); - break; - } - - if (glfwCursor) { - glfwSetCursor(handle_, glfwCursor); - } -#else - (void)c; -#endif -} - -void GLFWWindow::showCursor(bool show) { -#ifndef __SWITCH__ - if (handle_) { - glfwSetInputMode(handle_, GLFW_CURSOR, - show ? GLFW_CURSOR_NORMAL : GLFW_CURSOR_HIDDEN); - cursorVisible_ = show; - } -#else - (void)show; -#endif -} - -void GLFWWindow::lockCursor(bool lock) { -#ifndef __SWITCH__ - if (handle_) { - glfwSetInputMode(handle_, GLFW_CURSOR, - lock ? GLFW_CURSOR_DISABLED : GLFW_CURSOR_NORMAL); - cursorLocked_ = lock; - } -#else - (void)lock; -#endif -} - -void *GLFWWindow::native() const { return handle_; } - -bool GLFWWindow::initGLFW() { - static int glfwInitCount = 0; - if (glfwInitCount == 0) { - if (!glfwInit()) { - E2D_ERROR("初始化 GLFW 失败"); - return false; - } - glfwInitCount++; - } - return true; -} - -void GLFWWindow::deinitGLFW() { - static int glfwInitCount = 1; - glfwInitCount--; - if (glfwInitCount == 0) { - glfwTerminate(); - } -} - -void GLFWWindow::updateContentScale() { - if (handle_) { - int fbWidth, fbHeight; - glfwGetFramebufferSize(handle_, &fbWidth, &fbHeight); - scaleX_ = fbWidth > 0 ? static_cast(fbWidth) / w_ : 1.0f; - scaleY_ = fbHeight > 0 ? static_cast(fbHeight) / h_ : 1.0f; - } -} - -// 静态回调函数 -void GLFWWindow::framebufferSizeCallback(GLFWwindow *window, int width, - int height) { - GLFWWindow *self = - static_cast(glfwGetWindowUserPointer(window)); - if (self) { - self->w_ = width; - self->h_ = height; - self->updateContentScale(); - if (self->resizeFn_) { - self->resizeFn_(width, height); - } - } - - // 将事件推送到事件服务 - auto eventService = ServiceLocator::instance().get(); - if (eventService) { - Event e = Event::windowResize(width, height); - eventService->push(e); - } -} - -void GLFWWindow::windowCloseCallback(GLFWwindow *window) { - GLFWWindow *self = - static_cast(glfwGetWindowUserPointer(window)); - if (self) { - self->shouldClose_ = true; - if (self->closeFn_) { - self->closeFn_(); - } - } - - // 将事件推送到事件服务 - auto eventService = ServiceLocator::instance().get(); - if (eventService) { - Event e = Event::windowClose(); - eventService->push(e); - } -} - -void GLFWWindow::windowFocusCallback(GLFWwindow *window, int focused) { - GLFWWindow *self = - static_cast(glfwGetWindowUserPointer(window)); - if (self) { - self->focused_ = (focused == GLFW_TRUE); - if (self->focusFn_) { - self->focusFn_(self->focused_); - } - } -} - -void GLFWWindow::windowIconifyCallback(GLFWwindow *window, int iconified) { - GLFWWindow *self = - static_cast(glfwGetWindowUserPointer(window)); - if (self) { - self->minimized_ = (iconified == GLFW_TRUE); - } -} - -void GLFWWindow::cursorPosCallback(GLFWwindow *window, double xpos, - double ypos) { - GLFWWindow *self = - static_cast(glfwGetWindowUserPointer(window)); - - // 将事件推送到事件服务 - auto eventService = ServiceLocator::instance().get(); - if (eventService) { - Vec2 pos{static_cast(xpos), static_cast(ypos)}; - Vec2 delta{0.0f, 0.0f}; // GLFW 回调中没有增量,需要在其他地方计算 - Event e = Event::mouseMove(pos, delta); - eventService->push(e); - } -} - -void GLFWWindow::mouseButtonCallback(GLFWwindow *window, int button, int action, - int mods) { - GLFWWindow *self = - static_cast(glfwGetWindowUserPointer(window)); - - // 将事件推送到事件服务 - auto eventService = ServiceLocator::instance().get(); - if (eventService) { - double x, y; - glfwGetCursorPos(window, &x, &y); - Vec2 pos{static_cast(x), static_cast(y)}; - - if (action == GLFW_PRESS) { - Event e = Event::mousePress(button, mods, pos); - eventService->push(e); - } else if (action == GLFW_RELEASE) { - Event e = Event::mouseRelease(button, mods, pos); - eventService->push(e); - } - } -} - -void GLFWWindow::scrollCallback(GLFWwindow *window, double xoffset, - double yoffset) { - GLFWWindow *self = - static_cast(glfwGetWindowUserPointer(window)); - - // 将事件推送到事件服务 - auto eventService = ServiceLocator::instance().get(); - if (eventService) { - double x, y; - glfwGetCursorPos(window, &x, &y); - Vec2 offset{static_cast(xoffset), static_cast(yoffset)}; - Vec2 pos{static_cast(x), static_cast(y)}; - Event e = Event::mouseScroll(offset, pos); - eventService->push(e); - } -} - -void GLFWWindow::keyCallback(GLFWwindow *window, int key, int scancode, - int action, int mods) { - GLFWWindow *self = - static_cast(glfwGetWindowUserPointer(window)); - - // 将事件推送到事件服务 - auto eventService = ServiceLocator::instance().get(); - if (eventService) { - // 将 GLFW key code 转换为引擎 Key 枚举值 - Key eKey = Key::None; - switch (key) { - case GLFW_KEY_A: - eKey = Key::A; - break; - case GLFW_KEY_B: - eKey = Key::B; - break; - case GLFW_KEY_C: - eKey = Key::C; - break; - case GLFW_KEY_D: - eKey = Key::D; - break; - case GLFW_KEY_E: - eKey = Key::E; - break; - case GLFW_KEY_F: - eKey = Key::F; - break; - case GLFW_KEY_G: - eKey = Key::G; - break; - case GLFW_KEY_H: - eKey = Key::H; - break; - case GLFW_KEY_I: - eKey = Key::I; - break; - case GLFW_KEY_J: - eKey = Key::J; - break; - case GLFW_KEY_K: - eKey = Key::K; - break; - case GLFW_KEY_L: - eKey = Key::L; - break; - case GLFW_KEY_M: - eKey = Key::M; - break; - case GLFW_KEY_N: - eKey = Key::N; - break; - case GLFW_KEY_O: - eKey = Key::O; - break; - case GLFW_KEY_P: - eKey = Key::P; - break; - case GLFW_KEY_Q: - eKey = Key::Q; - break; - case GLFW_KEY_R: - eKey = Key::R; - break; - case GLFW_KEY_S: - eKey = Key::S; - break; - case GLFW_KEY_T: - eKey = Key::T; - break; - case GLFW_KEY_U: - eKey = Key::U; - break; - case GLFW_KEY_V: - eKey = Key::V; - break; - case GLFW_KEY_W: - eKey = Key::W; - break; - case GLFW_KEY_X: - eKey = Key::X; - break; - case GLFW_KEY_Y: - eKey = Key::Y; - break; - case GLFW_KEY_Z: - eKey = Key::Z; - break; - case GLFW_KEY_0: - eKey = Key::Num0; - break; - case GLFW_KEY_1: - eKey = Key::Num1; - break; - case GLFW_KEY_2: - eKey = Key::Num2; - break; - case GLFW_KEY_3: - eKey = Key::Num3; - break; - case GLFW_KEY_4: - eKey = Key::Num4; - break; - case GLFW_KEY_5: - eKey = Key::Num5; - break; - case GLFW_KEY_6: - eKey = Key::Num6; - break; - case GLFW_KEY_7: - eKey = Key::Num7; - break; - case GLFW_KEY_8: - eKey = Key::Num8; - break; - case GLFW_KEY_9: - eKey = Key::Num9; - break; - case GLFW_KEY_F1: - eKey = Key::F1; - break; - case GLFW_KEY_F2: - eKey = Key::F2; - break; - case GLFW_KEY_F3: - eKey = Key::F3; - break; - case GLFW_KEY_F4: - eKey = Key::F4; - break; - case GLFW_KEY_F5: - eKey = Key::F5; - break; - case GLFW_KEY_F6: - eKey = Key::F6; - break; - case GLFW_KEY_F7: - eKey = Key::F7; - break; - case GLFW_KEY_F8: - eKey = Key::F8; - break; - case GLFW_KEY_F9: - eKey = Key::F9; - break; - case GLFW_KEY_F10: - eKey = Key::F10; - break; - case GLFW_KEY_F11: - eKey = Key::F11; - break; - case GLFW_KEY_F12: - eKey = Key::F12; - break; - case GLFW_KEY_SPACE: - eKey = Key::Space; - break; - case GLFW_KEY_ENTER: - eKey = Key::Enter; - break; - case GLFW_KEY_ESCAPE: - eKey = Key::Escape; - break; - case GLFW_KEY_TAB: - eKey = Key::Tab; - break; - case GLFW_KEY_BACKSPACE: - eKey = Key::Backspace; - break; - case GLFW_KEY_INSERT: - eKey = Key::Insert; - break; - case GLFW_KEY_DELETE: - eKey = Key::Delete; - break; - case GLFW_KEY_HOME: - eKey = Key::Home; - break; - case GLFW_KEY_END: - eKey = Key::End; - break; - case GLFW_KEY_PAGE_UP: - eKey = Key::PageUp; - break; - case GLFW_KEY_PAGE_DOWN: - eKey = Key::PageDown; - break; - case GLFW_KEY_UP: - eKey = Key::Up; - break; - case GLFW_KEY_DOWN: - eKey = Key::Down; - break; - case GLFW_KEY_LEFT: - eKey = Key::Left; - break; - case GLFW_KEY_RIGHT: - eKey = Key::Right; - break; - case GLFW_KEY_LEFT_SHIFT: - eKey = Key::LShift; - break; - case GLFW_KEY_RIGHT_SHIFT: - eKey = Key::RShift; - break; - case GLFW_KEY_LEFT_CONTROL: - eKey = Key::LCtrl; - break; - case GLFW_KEY_RIGHT_CONTROL: - eKey = Key::RCtrl; - break; - case GLFW_KEY_LEFT_ALT: - eKey = Key::LAlt; - break; - case GLFW_KEY_RIGHT_ALT: - eKey = Key::RAlt; - break; - case GLFW_KEY_CAPS_LOCK: - eKey = Key::CapsLock; - break; - case GLFW_KEY_NUM_LOCK: - eKey = Key::NumLock; - break; - case GLFW_KEY_SCROLL_LOCK: - eKey = Key::ScrollLock; - break; - default: - eKey = Key::None; - break; - } - - if (eKey != Key::None) { - i32 keyCode = static_cast(eKey); - if (action == GLFW_PRESS) { - Event e = Event::keyPress(keyCode, scancode, mods); - eventService->push(e); - } else if (action == GLFW_RELEASE) { - Event e = Event::keyRelease(keyCode, scancode, mods); - eventService->push(e); - } - } - } -} - -void GLFWWindow::joystickCallback(int jid, int event) { - // 通过全局回调找到对应的窗口实例 - // 由于 GLFW 的 joystick 回调没有窗口参数,我们需要其他方式处理 - // 这里简化处理,让输入系统在 update() 中轮询 -} - -} // namespace extra2d diff --git a/Extra2D/src/platform/window_module.cpp b/Extra2D/src/platform/window_module.cpp deleted file mode 100644 index 9534b53..0000000 --- a/Extra2D/src/platform/window_module.cpp +++ /dev/null @@ -1,58 +0,0 @@ -#include -#include -#include - -#include - -#ifdef __SWITCH__ -#include -#endif - -namespace extra2d { - -WindowModule::WindowModule(std::function configFn) { - configFn(cfg_); -} - -WindowModule::~WindowModule() { - if (initialized_) { - shutdown(); - } -} - -bool WindowModule::init() { - if (initialized_) - return true; - - E2D_INFO("正在创建 GLFW 窗口,尺寸 {}x{}", cfg_.w, cfg_.h); - - win_ = ptr::makeUnique(); - if (!win_) { - E2D_ERROR("创建窗口失败"); - return false; - } - - if (!win_->create(cfg_.title, cfg_.w, cfg_.h, cfg_.vsync)) { - E2D_ERROR("创建窗口失败"); - shutdown(); - return false; - } - - E2D_INFO("窗口创建成功"); - initialized_ = true; - return true; -} - -void WindowModule::shutdown() { - if (!initialized_) - return; - - if (win_) { - win_->destroy(); - win_.reset(); - } - - initialized_ = false; -} - -} // namespace extra2d diff --git a/Extra2D/src/services/asset_service.cpp b/Extra2D/src/services/asset_service.cpp deleted file mode 100644 index f04cf60..0000000 --- a/Extra2D/src/services/asset_service.cpp +++ /dev/null @@ -1,332 +0,0 @@ -#include "extra2d/services/asset_service.h" - -#include - -namespace extra2d { - -// --------------------------------------------------------------------------- -// AssetService 实现 -// --------------------------------------------------------------------------- - -AssetService::AssetService() : cache_(ptr::makeUnique()) {} - -AssetService::~AssetService() { shutdown(); } - -bool AssetService::init() { - if (initialized()) { - return true; - } - - setState(ServiceState::Initializing); - - registerLoader(AssetLoaderFactory::createTextureLoader()); - registerLoader(AssetLoaderFactory::createFontLoader()); - registerLoader(AssetLoaderFactory::createShaderLoader()); - registerLoader(AssetLoaderFactory::createAudioLoader()); - registerLoader(AssetLoaderFactory::createDataLoader()); - - running_ = true; - workerThread_ = std::thread(&AssetService::workerFunc, this); - - setState(ServiceState::Running); - return true; -} - -void AssetService::shutdown() { - if (!initialized()) { - return; - } - - setState(ServiceState::Stopping); - - running_ = false; - taskCv_.notify_all(); - - if (workerThread_.joinable()) { - workerThread_.join(); - } - - clear(); - packManager_.unmountAll(); - loaders_.clear(); - - setState(ServiceState::Stopped); -} - -bool AssetService::isLoaded(const std::string &path) const { - std::shared_lock lock(mutex_); - auto it = states_.find(AssetID(path)); - return it != states_.end() && it->second == AssetState::Loaded; -} - -bool AssetService::isLoading(const std::string &path) const { - std::shared_lock lock(mutex_); - auto it = states_.find(AssetID(path)); - return it != states_.end() && it->second == AssetState::Loading; -} - -void AssetService::unload(const std::string &path) { - std::unique_lock lock(mutex_); - AssetID id(path); - assets_.erase(id); - states_.erase(id); -} - -void AssetService::setLimit(size_t maxBytes) { cache_->setLimit(maxBytes); } - -size_t AssetService::size() const { return cache_->size(); } - -void AssetService::purge() { - std::unique_lock lock(mutex_); - - std::vector toRemove; - for (const auto &[id, loaded] : assets_) { - if (loaded.asset.use_count() <= 2) { - toRemove.push_back(id); - } - } - for (const auto &id : toRemove) { - assets_.erase(id); - states_.erase(id); - } - - cache_->purge(); -} - -void AssetService::clear() { - std::unique_lock lock(mutex_); - assets_.clear(); - states_.clear(); - cache_->clear(); -} - -CacheStats AssetService::stats() const { return cache_->stats(); } - -bool AssetService::mount(const std::string &path) { - return packManager_.mount(path); -} - -void AssetService::unmount(const std::string &path) { - packManager_.unmount(path); -} - -void AssetService::setPipe(DataPipe pipe) { pipe_ = std::move(pipe); } - -void AssetService::setRoot(const std::string &path) { root_ = path; } - -std::string AssetService::root() const { return root_; } - -void AssetService::process() { - std::queue> callbacks; - { - std::lock_guard lock(callbackMutex_); - callbacks = std::move(callbackQueue_); - callbackQueue_ = {}; - } - - while (!callbacks.empty()) { - callbacks.front()(); - callbacks.pop(); - } -} - -AssetHandleBase AssetService::loadImpl(const AssetID &id, - std::type_index type) { - { - std::shared_lock lock(mutex_); - auto it = assets_.find(id); - if (it != assets_.end()) { - cache_->hit(); - return AssetHandleBase(id, it->second.asset); - } - } - - cache_->miss(); - - Ref asset = loadFromFile(id, type); - if (!asset) { - asset = loadFromPack(id, type); - } - - if (!asset) { - return AssetHandleBase(); - } - - { - std::unique_lock lock(mutex_); - assets_[id] = {asset, type}; - states_[id] = AssetState::Loaded; - } - - cache_->add(asset); - return AssetHandleBase(id, asset); -} - -void AssetService::loadAsyncImpl( - const AssetID &id, std::type_index type, - std::function callback) { - { - std::shared_lock lock(mutex_); - auto it = assets_.find(id); - if (it != assets_.end()) { - cache_->hit(); - callback(AssetHandleBase(id, it->second.asset)); - return; - } - } - - cache_->miss(); - - { - std::unique_lock lock(mutex_); - states_[id] = AssetState::Loading; - } - - { - std::lock_guard lock(taskMutex_); - taskQueue_.push({id, type, std::move(callback)}); - } - taskCv_.notify_one(); -} - -AssetHandleBase AssetService::getImpl(const AssetID &id, std::type_index type) { - (void)type; - std::shared_lock lock(mutex_); - auto it = assets_.find(id); - if (it != assets_.end()) { - cache_->hit(); - return AssetHandleBase(id, it->second.asset); - } - - cache_->miss(); - return AssetHandleBase(); -} - -void AssetService::preloadImpl(const AssetID &id, std::type_index type) { - { - std::shared_lock lock(mutex_); - if (assets_.find(id) != assets_.end()) { - return; - } - } - - { - std::lock_guard lock(taskMutex_); - taskQueue_.push({id, type, nullptr}); - } - taskCv_.notify_one(); -} - -void AssetService::registerLoaderImpl(std::type_index type, - Unique loader) { - std::unique_lock lock(mutex_); - loaders_[type] = std::move(loader); -} - -void AssetService::workerFunc() { - while (running_) { - LoadTask task; - { - std::unique_lock lock(taskMutex_); - taskCv_.wait(lock, [this] { return !taskQueue_.empty() || !running_; }); - - if (!running_) { - break; - } - - if (taskQueue_.empty()) { - continue; - } - - task = std::move(taskQueue_.front()); - taskQueue_.pop(); - } - - Ref asset = loadFromFile(task.id, task.type); - if (!asset) { - asset = loadFromPack(task.id, task.type); - } - - if (asset) { - std::unique_lock lock(mutex_); - assets_[task.id] = {asset, task.type}; - states_[task.id] = AssetState::Loaded; - cache_->add(asset); - } else { - std::unique_lock lock(mutex_); - states_[task.id] = AssetState::Failed; - } - - if (task.callback) { - std::lock_guard lock(callbackMutex_); - callbackQueue_.push( - [task, asset]() { task.callback(AssetHandleBase(task.id, asset)); }); - } - } -} - -Ref AssetService::loadFromFile(const AssetID &id, std::type_index type) { - auto *loader = getLoader(type); - if (!loader) { - return nullptr; - } - - std::string fullPath = root_.empty() ? id.path : root_ + "/" + id.path; - - if (!std::filesystem::exists(fullPath)) { - return nullptr; - } - - return loader->loadBase(fullPath); -} - -Ref AssetService::loadFromPack(const AssetID &id, std::type_index type) { - auto *loader = getLoader(type); - if (!loader) { - return nullptr; - } - - auto *pack = packManager_.find(id); - if (!pack) { - return nullptr; - } - - auto data = pack->read(id); - if (data.empty()) { - return nullptr; - } - - if (!pipe_.empty()) { - data = pipe_.process(data); - } - - return loader->loadFromMemoryBase(data.data(), data.size()); -} - -AssetLoaderBase *AssetService::getLoader(std::type_index type) { - auto it = loaders_.find(type); - return it != loaders_.end() ? it->second.get() : nullptr; -} - -std::type_index AssetService::inferType(const std::string &path) { - std::filesystem::path p(path); - std::string ext = p.extension().string(); - - AssetType type = AssetLoaderFactory::getTypeByExtension(ext); - switch (type) { - case AssetType::Texture: - return typeid(TextureAsset); - case AssetType::Font: - return typeid(FontAsset); - case AssetType::Shader: - return typeid(ShaderAsset); - case AssetType::Audio: - return typeid(AudioAsset); - case AssetType::Data: - return typeid(DataAsset); - default: - return typeid(DataAsset); - } -} - -} // namespace extra2d diff --git a/Extra2D/src/services/event_service.cpp b/Extra2D/src/services/event_service.cpp deleted file mode 100644 index 963b0d0..0000000 --- a/Extra2D/src/services/event_service.cpp +++ /dev/null @@ -1,64 +0,0 @@ -#include - -namespace extra2d { - -EventService::EventService() { - info_.name = "EventService"; - info_.priority = ServicePriority::Event; - info_.enabled = true; -} - -ServiceInfo EventService::info() const { return info_; } - -bool EventService::init() { - setState(ServiceState::Running); - return true; -} - -void EventService::shutdown() { - queue_.clear(); - dispatcher_.offAll(); - setState(ServiceState::Stopped); -} - -void EventService::update(f32 dt) { - if (state() == ServiceState::Running) { - process(); - } -} - -void EventService::push(const Event &event) { queue_.push(event); } - -void EventService::push(Event &&event) { queue_.push(std::move(event)); } - -bool EventService::poll(Event &event) { return queue_.poll(event); } - -ListenerID EventService::on(EventType type, EventDispatcher::EventFn fn) { - return dispatcher_.on(type, fn); -} - -void EventService::off(ListenerID id) { - dispatcher_.off(id); -} - -void EventService::offAll(EventType type) { - dispatcher_.offAll(type); -} - -void EventService::offAll() { dispatcher_.offAll(); } - -void EventService::dispatch(Event &event) { dispatcher_.dispatch(event); } - -void EventService::process() { dispatcher_.process(queue_); } - -size_t EventService::listenerCount(EventType type) const { - return dispatcher_.listenerCount(type); -} - -size_t EventService::totalListeners() const { - return dispatcher_.totalListeners(); -} - -size_t EventService::queueSize() const { return queue_.size(); } - -} // namespace extra2d diff --git a/Extra2D/src/services/logger_service.cpp b/Extra2D/src/services/logger_service.cpp deleted file mode 100644 index 71cf410..0000000 --- a/Extra2D/src/services/logger_service.cpp +++ /dev/null @@ -1,254 +0,0 @@ -#include -#include -#include -#include -#include - -#ifdef _WIN32 -#include -#endif - -namespace extra2d { - -#ifdef _WIN32 -static bool enableWindowsConsoleFeatures() { - bool success = true; - - HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE); - if (hOut != INVALID_HANDLE_VALUE) { - DWORD dwMode = 0; - if (GetConsoleMode(hOut, &dwMode)) { - dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING; - if (!SetConsoleMode(hOut, dwMode)) { - success = false; - } - } else { - success = false; - } - } - - SetConsoleOutputCP(CP_UTF8); - SetConsoleCP(CP_UTF8); - - return success; -} - -static bool g_windowsConsoleInitialized = false; -#endif - -class ConsoleLogger::Impl { -public: - std::mutex mutex_; -}; - -ConsoleLogger::ConsoleLogger() - : level_(LogLevel::Info), colors_(true), - impl_(std::make_unique()) { - info_.name = "ConsoleLogger"; - info_.priority = ServicePriority::Core; - - levelColors_[static_cast(LogLevel::Trace)] = LogColor::Gray(); - levelColors_[static_cast(LogLevel::Debug)] = LogColor::Cyan(); - levelColors_[static_cast(LogLevel::Info)] = LogColor::SkyLight(); - levelColors_[static_cast(LogLevel::Registry)] = LogColor::IndigoLight(); - levelColors_[static_cast(LogLevel::Warn)] = LogColor::Yellow(); - levelColors_[static_cast(LogLevel::Error)] = LogColor::Red(); - levelColors_[static_cast(LogLevel::Fatal)] = LogColor::Magenta(); - -#ifdef _WIN32 - if (!g_windowsConsoleInitialized) { - g_windowsConsoleInitialized = enableWindowsConsoleFeatures(); - } -#endif -} - -ConsoleLogger::~ConsoleLogger() = default; - -bool ConsoleLogger::init() { - setState(ServiceState::Running); - return true; -} - -void ConsoleLogger::shutdown() { setState(ServiceState::Stopped); } - -void ConsoleLogger::level(LogLevel lvl) { level_ = lvl; } - -LogLevel ConsoleLogger::level() const { return level_; } - -bool ConsoleLogger::enabled(LogLevel lvl) const { - return static_cast(lvl) >= static_cast(level_); -} - -void ConsoleLogger::log(LogLevel lvl, const char *fmt, ...) { - if (!enabled(lvl)) - return; - - char buffer[1024]; - va_list args; - va_start(args, fmt); - vsnprintf(buffer, sizeof(buffer), fmt, args); - va_end(args); - - output(lvl, buffer); -} - -void ConsoleLogger::log(LogLevel lvl, const std::string &msg) { - if (!enabled(lvl)) - return; - output(lvl, msg.c_str()); -} - -void ConsoleLogger::trace(const char *fmt, ...) { - if (!enabled(LogLevel::Trace)) - return; - char buffer[1024]; - va_list args; - va_start(args, fmt); - vsnprintf(buffer, sizeof(buffer), fmt, args); - va_end(args); - output(LogLevel::Trace, buffer); -} - -void ConsoleLogger::debug(const char *fmt, ...) { - if (!enabled(LogLevel::Debug)) - return; - char buffer[1024]; - va_list args; - va_start(args, fmt); - vsnprintf(buffer, sizeof(buffer), fmt, args); - va_end(args); - output(LogLevel::Debug, buffer); -} - -void ConsoleLogger::info(const char *fmt, ...) { - if (!enabled(LogLevel::Info)) - return; - char buffer[1024]; - va_list args; - va_start(args, fmt); - vsnprintf(buffer, sizeof(buffer), fmt, args); - va_end(args); - output(LogLevel::Info, buffer); -} - -void ConsoleLogger::registry(const char *fmt, ...) { - if (!enabled(LogLevel::Registry)) - return; - char buffer[1024]; - va_list args; - va_start(args, fmt); - vsnprintf(buffer, sizeof(buffer), fmt, args); - va_end(args); - output(LogLevel::Registry, buffer); -} - -void ConsoleLogger::warn(const char *fmt, ...) { - if (!enabled(LogLevel::Warn)) - return; - char buffer[1024]; - va_list args; - va_start(args, fmt); - vsnprintf(buffer, sizeof(buffer), fmt, args); - va_end(args); - output(LogLevel::Warn, buffer); -} - -void ConsoleLogger::error(const char *fmt, ...) { - if (!enabled(LogLevel::Error)) - return; - char buffer[1024]; - va_list args; - va_start(args, fmt); - vsnprintf(buffer, sizeof(buffer), fmt, args); - va_end(args); - output(LogLevel::Error, buffer); -} - -void ConsoleLogger::fatal(const char *fmt, ...) { - if (!enabled(LogLevel::Fatal)) - return; - char buffer[1024]; - va_list args; - va_start(args, fmt); - vsnprintf(buffer, sizeof(buffer), fmt, args); - va_end(args); - output(LogLevel::Fatal, buffer); -} - -void ConsoleLogger::levelColor(LogLevel lvl, const LogColor &c) { - int idx = static_cast(lvl); - if (idx >= 0 && idx < 7) { - levelColors_[idx] = c; - } -} - -LogColor ConsoleLogger::levelColor(LogLevel lvl) const { - int idx = static_cast(lvl); - if (idx >= 0 && idx < 7) { - return levelColors_[idx]; - } - return LogColor::White(); -} - -void ConsoleLogger::colors(bool on) { colors_ = on; } - -bool ConsoleLogger::colors() const { return colors_; } - -std::string ConsoleLogger::ansiColor(LogLevel lvl) { - const LogColor &c = levelColor(lvl); - char buf[32]; - snprintf(buf, sizeof(buf), "\033[38;2;%d;%d;%dm", c.r, c.g, c.b); - return std::string(buf); -} - -void ConsoleLogger::output(LogLevel lvl, const char *msg) { - std::lock_guard lock(impl_->mutex_); - - auto now = std::chrono::system_clock::now(); - auto time = std::chrono::system_clock::to_time_t(now); - auto ms = std::chrono::duration_cast( - now.time_since_epoch()) % - 1000; - - std::tm tm; -#ifdef _WIN32 - localtime_s(&tm, &time); -#else - localtime_r(&time, &tm); -#endif - - const char *levelStr = levelString(lvl); - const char *reset = "\033[0m"; - - if (colors_) { - std::string color = ansiColor(lvl); - printf("%s[%02d:%02d:%02d.%03d] [%s] %s%s\n", color.c_str(), tm.tm_hour, - tm.tm_min, tm.tm_sec, (int)ms.count(), levelStr, msg, reset); - } else { - printf("[%02d:%02d:%02d.%03d] [%s] %s\n", tm.tm_hour, tm.tm_min, tm.tm_sec, - (int)ms.count(), levelStr, msg); - } -} - -const char *ConsoleLogger::levelString(LogLevel lvl) { - switch (lvl) { - case LogLevel::Trace: - return "TRACE"; - case LogLevel::Debug: - return "DEBUG"; - case LogLevel::Info: - return "INFO"; - case LogLevel::Registry: - return "REGISTRY"; - case LogLevel::Warn: - return "WARN"; - case LogLevel::Error: - return "ERROR"; - case LogLevel::Fatal: - return "FATAL"; - default: - return "UNKNOWN"; - } -} - -} // namespace extra2d diff --git a/Extra2D/src/services/timer_service.cpp b/Extra2D/src/services/timer_service.cpp deleted file mode 100644 index 9253660..0000000 --- a/Extra2D/src/services/timer_service.cpp +++ /dev/null @@ -1,51 +0,0 @@ -#include - -namespace extra2d { - -TimerService::TimerService() { - info_.name = "TimerService"; - info_.priority = ServicePriority::Timer; - info_.enabled = true; -} - -ServiceInfo TimerService::info() const { return info_; } - -bool TimerService::init() { - setState(ServiceState::Running); - return true; -} - -void TimerService::shutdown() { - mgr_.clear(); - setState(ServiceState::Stopped); -} - -void TimerService::update(f32 dt) { - if (state() == ServiceState::Running) { - mgr_.update(dt); - } -} - -u32 TimerService::add(f32 delay, Timer::Fn fn) { - return mgr_.add(delay, fn); -} - -u32 TimerService::addRepeat(f32 interval, Timer::Fn fn) { - return mgr_.addRepeat(interval, fn); -} - -void TimerService::cancel(u32 timerId) { - mgr_.cancel(timerId); -} - -void TimerService::pauseTimer(u32 timerId) { mgr_.pause(timerId); } - -void TimerService::resumeTimer(u32 timerId) { - mgr_.resume(timerId); -} - -void TimerService::clear() { mgr_.clear(); } - -size_t TimerService::count() const { return mgr_.count(); } - -} // namespace extra2d diff --git a/Tests/test_asset_cache.cpp b/Tests/test_asset_cache.cpp deleted file mode 100644 index 2e6eb77..0000000 --- a/Tests/test_asset_cache.cpp +++ /dev/null @@ -1,307 +0,0 @@ -/** - * @file test_asset_cache.cpp - * @brief AssetCache 单元测试 - * - * 测试资源缓存的添加、获取、淘汰等功能。 - */ - -#include "test_framework.h" -#include -#include - -using namespace extra2d; -using namespace extra2d::test; - -// --------------------------------------------------------------------------- -// 测试辅助类 -// --------------------------------------------------------------------------- - -/** - * @brief 测试用资源类 - */ -class TestAsset : public Asset { -public: - AssetType type() const override { return AssetType::Data; } - - bool loaded() const override { - return state_.load(std::memory_order_acquire) == AssetState::Loaded; - } - - size_t memSize() const override { return dataSize_; } - - void setData(size_t size) { - dataSize_ = size; - setState(AssetState::Loaded); - } - - static Ref create(const std::string& path, size_t size = 100) { - auto asset = std::make_shared(); - asset->setId(AssetID(path)); - asset->setPath(path); - asset->setData(size); - return asset; - } - -private: - size_t dataSize_ = 0; -}; - -// --------------------------------------------------------------------------- -// 基本功能测试 -// --------------------------------------------------------------------------- - -TEST(AssetCache, Create) { - AssetCache cache; - - TEST_ASSERT_EQ(0u, cache.count()); - TEST_ASSERT_EQ(0u, cache.bytes()); - TEST_ASSERT_EQ(0u, cache.limit()); -} - -TEST(AssetCache, CreateWithLimit) { - AssetCache cache(1024 * 1024); - - TEST_ASSERT_EQ(1024 * 1024, cache.limit()); -} - -TEST(AssetCache, AddAsset) { - AssetCache cache; - - auto asset = TestAsset::create("test.asset", 100); - auto handle = cache.add(asset); - - TEST_ASSERT_TRUE(handle.valid()); - TEST_ASSERT_EQ(1u, cache.count()); - TEST_ASSERT_EQ(100u, cache.bytes()); -} - -TEST(AssetCache, AddNullAsset) { - AssetCache cache; - - Ref nullAsset; - auto handle = cache.add(nullAsset); - - TEST_ASSERT_FALSE(handle.valid()); - TEST_ASSERT_EQ(0u, cache.count()); -} - -TEST(AssetCache, GetAsset) { - AssetCache cache; - - auto asset = TestAsset::create("test.asset", 200); - cache.add(asset); - - auto handle = cache.get(AssetID("test.asset")); - - TEST_ASSERT_TRUE(handle.valid()); - TEST_ASSERT_TRUE(handle.get() != nullptr); - TEST_ASSERT_EQ(asset.get(), handle.get().get()); -} - -TEST(AssetCache, GetNonExistent) { - AssetCache cache; - - auto handle = cache.get(AssetID("nonexistent.asset")); - - TEST_ASSERT_FALSE(handle.valid()); -} - -TEST(AssetCache, HasAsset) { - AssetCache cache; - - auto asset = TestAsset::create("test.asset", 100); - cache.add(asset); - - TEST_ASSERT_TRUE(cache.has(AssetID("test.asset"))); - TEST_ASSERT_FALSE(cache.has(AssetID("other.asset"))); -} - -TEST(AssetCache, RemoveAsset) { - AssetCache cache; - - auto asset = TestAsset::create("test.asset", 100); - cache.add(asset); - - TEST_ASSERT_TRUE(cache.remove(AssetID("test.asset"))); - TEST_ASSERT_EQ(0u, cache.count()); - TEST_ASSERT_FALSE(cache.has(AssetID("test.asset"))); -} - -TEST(AssetCache, RemoveNonExistent) { - AssetCache cache; - - TEST_ASSERT_FALSE(cache.remove(AssetID("nonexistent.asset"))); -} - -// --------------------------------------------------------------------------- -// 缓存统计测试 -// --------------------------------------------------------------------------- - -TEST(AssetCache, Stats_Initial) { - AssetCache cache; - - auto stats = cache.stats(); - - TEST_ASSERT_EQ(0u, stats.hits); - TEST_ASSERT_EQ(0u, stats.misses); - TEST_ASSERT_EQ(0u, stats.count); -} - -TEST(AssetCache, Stats_HitMiss) { - AssetCache cache; - - auto asset = TestAsset::create("test.asset", 100); - cache.add(asset); - - cache.get(AssetID("test.asset")); - cache.get(AssetID("nonexistent.asset")); - - auto stats = cache.stats(); - - TEST_ASSERT_EQ(1u, stats.hits); - TEST_ASSERT_EQ(1u, stats.misses); -} - -TEST(AssetCache, Stats_Reset) { - AssetCache cache; - - auto asset = TestAsset::create("test.asset", 100); - cache.add(asset); - - cache.get(AssetID("test.asset")); - cache.get(AssetID("nonexistent.asset")); - - cache.resetStats(); - - auto stats = cache.stats(); - - TEST_ASSERT_EQ(0u, stats.hits); - TEST_ASSERT_EQ(0u, stats.misses); -} - -// --------------------------------------------------------------------------- -// 内存限制测试 -// --------------------------------------------------------------------------- - -TEST(AssetCache, SetLimit) { - AssetCache cache; - - cache.setLimit(500); - - TEST_ASSERT_EQ(500u, cache.limit()); -} - -TEST(AssetCache, MemoryTracking) { - AssetCache cache; - - auto asset1 = TestAsset::create("asset1", 100); - auto asset2 = TestAsset::create("asset2", 200); - - cache.add(asset1); - cache.add(asset2); - - TEST_ASSERT_EQ(300u, cache.bytes()); -} - -TEST(AssetCache, Clear) { - AssetCache cache; - - auto asset1 = TestAsset::create("asset1", 100); - auto asset2 = TestAsset::create("asset2", 200); - - cache.add(asset1); - cache.add(asset2); - - cache.clear(); - - TEST_ASSERT_EQ(0u, cache.count()); - TEST_ASSERT_EQ(0u, cache.bytes()); -} - -// --------------------------------------------------------------------------- -// LRU 测试 -// --------------------------------------------------------------------------- - -TEST(AssetCache, LRU_AccessOrder) { - AssetCache cache; - - auto asset1 = TestAsset::create("asset1", 100); - auto asset2 = TestAsset::create("asset2", 100); - auto asset3 = TestAsset::create("asset3", 100); - - cache.add(asset1); - cache.add(asset2); - cache.add(asset3); - - cache.get(AssetID("asset1")); - - TEST_ASSERT_TRUE(cache.has(AssetID("asset1"))); - TEST_ASSERT_TRUE(cache.has(AssetID("asset2"))); - TEST_ASSERT_TRUE(cache.has(AssetID("asset3"))); -} - -// --------------------------------------------------------------------------- -// Purge 测试 -// --------------------------------------------------------------------------- - -TEST(AssetCache, Purge_NoReferences) { - AssetCache cache; - - { - auto asset = TestAsset::create("test.asset", 100); - cache.add(asset); - } - - size_t purged = cache.purge(); - - TEST_ASSERT_EQ(1u, purged); - TEST_ASSERT_EQ(0u, cache.count()); -} - -TEST(AssetCache, Purge_WithReferences) { - AssetCache cache; - - auto asset = TestAsset::create("test.asset", 100); - auto handle = cache.add(asset); - - size_t purged = cache.purge(); - - TEST_ASSERT_EQ(0u, purged); - TEST_ASSERT_EQ(1u, cache.count()); -} - -// --------------------------------------------------------------------------- -// 多资源测试 -// --------------------------------------------------------------------------- - -TEST(AssetCache, MultipleAssets) { - AssetCache cache; - - for (int i = 0; i < 10; ++i) { - auto asset = TestAsset::create("asset" + std::to_string(i), 100); - cache.add(asset); - } - - TEST_ASSERT_EQ(10u, cache.count()); - TEST_ASSERT_EQ(1000u, cache.bytes()); - - for (int i = 0; i < 10; ++i) { - TEST_ASSERT_TRUE(cache.has(AssetID("asset" + std::to_string(i)))); - } -} - -TEST(AssetCache, RemoveMultiple) { - AssetCache cache; - - for (int i = 0; i < 5; ++i) { - auto asset = TestAsset::create("asset" + std::to_string(i), 100); - cache.add(asset); - } - - for (int i = 0; i < 3; ++i) { - cache.remove(AssetID("asset" + std::to_string(i))); - } - - TEST_ASSERT_EQ(2u, cache.count()); - TEST_ASSERT_EQ(200u, cache.bytes()); -} diff --git a/Tests/test_asset_pack.cpp b/Tests/test_asset_pack.cpp deleted file mode 100644 index 81c057c..0000000 --- a/Tests/test_asset_pack.cpp +++ /dev/null @@ -1,427 +0,0 @@ -/** - * @file test_asset_pack.cpp - * @brief AssetPack 单元测试 - * - * 测试资源包的创建、读取和管理功能。 - */ - -#include "test_framework.h" -#include -#include -#include - -using namespace extra2d; -using namespace extra2d::test; - -// --------------------------------------------------------------------------- -// 测试辅助函数 -// --------------------------------------------------------------------------- - -namespace { -std::string testDir = "test_temp"; - -void setupTestDir() { std::filesystem::create_directories(testDir); } - -void cleanupTestDir() { std::filesystem::remove_all(testDir); } - -std::string getTestPath(const std::string &name) { - return testDir + "/" + name; -} - -void writeTestFile(const std::string &path, const std::vector &data) { - std::ofstream file(path, std::ios::binary); - file.write(reinterpret_cast(data.data()), data.size()); -} -} // namespace - -// --------------------------------------------------------------------------- -// AssetPackBuilder 测试 -// --------------------------------------------------------------------------- - -TEST(AssetPackBuilder, Create) { - AssetPackBuilder builder; - - TEST_ASSERT_EQ(0u, builder.count()); - TEST_ASSERT_EQ(0u, builder.totalOriginalSize()); - TEST_ASSERT_EQ(0u, builder.totalCompressedSize()); -} - -TEST(AssetPackBuilder, AddData) { - AssetPackBuilder builder; - - std::vector data = createTestData(1024); - builder.add("test.bin", data); - - TEST_ASSERT_EQ(1u, builder.count()); - TEST_ASSERT_EQ(1024u, builder.totalOriginalSize()); -} - -TEST(AssetPackBuilder, AddMultipleData) { - AssetPackBuilder builder; - - builder.add("file1.bin", createTestData(100)); - builder.add("file2.bin", createTestData(200)); - builder.add("file3.bin", createTestData(300)); - - TEST_ASSERT_EQ(3u, builder.count()); - TEST_ASSERT_EQ(600u, builder.totalOriginalSize()); -} - -TEST(AssetPackBuilder, AddMovedData) { - AssetPackBuilder builder; - - std::vector data = createTestData(512); - builder.add("moved.bin", std::move(data)); - - TEST_ASSERT_EQ(1u, builder.count()); -} - -TEST(AssetPackBuilder, Clear) { - AssetPackBuilder builder; - - builder.add("test.bin", createTestData(100)); - builder.clear(); - - TEST_ASSERT_EQ(0u, builder.count()); - TEST_ASSERT_EQ(0u, builder.totalOriginalSize()); -} - -TEST(AssetPackBuilder, BuildSimple) { - setupTestDir(); - - AssetPackBuilder builder; - builder.add("test.bin", createTestData(256)); - - std::string packPath = getTestPath("simple.pack"); - bool result = builder.build(packPath); - - TEST_ASSERT_TRUE(result); - TEST_ASSERT_TRUE(std::filesystem::exists(packPath)); - - cleanupTestDir(); -} - -TEST(AssetPackBuilder, BuildMultipleFiles) { - setupTestDir(); - - AssetPackBuilder builder; - builder.add("file1.bin", createTestData(100, 1)); - builder.add("file2.bin", createTestData(200, 2)); - builder.add("file3.bin", createTestData(300, 3)); - - std::string packPath = getTestPath("multi.pack"); - bool result = builder.build(packPath); - - TEST_ASSERT_TRUE(result); - TEST_ASSERT_TRUE(std::filesystem::exists(packPath)); - - cleanupTestDir(); -} - -TEST(AssetPackBuilder, SetEncryption) { - AssetPackBuilder builder; - builder.setEncryption("secret-key", Decryptor::Type::XOR); - - builder.add("encrypted.bin", createTestData(128)); - - TEST_ASSERT_EQ(1u, builder.count()); -} - -// --------------------------------------------------------------------------- -// AssetPack 测试 -// --------------------------------------------------------------------------- - -TEST(AssetPack, Create) { - AssetPack pack; - - TEST_ASSERT_FALSE(pack.isOpen()); - TEST_ASSERT_EQ(0u, pack.count()); -} - -TEST(AssetPack, OpenNonExistent) { - AssetPack pack; - - bool result = pack.open("nonexistent.pack"); - - TEST_ASSERT_FALSE(result); - TEST_ASSERT_FALSE(pack.isOpen()); -} - -TEST(AssetPack, OpenAndClose) { - setupTestDir(); - - AssetPackBuilder builder; - builder.add("test.bin", createTestData(100)); - builder.build(getTestPath("test.pack")); - - AssetPack pack; - bool opened = pack.open(getTestPath("test.pack")); - - TEST_ASSERT_TRUE(opened); - TEST_ASSERT_TRUE(pack.isOpen()); - - pack.close(); - - TEST_ASSERT_FALSE(pack.isOpen()); - - cleanupTestDir(); -} - -TEST(AssetPack, ReadAsset) { - setupTestDir(); - - std::vector originalData = createTestData(512); - - AssetPackBuilder builder; - builder.add("data.bin", originalData); - builder.build(getTestPath("read.pack")); - - AssetPack pack; - pack.open(getTestPath("read.pack")); - - std::vector readData = pack.read("data.bin"); - - TEST_ASSERT_TRUE(dataEquals(originalData, readData)); - - pack.close(); - cleanupTestDir(); -} - -TEST(AssetPack, HasAsset) { - setupTestDir(); - - AssetPackBuilder builder; - builder.add("exists.bin", createTestData(100)); - builder.build(getTestPath("has.pack")); - - AssetPack pack; - pack.open(getTestPath("has.pack")); - - TEST_ASSERT_TRUE(pack.has("exists.bin")); - TEST_ASSERT_FALSE(pack.has("not_exists.bin")); - - pack.close(); - cleanupTestDir(); -} - -TEST(AssetPack, GetAssets) { - setupTestDir(); - - AssetPackBuilder builder; - builder.add("file1.bin", createTestData(100)); - builder.add("file2.bin", createTestData(100)); - builder.add("file3.bin", createTestData(100)); - builder.build(getTestPath("assets.pack")); - - AssetPack pack; - pack.open(getTestPath("assets.pack")); - - std::vector assets = pack.assets(); - - TEST_ASSERT_EQ(3u, assets.size()); - - pack.close(); - cleanupTestDir(); -} - -TEST(AssetPack, MoveSemantics) { - setupTestDir(); - - AssetPackBuilder builder; - builder.add("test.bin", createTestData(100)); - builder.build(getTestPath("move.pack")); - - AssetPack pack1; - pack1.open(getTestPath("move.pack")); - - AssetPack pack2 = std::move(pack1); - - TEST_ASSERT_TRUE(pack2.isOpen()); - TEST_ASSERT_FALSE(pack1.isOpen()); - - pack2.close(); - cleanupTestDir(); -} - -// --------------------------------------------------------------------------- -// PackManager 测试 -// --------------------------------------------------------------------------- - -TEST(PackManager, Create) { - PackManager manager; - - TEST_ASSERT_EQ(0u, manager.count()); -} - -TEST(PackManager, MountUnmount) { - setupTestDir(); - - AssetPackBuilder builder; - builder.add("test.bin", createTestData(100)); - builder.build(getTestPath("manager.pack")); - - PackManager manager; - bool mounted = manager.mount(getTestPath("manager.pack")); - - TEST_ASSERT_TRUE(mounted); - TEST_ASSERT_EQ(1u, manager.count()); - - manager.unmount(getTestPath("manager.pack")); - - TEST_ASSERT_EQ(0u, manager.count()); - - cleanupTestDir(); -} - -TEST(PackManager, MountNonExistent) { - PackManager manager; - - bool mounted = manager.mount("nonexistent.pack"); - - TEST_ASSERT_FALSE(mounted); - TEST_ASSERT_EQ(0u, manager.count()); -} - -TEST(PackManager, HasAsset) { - setupTestDir(); - - AssetPackBuilder builder; - builder.add("resource.bin", createTestData(100)); - builder.build(getTestPath("has.pack")); - - PackManager manager; - manager.mount(getTestPath("has.pack")); - - TEST_ASSERT_TRUE(manager.has("resource.bin")); - TEST_ASSERT_FALSE(manager.has("missing.bin")); - - manager.unmountAll(); - cleanupTestDir(); -} - -TEST(PackManager, ReadAsset) { - setupTestDir(); - - std::vector originalData = createTestData(256); - - AssetPackBuilder builder; - builder.add("data.bin", originalData); - builder.build(getTestPath("read.pack")); - - PackManager manager; - manager.mount(getTestPath("read.pack")); - - std::vector readData = manager.read("data.bin"); - - TEST_ASSERT_TRUE(dataEquals(originalData, readData)); - - manager.unmountAll(); - cleanupTestDir(); -} - -TEST(PackManager, MultiplePacks) { - setupTestDir(); - - AssetPackBuilder builder1; - builder1.add("pack1/file1.bin", createTestData(100)); - builder1.build(getTestPath("pack1.pack")); - - AssetPackBuilder builder2; - builder2.add("pack2/file2.bin", createTestData(200)); - builder2.build(getTestPath("pack2.pack")); - - PackManager manager; - manager.mount(getTestPath("pack1.pack")); - manager.mount(getTestPath("pack2.pack")); - - TEST_ASSERT_EQ(2u, manager.count()); - TEST_ASSERT_TRUE(manager.has("pack1/file1.bin")); - TEST_ASSERT_TRUE(manager.has("pack2/file2.bin")); - - manager.unmountAll(); - cleanupTestDir(); -} - -TEST(PackManager, UnmountAll) { - setupTestDir(); - - AssetPackBuilder builder; - builder.add("test.bin", createTestData(100)); - builder.build(getTestPath("unmount.pack")); - - PackManager manager; - manager.mount(getTestPath("unmount.pack")); - - manager.unmountAll(); - - TEST_ASSERT_EQ(0u, manager.count()); - - cleanupTestDir(); -} - -TEST(PackManager, AllAssets) { - setupTestDir(); - - AssetPackBuilder builder; - builder.add("file1.bin", createTestData(100)); - builder.add("file2.bin", createTestData(100)); - builder.build(getTestPath("all.pack")); - - PackManager manager; - manager.mount(getTestPath("all.pack")); - - std::vector assets = manager.allAssets(); - - TEST_ASSERT_EQ(2u, assets.size()); - - manager.unmountAll(); - cleanupTestDir(); -} - -TEST(PackManager, MountedPacks) { - setupTestDir(); - - AssetPackBuilder builder; - builder.add("test.bin", createTestData(100)); - builder.build(getTestPath("mounted.pack")); - - PackManager manager; - manager.mount(getTestPath("mounted.pack")); - - std::vector mounted = manager.mountedPacks(); - - TEST_ASSERT_EQ(1u, mounted.size()); - - manager.unmountAll(); - cleanupTestDir(); -} - -// --------------------------------------------------------------------------- -// 加密资源包测试 -// --------------------------------------------------------------------------- - -TEST(AssetPack, EncryptedPack) { - setupTestDir(); - - std::vector originalData = createTestData(256); - std::string key = "encryption-key"; - - AssetPackBuilder builder(Compression::None); - builder.setEncryption(key, Decryptor::Type::XOR); - builder.add("encrypted.bin", originalData); - builder.build(getTestPath("encrypted.pack")); - - AssetPack pack; - pack.open(getTestPath("encrypted.pack")); - - DataPipe pipe; - pipe.decrypt(key, Decryptor::Type::XOR); - pack.setPipe(std::move(pipe)); - - std::vector readData = pack.read("encrypted.bin"); - - TEST_ASSERT_TRUE(dataEquals(originalData, readData)); - - pack.close(); - cleanupTestDir(); -} diff --git a/Tests/test_asset_service.cpp b/Tests/test_asset_service.cpp deleted file mode 100644 index 4b7b75c..0000000 --- a/Tests/test_asset_service.cpp +++ /dev/null @@ -1,500 +0,0 @@ -/** - * @file test_asset_service.cpp - * @brief AssetService 单元测试 - * - * 测试资源服务的加载、缓存、异步加载等功能。 - */ - -#include "test_framework.h" -#include -#include -#include -#include -#include -#include - -using namespace extra2d; -using namespace extra2d::test; - -// --------------------------------------------------------------------------- -// 测试辅助函数 -// --------------------------------------------------------------------------- - -namespace { - std::string testDir = "test_temp_service"; - - void setupTestDir() { - std::filesystem::create_directories(testDir); - } - - void cleanupTestDir() { - std::filesystem::remove_all(testDir); - } - - std::string getTestPath(const std::string& name) { - return testDir + "/" + name; - } - - void writeTestFile(const std::string& path, const std::vector& data) { - std::ofstream file(path, std::ios::binary); - file.write(reinterpret_cast(data.data()), data.size()); - } -} - -// --------------------------------------------------------------------------- -// 测试用资源类和加载器 -// --------------------------------------------------------------------------- - -/** - * @brief 测试用资源类 - */ -class TestResource : public Asset { -public: - AssetType type() const override { return AssetType::Data; } - - bool loaded() const override { - return state_.load(std::memory_order_acquire) == AssetState::Loaded; - } - - size_t memSize() const override { return data_.size(); } - - const std::vector& data() const { return data_; } - - void setData(const std::vector& data) { - data_ = data; - setState(AssetState::Loaded); - } - - void initId(const std::string& path) { - setId(AssetID(path)); - setPath(path); - } - -private: - std::vector data_; -}; - -/** - * @brief 测试用资源加载器 - */ -class TestResourceLoader : public AssetLoader { -public: - Ref load(const std::string& path) override { - std::ifstream file(path, std::ios::binary | std::ios::ate); - if (!file.is_open()) { - return nullptr; - } - - size_t size = static_cast(file.tellg()); - file.seekg(0); - - std::vector data(size); - file.read(reinterpret_cast(data.data()), size); - - auto resource = std::make_shared(); - resource->initId(path); - resource->setData(data); - - return resource; - } - - Ref loadFromMemory(const u8* data, size_t size) override { - auto resource = std::make_shared(); - resource->setData(std::vector(data, data + size)); - return resource; - } - - bool canLoad(const std::string& path) const override { - return path.find(".test") != std::string::npos; - } - - AssetType type() const override { return AssetType::Data; } - - std::vector extensions() const override { - return {".test"}; - } -}; - -// --------------------------------------------------------------------------- -// AssetService 基本测试 -// --------------------------------------------------------------------------- - -TEST(AssetService, Create) { - AssetService service; - - TEST_ASSERT_FALSE(service.isLoaded("nonexistent")); - TEST_ASSERT_FALSE(service.isLoading("nonexistent")); - TEST_ASSERT_EQ(0u, service.size()); -} - -TEST(AssetService, InitShutdown) { - AssetService service; - - bool initResult = service.init(); - - TEST_ASSERT_TRUE(initResult); - - service.shutdown(); -} - -TEST(AssetService, SetRoot) { - AssetService service; - service.init(); - - service.setRoot("assets"); - - TEST_ASSERT_EQ("assets", service.root()); - - service.shutdown(); -} - -TEST(AssetService, SetLimit) { - AssetService service; - service.init(); - - service.setLimit(1024 * 1024); - - service.shutdown(); -} - -// --------------------------------------------------------------------------- -// 加载器注册测试 -// --------------------------------------------------------------------------- - -TEST(AssetService, RegisterLoader) { - AssetService service; - service.init(); - - service.registerLoader(std::make_unique()); - - service.shutdown(); -} - -// --------------------------------------------------------------------------- -// 资源加载测试 -// --------------------------------------------------------------------------- - -TEST(AssetService, LoadNonExistent) { - setupTestDir(); - - AssetService service; - service.init(); - service.setRoot(testDir); - service.registerLoader(std::make_unique()); - - auto handle = service.load("nonexistent.test"); - - TEST_ASSERT_FALSE(handle.valid()); - - service.shutdown(); - cleanupTestDir(); -} - -TEST(AssetService, LoadFromMemory) { - AssetService service; - service.init(); - service.registerLoader(std::make_unique()); - - std::vector testData = createTestData(256); - - service.shutdown(); -} - -TEST(AssetService, IsLoaded) { - setupTestDir(); - - std::string filePath = getTestPath("resource.test"); - writeTestFile(filePath, createTestData(256)); - - AssetService service; - service.init(); - service.setRoot(testDir); - service.registerLoader(std::make_unique()); - - TEST_ASSERT_FALSE(service.isLoaded("resource.test")); - - auto handle = service.load("resource.test"); - - TEST_ASSERT_TRUE(service.isLoaded("resource.test")); - - service.shutdown(); - cleanupTestDir(); -} - -// --------------------------------------------------------------------------- -// 缓存测试 -// --------------------------------------------------------------------------- - -TEST(AssetService, CacheHit) { - setupTestDir(); - - std::string filePath = getTestPath("cached.test"); - writeTestFile(filePath, createTestData(256)); - - AssetService service; - service.init(); - service.setRoot(testDir); - service.registerLoader(std::make_unique()); - - auto handle1 = service.load("cached.test"); - auto handle2 = service.get("cached.test"); - - TEST_ASSERT_TRUE(handle1.valid()); - TEST_ASSERT_TRUE(handle2.valid()); - TEST_ASSERT_EQ(handle1.get().get(), handle2.get().get()); - - auto stats = service.stats(); - TEST_ASSERT_GE(stats.hits, 1u); - - service.shutdown(); - cleanupTestDir(); -} - -TEST(AssetService, CacheMiss) { - AssetService service; - service.init(); - - auto handle = service.get("not_in_cache.test"); - - TEST_ASSERT_FALSE(handle.valid()); - - auto stats = service.stats(); - TEST_ASSERT_GE(stats.misses, 1u); - - service.shutdown(); -} - -TEST(AssetService, Purge) { - setupTestDir(); - - std::string filePath = getTestPath("purge.test"); - writeTestFile(filePath, createTestData(256)); - - AssetService service; - service.init(); - service.setRoot(testDir); - service.registerLoader(std::make_unique()); - - { - auto handle = service.load("purge.test"); - } - - service.purge(); - - TEST_ASSERT_EQ(0u, service.size()); - - service.shutdown(); - cleanupTestDir(); -} - -TEST(AssetService, Clear) { - setupTestDir(); - - std::string filePath = getTestPath("clear.test"); - writeTestFile(filePath, createTestData(256)); - - AssetService service; - service.init(); - service.setRoot(testDir); - service.registerLoader(std::make_unique()); - - auto handle = service.load("clear.test"); - - TEST_ASSERT_GT(service.size(), 0u); - - service.clear(); - - TEST_ASSERT_EQ(0u, service.size()); - - service.shutdown(); - cleanupTestDir(); -} - -// --------------------------------------------------------------------------- -// 卸载测试 -// --------------------------------------------------------------------------- - -TEST(AssetService, Unload) { - setupTestDir(); - - std::string filePath = getTestPath("unload.test"); - writeTestFile(filePath, createTestData(256)); - - AssetService service; - service.init(); - service.setRoot(testDir); - service.registerLoader(std::make_unique()); - - auto handle = service.load("unload.test"); - - TEST_ASSERT_TRUE(service.isLoaded("unload.test")); - - service.unload("unload.test"); - - TEST_ASSERT_FALSE(service.isLoaded("unload.test")); - - service.shutdown(); - cleanupTestDir(); -} - -// --------------------------------------------------------------------------- -// 资源包挂载测试 -// --------------------------------------------------------------------------- - -TEST(AssetService, MountPack) { - setupTestDir(); - - AssetPackBuilder builder; - builder.add("packed.test", createTestData(256)); - builder.build(getTestPath("service.pack")); - - AssetService service; - service.init(); - service.registerLoader(std::make_unique()); - - bool mounted = service.mount(getTestPath("service.pack")); - - TEST_ASSERT_TRUE(mounted); - - service.shutdown(); - cleanupTestDir(); -} - -TEST(AssetService, UnmountPack) { - setupTestDir(); - - AssetPackBuilder builder; - builder.add("packed.test", createTestData(256)); - builder.build(getTestPath("unmount.pack")); - - AssetService service; - service.init(); - service.registerLoader(std::make_unique()); - - service.mount(getTestPath("unmount.pack")); - service.unmount(getTestPath("unmount.pack")); - - service.shutdown(); - cleanupTestDir(); -} - -// --------------------------------------------------------------------------- -// 统计测试 -// --------------------------------------------------------------------------- - -TEST(AssetService, Stats) { - AssetService service; - service.init(); - - auto stats = service.stats(); - - TEST_ASSERT_EQ(0u, stats.hits); - TEST_ASSERT_EQ(0u, stats.misses); - TEST_ASSERT_EQ(0u, stats.count); - - service.shutdown(); -} - -// --------------------------------------------------------------------------- -// 数据处理管道测试 -// --------------------------------------------------------------------------- - -TEST(AssetService, SetPipe) { - AssetService service; - service.init(); - - DataPipe pipe; - pipe.encrypt("key", Decryptor::Type::XOR); - - service.setPipe(std::move(pipe)); - - service.shutdown(); -} - -// --------------------------------------------------------------------------- -// 异步加载测试 -// --------------------------------------------------------------------------- - -TEST(AssetService, AsyncLoad) { - setupTestDir(); - - std::string filePath = getTestPath("async.test"); - writeTestFile(filePath, createTestData(512)); - - AssetService service; - service.init(); - service.setRoot(testDir); - service.registerLoader(std::make_unique()); - - std::atomic callbackCalled{false}; - AssetHandle resultHandle; - - service.loadAsync("async.test", - [&](AssetHandle handle) { - resultHandle = handle; - callbackCalled = true; - }); - - for (int i = 0; i < 100 && !callbackCalled; ++i) { - service.process(); - std::this_thread::sleep_for(std::chrono::milliseconds(10)); - } - - service.process(); - - TEST_ASSERT_TRUE(callbackCalled); - TEST_ASSERT_TRUE(resultHandle.valid()); - - service.shutdown(); - cleanupTestDir(); -} - -TEST(AssetService, Preload) { - setupTestDir(); - - std::string filePath = getTestPath("preload.test"); - writeTestFile(filePath, createTestData(256)); - - AssetService service; - service.init(); - service.setRoot(testDir); - service.registerLoader(std::make_unique()); - - service.preload("preload.test"); - - for (int i = 0; i < 50; ++i) { - service.process(); - std::this_thread::sleep_for(std::chrono::milliseconds(10)); - } - - service.shutdown(); - cleanupTestDir(); -} - -// --------------------------------------------------------------------------- -// 多资源测试 -// --------------------------------------------------------------------------- - -TEST(AssetService, LoadMultiple) { - setupTestDir(); - - for (int i = 0; i < 5; ++i) { - std::string filePath = getTestPath("multi" + std::to_string(i) + ".test"); - writeTestFile(filePath, createTestData(100 * (i + 1))); - } - - AssetService service; - service.init(); - service.setRoot(testDir); - service.registerLoader(std::make_unique()); - - for (int i = 0; i < 5; ++i) { - auto handle = service.load("multi" + std::to_string(i) + ".test"); - TEST_ASSERT_TRUE(handle.valid()); - } - - TEST_ASSERT_GT(service.size(), 0u); - - service.shutdown(); - cleanupTestDir(); -} diff --git a/Tests/test_data_processor.cpp b/Tests/test_data_processor.cpp deleted file mode 100644 index 89522bb..0000000 --- a/Tests/test_data_processor.cpp +++ /dev/null @@ -1,260 +0,0 @@ -/** - * @file test_data_processor.cpp - * @brief DataProcessor 单元测试 - * - * 测试数据处理管道、加密/解密、压缩/解压功能。 - */ - -#include "test_framework.h" -#include - -using namespace extra2d; -using namespace extra2d::test; - -// --------------------------------------------------------------------------- -// XOR 加密/解密测试 -// --------------------------------------------------------------------------- - -TEST(DataProcessor, XOR_EncryptDecrypt) { - std::vector original = createTestData(1024); - std::string key = "test-key-12345"; - - Encryptor encryptor(key, Decryptor::Type::XOR); - Decryptor decryptor(key, Decryptor::Type::XOR); - - std::vector encrypted = encryptor.process(original); - std::vector decrypted = decryptor.process(encrypted); - - TEST_ASSERT_TRUE(dataEquals(original, decrypted)); - TEST_ASSERT_FALSE(dataEquals(original, encrypted)); -} - -TEST(DataProcessor, XOR_SameKeyProducesSameResult) { - std::vector data = createTestData(512); - std::string key = "secret"; - - Encryptor enc1(key, Decryptor::Type::XOR); - Encryptor enc2(key, Decryptor::Type::XOR); - - std::vector result1 = enc1.process(data); - std::vector result2 = enc2.process(data); - - TEST_ASSERT_TRUE(dataEquals(result1, result2)); -} - -TEST(DataProcessor, XOR_DifferentKeysProduceDifferentResults) { - std::vector data = createTestData(512); - - Encryptor enc1("key1", Decryptor::Type::XOR); - Encryptor enc2("key2", Decryptor::Type::XOR); - - std::vector result1 = enc1.process(data); - std::vector result2 = enc2.process(data); - - TEST_ASSERT_FALSE(dataEquals(result1, result2)); -} - -TEST(DataProcessor, XOR_EmptyKey) { - std::vector data = createTestData(256); - - Encryptor encryptor("", Decryptor::Type::XOR); - Decryptor decryptor("", Decryptor::Type::XOR); - - std::vector encrypted = encryptor.process(data); - std::vector decrypted = decryptor.process(encrypted); - - TEST_ASSERT_TRUE(dataEquals(data, decrypted)); -} - -TEST(DataProcessor, XOR_EmptyData) { - std::vector empty; - std::string key = "test"; - - Encryptor encryptor(key, Decryptor::Type::XOR); - Decryptor decryptor(key, Decryptor::Type::XOR); - - std::vector encrypted = encryptor.process(empty); - std::vector decrypted = decryptor.process(encrypted); - - TEST_ASSERT_TRUE(decrypted.empty()); -} - -// --------------------------------------------------------------------------- -// DataPipe 测试 -// --------------------------------------------------------------------------- - -TEST(DataProcessor, DataPipe_Empty) { - DataPipe pipe; - - TEST_ASSERT_TRUE(pipe.empty()); - TEST_ASSERT_EQ(0u, pipe.size()); - - std::vector data = createTestData(100); - std::vector result = pipe.process(data); - - TEST_ASSERT_TRUE(dataEquals(data, result)); -} - -TEST(DataProcessor, DataPipe_SingleProcessor) { - DataPipe pipe; - pipe.encrypt("key", Decryptor::Type::XOR); - - TEST_ASSERT_FALSE(pipe.empty()); - TEST_ASSERT_EQ(1u, pipe.size()); - - std::vector original = createTestData(256); - std::vector processed = pipe.process(original); - - TEST_ASSERT_FALSE(dataEquals(original, processed)); -} - -TEST(DataProcessor, DataPipe_MultipleProcessors) { - DataPipe pipe; - pipe.encrypt("key", Decryptor::Type::XOR); - pipe.encrypt("key2", Decryptor::Type::XOR); - pipe.encrypt("key", Decryptor::Type::XOR); - pipe.encrypt("key2", Decryptor::Type::XOR); - - TEST_ASSERT_EQ(4u, pipe.size()); - - std::vector original = createTestData(512); - std::vector processed = pipe.process(original); - - TEST_ASSERT_TRUE(dataEquals(original, processed)); -} - -TEST(DataProcessor, DataPipe_EncryptDecrypt) { - DataPipe encryptPipe; - encryptPipe.encrypt("secret-key", Decryptor::Type::XOR); - - DataPipe decryptPipe; - decryptPipe.decrypt("secret-key", Decryptor::Type::XOR); - - std::vector original = createTestData(1024); - std::vector encrypted = encryptPipe.process(original); - std::vector decrypted = decryptPipe.process(encrypted); - - TEST_ASSERT_TRUE(dataEquals(original, decrypted)); -} - -TEST(DataProcessor, DataPipe_Clear) { - DataPipe pipe; - pipe.encrypt("key", Decryptor::Type::XOR); - pipe.encrypt("key2", Decryptor::Type::XOR); - - TEST_ASSERT_EQ(2u, pipe.size()); - - pipe.clear(); - - TEST_ASSERT_TRUE(pipe.empty()); - TEST_ASSERT_EQ(0u, pipe.size()); -} - -TEST(DataProcessor, DataPipe_MoveSemantics) { - DataPipe pipe1; - pipe1.encrypt("key", Decryptor::Type::XOR); - - DataPipe pipe2 = std::move(pipe1); - - TEST_ASSERT_EQ(1u, pipe2.size()); - - std::vector data = createTestData(128); - std::vector result = pipe2.process(data); - - TEST_ASSERT_FALSE(dataEquals(data, result)); -} - -// --------------------------------------------------------------------------- -// 校验和测试 -// --------------------------------------------------------------------------- - -TEST(DataProcessor, Checksum_Consistency) { - std::vector data = createTestData(1024); - - std::vector checksum1 = computeChecksum(data); - std::vector checksum2 = computeChecksum(data); - - TEST_ASSERT_TRUE(dataEquals(checksum1, checksum2)); -} - -TEST(DataProcessor, Checksum_DifferentData) { - std::vector data1 = createTestData(1024, 42); - std::vector data2 = createTestData(1024, 43); - - std::vector checksum1 = computeChecksum(data1); - std::vector checksum2 = computeChecksum(data2); - - TEST_ASSERT_FALSE(dataEquals(checksum1, checksum2)); -} - -TEST(DataProcessor, Checksum_Verify) { - std::vector data = createTestData(2048); - std::vector checksum = computeChecksum(data); - - TEST_ASSERT_TRUE(verifyChecksum(data, checksum)); -} - -TEST(DataProcessor, Checksum_VerifyTampered) { - std::vector data = createTestData(2048); - std::vector checksum = computeChecksum(data); - - data[100] ^= 0xFF; - - TEST_ASSERT_FALSE(verifyChecksum(data, checksum)); -} - -TEST(DataProcessor, Checksum_Size) { - std::vector data = createTestData(100); - std::vector checksum = computeChecksum(data); - - TEST_ASSERT_EQ(32u, checksum.size()); -} - -// --------------------------------------------------------------------------- -// 处理器链测试 -// --------------------------------------------------------------------------- - -TEST(DataProcessor, Chain_EncryptDecrypt) { - std::vector original = createTestData(512); - std::string key = "chain-test-key"; - - Encryptor encryptor(key, Decryptor::Type::XOR); - Decryptor decryptor(key, Decryptor::Type::XOR); - - encryptor.setNext(std::make_unique(key, Decryptor::Type::XOR)); - - std::vector result = encryptor.process(original); - - TEST_ASSERT_TRUE(dataEquals(original, result)); -} - -// --------------------------------------------------------------------------- -// 边界条件测试 -// --------------------------------------------------------------------------- - -TEST(DataProcessor, Boundary_SingleByte) { - std::vector data = {0x42}; - std::string key = "key"; - - Encryptor encryptor(key, Decryptor::Type::XOR); - Decryptor decryptor(key, Decryptor::Type::XOR); - - std::vector encrypted = encryptor.process(data); - std::vector decrypted = decryptor.process(encrypted); - - TEST_ASSERT_TRUE(dataEquals(data, decrypted)); - TEST_ASSERT_EQ(1u, encrypted.size()); -} - -TEST(DataProcessor, Boundary_LargeData) { - std::vector data = createTestData(1024 * 1024); - std::string key = "large-data-key"; - - Encryptor encryptor(key, Decryptor::Type::XOR); - Decryptor decryptor(key, Decryptor::Type::XOR); - - std::vector encrypted = encryptor.process(data); - std::vector decrypted = decryptor.process(encrypted); - - TEST_ASSERT_TRUE(dataEquals(data, decrypted)); -} diff --git a/Tools/asset_packer.cpp b/Tools/asset_packer.cpp deleted file mode 100644 index 0961c1d..0000000 --- a/Tools/asset_packer.cpp +++ /dev/null @@ -1,460 +0,0 @@ -/** - * @file asset_packer.cpp - * @brief 资源打包工具 - * - * 命令行工具,用于创建、查看和提取资源包。 - * - * 用法: - * asset_packer create [options] - * asset_packer list - * asset_packer extract - * - * 选项: - * -c, --compression 压缩算法 (none, zstd, lz4, zlib),默认 zstd - * -l, --level 压缩级别 (1-22),默认 3 - * -e, --encrypt 加密密钥 - * -t, --encrypt-type 加密类型 (xor, aes),默认 xor - * -v, --verbose 详细输出 - * -h, --help 显示帮助 - */ - -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -namespace fs = std::filesystem; - -// --------------------------------------------------------------------------- -// 命令行参数解析 -// --------------------------------------------------------------------------- - -struct Options { - std::string command; - std::string outputFile; - std::string packFile; - std::string outputDir; - std::vector inputs; - - extra2d::Compression compression = extra2d::Compression::Zstd; - int compressionLevel = 3; - std::string encryptKey; - extra2d::Decryptor::Type encryptType = extra2d::Decryptor::Type::XOR; - - bool verbose = false; - bool showHelp = false; -}; - -/** - * @brief 打印帮助信息 - */ -void printHelp(const char* programName) { - std::cout << "Extra2D 资源打包工具 v1.0.0\n\n"; - std::cout << "用法:\n"; - std::cout << " " << programName << " create [options] \n"; - std::cout << " " << programName << " list \n"; - std::cout << " " << programName << " extract \n\n"; - std::cout << "命令:\n"; - std::cout << " create 创建资源包\n"; - std::cout << " list 列出资源包内容\n"; - std::cout << " extract 提取资源包内容\n\n"; - std::cout << "选项:\n"; - std::cout << " -c, --compression 压缩算法 (none, zstd, lz4, zlib),默认 zstd\n"; - std::cout << " -l, --level 压缩级别 (1-22),默认 3\n"; - std::cout << " -e, --encrypt 加密密钥\n"; - std::cout << " -t, --encrypt-type 加密类型 (xor, aes),默认 xor\n"; - std::cout << " -v, --verbose 详细输出\n"; - std::cout << " -h, --help 显示帮助\n"; -} - -/** - * @brief 解析压缩算法名称 - */ -extra2d::Compression parseCompression(const std::string& name) { - if (name == "none" || name == "None" || name == "NONE") { - return extra2d::Compression::None; - } else if (name == "zstd" || name == "Zstd" || name == "ZSTD") { - return extra2d::Compression::Zstd; - } else if (name == "lz4" || name == "LZ4") { - return extra2d::Compression::LZ4; - } else if (name == "zlib" || name == "Zlib" || name == "ZLIB") { - return extra2d::Compression::Zlib; - } - return extra2d::Compression::Zstd; -} - -/** - * @brief 解析加密类型 - */ -extra2d::Decryptor::Type parseEncryptType(const std::string& name) { - if (name == "xor" || name == "XOR") { - return extra2d::Decryptor::Type::XOR; - } else if (name == "aes" || name == "AES" || name == "aes256") { - return extra2d::Decryptor::Type::AES256; - } - return extra2d::Decryptor::Type::XOR; -} - -/** - * @brief 解析命令行参数 - */ -bool parseArgs(int argc, char* argv[], Options& opts) { - if (argc < 2) { - opts.showHelp = true; - return false; - } - - opts.command = argv[1]; - - if (opts.command == "-h" || opts.command == "--help") { - opts.showHelp = true; - return true; - } - - for (int i = 2; i < argc; ++i) { - std::string arg = argv[i]; - - if (arg == "-h" || arg == "--help") { - opts.showHelp = true; - return true; - } else if (arg == "-v" || arg == "--verbose") { - opts.verbose = true; - } else if (arg == "-c" || arg == "--compression") { - if (i + 1 < argc) { - opts.compression = parseCompression(argv[++i]); - } - } else if (arg == "-l" || arg == "--level") { - if (i + 1 < argc) { - opts.compressionLevel = std::stoi(argv[++i]); - } - } else if (arg == "-e" || arg == "--encrypt") { - if (i + 1 < argc) { - opts.encryptKey = argv[++i]; - } - } else if (arg == "-t" || arg == "--encrypt-type") { - if (i + 1 < argc) { - opts.encryptType = parseEncryptType(argv[++i]); - } - } else if (arg[0] != '-') { - if (opts.command == "create") { - if (opts.outputFile.empty()) { - opts.outputFile = arg; - } else { - opts.inputs.push_back(arg); - } - } else if (opts.command == "list") { - if (opts.packFile.empty()) { - opts.packFile = arg; - } - } else if (opts.command == "extract") { - if (opts.packFile.empty()) { - opts.packFile = arg; - } else if (opts.outputDir.empty()) { - opts.outputDir = arg; - } - } - } - } - - return true; -} - -// --------------------------------------------------------------------------- -// 创建资源包 -// --------------------------------------------------------------------------- - -/** - * @brief 格式化文件大小 - */ -std::string formatSize(size_t bytes) { - const char* units[] = {"B", "KB", "MB", "GB"}; - int unitIndex = 0; - double size = static_cast(bytes); - - while (size >= 1024.0 && unitIndex < 3) { - size /= 1024.0; - ++unitIndex; - } - - std::ostringstream ss; - ss.precision(2); - ss << size << " " << units[unitIndex]; - return ss.str(); -} - -/** - * @brief 创建资源包 - */ -int createPack(const Options& opts) { - if (opts.outputFile.empty()) { - std::cerr << "错误: 未指定输出文件\n"; - return 1; - } - - if (opts.inputs.empty()) { - std::cerr << "错误: 未指定输入文件或目录\n"; - return 1; - } - - auto startTime = std::chrono::high_resolution_clock::now(); - - extra2d::AssetPackBuilder builder(opts.compression, opts.compressionLevel); - - if (!opts.encryptKey.empty()) { - builder.setEncryption(opts.encryptKey, opts.encryptType); - } - - size_t totalFiles = 0; - size_t totalSize = 0; - - for (const auto& input : opts.inputs) { - fs::path inputPath(input); - - if (!fs::exists(inputPath)) { - std::cerr << "警告: 跳过不存在的路径: " << input << "\n"; - continue; - } - - if (fs::is_directory(inputPath)) { - if (opts.verbose) { - std::cout << "扫描目录: " << input << "\n"; - } - - for (const auto& entry : fs::recursive_directory_iterator(inputPath)) { - if (entry.is_regular_file()) { - fs::path relativePath = fs::relative(entry.path(), inputPath); - std::string packPath = relativePath.generic_string(); - - if (opts.verbose) { - std::cout << " 添加: " << packPath << "\n"; - } - - if (builder.addFile(entry.path().string(), packPath)) { - ++totalFiles; - totalSize += fs::file_size(entry.path()); - } - } - } - } else if (fs::is_regular_file(inputPath)) { - std::string packPath = inputPath.filename().string(); - - if (opts.verbose) { - std::cout << "添加文件: " << packPath << "\n"; - } - - if (builder.addFile(inputPath.string(), packPath)) { - ++totalFiles; - totalSize += fs::file_size(inputPath); - } - } - } - - if (totalFiles == 0) { - std::cerr << "错误: 没有文件被添加到资源包\n"; - return 1; - } - - if (opts.verbose) { - std::cout << "\n构建资源包: " << opts.outputFile << "\n"; - } - - if (!builder.build(opts.outputFile)) { - std::cerr << "错误: 构建资源包失败\n"; - return 1; - } - - auto endTime = std::chrono::high_resolution_clock::now(); - auto duration = std::chrono::duration_cast(endTime - startTime); - - size_t packSize = fs::file_size(opts.outputFile); - double ratio = totalSize > 0 ? (100.0 * packSize / totalSize) : 0.0; - - std::cout << "\n完成!\n"; - std::cout << " 文件数量: " << totalFiles << "\n"; - std::cout << " 原始大小: " << formatSize(totalSize) << "\n"; - std::cout << " 压缩大小: " << formatSize(packSize) << "\n"; - std::cout << " 压缩比率: " << std::fixed << std::setprecision(1) << ratio << "%\n"; - std::cout << " 耗时: " << duration.count() << "ms\n"; - - return 0; -} - -// --------------------------------------------------------------------------- -// 列出资源包内容 -// --------------------------------------------------------------------------- - -/** - * @brief 列出资源包内容 - */ -int listPack(const Options& opts) { - if (opts.packFile.empty()) { - std::cerr << "错误: 未指定资源包文件\n"; - return 1; - } - - if (!fs::exists(opts.packFile)) { - std::cerr << "错误: 资源包文件不存在: " << opts.packFile << "\n"; - return 1; - } - - extra2d::AssetPack pack; - if (!pack.open(opts.packFile)) { - std::cerr << "错误: 无法打开资源包\n"; - return 1; - } - - const auto& header = pack.header(); - - std::cout << "资源包: " << opts.packFile << "\n"; - std::cout << "----------------------------------------\n"; - std::cout << "版本: " << header.version << "\n"; - std::cout << "条目数: " << pack.count() << "\n"; - std::cout << "----------------------------------------\n\n"; - - auto assets = pack.assets(); - - std::cout << std::left << std::setw(40) << "路径" - << std::right << std::setw(12) << "大小" << "\n"; - std::cout << std::string(52, '-') << "\n"; - - size_t totalSize = 0; - for (const auto& id : assets) { - auto* entry = pack.getEntry(id); - if (entry) { - std::cout << std::left << std::setw(40) << id.path - << std::right << std::setw(12) << formatSize(entry->size) << "\n"; - totalSize += entry->size; - } - } - - std::cout << std::string(52, '-') << "\n"; - std::cout << std::left << std::setw(40) << "总计" - << std::right << std::setw(12) << formatSize(totalSize) << "\n"; - - pack.close(); - return 0; -} - -// --------------------------------------------------------------------------- -// 提取资源包 -// --------------------------------------------------------------------------- - -/** - * @brief 提取资源包内容 - */ -int extractPack(const Options& opts) { - if (opts.packFile.empty()) { - std::cerr << "错误: 未指定资源包文件\n"; - return 1; - } - - if (opts.outputDir.empty()) { - std::cerr << "错误: 未指定输出目录\n"; - return 1; - } - - if (!fs::exists(opts.packFile)) { - std::cerr << "错误: 资源包文件不存在: " << opts.packFile << "\n"; - return 1; - } - - extra2d::AssetPack pack; - if (!pack.open(opts.packFile)) { - std::cerr << "错误: 无法打开资源包\n"; - return 1; - } - - fs::path outputDir(opts.outputDir); - - if (!fs::exists(outputDir)) { - fs::create_directories(outputDir); - } - - auto assets = pack.assets(); - size_t extracted = 0; - size_t failed = 0; - - std::cout << "提取资源包到: " << opts.outputDir << "\n\n"; - - for (const auto& id : assets) { - fs::path outputPath = outputDir / id.path; - fs::path parentDir = outputPath.parent_path(); - - if (!parentDir.empty() && !fs::exists(parentDir)) { - fs::create_directories(parentDir); - } - - auto data = pack.read(id); - - if (data.empty()) { - std::cerr << "警告: 无法读取: " << id.path << "\n"; - ++failed; - continue; - } - - std::ofstream outFile(outputPath, std::ios::binary); - if (!outFile) { - std::cerr << "警告: 无法创建文件: " << outputPath.string() << "\n"; - ++failed; - continue; - } - - outFile.write(reinterpret_cast(data.data()), data.size()); - - if (opts.verbose) { - std::cout << " 提取: " << id.path << " (" << formatSize(data.size()) << ")\n"; - } - - ++extracted; - } - - pack.close(); - - std::cout << "\n完成! 提取 " << extracted << " 个文件"; - if (failed > 0) { - std::cout << ", " << failed << " 个失败"; - } - std::cout << "\n"; - - return failed > 0 ? 1 : 0; -} - -// --------------------------------------------------------------------------- -// 主函数 -// --------------------------------------------------------------------------- - -#include - -int main(int argc, char* argv[]) { - Options opts; - - if (!parseArgs(argc, argv, opts)) { - printHelp(argv[0]); - return 1; - } - - if (opts.showHelp) { - printHelp(argv[0]); - return 0; - } - - if (opts.command == "create") { - return createPack(opts); - } else if (opts.command == "list") { - return listPack(opts); - } else if (opts.command == "extract") { - return extractPack(opts); - } else { - std::cerr << "错误: 未知命令: " << opts.command << "\n"; - printHelp(argv[0]); - return 1; - } -} diff --git a/xmake.lua b/xmake.lua index b6ede1a..65187d3 100644 --- a/xmake.lua +++ b/xmake.lua @@ -1,9 +1,6 @@ -- ============================================== -- Extra2D - 2D Game Engine -- Build System: Xmake --- --- 窗口后端: GLFW --- 渲染后端: Vulkan -- ============================================== -- 项目元信息 @@ -32,7 +29,7 @@ option_end() -- 添加依赖包 -- ============================================== -add_requires("glm", "nlohmann_json", "glfw", "zstd", "lz4", "zlib", "vulkan-headers", "vulkan-loader", "vulkan-memory-allocator") +add_requires("glm", "nlohmann_json") -- ============================================== -- 引擎库目标 @@ -44,21 +41,11 @@ target("extra2d") -- 引擎核心源文件 add_files("Extra2D/src/**.cpp") - -- GLFW 窗口后端源文件 - add_files("Extra2D/src/platform/glfw/*.cpp") - -- 头文件路径 add_includedirs("Extra2D/include", {public = true}) - add_includedirs("Extra2D/include/extra2d/platform", {public = true}) -- 依赖包 - add_packages("glm", "nlohmann_json", "glfw", "zstd", "lz4", "zlib", "vulkan-headers", "vulkan-loader", "vulkan-memory-allocator", {public = true}) - - -- 系统库链接 - add_syslinks("winmm", "imm32", "version", "setupapi", {public = true}) - - -- 启用压缩库 - add_defines("E2D_USE_ZSTD", "E2D_USE_LZ4", "E2D_USE_ZLIB", {public = true}) + add_packages("glm", "nlohmann_json", {public = true}) -- 编译器标志 (MSVC) add_cxflags("/W4", {force = true}) @@ -84,10 +71,6 @@ target("extra2d_tests") -- 测试源文件 add_files("Tests/test_framework.cpp") add_files("Tests/test_main.cpp") - add_files("Tests/test_data_processor.cpp") - add_files("Tests/test_asset_cache.cpp") - add_files("Tests/test_asset_pack.cpp") - add_files("Tests/test_asset_service.cpp") -- 头文件路径 add_includedirs("Extra2D/include", {public = true}) @@ -97,8 +80,7 @@ target("extra2d_tests") add_deps("extra2d") -- 依赖包 - add_packages("glm", "nlohmann_json", "glfw", "zstd", "lz4", "zlib") - add_syslinks("winmm", "imm32", "version", "setupapi") + add_packages("glm", "nlohmann_json") -- 编译器标志 (MSVC) add_cxflags("/W4", {force = true}) @@ -112,38 +94,3 @@ target("extra2d_tests") add_cxflags("/O2", {force = true}) end target_end() - --- ============================================== --- 资源打包工具 --- ============================================== - -target("asset_packer") - set_kind("binary") - set_default(false) - - -- 源文件 - add_files("Tools/asset_packer.cpp") - - -- 头文件路径 - add_includedirs("Extra2D/include") - - -- 依赖引擎库 - add_deps("extra2d") - - -- 依赖包 - add_packages("glm", "nlohmann_json", "glfw", "zstd", "lz4", "zlib") - add_syslinks("winmm", "imm32", "version", "setupapi") - - -- 编译器标志 (MSVC) - add_cxflags("/W4", {force = true}) - add_cxflags("/wd4100", "/wd4189", "/wd4458", "/wd4324", "/wd4310", "/wd4505", "/wd4702", {force = true}) - - if is_mode("debug") then - add_defines("_DEBUG") - add_cxflags("/Od", "/Zi", {force = true}) - else - add_defines("NDEBUG") - add_cxflags("/O2", {force = true}) - end -target_end() -