refactor(resource): 移除资源系统相关代码

移除资源管理器及其相关组件,包括纹理、着色器、材质、字体、音频等资源类
删除资源加载示例及其构建配置
清理引擎上下文中与资源管理相关的接口和实现
This commit is contained in:
ChestnutYueyue 2026-03-01 23:21:54 +08:00
parent bbdc1435ce
commit 46393fd027
24 changed files with 1 additions and 3402 deletions

View File

@ -1,100 +0,0 @@
# 资源加载示例
演示如何使用 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 语法

View File

@ -1,221 +0,0 @@
#include <extra2d.h>
#include <cstdio>
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;
}

View File

@ -1,75 +0,0 @@
-- ==============================================
-- 资源加载示例 - 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()

View File

@ -9,7 +9,6 @@ namespace extra2d {
class ModuleRegistry;
class PluginLoader;
class TimerModule;
class ResourceManager;
/**
* @brief
@ -67,11 +66,6 @@ public:
*/
TimerModule& timer();
/**
* @brief
*/
ResourceManager& resources();
private:
/**
* @brief
@ -96,7 +90,6 @@ private:
private:
std::unique_ptr<PluginLoader> pluginLoader_;
std::unique_ptr<TimerModule> timerModule_;
std::unique_ptr<ResourceManager> resourceManager_;
float totalTime_ = 0.0f;
uint64 frameCount_ = 0;

View File

@ -41,18 +41,6 @@
#include <config/app_config.h>
#include <config/window_config.h>
// Resource System
#include <resource/resource.h>
#include <resource/resource_cache.h>
#include <resource/resource_manager.h>
#include <resource/texture.h>
#include <resource/shader.h>
#include <resource/material.h>
#include <resource/font.h>
#include <resource/font_atlas.h>
#include <resource/text.h>
#include <resource/audio.h>
// Application
#include <app/application.h>

View File

@ -1,96 +0,0 @@
#pragma once
#include <resource/resource.h>
#include <SDL2/SDL_mixer.h>
#include <string>
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

View File

@ -1,174 +0,0 @@
#pragma once
#include <resource/resource.h>
#include <resource/font_atlas.h>
#include <types/math/vec2.h>
#include <types/base/types.h>
#include <string>
#include <vector>
// FreeType 头文件
#include <ft2build.h>
#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<uint8>& 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<FontAtlas> atlas_; // 字体图集
float baseScale_ = 1.0f; // 基础缩放比例
std::vector<uint8> 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<uint8>& outBitmap);
/**
* @brief UTF-8 Unicode
* @param text UTF-8
* @return Unicode
*/
static std::vector<uint32> utf8ToCodepoints(const std::string& text);
};
} // namespace extra2d

View File

@ -1,178 +0,0 @@
#pragma once
#include <resource/resource.h>
#include <resource/texture.h>
#include <types/math/vec2.h>
#include <types/base/types.h>
#include <types/ptr/intrusive_ptr.h>
#include <unordered_map>
#include <vector>
#include <memory>
// STB 矩形打包
#include <stb/stb_rect_pack.h>
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> texture; // 纹理
stbrp_context packContext; // 打包上下文
std::vector<stbrp_node> packNodes; // 打包节点数组
std::vector<uint8> 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<uint32>(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<std::unique_ptr<AtlasPage>> pages_; // 图集页数组
std::unordered_map<uint32, GlyphInfo> glyphs_; // 字符信息映射表
uint32 pageSize_ = AtlasPage::DEFAULT_SIZE; // 图集页尺寸
/**
* @brief
* @return
*/
AtlasPage* getOrCreatePage();
};
} // namespace extra2d

View File

@ -1,110 +0,0 @@
#pragma once
#include <resource/resource.h>
#include <resource/shader.h>
#include <resource/texture.h>
#include <types/math/color.h>
#include <types/math/vec2.h>
#include <types/math/vec3.h>
#include <types/ptr/intrusive_ptr.h>
#include <string>
#include <unordered_map>
#include <variant>
namespace extra2d {
// 材质属性类型定义
using MaterialProperty = std::variant<
bool,
int,
float,
Vec2,
Vec3,
Color,
Ptr<Texture>
>;
/**
* @brief
*
*
*/
class Material : public Resource {
public:
Material();
~Material() override;
/**
* @brief
*/
ResourceType getType() const override { return ResourceType::Material; }
/**
* @brief
* @param shader
*/
void setShader(Ptr<Shader> 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> texture);
/**
* @brief
*/
void apply();
/**
* @brief
* @return
*/
Ptr<Material> clone() const;
private:
Ptr<Shader> shader_; // 着色器
std::unordered_map<std::string, MaterialProperty> properties_; // 属性表
mutable uint32 textureUnit_ = 0; // 当前纹理单元
/**
* @brief
*/
void applyProperties();
};
} // namespace extra2d

View File

@ -1,62 +0,0 @@
#pragma once
#include <types/ptr/ref_counted.h>
#include <types/base/types.h>
#include <string>
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

View File

@ -1,135 +0,0 @@
#pragma once
#include <resource/resource.h>
#include <types/ptr/intrusive_ptr.h>
#include <unordered_map>
#include <string>
#include <mutex>
#include <functional>
#include <memory>
namespace extra2d {
/**
* @brief
*
* 线
* T Resource
*/
template<typename T, typename... LoadArgs>
class ResourceCache {
public:
using Loader = std::function<Ptr<T>(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<T> get(const std::string& path, LoadArgs... args) {
std::lock_guard<std::mutex> lock(mutex_);
auto it = cache_.find(path);
if (it != cache_.end()) {
return it->second;
}
Ptr<T> 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<T> 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<std::mutex> lock(mutex_);
return cache_.find(path) != cache_.end();
}
/**
* @brief
* @param path
*/
void remove(const std::string& path) {
std::lock_guard<std::mutex> lock(mutex_);
cache_.erase(path);
}
/**
* @brief
*/
void clear() {
std::lock_guard<std::mutex> lock(mutex_);
cache_.clear();
}
/**
* @brief
* @param loader
*/
void setLoader(Loader loader) {
std::lock_guard<std::mutex> lock(mutex_);
customLoader_ = loader;
}
/**
* @brief
* @return
*/
size_t size() const {
std::lock_guard<std::mutex> lock(mutex_);
return cache_.size();
}
private:
mutable std::mutex mutex_;
std::unordered_map<std::string, Ptr<T>> cache_;
Loader customLoader_;
/**
* @brief
*/
Ptr<T> defaultLoad(const std::string& path, LoadArgs... args) {
Ptr<T> resource = makePtr<T>();
if (resource->loadFromFile(path, args...)) {
return resource;
}
return nullptr;
}
};
} // namespace extra2d

View File

@ -1,100 +0,0 @@
#pragma once
#include <resource/resource_cache.h>
#include <resource/texture.h>
#include <resource/shader.h>
#include <resource/material.h>
#include <resource/font.h>
#include <resource/text.h>
#include <resource/audio.h>
#include <module/imodule.h>
#include <string>
#include <unordered_map>
#include <vector>
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<Texture> getTexture(const std::string& path);
Ptr<Texture> createTexture(uint32 width, uint32 height, TextureFormat format);
// 着色器管理
Ptr<Shader> getShader(const std::string& vsPath, const std::string& fsPath);
Ptr<Shader> getShaderFromSource(const std::string& name,
const std::string& vsSource,
const std::string& fsSource);
// 材质管理
Ptr<Material> createMaterial(Ptr<Shader> shader);
Ptr<Material> getMaterial(const std::string& name);
void registerMaterial(const std::string& name, Ptr<Material> material);
// 字体管理
Ptr<Font> getFont(const std::string& path, const FontConfig& config = {});
// 文本管理
Ptr<Text> createText(Ptr<Font> font, const std::string& content = "");
// 音频管理
Ptr<Audio> getAudio(const std::string& path, AudioType type);
// 默认资源(延迟初始化,确保 OpenGL 上下文已创建)
Ptr<Texture> getDefaultTexture();
Ptr<Shader> getDefaultShader();
Ptr<Material> getDefaultMaterial();
Ptr<Font> getDefaultFont();
// 检查默认资源是否已创建
bool areDefaultResourcesCreated() const { return defaultResourcesCreated_; }
// 垃圾回收
void collectGarbage();
// 统计信息
struct Stats {
size_t textureCount;
size_t shaderCount;
size_t materialCount;
size_t fontCount;
size_t textCount;
size_t audioCount;
};
Stats getStats() const;
private:
ResourceCache<Texture> textureCache_;
std::unordered_map<std::string, Ptr<Shader>> shaderCache_;
std::unordered_map<std::string, Ptr<Material>> materials_;
std::unordered_map<std::string, Ptr<Font>> fontCache_;
std::vector<Ptr<Text>> texts_;
std::unordered_map<std::string, Ptr<Audio>> audioCache_;
// 默认资源
Ptr<Texture> defaultTexture_;
Ptr<Shader> defaultShader_;
Ptr<Material> defaultMaterial_;
Ptr<Font> defaultFont_;
bool defaultResourcesCreated_ = false;
bool createDefaultResources();
};
} // namespace extra2d

