From bdf78f5ecadedeac7b76896b2c82aaa77951f88e Mon Sep 17 00:00:00 2001 From: ChestnutYueyue <952134128@qq.com> Date: Sun, 1 Mar 2026 15:39:07 +0800 Subject: [PATCH] =?UTF-8?q?feat(resource):=20=E5=AE=9E=E7=8E=B0=E8=B5=84?= =?UTF-8?q?=E6=BA=90=E7=AE=A1=E7=90=86=E7=B3=BB=E7=BB=9F=E5=8F=8A=E7=A4=BA?= =?UTF-8?q?=E4=BE=8B=E7=A8=8B=E5=BA=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 新增资源管理系统,支持纹理、字体、着色器、音频等资源的加载和管理: - 添加 ResourceManager 核心模块 - 实现 Texture、Font、Shader、Audio 等资源类 - 添加 FontAtlas 动态字符图集管理 - 实现 Material 材质系统 - 添加 Text 文本渲染支持 - 新增资源加载示例程序 - 更新构建系统以支持资源加载示例 - 完善文档和 README --- examples/resource_loading/README.md | 100 ++++++++ examples/resource_loading/main.cpp | 221 ++++++++++++++++++ examples/resource_loading/xmake.lua | 75 ++++++ include/context/context.h | 8 + include/extra2d.h | 12 + include/resource/audio.h | 96 ++++++++ include/resource/font.h | 174 ++++++++++++++ include/resource/font_atlas.h | 178 +++++++++++++++ include/resource/material.h | 110 +++++++++ include/resource/resource.h | 62 +++++ include/resource/resource_cache.h | 135 +++++++++++ include/resource/resource_manager.h | 100 ++++++++ include/resource/shader.h | 125 ++++++++++ include/resource/text.h | 171 ++++++++++++++ include/resource/texture.h | 152 +++++++++++++ include/types/ptr/intrusive_ptr.h | 2 + include/types/ptr/ref_counted.h | 3 +- src/context/context.cpp | 14 +- src/platform/window_module.cpp | 5 + src/resource/audio.cpp | 114 ++++++++++ src/resource/font.cpp | 340 ++++++++++++++++++++++++++++ src/resource/font_atlas.cpp | 224 ++++++++++++++++++ src/resource/material.cpp | 87 +++++++ src/resource/resource_manager.cpp | 238 +++++++++++++++++++ src/resource/shader.cpp | 180 +++++++++++++++ src/resource/text.cpp | 213 +++++++++++++++++ src/resource/texture.cpp | 271 ++++++++++++++++++++++ src/types/math/color.cpp | 15 ++ xmake.lua | 3 +- xmake/engine.lua | 6 +- 30 files changed, 3429 insertions(+), 5 deletions(-) create mode 100644 examples/resource_loading/README.md create mode 100644 examples/resource_loading/main.cpp create mode 100644 examples/resource_loading/xmake.lua create mode 100644 include/resource/audio.h create mode 100644 include/resource/font.h create mode 100644 include/resource/font_atlas.h create mode 100644 include/resource/material.h create mode 100644 include/resource/resource.h create mode 100644 include/resource/resource_cache.h create mode 100644 include/resource/resource_manager.h create mode 100644 include/resource/shader.h create mode 100644 include/resource/text.h create mode 100644 include/resource/texture.h create mode 100644 src/resource/audio.cpp create mode 100644 src/resource/font.cpp create mode 100644 src/resource/font_atlas.cpp create mode 100644 src/resource/material.cpp create mode 100644 src/resource/resource_manager.cpp create mode 100644 src/resource/shader.cpp create mode 100644 src/resource/text.cpp create mode 100644 src/resource/texture.cpp create mode 100644 src/types/math/color.cpp diff --git a/examples/resource_loading/README.md b/examples/resource_loading/README.md new file mode 100644 index 0000000..783247e --- /dev/null +++ b/examples/resource_loading/README.md @@ -0,0 +1,100 @@ +# 资源加载示例 + +演示如何使用 Extra2D 引擎的资源管理器加载各种资源。 + +## 功能演示 + +1. **纹理加载** - 从文件加载 PNG/JPG 纹理 +2. **字体加载** - 使用 FreeType 加载 TTF 字体,支持中英文 +3. **文本创建** - 创建可渲染的文本对象 +4. **着色器加载** - 从文件或源码加载 GLSL 着色器 +5. **材质创建** - 组合纹理和着色器创建材质 +6. **音频加载** - 加载音效和音乐 +7. **资源统计** - 查看已加载资源的统计信息 + +## 目录结构 + +``` +resource_loading/ +├── main.cpp # 示例主文件 +├── xmake.lua # 构建脚本 +├── README.md # 本文件 +└── romfs/ + └── assets/ # 资源目录 + ├── textures/ # 纹理文件 + ├── fonts/ # 字体文件 + ├── audio/ # 音频文件 + └── shaders/ # 着色器文件 +``` + +## 构建 + +### MinGW (Windows) + +```bash +xmake f -p mingw -a x86_64 +xmake resource_loading +``` + +### Nintendo Switch + +```bash +xmake f -p switch -a aarch64 +xmake resource_loading +``` + +## 运行 + +### Windows + +```bash +./build/examples/resource_loading/resource_loading.exe +``` + +### Switch + +将生成的 `resource_loading.nro` 复制到 Switch SD 卡运行。 + +## 资源管理 API 示例 + +```cpp +// 获取资源管理器 +auto& resources = app->context().resources(); + +// 加载纹理 +auto texture = resources.getTexture("assets/textures/image.png"); + +// 加载字体 +FontConfig config; +config.fontSize = 24; +config.preloadCommon = true; +auto font = resources.getFont("assets/fonts/font.ttf", config); + +// 创建文本 +auto text = resources.createText(font, "Hello 世界!"); +text->setFontSize(32); +text->rebuild(); + +// 加载着色器 +auto shader = resources.getShader("vs.glsl", "fs.glsl"); + +// 创建材质 +auto material = resources.createMaterial(shader); +material->setTexture("uTexture", texture); +material->setColor("uColor", Color::Red); + +// 加载音频 +auto sound = resources.getAudio("jump.wav", AudioType::Sound); +auto music = resources.getAudio("bgm.mp3", AudioType::Music); + +// 获取资源统计 +auto stats = resources.getStats(); +printf("Textures: %zu\n", stats.textureCount); +``` + +## 注意事项 + +- 字体文件需要是 TTF 格式 +- 纹理支持 PNG、JPG、BMP 等格式(通过 stb_image) +- 音频支持 WAV、MP3、OGG 等格式(通过 SDL2_mixer) +- 着色器使用 OpenGL ES 3.2 语法 diff --git a/examples/resource_loading/main.cpp b/examples/resource_loading/main.cpp new file mode 100644 index 0000000..8004a2e --- /dev/null +++ b/examples/resource_loading/main.cpp @@ -0,0 +1,221 @@ +#include +#include + +using namespace extra2d; + +/** + * @brief 资源加载示例 + * + * 演示如何使用资源管理器加载各种资源: + * - 纹理 (Texture) + * - 字体 (Font) + * - 着色器 (Shader) + * - 音频 (Audio) + */ + +int main(int argc, char **argv) { + // 创建应用(自动创建窗口) + auto app = Application::create(); + + AppConfig config; + config.title = "Resource Loading Example"; + config.width = 1280; + config.height = 720; + + if (!app->init(config)) { + printf("Failed to initialize!\n"); + return -1; + } + + // 获取资源管理器 + auto& resources = app->getContext()->resources(); + + printf("=== Resource Loading Example ===\n\n"); + + // ============================================ + // 1. 加载纹理 + // ============================================ + printf("1. Loading Textures...\n"); + + // 从文件加载纹理 + auto texture = resources.getTexture("assets/textures/test.png"); + if (texture) { + printf(" ✓ Loaded texture: %dx%d\n", texture->getWidth(), texture->getHeight()); + } else { + printf(" ✗ Failed to load texture (file not found)\n"); + } + + // 使用默认纹理 + auto defaultTex = resources.getDefaultTexture(); + if (defaultTex) { + printf(" ✓ Default texture available: %dx%d\n", + defaultTex->getWidth(), defaultTex->getHeight()); + } + + // ============================================ + // 2. 加载字体 + // ============================================ + printf("\n2. Loading Fonts...\n"); + + FontConfig fontConfig; + fontConfig.fontSize = 24; + fontConfig.preloadCommon = true; // 预加载常用字符 + + auto font = resources.getFont("assets/fonts/font.ttf", fontConfig); + if (font) { + printf(" ✓ Loaded font with size %d\n", fontConfig.fontSize); + + // 测试字体测量 + Vec2 textSize = font->measureText("Hello 世界!", 24.0f); + printf(" ✓ Text 'Hello 世界!' size: %.1f x %.1f\n", textSize.x, textSize.y); + + // 获取字符信息(会自动渲染并缓存) + const GlyphInfo* glyph = font->getGlyph('A'); + if (glyph && glyph->valid) { + printf(" ✓ Glyph 'A' cached in atlas page %d\n", glyph->pageIndex); + } + } else { + printf(" ✗ Failed to load font (file not found)\n"); + } + + // ============================================ + // 3. 创建文本对象 + // ============================================ + printf("\n3. Creating Text Objects...\n"); + + if (font) { + auto text = resources.createText(font, "Resource Loading Example\n你好,世界!"); + text->setFontSize(32); + text->setColor(Color::White); + text->setAlign(TextAlign::Center); + text->rebuild(); + + Vec2 size = text->getSize(); + printf(" ✓ Created text object: %.1f x %.1f\n", size.x, size.y); + printf(" ✓ Text has %zu lines\n", text->getLines().size()); + } + + // ============================================ + // 4. 加载着色器 + // ============================================ + printf("\n4. Loading Shaders...\n"); + + // 从文件加载着色器 + auto shader = resources.getShader("assets/shaders/sprite.vs", + "assets/shaders/sprite.fs"); + if (shader) { + printf(" ✓ Loaded shader from files\n"); + } else { + printf(" ✗ Failed to load shader (files not found)\n"); + } + + // 使用默认着色器 + auto defaultShader = resources.getDefaultShader(); + if (defaultShader) { + printf(" ✓ Default shader available\n"); + } + + // 从源码创建着色器 + const char* vsSource = R"( + #version 320 es + layout(location = 0) in vec2 aPosition; + layout(location = 1) in vec2 aTexCoord; + out vec2 vTexCoord; + void main() { + gl_Position = vec4(aPosition, 0.0, 1.0); + vTexCoord = aTexCoord; + } + )"; + + const char* fsSource = R"( + #version 320 es + precision mediump float; + in vec2 vTexCoord; + out vec4 fragColor; + void main() { + fragColor = vec4(vTexCoord, 0.5, 1.0); + } + )"; + + auto runtimeShader = resources.getShaderFromSource("runtime_shader", vsSource, fsSource); + if (runtimeShader) { + printf(" ✓ Created shader from source\n"); + } + + // ============================================ + // 5. 创建材质 + // ============================================ + printf("\n5. Creating Materials...\n"); + + auto material = resources.createMaterial(defaultShader); + if (material) { + material->setTexture("uTexture", defaultTex); + material->setColor("uColor", Color(1.0f, 0.5f, 0.2f, 1.0f)); + material->setFloat("uTime", 0.0f); + printf(" ✓ Created material with texture and uniforms\n"); + } + + // 注册命名材质以便后续获取 + resources.registerMaterial("sprite_mat", material); + auto retrievedMat = resources.getMaterial("sprite_mat"); + if (retrievedMat) { + printf(" ✓ Retrieved material by name\n"); + } + + // ============================================ + // 6. 加载音频 + // ============================================ + printf("\n6. Loading Audio...\n"); + + // 加载音效 + auto sound = resources.getAudio("assets/audio/jump.wav", AudioType::Sound); + if (sound) { + printf(" ✓ Loaded sound effect\n"); + // sound->play(); // 播放一次 + } else { + printf(" ✗ Failed to load sound (file not found)\n"); + } + + // 加载音乐 + auto music = resources.getAudio("assets/audio/bgm.mp3", AudioType::Music); + if (music) { + printf(" ✓ Loaded music\n"); + // music->play(-1); // 循环播放 + } else { + printf(" ✗ Failed to load music (file not found)\n"); + } + + // ============================================ + // 7. 资源统计 + // ============================================ + printf("\n7. Resource Statistics...\n"); + + auto stats = resources.getStats(); + printf(" Textures: %zu\n", stats.textureCount); + printf(" Shaders: %zu\n", stats.shaderCount); + printf(" Materials: %zu\n", stats.materialCount); + printf(" Fonts: %zu\n", stats.fontCount); + printf(" Texts: %zu\n", stats.textCount); + printf(" Audio: %zu\n", stats.audioCount); + + // ============================================ + // 8. 默认资源 + // ============================================ + printf("\n8. Default Resources...\n"); + + auto defTex = resources.getDefaultTexture(); + auto defShader = resources.getDefaultShader(); + auto defMaterial = resources.getDefaultMaterial(); + + printf(" ✓ Default Texture: %s\n", defTex ? "available" : "null"); + printf(" ✓ Default Shader: %s\n", defShader ? "available" : "null"); + printf(" ✓ Default Material: %s\n", defMaterial ? "available" : "null"); + + printf("\n=== Resource Loading Complete ===\n"); + + // 运行应用(主循环) + printf("\nRunning main loop...\n"); + app->run(); + + return 0; +} diff --git a/examples/resource_loading/xmake.lua b/examples/resource_loading/xmake.lua new file mode 100644 index 0000000..8e80fa1 --- /dev/null +++ b/examples/resource_loading/xmake.lua @@ -0,0 +1,75 @@ +-- ============================================== +-- 资源加载示例 - Xmake 构建脚本 +-- 支持平台: MinGW (Windows), Nintendo Switch +-- ============================================== + +-- 获取当前脚本所在目录(示例根目录) +local example_dir = os.scriptdir() + +-- 可执行文件目标 +target("resource_loading") + set_kind("binary") + add_files("main.cpp") + add_includedirs("../../include") + add_deps("extra2d") + + -- 使用与主项目相同的平台配置 + if is_plat("switch") then + set_targetdir("../../build/examples/resource_loading") + + -- 构建后生成 NRO 文件 + after_build(function (target) + local devkitPro = os.getenv("DEVKITPRO") or "C:/devkitPro" + local elf_file = target:targetfile() + local output_dir = path.directory(elf_file) + local nacp_file = path.join(output_dir, "resource_loading.nacp") + local nro_file = path.join(output_dir, "resource_loading.nro") + local nacptool = path.join(devkitPro, "tools/bin/nacptool.exe") + local elf2nro = path.join(devkitPro, "tools/bin/elf2nro.exe") + + if os.isfile(nacptool) and os.isfile(elf2nro) then + os.vrunv(nacptool, {"--create", "Resource Loading Example", "Extra2D Team", "1.0.0", nacp_file}) + local romfs = path.join(example_dir, "romfs") + if os.isdir(romfs) then + os.vrunv(elf2nro, {elf_file, nro_file, "--nacp=" .. nacp_file, "--romfsdir=" .. romfs}) + else + os.vrunv(elf2nro, {elf_file, nro_file, "--nacp=" .. nacp_file}) + end + print("Generated NRO: " .. nro_file) + end + end) + + -- 打包时将 NRO 文件复制到 package 目录 + after_package(function (target) + local nro_file = path.join(target:targetdir(), "resource_loading.nro") + local package_dir = target:packagedir() + if os.isfile(nro_file) and package_dir then + os.cp(nro_file, package_dir) + print("Copied NRO to package: " .. package_dir) + end + end) + + elseif is_plat("mingw") then + set_targetdir("../../build/examples/resource_loading") + + -- 复制资源到输出目录 + after_build(function (target) + local romfs = path.join(example_dir, "romfs") + if os.isdir(romfs) then + local target_dir = path.directory(target:targetfile()) + local assets_dir = path.join(target_dir, "assets") + + -- 创建 assets 目录 + if not os.isdir(assets_dir) then + os.mkdir(assets_dir) + end + + -- 复制所有资源文件(包括子目录) + os.cp(path.join(romfs, "assets/**"), assets_dir) + print("Copied assets from " .. romfs .. " to " .. assets_dir) + else + print("Warning: romfs directory not found at " .. romfs) + end + end) + end +target_end() diff --git a/include/context/context.h b/include/context/context.h index 306fd99..8cafe8d 100644 --- a/include/context/context.h +++ b/include/context/context.h @@ -9,6 +9,7 @@ namespace extra2d { class ModuleRegistry; class PluginLoader; class TimerModule; +class ResourceManager; /** * @brief 引擎上下文 @@ -66,6 +67,12 @@ public: */ TimerModule& timer(); + /** + * @brief 获取资源管理器 + */ + ResourceManager& resources(); + +private: /** * @brief 获取总运行时间 */ @@ -89,6 +96,7 @@ public: private: std::unique_ptr pluginLoader_; std::unique_ptr timerModule_; + std::unique_ptr resourceManager_; float totalTime_ = 0.0f; uint64 frameCount_ = 0; diff --git a/include/extra2d.h b/include/extra2d.h index 356dbd2..0e29f3d 100644 --- a/include/extra2d.h +++ b/include/extra2d.h @@ -41,6 +41,18 @@ #include #include +// Resource System +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + // Application #include diff --git a/include/resource/audio.h b/include/resource/audio.h new file mode 100644 index 0000000..b842407 --- /dev/null +++ b/include/resource/audio.h @@ -0,0 +1,96 @@ +#pragma once + +#include +#include +#include + +namespace extra2d { + +/** + * @brief 音频类型枚举 + */ +enum class AudioType : uint8 { + Sound, // 音效(短音频,完全加载到内存) + Music // 音乐(流式播放) +}; + +/** + * @brief 音频资源类 + * + * 封装 SDL2_mixer 音频 + */ +class Audio : public Resource { +public: + Audio(); + ~Audio() override; + + /** + * @brief 获取资源类型 + */ + ResourceType getType() const override { return ResourceType::Audio; } + + /** + * @brief 从文件加载音频 + * @param path 文件路径 + * @param type 音频类型 + * @return 是否加载成功 + */ + bool loadFromFile(const std::string& path, AudioType type); + + /** + * @brief 播放音频 + * @param loops 循环次数,-1 表示无限循环,0 表示播放一次 + */ + void play(int loops = 0); + + /** + * @brief 暂停播放 + */ + void pause(); + + /** + * @brief 恢复播放 + */ + void resume(); + + /** + * @brief 停止播放 + */ + void stop(); + + /** + * @brief 设置音量 + * @param volume 音量(0.0 - 1.0) + */ + void setVolume(float volume); + + /** + * @brief 获取音量 + * @return 音量(0.0 - 1.0) + */ + float getVolume() const; + + /** + * @brief 检查是否正在播放 + */ + bool isPlaying() const; + + /** + * @brief 检查是否已暂停 + */ + bool isPaused() const; + + /** + * @brief 获取音频类型 + */ + AudioType getAudioType() const { return type_; } + +private: + AudioType type_ = AudioType::Sound; // 音频类型 + Mix_Chunk* chunk_ = nullptr; // 音效数据 + Mix_Music* music_ = nullptr; // 音乐数据 + int channel_ = -1; // 音效通道 + float volume_ = 1.0f; // 音量 +}; + +} // namespace extra2d diff --git a/include/resource/font.h b/include/resource/font.h new file mode 100644 index 0000000..f6f8c64 --- /dev/null +++ b/include/resource/font.h @@ -0,0 +1,174 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +// FreeType 头文件 +#include +#include FT_FREETYPE_H + +namespace extra2d { + +/** + * @brief 常用字符集 + */ +namespace CommonCharset { + // ASCII 可打印字符 + constexpr const char* ASCII = + " !\"#$%&'()*+,-./0123456789:;<=>?" + "@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_" + "`abcdefghijklmnopqrstuvwxyz{|}~"; + + // 常用中文(前100个高频字) + constexpr const char* CHINESE_COMMON = + "的一是在不了有和人这中大为上个国我以要他时来用们生到作地于出就分对成" + "会可主发年动同工也能下过子说产种面而方后多定行学法所民得经十三之进着" + "等部度家电力里如水化高自二理起小物现实加量都两体制机当使点从业本去把"; + + // 英文标点 + constexpr const char* PUNCTUATION_EN = ".,;:!?-'\"()[]{}/|\\@#$%&*+-_=<>~`"; + + // 中文标点 + constexpr const char* PUNCTUATION_CN = ",。、;:!?\"\"''()【】《》"; +} + +/** + * @brief 字体配置结构 + */ +struct FontConfig { + uint32 fontSize = 16; // 字体大小(像素) + bool useSDF = false; // 是否使用 SDF(有向距离场) + uint32 sdfPadding = 4; // SDF 边距 + bool preloadCommon = true; // 是否预加载常用字符 + bool hinting = true; // 是否使用字体 hinting +}; + +/** + * @brief 字体资源类 + * + * 使用 FreeType 加载字体,动态生成字符图集 + */ +class Font : public Resource { +public: + Font(); + ~Font() override; + + /** + * @brief 获取资源类型 + */ + ResourceType getType() const override { return ResourceType::Font; } + + /** + * @brief 从文件加载字体 + * @param path 字体文件路径 + * @param config 字体配置 + * @return 是否加载成功 + */ + bool loadFromFile(const std::string& path, const FontConfig& config); + + /** + * @brief 从内存加载字体 + * @param data 字体数据 + * @param config 字体配置 + * @return 是否加载成功 + */ + bool loadFromMemory(const std::vector& data, const FontConfig& config); + + /** + * @brief 获取或创建字符 + * @param codepoint Unicode 码点 + * @return 字符信息指针,如果失败返回 nullptr + */ + const GlyphInfo* getGlyph(uint32 codepoint); + + /** + * @brief 测量文本尺寸 + * @param text 文本内容 + * @param fontSize 字体大小(0 表示使用默认大小) + * @return 文本尺寸(宽度和高度) + */ + Vec2 measureText(const std::string& text, float fontSize = 0.0f); + + /** + * @brief 获取两个字之间的字距 + * @param codepoint1 第一个字符 + * @param codepoint2 第二个字符 + * @return 字距调整值 + */ + float getKerning(uint32 codepoint1, uint32 codepoint2); + + /** + * @brief 获取行高 + * @param fontSize 字体大小(0 表示使用默认大小) + * @return 行高 + */ + float getLineHeight(float fontSize = 0.0f) const; + + /** + * @brief 获取字体图集 + */ + FontAtlas* getAtlas() const { return atlas_.get(); } + + /** + * @brief 预加载字符集 + * @param charset 字符集字符串 + */ + void preloadCharset(const std::string& charset); + + /** + * @brief 获取字体配置 + */ + const FontConfig& getConfig() const { return config_; } + + /** + * @brief 获取字体度量信息 + * @param outAscender 输出上升高度 + * @param outDescender 输出下降高度 + * @param outLineHeight 输出行高 + */ + void getMetrics(float& outAscender, float& outDescender, float& outLineHeight) const; + +private: + FT_Face face_ = nullptr; // FreeType 字体面 + FontConfig config_; // 字体配置 + Ptr atlas_; // 字体图集 + float baseScale_ = 1.0f; // 基础缩放比例 + std::vector fontData_; // 字体数据(保持内存中) + + // FreeType 库静态实例 + static FT_Library ftLibrary_; + static bool ftInitialized_; + + /** + * @brief 初始化 FreeType 库 + */ + static bool initFreeType(); + + /** + * @brief 渲染字符到位图 + * @param codepoint Unicode 码点 + * @param outWidth 输出宽度 + * @param outHeight 输出高度 + * @param outBearingX 输出水平基线偏移 + * @param outBearingY 输出垂直基线偏移 + * @param outAdvance 输出水平步进 + * @param outBitmap 输出位图数据 + * @return 是否渲染成功 + */ + bool renderGlyph(uint32 codepoint, uint32& outWidth, uint32& outHeight, + float& outBearingX, float& outBearingY, float& outAdvance, + std::vector& outBitmap); + + /** + * @brief 将 UTF-8 字符串转换为 Unicode 码点数组 + * @param text UTF-8 字符串 + * @return Unicode 码点数组 + */ + static std::vector utf8ToCodepoints(const std::string& text); +}; + +} // namespace extra2d diff --git a/include/resource/font_atlas.h b/include/resource/font_atlas.h new file mode 100644 index 0000000..fc3973c --- /dev/null +++ b/include/resource/font_atlas.h @@ -0,0 +1,178 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +// STB 矩形打包 +#include + +namespace extra2d { + +/** + * @brief 字符信息结构 + */ +struct GlyphInfo { + Vec2 uv0; // 左下角 UV 坐标 + Vec2 uv1; // 右上角 UV 坐标 + Vec2 size; // 像素尺寸 + Vec2 bearing; // 基线偏移(左下角相对原点的偏移) + float advance; // 水平步进(到下一个字符的距离) + uint32 pageIndex; // 所在图集页索引 + bool valid = false; // 是否有效 +}; + +/** + * @brief 图集页结构 + * + * 使用 stb_rect_pack 进行矩形打包 + */ +struct AtlasPage { + static constexpr uint32 DEFAULT_SIZE = 1024; // 默认图集尺寸 + + Ptr texture; // 纹理 + stbrp_context packContext; // 打包上下文 + std::vector packNodes; // 打包节点数组 + std::vector pixelData; // CPU 端像素数据 + uint32 width = DEFAULT_SIZE; // 图集宽度 + uint32 height = DEFAULT_SIZE; // 图集高度 + bool dirty = false; // 是否需要更新 GPU 纹理 + + /** + * @brief 构造函数 + */ + AtlasPage(); + + /** + * @brief 初始化图集页 + * @param size 图集尺寸 + * @return 是否初始化成功 + */ + bool init(uint32 size); + + /** + * @brief 打包矩形到图集 + * @param width 矩形宽度 + * @param height 矩形高度 + * @param outX 输出 X 坐标 + * @param outY 输出 Y 坐标 + * @return 是否打包成功 + */ + bool packRect(uint32 width, uint32 height, uint32& outX, uint32& outY); + + /** + * @brief 写入像素数据到图集 + * @param x 起始 X 坐标 + * @param y 起始 Y 坐标 + * @param width 宽度 + * @param height 高度 + * @param data 像素数据(单通道) + */ + void writePixels(uint32 x, uint32 y, uint32 width, uint32 height, const uint8* data); + + /** + * @brief 更新 GPU 纹理 + */ + void updateTexture(); + + /** + * @brief 清空图集 + */ + void clear(); +}; + +/** + * @brief 字体图集类 + * + * 管理动态字符图集,支持多页扩展 + */ +class FontAtlas : public Resource { +public: + FontAtlas(); + ~FontAtlas() override; + + /** + * @brief 获取资源类型 + */ + ResourceType getType() const override { return ResourceType::FontAtlas; } + + /** + * @brief 初始化字体图集 + * @param pageSize 图集页尺寸 + * @return 是否初始化成功 + */ + bool init(uint32 pageSize = AtlasPage::DEFAULT_SIZE); + + /** + * @brief 添加字符到图集 + * @param codepoint Unicode 码点 + * @param bitmap 字符位图数据(单通道) + * @param width 位图宽度 + * @param height 位图高度 + * @param bearingX 水平基线偏移 + * @param bearingY 垂直基线偏移 + * @param advance 水平步进 + * @return 是否添加成功 + */ + bool addGlyph(uint32 codepoint, const uint8* bitmap, uint32 width, uint32 height, + float bearingX, float bearingY, float advance); + + /** + * @brief 获取字符信息 + * @param codepoint Unicode 码点 + * @return 字符信息指针,如果不存在返回 nullptr + */ + const GlyphInfo* getGlyph(uint32 codepoint) const; + + /** + * @brief 检查字符是否已缓存 + * @param codepoint Unicode 码点 + * @return 是否已缓存 + */ + bool hasGlyph(uint32 codepoint) const; + + /** + * @brief 获取图集页数量 + */ + uint32 getPageCount() const { return static_cast(pages_.size()); } + + /** + * @brief 获取指定页的纹理 + * @param pageIndex 页索引 + * @return 纹理指针 + */ + Texture* getPageTexture(uint32 pageIndex) const; + + /** + * @brief 更新所有需要更新的图集页纹理 + */ + void updateTextures(); + + /** + * @brief 清空所有图集 + */ + void clear(); + + /** + * @brief 获取图集页尺寸 + */ + uint32 getPageSize() const { return pageSize_; } + +private: + std::vector> pages_; // 图集页数组 + std::unordered_map glyphs_; // 字符信息映射表 + uint32 pageSize_ = AtlasPage::DEFAULT_SIZE; // 图集页尺寸 + + /** + * @brief 获取或创建可用的图集页 + * @return 图集页指针 + */ + AtlasPage* getOrCreatePage(); +}; + +} // namespace extra2d diff --git a/include/resource/material.h b/include/resource/material.h new file mode 100644 index 0000000..92c8985 --- /dev/null +++ b/include/resource/material.h @@ -0,0 +1,110 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace extra2d { + +// 材质属性类型定义 +using MaterialProperty = std::variant< + bool, + int, + float, + Vec2, + Vec3, + Color, + Ptr +>; + +/** + * @brief 材质资源类 + * + * 封装着色器和材质属性 + */ +class Material : public Resource { +public: + Material(); + ~Material() override; + + /** + * @brief 获取资源类型 + */ + ResourceType getType() const override { return ResourceType::Material; } + + /** + * @brief 设置着色器 + * @param shader 着色器指针 + */ + void setShader(Ptr shader); + + /** + * @brief 获取着色器 + */ + Shader* getShader() const { return shader_.get(); } + + /** + * @brief 设置布尔属性 + */ + void setBool(const std::string& name, bool value); + + /** + * @brief 设置整数属性 + */ + void setInt(const std::string& name, int value); + + /** + * @brief 设置浮点数属性 + */ + void setFloat(const std::string& name, float value); + + /** + * @brief 设置 Vec2 属性 + */ + void setVec2(const std::string& name, const Vec2& value); + + /** + * @brief 设置 Vec3 属性 + */ + void setVec3(const std::string& name, const Vec3& value); + + /** + * @brief 设置颜色属性 + */ + void setColor(const std::string& name, const Color& value); + + /** + * @brief 设置纹理属性 + */ + void setTexture(const std::string& name, Ptr texture); + + /** + * @brief 应用材质(绑定着色器并设置属性) + */ + void apply(); + + /** + * @brief 克隆材质 + * @return 新材质指针 + */ + Ptr clone() const; + +private: + Ptr shader_; // 着色器 + std::unordered_map properties_; // 属性表 + mutable uint32 textureUnit_ = 0; // 当前纹理单元 + + /** + * @brief 应用所有属性到着色器 + */ + void applyProperties(); +}; + +} // namespace extra2d diff --git a/include/resource/resource.h b/include/resource/resource.h new file mode 100644 index 0000000..25fafb1 --- /dev/null +++ b/include/resource/resource.h @@ -0,0 +1,62 @@ +#pragma once + +#include +#include +#include + +namespace extra2d { + +/** + * @brief 资源类型枚举 + */ +enum class ResourceType : uint8 { + Texture, // 纹理 + Shader, // 着色器 + Material, // 材质 + Font, // 字体 + FontAtlas, // 字体图集 + Text, // 文本 + Audio, // 音频 + Mesh // 网格 +}; + +/** + * @brief 资源基类 + * + * 所有引擎资源都继承此类,支持引用计数生命周期管理 + */ +class Resource : public RefCounted { +public: + virtual ~Resource() = default; + + /** + * @brief 获取资源类型 + * @return 资源类型枚举 + */ + virtual ResourceType getType() const = 0; + + /** + * @brief 获取资源名称 + * @return 资源名称 + */ + virtual const char* getName() const { return name_.c_str(); } + + /** + * @brief 获取资源ID + * @return 资源唯一ID + */ + uint32 getId() const { return id_; } + + /** + * @brief 检查资源是否已加载 + * @return 是否已加载 + */ + bool isLoaded() const { return loaded_; } + +protected: + std::string name_; // 资源名称 + uint32 id_ = 0; // 资源唯一ID + bool loaded_ = false; // 是否已加载 +}; + +} // namespace extra2d diff --git a/include/resource/resource_cache.h b/include/resource/resource_cache.h new file mode 100644 index 0000000..a2f0a54 --- /dev/null +++ b/include/resource/resource_cache.h @@ -0,0 +1,135 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace extra2d { + +/** + * @brief 资源缓存模板类 + * + * 管理资源的加载和缓存,支持线程安全 + * T 必须是 Resource 的子类 + */ +template +class ResourceCache { +public: + using Loader = std::function(const std::string&, LoadArgs...)>; + + ResourceCache() = default; + ~ResourceCache() = default; + + // 禁止拷贝 + ResourceCache(const ResourceCache&) = delete; + ResourceCache& operator=(const ResourceCache&) = delete; + + // 允许移动 + ResourceCache(ResourceCache&&) = default; + ResourceCache& operator=(ResourceCache&&) = default; + + /** + * @brief 获取资源(如果不存在则加载) + * @param path 资源路径 + * @param args 加载参数 + * @return 资源指针 + */ + Ptr get(const std::string& path, LoadArgs... args) { + std::lock_guard lock(mutex_); + + auto it = cache_.find(path); + if (it != cache_.end()) { + return it->second; + } + + Ptr resource; + if (customLoader_) { + resource = customLoader_(path, args...); + } else { + resource = defaultLoad(path, args...); + } + + if (resource && resource->isLoaded()) { + cache_[path] = resource; + } + + return resource; + } + + /** + * @brief 预加载资源 + * @param path 资源路径 + * @param args 加载参数 + * @return 资源指针 + */ + Ptr preload(const std::string& path, LoadArgs... args) { + return get(path, args...); + } + + /** + * @brief 检查资源是否已缓存 + * @param path 资源路径 + * @return 是否已缓存 + */ + bool has(const std::string& path) const { + std::lock_guard lock(mutex_); + return cache_.find(path) != cache_.end(); + } + + /** + * @brief 移除资源 + * @param path 资源路径 + */ + void remove(const std::string& path) { + std::lock_guard lock(mutex_); + cache_.erase(path); + } + + /** + * @brief 清空缓存 + */ + void clear() { + std::lock_guard lock(mutex_); + cache_.clear(); + } + + /** + * @brief 设置自定义加载器 + * @param loader 自定义加载函数 + */ + void setLoader(Loader loader) { + std::lock_guard lock(mutex_); + customLoader_ = loader; + } + + /** + * @brief 获取缓存大小 + * @return 缓存中的资源数量 + */ + size_t size() const { + std::lock_guard lock(mutex_); + return cache_.size(); + } + +private: + mutable std::mutex mutex_; + std::unordered_map> cache_; + Loader customLoader_; + + /** + * @brief 默认加载函数 + */ + Ptr defaultLoad(const std::string& path, LoadArgs... args) { + Ptr resource = makePtr(); + if (resource->loadFromFile(path, args...)) { + return resource; + } + return nullptr; + } +}; + +} // namespace extra2d diff --git a/include/resource/resource_manager.h b/include/resource/resource_manager.h new file mode 100644 index 0000000..30e9afe --- /dev/null +++ b/include/resource/resource_manager.h @@ -0,0 +1,100 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace extra2d { + +/** + * @brief 资源管理器类 + * + * 统一管理所有资源的加载和缓存 + */ +class ResourceManager : public IModule { +public: + ResourceManager(); + ~ResourceManager() override; + + // IModule 接口 + const char* name() const override { return "ResourceManager"; } + ModuleType type() const override { return ModuleType::Core; } + int priority() const override { return 10; } + bool init() override; + void shutdown() override; + + // 纹理管理 + Ptr getTexture(const std::string& path); + Ptr createTexture(uint32 width, uint32 height, TextureFormat format); + + // 着色器管理 + Ptr getShader(const std::string& vsPath, const std::string& fsPath); + Ptr getShaderFromSource(const std::string& name, + const std::string& vsSource, + const std::string& fsSource); + + // 材质管理 + Ptr createMaterial(Ptr shader); + Ptr getMaterial(const std::string& name); + void registerMaterial(const std::string& name, Ptr material); + + // 字体管理 + Ptr getFont(const std::string& path, const FontConfig& config = {}); + + // 文本管理 + Ptr createText(Ptr font, const std::string& content = ""); + + // 音频管理 + Ptr