View File

@ -1,125 +0,0 @@
#pragma once
#include <resource/resource.h>
#include <glad/glad.h>
#include <types/math/vec2.h>
#include <types/math/vec3.h>
#include <types/math/color.h>
#include <types/base/types.h>
#include <string>
#include <unordered_map>
namespace extra2d {
/**
* @brief
*/
enum class ShaderType : uint8 {
Vertex, // 顶点着色器
Fragment, // 片段着色器
Geometry, // 几何着色器
Compute // 计算着色器
};
/**
* @brief
*
* OpenGL ES 3.2
*/
class Shader : public Resource {
public:
Shader();
~Shader() override;
/**
* @brief
*/
ResourceType getType() const override { return ResourceType::Shader; }
/**
* @brief
* @param vsPath
* @param fsPath
* @return
*/
bool loadFromFile(const std::string& vsPath, const std::string& fsPath);
/**
* @brief
* @param vsSource
* @param fsSource
* @return
*/
bool loadFromSource(const std::string& vsSource, const std::string& fsSource);
/**
* @brief 使
*/
void use() const;
/**
* @brief 使
*/
void unuse() const;
/**
* @brief Uniform
*/
void setBool(const std::string& name, bool value);
/**
* @brief Uniform
*/
void setInt(const std::string& name, int value);
/**
* @brief Uniform
*/
void setFloat(const std::string& name, float value);
/**
* @brief Vec2 Uniform
*/
void setVec2(const std::string& name, const Vec2& value);
/**
* @brief Vec3 Uniform
*/
void setVec3(const std::string& name, const Vec3& value);
/**
* @brief Uniform
*/
void setColor(const std::string& name, const Color& value);
/**
* @brief 4x4 Uniform
*/
void setMat4(const std::string& name, const float* value);
/**
* @brief OpenGL
*/
GLuint getProgram() const { return program_; }
private:
GLuint program_ = 0; // OpenGL 程序句柄
std::unordered_map<std::string, GLint> uniformCache_; // Uniform 位置缓存
/**
* @brief
* @param type
* @param source
* @return 0
*/
GLuint compileShader(GLenum type, const std::string& source);
/**
* @brief Uniform
* @param name Uniform
* @return Uniform
*/
GLint getUniformLocation(const std::string& name);
};
} // namespace extra2d

View File

@ -1,171 +0,0 @@
#pragma once
#include <resource/resource.h>
#include <resource/font.h>
#include <types/math/color.h>
#include <types/math/vec2.h>
#include <vector>
#include <string>
namespace extra2d {
/**
* @brief
*/
enum class TextAlign : uint8 {
Left, // 左对齐
Center, // 居中对齐
Right // 右对齐
};
/**
* @brief
*/
struct TextVertex {
Vec2 position; // 位置
Vec2 uv; // UV 坐标
Color color; // 颜色
};
/**
* @brief
*/
struct TextLine {
std::vector<TextVertex> vertices; // 顶点数组
std::vector<uint16> indices; // 索引数组
float width = 0.0f; // 行宽度
float yOffset = 0.0f; // Y 偏移
};
/**
* @brief
*
*
*/
class Text : public Resource {
public:
Text();
~Text() override;
/**
* @brief
*/
ResourceType getType() const override { return ResourceType::Text; }
/**
* @brief
* @param font
*/
void setFont(Ptr<Font> font);
/**
* @brief
*/
Font* getFont() const { return font_.get(); }
/**
* @brief
* @param text
*/
void setText(const std::string& text);
/**
* @brief
*/
const std::string& getText() const { return text_; }
/**
* @brief
* @param size
*/
void setFontSize(float size);
/**
* @brief
*/
float getFontSize() const { return fontSize_; }
/**
* @brief
* @param color
*/
void setColor(const Color& color);
/**
* @brief
*/
const Color& getColor() const { return color_; }
/**
* @brief
* @param align
*/
void setAlign(TextAlign align);
/**
* @brief
*/
TextAlign getAlign() const { return align_; }
/**
* @brief
* @param width 0
*/
void setMaxWidth(float width);
/**
* @brief
*/
float getMaxWidth() const { return maxWidth_; }
/**
* @brief
* @param spacing
*/
void setLineSpacing(float spacing);
/**
* @brief
*/
float getLineSpacing() const { return lineSpacing_; }
/**
* @brief
*/
Vec2 getSize() const { return size_; }
/**
* @brief
*/
const std::vector<TextLine>& getLines() const { return lines_; }
/**
* @brief
*/
void rebuild();
/**
* @brief
*/
bool isDirty() const { return dirty_; }
private:
Ptr<Font> font_; // 字体
std::string text_; // 文本内容
float fontSize_ = 16.0f; // 字体大小
Color color_ = Color::White; // 颜色
TextAlign align_ = TextAlign::Left; // 对齐方式
float maxWidth_ = 0.0f; // 最大宽度
float lineSpacing_ = 1.2f; // 行间距
Vec2 size_; // 文本尺寸
std::vector<TextLine> lines_; // 文本行数组
bool dirty_ = true; // 是否需要重建
/**
* @brief
*/
void buildVertices();
};
} // namespace extra2d

View File

@ -1,152 +0,0 @@
#pragma once
#include <resource/resource.h>
#include <glad/glad.h>
#include <types/base/types.h>
namespace extra2d {
/**
* @brief
*/
enum class TextureFormat : uint8 {
RGB8, // RGB 8位每通道
RGBA8, // RGBA 8位每通道
R8, // 单通道(字体用)
Depth, // 深度缓冲
DepthStencil // 深度模板缓冲
};
/**
* @brief
*/
enum class TextureFilter : uint8 {
Nearest, // 最近邻
Linear, // 线性
MipmapNearest, // 最近邻 Mipmap
MipmapLinear // 线性 Mipmap
};
/**
* @brief
*/
enum class TextureWrap : uint8 {
Repeat, // 重复
Clamp, // 边缘钳制
Mirror // 镜像重复
};
/**
* @brief
*
* OpenGL ES 3.2
*/
class Texture : public Resource {
public:
Texture();
~Texture() override;
/**
* @brief
*/
ResourceType getType() const override { return ResourceType::Texture; }
/**
* @brief
* @param width
* @param height
* @param format
* @return
*/
bool create(uint32 width, uint32 height, TextureFormat format);
/**
* @brief
* @param path
* @return
*/
bool loadFromFile(const std::string& path);
/**
* @brief
* @param data
* @param width
* @param height
* @param format
* @return
*/
bool loadFromMemory(const uint8* data, uint32 width, uint32 height, TextureFormat format);
/**
* @brief
* @param x X坐标
* @param y Y坐标
* @param width
* @param height
* @param data
*/
void updateRegion(uint32 x, uint32 y, uint32 width, uint32 height, const uint8* data);
/**
* @brief
* @param slot 0-31
*/
void bind(uint32 slot = 0) const;
/**
* @brief
*/
void unbind() const;
/**
* @brief
* @param minFilter
* @param magFilter
*/
void setFilter(TextureFilter minFilter, TextureFilter magFilter);
/**
* @brief
* @param wrapS S轴环绕模式
* @param wrapT T轴环绕模式
*/
void setWrap(TextureWrap wrapS, TextureWrap wrapT);
/**
* @brief Mipmap
*/
void generateMipmap();
/**
* @brief
*/
uint32 getWidth() const { return width_; }
/**
* @brief
*/
uint32 getHeight() const { return height_; }
/**
* @brief
*/
TextureFormat getFormat() const { return format_; }
/**
* @brief OpenGL
*/
GLuint getHandle() const { return handle_; }
private:
GLuint handle_ = 0; // OpenGL 纹理句柄
uint32 width_ = 0; // 纹理宽度
uint32 height_ = 0; // 纹理高度
TextureFormat format_ = TextureFormat::RGBA8; // 纹理格式
// 将 TextureFormat 转换为 OpenGL 格式
static GLint getGLInternalFormat(TextureFormat format);
static GLenum getGLFormat(TextureFormat format);
static GLenum getGLType(TextureFormat format);
};
} // namespace extra2d

View File

@ -3,14 +3,12 @@
#include <module/module_registry.h>
#include <plugin/plugin_loader.h>
#include <utils/timer_module.h>
#include <resource/resource_manager.h>
namespace extra2d {
Context::Context()
: pluginLoader_(std::make_unique<PluginLoader>()),
timerModule_(std::make_unique<TimerModule>()),
resourceManager_(std::make_unique<ResourceManager>()) {}
timerModule_(std::make_unique<TimerModule>()) {}
Context::~Context() {
if (inited_) {
@ -33,11 +31,6 @@ bool Context::init() {
// 发送引擎初始化事件
events::OnInit::emit();
// 初始化资源管理器
if (!resourceManager_->init()) {
return false;
}
// 初始化定时器模块
if (!timerModule_->init()) {
return false;
@ -69,9 +62,6 @@ void Context::shutdown() {
// 发送引擎关闭事件
events::OnShutdown::emit();
// 关闭资源管理器
resourceManager_->shutdown();
inited_ = false;
}
@ -100,6 +90,4 @@ PluginLoader &Context::plugins() { return *pluginLoader_; }
TimerModule &Context::timer() { return *timerModule_; }
ResourceManager &Context::resources() { return *resourceManager_; }
} // namespace extra2d

View File

@ -1,114 +0,0 @@
#include <resource/audio.h>
#include <utils/logger.h>
namespace extra2d {
Audio::Audio() = default;
Audio::~Audio() {
stop();
if (chunk_) {
Mix_FreeChunk(chunk_);
chunk_ = nullptr;
}
if (music_) {
Mix_FreeMusic(music_);
music_ = nullptr;
}
}
bool Audio::loadFromFile(const std::string& path, AudioType type) {
type_ = type;
if (type_ == AudioType::Sound) {
chunk_ = Mix_LoadWAV(path.c_str());
if (!chunk_) {
Logger::log(LogLevel::Error, "Failed to load sound: {} - {}", path.c_str(), Mix_GetError());
return false;
}
} else {
music_ = Mix_LoadMUS(path.c_str());
if (!music_) {
Logger::log(LogLevel::Error, "Failed to load music: {} - {}", path.c_str(), Mix_GetError());
return false;
}
}
name_ = path;
loaded_ = true;
Logger::log(LogLevel::Info, "Loaded audio: {}", path.c_str());
return true;
}
void Audio::play(int loops) {
if (type_ == AudioType::Sound && chunk_) {
channel_ = Mix_PlayChannel(-1, chunk_, loops);
if (channel_ == -1) {
Logger::log(LogLevel::Error, "Failed to play sound: {}", Mix_GetError());
}
} else if (type_ == AudioType::Music && music_) {
if (Mix_PlayMusic(music_, loops) == -1) {
Logger::log(LogLevel::Error, "Failed to play music: {}", Mix_GetError());
}
}
}
void Audio::pause() {
if (type_ == AudioType::Sound && channel_ != -1) {
Mix_Pause(channel_);
} else if (type_ == AudioType::Music) {
Mix_PauseMusic();
}
}
void Audio::resume() {
if (type_ == AudioType::Sound && channel_ != -1) {
Mix_Resume(channel_);
} else if (type_ == AudioType::Music) {
Mix_ResumeMusic();
}
}
void Audio::stop() {
if (type_ == AudioType::Sound && channel_ != -1) {
Mix_HaltChannel(channel_);
channel_ = -1;
} else if (type_ == AudioType::Music) {
Mix_HaltMusic();
}
}
void Audio::setVolume(float volume) {
volume_ = std::max(0.0f, std::min(1.0f, volume));
int mixVolume = static_cast<int>(volume_ * MIX_MAX_VOLUME);
if (type_ == AudioType::Sound && chunk_) {
Mix_VolumeChunk(chunk_, mixVolume);
} else if (type_ == AudioType::Music) {
Mix_VolumeMusic(mixVolume);
}
}
float Audio::getVolume() const {
return volume_;
}
bool Audio::isPlaying() const {
if (type_ == AudioType::Sound && channel_ != -1) {
return Mix_Playing(channel_) != 0;
} else if (type_ == AudioType::Music) {
return Mix_PlayingMusic() != 0;
}
return false;
}
bool Audio::isPaused() const {
if (type_ == AudioType::Sound && channel_ != -1) {
return Mix_Paused(channel_) != 0;
} else if (type_ == AudioType::Music) {
return Mix_PausedMusic() != 0;
}
return false;
}
} // namespace extra2d

View File

@ -1,340 +0,0 @@
#include <resource/font.h>
#include <utils/logger.h>
#include <fstream>
#include <cstring>
namespace extra2d {
// 静态成员初始化
FT_Library Font::ftLibrary_ = nullptr;
bool Font::ftInitialized_ = false;
Font::Font() = default;
Font::~Font() {
if (face_) {
FT_Done_Face(face_);
face_ = nullptr;
}
}
bool Font::initFreeType() {
if (ftInitialized_) {
return true;
}
FT_Error error = FT_Init_FreeType(&ftLibrary_);
if (error) {
Logger::log(LogLevel::Error, "Failed to initialize FreeType library");
return false;
}
ftInitialized_ = true;
return true;
}
bool Font::loadFromFile(const std::string& path, const FontConfig& config) {
// 读取字体文件到内存
std::ifstream file(path, std::ios::binary | std::ios::ate);
if (!file.is_open()) {
Logger::log(LogLevel::Error, "Failed to open font file: {}", path.c_str());
return false;
}
std::streamsize size = file.tellg();
file.seekg(0, std::ios::beg);
std::vector<uint8> data(size);
if (!file.read(reinterpret_cast<char*>(data.data()), size)) {
Logger::log(LogLevel::Error, "Failed to read font file: {}", path.c_str());
return false;
}
return loadFromMemory(data, config);
}
bool Font::loadFromMemory(const std::vector<uint8>& data, const FontConfig& config) {
if (!initFreeType()) {
return false;
}
config_ = config;
fontData_ = data; // 保存字体数据
// 从内存加载字体
FT_Error error = FT_New_Memory_Face(ftLibrary_,
fontData_.data(),
static_cast<FT_Long>(fontData_.size()),
0, &face_);
if (error) {
Logger::log(LogLevel::Error, "Failed to load font from memory");
return false;
}
// 设置字体大小
error = FT_Set_Pixel_Sizes(face_, 0, config_.fontSize);
if (error) {
Logger::log(LogLevel::Error, "Failed to set font size");
FT_Done_Face(face_);
face_ = nullptr;
return false;
}
// 创建字体图集
atlas_ = makePtr<FontAtlas>();
if (!atlas_->init()) {
Logger::log(LogLevel::Error, "Failed to create font atlas");
FT_Done_Face(face_);
face_ = nullptr;
return false;
}
// 预加载常用字符
if (config_.preloadCommon) {
preloadCharset(CommonCharset::ASCII);
preloadCharset(CommonCharset::PUNCTUATION_EN);
preloadCharset(CommonCharset::PUNCTUATION_CN);
preloadCharset(CommonCharset::CHINESE_COMMON);
}
name_ = "Font";
loaded_ = true;
Logger::log(LogLevel::Info, "Loaded font with size {}", config_.fontSize);
return true;
}
const GlyphInfo* Font::getGlyph(uint32 codepoint) {
if (!atlas_) return nullptr;
// 检查是否已缓存
const GlyphInfo* cached = atlas_->getGlyph(codepoint);
if (cached) {
return cached;
}
// 渲染字符
uint32 width, height;
float bearingX, bearingY, advance;
std::vector<uint8> bitmap;
if (!renderGlyph(codepoint, width, height, bearingX, bearingY, advance, bitmap)) {
return nullptr;
}
// 添加到图集
if (!atlas_->addGlyph(codepoint, bitmap.data(), width, height,
bearingX, bearingY, advance)) {
return nullptr;
}
// 更新纹理
atlas_->updateTextures();
return atlas_->getGlyph(codepoint);
}
bool Font::renderGlyph(uint32 codepoint, uint32& outWidth, uint32& outHeight,
float& outBearingX, float& outBearingY, float& outAdvance,
std::vector<uint8>& outBitmap) {
if (!face_) return false;
// 加载字符
FT_UInt glyphIndex = FT_Get_Char_Index(face_, codepoint);
if (glyphIndex == 0) {
// 字符不存在于字体中
return false;
}
FT_Int32 loadFlags = FT_LOAD_RENDER;
if (!config_.hinting) {
loadFlags |= FT_LOAD_NO_HINTING;
}
FT_Error error = FT_Load_Glyph(face_, glyphIndex, loadFlags);
if (error) {
return false;
}
FT_GlyphSlot slot = face_->glyph;
// 获取位图信息
outWidth = slot->bitmap.width;
outHeight = slot->bitmap.rows;
outBearingX = static_cast<float>(slot->bitmap_left);
outBearingY = static_cast<float>(slot->bitmap_top);
outAdvance = static_cast<float>(slot->advance.x >> 6); // 转换为像素
// 复制位图数据
if (outWidth > 0 && outHeight > 0) {
outBitmap.resize(outWidth * outHeight);
// FreeType 位图可能是灰度或单色
if (slot->bitmap.pixel_mode == FT_PIXEL_MODE_GRAY) {
for (uint32 y = 0; y < outHeight; ++y) {
std::memcpy(&outBitmap[y * outWidth],
&slot->bitmap.buffer[y * slot->bitmap.pitch],
outWidth);
}
} else if (slot->bitmap.pixel_mode == FT_PIXEL_MODE_MONO) {
// 单色位图需要展开
for (uint32 y = 0; y < outHeight; ++y) {
for (uint32 x = 0; x < outWidth; ++x) {
uint32 byteIndex = x / 8;
uint32 bitIndex = 7 - (x % 8);
uint8 byte = slot->bitmap.buffer[y * slot->bitmap.pitch + byteIndex];
outBitmap[y * outWidth + x] = (byte & (1 << bitIndex)) ? 255 : 0;
}
}
}
}
return true;
}
Vec2 Font::measureText(const std::string& text, float fontSize) {
if (!atlas_) return Vec2(0, 0);
float scale = (fontSize > 0) ? (fontSize / config_.fontSize) : 1.0f;
float lineHeight = getLineHeight(fontSize);
std::vector<uint32> codepoints = utf8ToCodepoints(text);
float width = 0.0f;
float maxWidth = 0.0f;
float height = lineHeight;
uint32 prevCodepoint = 0;
for (uint32 codepoint : codepoints) {
if (codepoint == '\n') {
maxWidth = std::max(maxWidth, width);
width = 0.0f;
height += lineHeight;
prevCodepoint = 0;
continue;
}
const GlyphInfo* glyph = getGlyph(codepoint);
if (!glyph) {
prevCodepoint = codepoint;
continue;
}
// 添加字距调整
if (prevCodepoint != 0) {
width += getKerning(prevCodepoint, codepoint) * scale;
}
width += glyph->advance * scale;
prevCodepoint = codepoint;
}
maxWidth = std::max(maxWidth, width);
return Vec2(maxWidth, height);
}
float Font::getKerning(uint32 codepoint1, uint32 codepoint2) {
if (!face_) return 0.0f;
FT_UInt index1 = FT_Get_Char_Index(face_, codepoint1);
FT_UInt index2 = FT_Get_Char_Index(face_, codepoint2);
FT_Vector kerning;
FT_Error error = FT_Get_Kerning(face_, index1, index2, FT_KERNING_DEFAULT, &kerning);
if (error) {
return 0.0f;
}
return static_cast<float>(kerning.x >> 6); // 转换为像素
}
float Font::getLineHeight(float fontSize) const {
if (!face_) return 0.0f;
float scale = (fontSize > 0) ? (fontSize / config_.fontSize) : 1.0f;
return static_cast<float>(face_->size->metrics.height >> 6) * scale;
}
void Font::preloadCharset(const std::string& charset) {
std::vector<uint32> codepoints = utf8ToCodepoints(charset);
for (uint32 codepoint : codepoints) {
getGlyph(codepoint);
}
// 批量更新纹理
if (atlas_) {
atlas_->updateTextures();
}
Logger::log(LogLevel::Info, "Preloaded {} glyphs", codepoints.size());
}
void Font::getMetrics(float& outAscender, float& outDescender, float& outLineHeight) const {
if (!face_) {
outAscender = outDescender = outLineHeight = 0.0f;
return;
}
outAscender = static_cast<float>(face_->size->metrics.ascender >> 6);
outDescender = static_cast<float>(face_->size->metrics.descender >> 6);
outLineHeight = static_cast<float>(face_->size->metrics.height >> 6);
}
std::vector<uint32> Font::utf8ToCodepoints(const std::string& text) {
std::vector<uint32> codepoints;
codepoints.reserve(text.length());
const uint8* data = reinterpret_cast<const uint8*>(text.data());
size_t length = text.length();
size_t i = 0;
while (i < length) {
uint32 codepoint = 0;
uint8 byte = data[i];
if ((byte & 0x80) == 0) {
// 单字节 ASCII
codepoint = byte;
i += 1;
} else if ((byte & 0xE0) == 0xC0) {
// 2 字节序列
if (i + 1 < length) {
codepoint = ((byte & 0x1F) << 6) | (data[i + 1] & 0x3F);
i += 2;
} else {
break;
}
} else if ((byte & 0xF0) == 0xE0) {
// 3 字节序列
if (i + 2 < length) {
codepoint = ((byte & 0x0F) << 12) |
((data[i + 1] & 0x3F) << 6) |
(data[i + 2] & 0x3F);
i += 3;
} else {
break;
}
} else if ((byte & 0xF8) == 0xF0) {
// 4 字节序列
if (i + 3 < length) {
codepoint = ((byte & 0x07) << 18) |
((data[i + 1] & 0x3F) << 12) |
((data[i + 2] & 0x3F) << 6) |
(data[i + 3] & 0x3F);
i += 4;
} else {
break;
}
} else {
// 无效的 UTF-8 序列,跳过
i += 1;
continue;
}
codepoints.push_back(codepoint);
}
return codepoints;
}
} // namespace extra2d

View File

@ -1,224 +0,0 @@
#include <resource/font_atlas.h>
#define STB_RECT_PACK_IMPLEMENTATION
#include <stb/stb_rect_pack.h>
#include <utils/logger.h>
#include <cstring>
namespace extra2d {
// AtlasPage 实现
AtlasPage::AtlasPage() = default;
bool AtlasPage::init(uint32 size) {
width = size;
height = size;
// 创建纹理
texture = makePtr<Texture>();
if (!texture->create(width, height, TextureFormat::R8)) {
Logger::log(LogLevel::Error, "Failed to create font atlas texture");
return false;
}
// 设置纹理过滤为最近邻,避免字体模糊
texture->setFilter(TextureFilter::Nearest, TextureFilter::Nearest);
// 初始化像素数据(清零)
pixelData.resize(width * height, 0);
// 初始化矩形打包上下文
// 节点数量至少为宽度,以保证最佳打包效果
packNodes.resize(width);
stbrp_init_target(&packContext, width, height, packNodes.data(), width);
dirty = false;
return true;
}
bool AtlasPage::packRect(uint32 w, uint32 h, uint32& outX, uint32& outY) {
stbrp_rect rect;
rect.id = 0;
rect.w = static_cast<stbrp_coord>(w);
rect.h = static_cast<stbrp_coord>(h);
rect.x = 0;
rect.y = 0;
rect.was_packed = 0;
int result = stbrp_pack_rects(&packContext, &rect, 1);
if (!rect.was_packed) {
return false;
}
outX = static_cast<uint32>(rect.x);
outY = static_cast<uint32>(rect.y);
return true;
}
void AtlasPage::writePixels(uint32 x, uint32 y, uint32 w, uint32 h, const uint8* data) {
if (!data || x + w > width || y + h > height) return;
// 逐行复制像素数据
for (uint32 row = 0; row < h; ++row) {
uint32 dstY = y + row;
uint32 srcY = row;
std::memcpy(&pixelData[dstY * width + x], &data[srcY * w], w);
}
dirty = true;
}
void AtlasPage::updateTexture() {
if (!dirty || !texture) return;
texture->updateRegion(0, 0, width, height, pixelData.data());
dirty = false;
}
void AtlasPage::clear() {
if (!pixelData.empty()) {
std::fill(pixelData.begin(), pixelData.end(), 0);
}
// 重新初始化打包上下文
if (!packNodes.empty()) {
stbrp_init_target(&packContext, width, height, packNodes.data(), width);
}
dirty = true;
}
// FontAtlas 实现
FontAtlas::FontAtlas() = default;
FontAtlas::~FontAtlas() = default;
bool FontAtlas::init(uint32 pageSize) {
pageSize_ = pageSize;
// 创建第一页
auto page = std::make_unique<AtlasPage>();
if (!page->init(pageSize_)) {
return false;
}
pages_.push_back(std::move(page));
loaded_ = true;
return true;
}
bool FontAtlas::addGlyph(uint32 codepoint, const uint8* bitmap, uint32 width, uint32 height,
float bearingX, float bearingY, float advance) {
if (!bitmap || width == 0 || height == 0) {
return false;
}
// 检查是否已存在
if (hasGlyph(codepoint)) {
return true;
}
// 尝试在所有现有页面中打包
uint32 pageIndex = 0;
uint32 x = 0, y = 0;
bool packed = false;
for (uint32 i = 0; i < pages_.size(); ++i) {
if (pages_[i]->packRect(width, height, x, y)) {
pageIndex = i;
packed = true;
break;
}
}
// 如果所有页面都满了,创建新页面
if (!packed) {
auto newPage = std::make_unique<AtlasPage>();
if (!newPage->init(pageSize_)) {
Logger::log(LogLevel::Error, "Failed to create new atlas page");
return false;
}
if (!newPage->packRect(width, height, x, y)) {
Logger::log(LogLevel::Error, "Glyph too large for atlas page: {}x{}", width, height);
return false;
}
pageIndex = static_cast<uint32>(pages_.size());
pages_.push_back(std::move(newPage));
packed = true;
}
// 写入像素数据
pages_[pageIndex]->writePixels(x, y, width, height, bitmap);
// 计算 UV 坐标
float invWidth = 1.0f / pages_[pageIndex]->width;
float invHeight = 1.0f / pages_[pageIndex]->height;
GlyphInfo info;
info.uv0 = Vec2(x * invWidth, y * invHeight);
info.uv1 = Vec2((x + width) * invWidth, (y + height) * invHeight);
info.size = Vec2(static_cast<float>(width), static_cast<float>(height));
info.bearing = Vec2(bearingX, bearingY);
info.advance = advance;
info.pageIndex = pageIndex;
info.valid = true;
glyphs_[codepoint] = info;
return true;
}
const GlyphInfo* FontAtlas::getGlyph(uint32 codepoint) const {
auto it = glyphs_.find(codepoint);
if (it != glyphs_.end()) {
return &it->second;
}
return nullptr;
}
bool FontAtlas::hasGlyph(uint32 codepoint) const {
return glyphs_.find(codepoint) != glyphs_.end();
}
Texture* FontAtlas::getPageTexture(uint32 pageIndex) const {
if (pageIndex < pages_.size()) {
return pages_[pageIndex]->texture.get();
}
return nullptr;
}
void FontAtlas::updateTextures() {
for (auto& page : pages_) {
page->updateTexture();
}
}
void FontAtlas::clear() {
glyphs_.clear();
for (auto& page : pages_) {
page->clear();
}
}
AtlasPage* FontAtlas::getOrCreatePage() {
// 尝试找到一个有空间的页面
for (auto& page : pages_) {
// 这里简化处理,实际应该检查剩余空间
return page.get();
}
// 创建新页面
auto newPage = std::make_unique<AtlasPage>();
if (newPage->init(pageSize_)) {
AtlasPage* ptr = newPage.get();
pages_.push_back(std::move(newPage));
return ptr;
}
return nullptr;
}
} // namespace extra2d

View File

@ -1,91 +0,0 @@
#include <resource/material.h>
namespace extra2d {
Material::Material() = default;
Material::~Material() = default;
void Material::setShader(Ptr<Shader> shader) { shader_ = shader; }
void Material::setBool(const std::string &name, bool value) {
properties_[name] = value;
}
void Material::setInt(const std::string &name, int value) {
properties_[name] = value;
}
void Material::setFloat(const std::string &name, float value) {
properties_[name] = value;
}
void Material::setVec2(const std::string &name, const Vec2 &value) {
properties_[name] = value;
}
void Material::setVec3(const std::string &name, const Vec3 &value) {
properties_[name] = value;
}
void Material::setColor(const std::string &name, const Color &value) {
properties_[name] = value;
}
void Material::setTexture(const std::string &name, Ptr<Texture> texture) {
properties_[name] = texture;
}
void Material::apply() {
if (!shader_)
return;
shader_->use();
applyProperties();
}
void Material::applyProperties() {
if (!shader_)
return;
textureUnit_ = 0;
for (const auto &pair : properties_) {
const auto &name = pair.first;
const auto &prop = pair.second;
std::visit(
[this, &name](auto &&value) {
using T = std::decay_t<decltype(value)>;
if constexpr (std::is_same_v<T, bool>) {
shader_->setBool(name, value);
} else if constexpr (std::is_same_v<T, int>) {
shader_->setInt(name, value);
} else if constexpr (std::is_same_v<T, float>) {
shader_->setFloat(name, value);
} else if constexpr (std::is_same_v<T, Vec2>) {
shader_->setVec2(name, value);
} else if constexpr (std::is_same_v<T, Vec3>) {
shader_->setVec3(name, value);
} else if constexpr (std::is_same_v<T, Color>) {
shader_->setColor(name, value);
} else if constexpr (std::is_same_v<T, Ptr<Texture>>) {
if (value) {
value->bind(textureUnit_);
shader_->setInt(name, textureUnit_);
textureUnit_++;
}
}
},
prop);
}
}
Ptr<Material> Material::clone() const {
Ptr<Material> newMat = makePtr<Material>();
newMat->setShader(shader_);
newMat->properties_ = properties_;
return newMat;
}
} // namespace extra2d

View File

@ -1,238 +0,0 @@
#include <resource/resource_manager.h>
#include <utils/logger.h>
#include <algorithm>
namespace extra2d {
ResourceManager::ResourceManager() = default;
ResourceManager::~ResourceManager() = default;
bool ResourceManager::init() {
Logger::log(LogLevel::Info, "Initializing ResourceManager...");
// 注意:默认资源延迟到首次访问时创建,以确保 OpenGL 上下文已就绪
return true;
}
void ResourceManager::shutdown() {
Logger::log(LogLevel::Info, "Shutting down ResourceManager...");
// 清理所有资源
texts_.clear();
materials_.clear();
audioCache_.clear();
fontCache_.clear();
shaderCache_.clear();
textureCache_.clear();
// 清理默认资源
defaultFont_.reset();
defaultMaterial_.reset();
defaultShader_.reset();
defaultTexture_.reset();
}
Ptr<Texture> ResourceManager::getTexture(const std::string& path) {
return textureCache_.get(path);
}
Ptr<Texture> ResourceManager::createTexture(uint32 width, uint32 height, TextureFormat format) {
Ptr<Texture> texture = makePtr<Texture>();
if (texture->create(width, height, format)) {
return texture;
}
return nullptr;
}
Ptr<Shader> ResourceManager::getShader(const std::string& vsPath, const std::string& fsPath) {
std::string key = vsPath + "|" + fsPath;
// 检查缓存
auto it = shaderCache_.find(key);
if (it != shaderCache_.end()) {
return it->second;
}
// 直接加载
Ptr<Shader> shader = makePtr<Shader>();
if (shader->loadFromFile(vsPath, fsPath)) {
shaderCache_[key] = shader;
return shader;
}
return nullptr;
}
Ptr<Shader> ResourceManager::getShaderFromSource(const std::string& name,
const std::string& vsSource,
const std::string& fsSource) {
Ptr<Shader> shader = makePtr<Shader>();
if (shader->loadFromSource(vsSource, fsSource)) {
return shader;
}
return nullptr;
}
Ptr<Material> ResourceManager::createMaterial(Ptr<Shader> shader) {
Ptr<Material> material = makePtr<Material>();
material->setShader(shader);
return material;
}
Ptr<Material> ResourceManager::getMaterial(const std::string& name) {
auto it = materials_.find(name);
if (it != materials_.end()) {
return it->second;
}
return nullptr;
}
void ResourceManager::registerMaterial(const std::string& name, Ptr<Material> material) {
materials_[name] = material;
}
Ptr<Font> ResourceManager::getFont(const std::string& path, const FontConfig& config) {
// 检查缓存
auto it = fontCache_.find(path);
if (it != fontCache_.end()) {
return it->second;
}
// 直接加载
Ptr<Font> font = makePtr<Font>();
if (font->loadFromFile(path, config)) {
fontCache_[path] = font;
return font;
}
return nullptr;
}
Ptr<Text> ResourceManager::createText(Ptr<Font> font, const std::string& content) {
Ptr<Text> text = makePtr<Text>();
text->setFont(font);
text->setText(content);
texts_.push_back(text);
return text;
}
Ptr<Audio> ResourceManager::getAudio(const std::string& path, AudioType type) {
// 检查缓存
auto it = audioCache_.find(path);
if (it != audioCache_.end()) {
return it->second;
}
// 直接加载
Ptr<Audio> audio = makePtr<Audio>();
if (audio->loadFromFile(path, type)) {
audioCache_[path] = audio;
return audio;
}
return nullptr;
}
Ptr<Texture> ResourceManager::getDefaultTexture() {
if (!defaultResourcesCreated_) {
createDefaultResources();
}
return defaultTexture_;
}
Ptr<Shader> ResourceManager::getDefaultShader() {
if (!defaultResourcesCreated_) {
createDefaultResources();
}
return defaultShader_;
}
Ptr<Material> ResourceManager::getDefaultMaterial() {
if (!defaultResourcesCreated_) {
createDefaultResources();
}
return defaultMaterial_;
}
Ptr<Font> ResourceManager::getDefaultFont() {
if (!defaultResourcesCreated_) {
createDefaultResources();
}
return defaultFont_;
}
void ResourceManager::collectGarbage() {
// 清理未引用的文本对象
texts_.erase(
std::remove_if(texts_.begin(), texts_.end(),
[](const Ptr<Text>& text) { return text.refCount() <= 1; }),
texts_.end()
);
}
ResourceManager::Stats ResourceManager::getStats() const {
Stats stats;
stats.textureCount = textureCache_.size();
stats.shaderCount = shaderCache_.size();
stats.materialCount = materials_.size();
stats.fontCount = fontCache_.size();
stats.textCount = texts_.size();
stats.audioCount = audioCache_.size();
return stats;
}
bool ResourceManager::createDefaultResources() {
if (defaultResourcesCreated_) {
return true;
}
// 创建默认纹理1x1 白色像素)
defaultTexture_ = makePtr<Texture>();
uint8 whitePixel[4] = {255, 255, 255, 255};
if (!defaultTexture_->loadFromMemory(whitePixel, 1, 1, TextureFormat::RGBA8)) {
Logger::log(LogLevel::Error, "Failed to create default texture");
return false;
}
// 创建默认着色器
defaultShader_ = makePtr<Shader>();
const char* vsSource = R"(
layout(location = 0) in vec2 aPosition;
layout(location = 1) in vec2 aTexCoord;
layout(location = 2) in vec4 aColor;
uniform mat4 uMVP;
out vec2 vTexCoord;
out vec4 vColor;
void main() {
gl_Position = uMVP * vec4(aPosition, 0.0, 1.0);
vTexCoord = aTexCoord;
vColor = aColor;
}
)";
const char* fsSource = R"(
in vec2 vTexCoord;
in vec4 vColor;
uniform sampler2D uTexture;
out vec4 fragColor;
void main() {
fragColor = vColor * texture(uTexture, vTexCoord);
}
)";
if (!defaultShader_->loadFromSource(vsSource, fsSource)) {
Logger::log(LogLevel::Error, "Failed to create default shader");
return false;
}
// 创建默认材质
defaultMaterial_ = makePtr<Material>();
defaultMaterial_->setShader(defaultShader_);
defaultMaterial_->setTexture("uTexture", defaultTexture_);
defaultMaterial_->setColor("uColor", Color::White);
defaultResourcesCreated_ = true;
Logger::log(LogLevel::Info, "Default resources created successfully");
return true;
}
} // namespace extra2d

View File

@ -1,180 +0,0 @@
#include <resource/shader.h>
#include <utils/logger.h>
#include <fstream>
#include <sstream>
namespace extra2d {
Shader::Shader() = default;
Shader::~Shader() {
if (program_ != 0) {
glDeleteProgram(program_);
program_ = 0;
}
}
bool Shader::loadFromFile(const std::string& vsPath, const std::string& fsPath) {
// 读取顶点着色器
std::ifstream vsFile(vsPath);
if (!vsFile.is_open()) {
Logger::log(LogLevel::Error, "Failed to open vertex shader: {}", vsPath.c_str());
return false;
}
std::stringstream vsStream;
vsStream << vsFile.rdbuf();
std::string vsSource = vsStream.str();
// 读取片段着色器
std::ifstream fsFile(fsPath);
if (!fsFile.is_open()) {
Logger::log(LogLevel::Error, "Failed to open fragment shader: {}", fsPath.c_str());
return false;
}
std::stringstream fsStream;
fsStream << fsFile.rdbuf();
std::string fsSource = fsStream.str();
return loadFromSource(vsSource, fsSource);
}
bool Shader::loadFromSource(const std::string& vsSource, const std::string& fsSource) {
// 删除旧程序
if (program_ != 0) {
glDeleteProgram(program_);
program_ = 0;
}
// 编译顶点着色器
GLuint vs = compileShader(GL_VERTEX_SHADER, vsSource);
if (vs == 0) {
return false;
}
// 编译片段着色器
GLuint fs = compileShader(GL_FRAGMENT_SHADER, fsSource);
if (fs == 0) {
glDeleteShader(vs);
return false;
}
// 链接着色器程序
program_ = glCreateProgram();
glAttachShader(program_, vs);
glAttachShader(program_, fs);
glLinkProgram(program_);
// 检查链接状态
GLint success;
glGetProgramiv(program_, GL_LINK_STATUS, &success);
if (!success) {
char infoLog[512];
glGetProgramInfoLog(program_, 512, nullptr, infoLog);
Logger::log(LogLevel::Error, "Shader program linking failed: {}", infoLog);
glDeleteProgram(program_);
program_ = 0;
glDeleteShader(vs);
glDeleteShader(fs);
return false;
}
// 清理着色器对象
glDeleteShader(vs);
glDeleteShader(fs);
// 清空 Uniform 缓存
uniformCache_.clear();
loaded_ = true;
Logger::log(LogLevel::Info, "Shader program created successfully");
return true;
}
void Shader::use() const {
if (program_ != 0) {
glUseProgram(program_);
}
}
void Shader::unuse() const {
glUseProgram(0);
}
void Shader::setBool(const std::string& name, bool value) {
glUniform1i(getUniformLocation(name), value ? 1 : 0);
}
void Shader::setInt(const std::string& name, int value) {
glUniform1i(getUniformLocation(name), value);
}
void Shader::setFloat(const std::string& name, float value) {
glUniform1f(getUniformLocation(name), value);
}
void Shader::setVec2(const std::string& name, const Vec2& value) {
glUniform2f(getUniformLocation(name), value.x, value.y);
}
void Shader::setVec3(const std::string& name, const Vec3& value) {
glUniform3f(getUniformLocation(name), value.x, value.y, value.z);
}
void Shader::setColor(const std::string& name, const Color& value) {
glUniform4f(getUniformLocation(name), value.r, value.g, value.b, value.a);
}
void Shader::setMat4(const std::string& name, const float* value) {
glUniformMatrix4fv(getUniformLocation(name), 1, GL_FALSE, value);
}
GLuint Shader::compileShader(GLenum type, const std::string& source) {
GLuint shader = glCreateShader(type);
// 添加 OpenGL ES 3.2 版本声明
std::string fullSource;
if (source.find("#version") == std::string::npos) {
fullSource = "#version 320 es\n";
if (type == GL_FRAGMENT_SHADER) {
fullSource += "precision mediump float;\n";
}
fullSource += source;
} else {
fullSource = source;
}
const char* src = fullSource.c_str();
glShaderSource(shader, 1, &src, nullptr);
glCompileShader(shader);
// 检查编译状态
GLint success;
glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
if (!success) {
char infoLog[512];
glGetShaderInfoLog(shader, 512, nullptr, infoLog);
Logger::log(LogLevel::Error, "Shader compilation failed: {}", infoLog);
glDeleteShader(shader);
return 0;
}
return shader;
}
GLint Shader::getUniformLocation(const std::string& name) {
if (program_ == 0) return -1;
// 检查缓存
auto it = uniformCache_.find(name);
if (it != uniformCache_.end()) {
return it->second;
}
// 查询 Uniform 位置
GLint location = glGetUniformLocation(program_, name.c_str());
uniformCache_[name] = location;
return location;
}
} // namespace extra2d

View File

@ -1,213 +0,0 @@
#include <resource/text.h>
#include <algorithm>
namespace extra2d {
Text::Text() = default;
Text::~Text() = default;
void Text::setFont(Ptr<Font> font) {
font_ = font;
dirty_ = true;
}
void Text::setText(const std::string& text) {
text_ = text;
dirty_ = true;
}
void Text::setFontSize(float size) {
fontSize_ = size;
dirty_ = true;
}
void Text::setColor(const Color& color) {
color_ = color;
dirty_ = true;
}
void Text::setAlign(TextAlign align) {
align_ = align;
dirty_ = true;
}
void Text::setMaxWidth(float width) {
maxWidth_ = width;
dirty_ = true;
}
void Text::setLineSpacing(float spacing) {
lineSpacing_ = spacing;
dirty_ = true;
}
void Text::rebuild() {
if (!dirty_) return;
buildVertices();
dirty_ = false;
}
void Text::buildVertices() {
lines_.clear();
if (!font_ || text_.empty()) {
size_ = Vec2(0, 0);
return;
}
float scale = fontSize_ / font_->getConfig().fontSize;
float lineHeight = font_->getLineHeight(fontSize_);
// 解析 UTF-8 文本
std::vector<uint32_t> codepoints;
const uint8_t* data = reinterpret_cast<const uint8_t*>(text_.data());
size_t len = text_.length();
size_t i = 0;
while (i < len) {
uint32_t codepoint = 0;
uint8_t byte = data[i];
if ((byte & 0x80) == 0) {
codepoint = byte;
i++;
} else if ((byte & 0xE0) == 0xC0 && i + 1 < len) {
codepoint = ((byte & 0x1F) << 6) | (data[i + 1] & 0x3F);
i += 2;
} else if ((byte & 0xF0) == 0xE0 && i + 2 < len) {
codepoint = ((byte & 0x0F) << 12) | ((data[i + 1] & 0x3F) << 6) | (data[i + 2] & 0x3F);
i += 3;
} else if ((byte & 0xF8) == 0xF0 && i + 3 < len) {
codepoint = ((byte & 0x07) << 18) | ((data[i + 1] & 0x3F) << 12) |
((data[i + 2] & 0x3F) << 6) | (data[i + 3] & 0x3F);
i += 4;
} else {
i++;
continue;
}
codepoints.push_back(codepoint);
}
// 分行处理
std::vector<std::vector<uint32_t>> lines;
std::vector<uint32_t> currentLine;
float currentWidth = 0.0f;
uint32_t prevCodepoint = 0;
for (uint32_t codepoint : codepoints) {
if (codepoint == '\n') {
lines.push_back(currentLine);
currentLine.clear();
currentWidth = 0.0f;
prevCodepoint = 0;
continue;
}
const GlyphInfo* glyph = font_->getGlyph(codepoint);
if (!glyph) {
prevCodepoint = codepoint;
continue;
}
float advance = glyph->advance * scale;
if (prevCodepoint != 0) {
advance += font_->getKerning(prevCodepoint, codepoint) * scale;
}
// 检查是否需要换行
if (maxWidth_ > 0 && currentWidth + advance > maxWidth_ && !currentLine.empty()) {
lines.push_back(currentLine);
currentLine.clear();
currentWidth = 0.0f;
prevCodepoint = 0;
}
currentLine.push_back(codepoint);
currentWidth += advance;
prevCodepoint = codepoint;
}
if (!currentLine.empty()) {
lines.push_back(currentLine);
}
// 构建每行的顶点数据
float totalHeight = 0.0f;
float maxLineWidth = 0.0f;
for (size_t lineIdx = 0; lineIdx < lines.size(); ++lineIdx) {
const auto& line = lines[lineIdx];
TextLine textLine;
float x = 0.0f;
float lineWidth = 0.0f;
prevCodepoint = 0;
// 首先计算行宽度
for (uint32_t codepoint : line) {
const GlyphInfo* glyph = font_->getGlyph(codepoint);
if (!glyph) continue;
float advance = glyph->advance * scale;
if (prevCodepoint != 0) {
advance += font_->getKerning(prevCodepoint, codepoint) * scale;
}
lineWidth += advance;
prevCodepoint = codepoint;
}
// 根据对齐方式计算起始 X
if (align_ == TextAlign::Center) {
x = -lineWidth * 0.5f;
} else if (align_ == TextAlign::Right) {
x = -lineWidth;
}
// 构建顶点
prevCodepoint = 0;
for (uint32_t codepoint : line) {
const GlyphInfo* glyph = font_->getGlyph(codepoint);
if (!glyph) continue;
if (prevCodepoint != 0) {
x += font_->getKerning(prevCodepoint, codepoint) * scale;
}
float xPos = x + glyph->bearing.x * scale;
float yPos = -glyph->bearing.y * scale;
float w = glyph->size.x * scale;
float h = glyph->size.y * scale;
// 添加四个顶点
uint16_t baseIndex = static_cast<uint16_t>(textLine.vertices.size());
textLine.vertices.push_back({Vec2(xPos, yPos + h), Vec2(glyph->uv0.x, glyph->uv1.y), color_});
textLine.vertices.push_back({Vec2(xPos + w, yPos + h), Vec2(glyph->uv1.x, glyph->uv1.y), color_});
textLine.vertices.push_back({Vec2(xPos + w, yPos), Vec2(glyph->uv1.x, glyph->uv0.y), color_});
textLine.vertices.push_back({Vec2(xPos, yPos), Vec2(glyph->uv0.x, glyph->uv0.y), color_});
// 添加两个三角形的索引
textLine.indices.push_back(baseIndex);
textLine.indices.push_back(baseIndex + 1);
textLine.indices.push_back(baseIndex + 2);
textLine.indices.push_back(baseIndex);
textLine.indices.push_back(baseIndex + 2);
textLine.indices.push_back(baseIndex + 3);
x += glyph->advance * scale;
prevCodepoint = codepoint;
}
textLine.width = lineWidth;
textLine.yOffset = totalHeight;
lines_.push_back(std::move(textLine));
maxLineWidth = std::max(maxLineWidth, lineWidth);
totalHeight += lineHeight * lineSpacing_;
}
size_ = Vec2(maxLineWidth, totalHeight);
}
} // namespace extra2d

View File

@ -1,271 +0,0 @@
#include <resource/texture.h>
#include <utils/logger.h>
#define STB_IMAGE_IMPLEMENTATION
#include <stb/stb_image.h>
namespace extra2d {
Texture::Texture() = default;
Texture::~Texture() {
if (handle_ != 0) {
glDeleteTextures(1, &handle_);
handle_ = 0;
}
}
bool Texture::create(uint32 width, uint32 height, TextureFormat format) {
if (handle_ != 0) {
glDeleteTextures(1, &handle_);
}
glGenTextures(1, &handle_);
if (handle_ == 0) {
Logger::log(LogLevel::Error, "Failed to generate texture");
return false;
}
width_ = width;
height_ = height;
format_ = format;
glBindTexture(GL_TEXTURE_2D, handle_);
GLint internalFormat = getGLInternalFormat(format);
GLenum glFormat = getGLFormat(format);
GLenum glType = getGLType(format);
glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, width, height, 0, glFormat, glType, nullptr);
// 设置默认过滤和环绕模式
setFilter(TextureFilter::Linear, TextureFilter::Linear);
setWrap(TextureWrap::Clamp, TextureWrap::Clamp);
glBindTexture(GL_TEXTURE_2D, 0);
loaded_ = true;
return true;
}
bool Texture::loadFromFile(const std::string& path) {
int width, height, channels;
stbi_set_flip_vertically_on_load(true);
unsigned char* data = stbi_load(path.c_str(), &width, &height, &channels, 0);
if (!data) {
Logger::log(LogLevel::Error, "Failed to load texture: {}", path.c_str());
return false;
}
TextureFormat format;
if (channels == 1) {
format = TextureFormat::R8;
} else if (channels == 3) {
format = TextureFormat::RGB8;
} else {
format = TextureFormat::RGBA8;
}
bool result = loadFromMemory(data, width, height, format);
stbi_image_free(data);
if (result) {
name_ = path;
Logger::log(LogLevel::Info, "Loaded texture: {} ({}x{})", path.c_str(), width, height);
}
return result;
}
bool Texture::loadFromMemory(const uint8* data, uint32 width, uint32 height, TextureFormat format) {
if (handle_ != 0) {
glDeleteTextures(1, &handle_);
}
glGenTextures(1, &handle_);
if (handle_ == 0) {
Logger::log(LogLevel::Error, "Failed to generate texture");
return false;
}
width_ = width;
height_ = height;
format_ = format;
glBindTexture(GL_TEXTURE_2D, handle_);
GLint internalFormat = getGLInternalFormat(format);
GLenum glFormat = getGLFormat(format);
GLenum glType = getGLType(format);
glTexImage2D(GL_TEXTURE_2D, 0, internalFormat, width, height, 0, glFormat, glType, data);
// 设置默认过滤和环绕模式
setFilter(TextureFilter::Linear, TextureFilter::Linear);
setWrap(TextureWrap::Clamp, TextureWrap::Clamp);
glBindTexture(GL_TEXTURE_2D, 0);
loaded_ = true;
return true;
}
void Texture::updateRegion(uint32 x, uint32 y, uint32 width, uint32 height, const uint8* data) {
if (handle_ == 0 || !data) return;
glBindTexture(GL_TEXTURE_2D, handle_);
GLenum glFormat = getGLFormat(format_);
GLenum glType = getGLType(format_);
glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, width, height, glFormat, glType, data);
glBindTexture(GL_TEXTURE_2D, 0);
}
void Texture::bind(uint32 slot) const {
if (handle_ != 0) {
glActiveTexture(GL_TEXTURE0 + slot);
glBindTexture(GL_TEXTURE_2D, handle_);
}
}
void Texture::unbind() const {
glBindTexture(GL_TEXTURE_2D, 0);
}
void Texture::setFilter(TextureFilter minFilter, TextureFilter magFilter) {
if (handle_ == 0) return;
glBindTexture(GL_TEXTURE_2D, handle_);
GLenum minFilterGL = GL_LINEAR;
GLenum magFilterGL = GL_LINEAR;
switch (minFilter) {
case TextureFilter::Nearest:
minFilterGL = GL_NEAREST;
break;
case TextureFilter::Linear:
minFilterGL = GL_LINEAR;
break;
case TextureFilter::MipmapNearest:
minFilterGL = GL_NEAREST_MIPMAP_NEAREST;
break;
case TextureFilter::MipmapLinear:
minFilterGL = GL_LINEAR_MIPMAP_LINEAR;
break;
default:
minFilterGL = GL_LINEAR;
break;
}
switch (magFilter) {
case TextureFilter::Nearest:
magFilterGL = GL_NEAREST;
break;
default:
magFilterGL = GL_LINEAR;
break;
}
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minFilterGL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, magFilterGL);
glBindTexture(GL_TEXTURE_2D, 0);
}
void Texture::setWrap(TextureWrap wrapS, TextureWrap wrapT) {
if (handle_ == 0) return;
glBindTexture(GL_TEXTURE_2D, handle_);
GLenum wrapSGL = GL_CLAMP_TO_EDGE;
GLenum wrapTGL = GL_CLAMP_TO_EDGE;
switch (wrapS) {
case TextureWrap::Repeat:
wrapSGL = GL_REPEAT;
break;
case TextureWrap::Clamp:
wrapSGL = GL_CLAMP_TO_EDGE;
break;
case TextureWrap::Mirror:
wrapSGL = GL_MIRRORED_REPEAT;
break;
default:
wrapSGL = GL_CLAMP_TO_EDGE;
break;
}
switch (wrapT) {
case TextureWrap::Repeat:
wrapTGL = GL_REPEAT;
break;
case TextureWrap::Clamp:
wrapTGL = GL_CLAMP_TO_EDGE;
break;
case TextureWrap::Mirror:
wrapTGL = GL_MIRRORED_REPEAT;
break;
default:
wrapTGL = GL_CLAMP_TO_EDGE;
break;
}
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapSGL);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapTGL);
glBindTexture(GL_TEXTURE_2D, 0);
}
void Texture::generateMipmap() {
if (handle_ != 0) {
glBindTexture(GL_TEXTURE_2D, handle_);
glGenerateMipmap(GL_TEXTURE_2D);
glBindTexture(GL_TEXTURE_2D, 0);
}
}
GLint Texture::getGLInternalFormat(TextureFormat format) {
switch (format) {
case TextureFormat::RGB8:
return GL_RGB8;
case TextureFormat::RGBA8:
return GL_RGBA8;
case TextureFormat::R8:
return GL_R8;
case TextureFormat::Depth:
return GL_DEPTH_COMPONENT24;
case TextureFormat::DepthStencil:
return GL_DEPTH24_STENCIL8;
default:
return GL_RGBA8;
}
}
GLenum Texture::getGLFormat(TextureFormat format) {
switch (format) {
case TextureFormat::RGB8:
return GL_RGB;
case TextureFormat::RGBA8:
return GL_RGBA;
case TextureFormat::R8:
return GL_RED;
case TextureFormat::Depth:
return GL_DEPTH_COMPONENT;
case TextureFormat::DepthStencil:
return GL_DEPTH_STENCIL;
default:
return GL_RGBA;
}
}
GLenum Texture::getGLType(TextureFormat format) {
switch (format) {
case TextureFormat::DepthStencil:
return GL_UNSIGNED_INT_24_8;
default:
return GL_UNSIGNED_BYTE;
}
}
} // namespace extra2d