Compare commits

..

17 Commits

Author SHA1 Message Date
ChestnutYueyue 583e866861 docs(module_system): 重构模块系统文档,简化内容并优化结构
- 移除冗余的配置系统实现细节,聚焦核心架构
- 优化模块与服务的对比表格,突出关键差异
- 简化示例代码,保留核心用法展示
- 更新架构图,更清晰展示模块与服务的层级关系
- 统一术语使用,增强文档一致性
2026-02-17 23:57:50 +08:00
ChestnutYueyue edd47a890a chore: 删除无用的图片文件1.jpg 2026-02-17 23:02:18 +08:00
ChestnutYueyue 69606230da docs: 更新架构图添加GLFW后端支持
在平台层架构图中添加GLFW后端支持,包括窗口系统和输入系统的关联
2026-02-17 22:57:46 +08:00
ChestnutYueyue 6babd376c9 docs: 更新 README 文档以反映最新架构变更
- 添加资源抽象层和显存管理功能说明
- 更新模块优先级和架构图
- 完善目录结构描述
- 新增性能优化策略说明
2026-02-17 22:53:41 +08:00
ChestnutYueyue 2748e80dea refactor(opengl): 重构渲染器资源管理并引入资源抽象层
- 引入资源抽象层接口(Buffer、Pipeline、Framebuffer等)
- 将OpenGL资源管理重构为GLBuffer、GLPipeline、GLFramebuffer等实现类
- 使用GLBuffer替代手动管理的VBO/IBO,提供更安全的资源生命周期管理
- 新增GLPipeline管理OpenGL管线状态,减少冗余状态切换
- 新增GLFramebuffer封装帧缓冲对象功能
- 更新GLRenderer使用新的资源管理方式
- 添加详细文档说明资源抽象层设计
- 修复相关内存泄漏问题

docs: 添加资源抽象层文档说明

- 新增docs/README.md详细说明资源抽象层设计
- 文档包含各接口功能说明和实现原则
2026-02-17 22:36:02 +08:00
ChestnutYueyue c8a6ea19e3 refactor(renderer): 优化文本渲染的批处理逻辑并调整代码格式
重构文本渲染的批处理逻辑,添加纹理变化检查并优化换行处理
同时调整部分代码格式以提高可读性
2026-02-17 20:44:33 +08:00
ChestnutYueyue 32e12b8c99 feat(渲染): 添加自动批处理功能并实现图片显示示例
添加自动精灵批处理功能,优化渲染性能
新增图片显示示例,展示如何使用RenderBackend抽象接口加载和显示图片
重构文本渲染示例以使用RenderBackend接口
添加flush方法用于手动控制批处理提交时机
2026-02-17 20:16:07 +08:00
ChestnutYueyue 6b4ce69657 feat(渲染): 优化字体图集和精灵批处理系统
- 在GLFontAtlas中重构字体图集实现,使用stb_rect_pack进行动态矩形打包
- 增加图集尺寸至1024x1024并添加字形间距
- 改进字形UV计算和纹理坐标处理
- 在GLSpriteBatch中保存viewProjection矩阵并优化着色器uniform设置
- 修正文本渲染中的字形位置计算和精灵中心点处理
- 优化纹理坐标生成逻辑以正确处理UV翻转
2026-02-17 19:55:47 +08:00
ChestnutYueyue 30b677f192 feat(示例): 添加文字渲染示例并修复场景过渡方法声明
添加新的文字渲染示例 demo_text_rendering,展示如何使用 GLFontAtlas 渲染文字
修复场景过渡类中缺少 override 关键字的方法声明
更新 README 文档以包含新示例
2026-02-17 19:24:50 +08:00
ChestnutYueyue 89fb955eb0 docs: 更新README以反映新增的Vulkan渲染后端和架构调整
更新文档以包含以下内容:
1. 新增Vulkan渲染后端支持
2. 渲染系统分层架构说明
3. 目录结构调整
4. 多平台支持状态更新
2026-02-17 14:50:13 +08:00
ChestnutYueyue f1cf6a6d85 docs: 更新README添加多后端渲染和高性能批处理特性 2026-02-17 14:50:05 +08:00
ChestnutYueyue a4276e4376 feat(渲染后端): 重构渲染系统支持多后端
- 新增渲染后端工厂类,支持OpenGL和Vulkan后端
- 将OpenGL相关代码移动到backends/opengl目录
- 添加Vulkan后端占位实现
- 重构Shader系统,支持JSON元数据定义多后端Shader
- 新增shape和sprite的JSON定义及GLSL文件
- 移除旧的组合Shader文件格式
- 更新构建系统支持选择渲染后端
- 重命名相关头文件路径保持一致性
2026-02-17 14:48:04 +08:00
ChestnutYueyue d2660a86bb refactor(config): 移除平台检测和配置相关代码
将应用配置信息移至Application类,移除平台检测和配置相关文件
简化ShaderManager的初始化逻辑,使用相对路径替代平台检测
2026-02-17 12:53:01 +08:00
ChestnutYueyue 61dea772f7 feat(服务定位器): 实现服务自动注册机制并重构服务初始化流程
添加服务自动注册宏E2D_AUTO_REGISTER_SERVICE,通过模板元编程实现编译期服务注册
重构Application初始化流程,移除手动服务注册代码,改为自动注册方式
统一各服务实现类的代码风格,优化缩进和格式
2026-02-17 12:12:22 +08:00
ChestnutYueyue 4f02ad0e39 refactor(模块系统): 重构模块配置方式为lambda函数
将InputModule、WindowModule、RenderModule等核心模块的配置方式从结构体改为lambda函数
添加独立的配置结构体(InputCfg/WindowCfg/RenderCfg)并更新文档
实现GLFW窗口居中功能
更新示例代码使用新的配置方式
2026-02-17 00:34:14 +08:00
ChestnutYueyue 4b1de5e36a refactor: 整理头文件顺序并格式化代码
重构头文件引入顺序以保持一致性,并统一代码格式化风格
2026-02-17 00:12:32 +08:00
ChestnutYueyue 9f83b8fde5 feat(platform): 添加 GLFW 后端支持并移除 SDL2 依赖
添加 GLFW 作为可选的窗口和输入后端,支持通过配置切换 SDL2 或 GLFW 后端
移除对 SDL2 的直接依赖,重构窗口模块以支持多后端
更新构建系统和文档以反映后端选择功能
2026-02-17 00:06:31 +08:00
140 changed files with 9106 additions and 7298 deletions

View File

@ -1,86 +0,0 @@
# CLAUDE.md
本文件为 Claude Code (claude.ai/code) 在此仓库中工作时提供指导。
## 项目概述
Extra2D 是一个使用 C++17 编写的轻量级跨平台 2D 游戏引擎。支持 Windows (MinGW)、Linux、macOS 和 Nintendo Switch。项目注释和文档使用中文。
## 构建系统
项目使用 **xmake**(非 CMake
```bash
# 配置 (Windows/MinGW 示例)
xmake f -p mingw -a x86_64 -m release -y
# 配置调试构建
xmake f -p mingw -a x86_64 -m debug -y
# 构建全部
xmake build
# 构建特定目标
xmake build demo_basic
xmake build demo_hello_module
# 运行示例
xmake run demo_basic
xmake run demo_hello_module
# Nintendo Switch 构建
export DEVKITPRO=/opt/devkitpro
xmake f -p switch -m release -y
xmake build
```
构建目标:`extra2d`(引擎静态库)、`demo_basic`、`demo_hello_module`、`hello_module_lib`。
调试构建定义 `E2D_DEBUG``_DEBUG`。`debug_logs` 选项启用调试日志。
## 架构
### 双系统设计:模块 + 服务
引擎有两个由 `Application`(单例)管理的并行系统:
- **模块**`Module` 基类):处理平台级初始化,具有生命周期钩子(`setupModule`、`destroyModule`、`onUpdate`、`onRender`、`handleEvent`)。通过 `E2D_MODULE` 宏注册,在静态初始化时自动发现。按优先级排序(数值越小越先初始化)。
- **服务**`IService` 基类):提供通过 `ServiceLocator` 访问的运行时功能。通过 `Application` 便捷方法获取:`app.scenes()`、`app.timers()`、`app.events()`、`app.camera()`。
### 模块自动注册
模块使用 `E2D_MODULE(类名, 优先级)` 宏,放置在 `.cpp` 文件末尾、**任何命名空间之外**。这会创建一个静态的 `ModuleAutoRegister<T>` 变量,在静态初始化期间向 `ModuleRegistry` 注册。静态链接需要 `--whole-archive` 链接器标志,以防止链接器剥离这些未引用的符号。
内置模块优先级顺序Logger(-1) → Config(0) → Platform(10) → Window(20) → Input(30) → Render(40)。用户模块应使用优先级 1000+。
### 模块上下文链
模块生命周期方法接收上下文对象(`UpdateContext`、`RenderContext`、`EventContext`)。**必须调用 `ctx.next()`** 以继续链式调用到下一个模块。
### 关键子系统
- **平台层**:统一的 SDL2 后端(`E2D_BACKEND_SDL2` 宏定义)。通过 `IWindow`/`IInput` 接口实现平台抽象。实现在 `Extra2D/src/platform/backends/sdl2/`
- **图形**:通过 glad 使用 OpenGL ES 3.2。渲染器(`gl_renderer`)、精灵批处理(`gl_sprite_batch`)、支持热重载的着色器管理。内置着色器在 `Extra2D/shaders/builtin/`(构建后复制到构建目录)。
- **场景图**:基于树的 `Node` 层级结构,支持变换继承。`Scene` 是根节点。专用节点:`ShapeNode`、`Sprite`。场景过渡效果(淡入淡出、滑动、翻转、缩放、方块)。
- **事件系统**`EventType` 枚举驱动。事件使用 `std::variant` 实现类型化数据(`KeyEvent`、`MouseEvent`、`GamepadEvent`)。输入使用扫描码(非键码)。
## 源码布局
- `Extra2D/include/extra2d/` — 按子系统组织的公共头文件
- `Extra2D/src/` — 实现,镜像 include 结构
- `Extra2D/shaders/` — GLSL 着色器文件(复制到构建输出)
- `examples/` — 示例程序(`basic/`、`hello_module/`
- `xmake/engine.lua` — 引擎静态库目标定义
- `xmake.lua` — 根构建配置,包含示例目标
## 依赖
由 xmake 管理:`glm`、`nlohmann_json`、`libsdl2`。内嵌:`glad`、`stb_image`、`stb_truetype`、`stb_rect_pack`、`KHR`。
## 约定
- 命名空间:`extra2d`
- 智能指针:`Ptr<T>`shared_ptr 别名)、`SharedPtr<T>`,通过 `makeShared<T>()` 创建
- 日志:`E2D_LOG_INFO(...)`、`E2D_LOG_WARN(...)`、`E2D_LOG_ERROR(...)`
- 导出宏:`E2D_API` 用于 DLL 可见符号
- 自定义模块直接编译到可执行文件(静态链接,推荐)或作为独立 DLL

View File

@ -1,10 +1,9 @@
#pragma once #pragma once
#include <extra2d/core/types.h>
#include <extra2d/core/module.h> #include <extra2d/core/module.h>
#include <extra2d/core/registry.h> #include <extra2d/core/registry.h>
#include <extra2d/core/service_locator.h> #include <extra2d/core/service_locator.h>
#include <extra2d/config/app_config.h> #include <extra2d/core/types.h>
#include <string> #include <string>
namespace extra2d { namespace extra2d {
@ -21,123 +20,117 @@ class InputModule;
*/ */
class Application { class Application {
public: public:
static Application& get(); static Application &get();
Application(const Application&) = delete; Application(const Application &) = delete;
Application& operator=(const Application&) = delete; Application &operator=(const Application &) = delete;
/** /**
* @brief * @brief
* @tparam T */
* @tparam Args std::string appName = "Extra2D App";
* @return std::string appVersion = "1.0.0";
*/ std::string organization = "";
template<typename T, typename... Args>
T* use(Args&&... args) { /**
return Registry::instance().use<T>(std::forward<Args>(args)...); * @brief
} * @tparam T
* @tparam Args
/** * @return
* @brief */
* @tparam T template <typename T, typename... Args> T *use(Args &&...args) {
* @return return Registry::instance().use<T>(std::forward<Args>(args)...);
*/ }
template<typename T>
T* get() const { /**
return Registry::instance().get<T>(); * @brief
} * @tparam T
* @return
/** */
* @brief template <typename T> T *get() const { return Registry::instance().get<T>(); }
* @return true
*/ /**
bool init(); * @brief
* @return true
/** */
* @brief bool init();
* @param config
* @return true /**
*/ * @brief
bool init(const AppConfig& config); */
void shutdown();
/**
* @brief /**
*/ * @brief
void shutdown(); */
void run();
/**
* @brief /**
*/ * @brief 退
void run(); */
void quit();
/**
* @brief 退 /**
*/ * @brief
void quit(); */
void pause();
/**
* @brief /**
*/ * @brief
void pause(); */
void resume();
/**
* @brief bool isPaused() const { return paused_; }
*/ bool isRunning() const { return running_; }
void resume();
/**
bool isPaused() const { return paused_; } * @brief
bool isRunning() const { return running_; } * @return
*/
/** IWindow *window();
* @brief
* @return /**
*/ * @brief
IWindow* window(); * @return
*/
/** RenderBackend *renderer();
* @brief
* @return /**
*/ * @brief
RenderBackend* renderer(); * @return
*/
/** IInput *input();
* @brief
* @return /**
*/ * @brief
IInput* input(); * @param scene
*/
/** void enterScene(Ptr<class Scene> scene);
* @brief
* @param scene float deltaTime() const { return deltaTime_; }
*/ float totalTime() const { return totalTime_; }
void enterScene(Ptr<class Scene> scene); int fps() const { return currentFps_; }
float deltaTime() const { return deltaTime_; }
float totalTime() const { return totalTime_; }
int fps() const { return currentFps_; }
private: private:
Application(); Application();
~Application(); ~Application();
void mainLoop(); void mainLoop();
void update(); void update();
void render(); void render();
void registerCoreServices(); void configureCameraService();
bool initialized_ = false; bool initialized_ = false;
bool running_ = false; bool running_ = false;
bool paused_ = false; bool paused_ = false;
bool shouldQuit_ = false; bool shouldQuit_ = false;
float deltaTime_ = 0.0f; float deltaTime_ = 0.0f;
float totalTime_ = 0.0f; float totalTime_ = 0.0f;
double lastFrameTime_ = 0.0; double lastFrameTime_ = 0.0;
int frameCount_ = 0; int frameCount_ = 0;
float fpsTimer_ = 0.0f; float fpsTimer_ = 0.0f;
int currentFps_ = 0; int currentFps_ = 0;
AppConfig appConfig_;
}; };
} // namespace extra2d } // namespace extra2d

View File

@ -1,57 +0,0 @@
#pragma once
#include <extra2d/core/export.h>
#include <extra2d/core/types.h>
#include <string>
namespace extra2d {
/**
* @file app_config.h
* @brief
*
*
* RenderModuleConfig
*/
/**
* @brief
*
*/
struct E2D_API AppConfig {
std::string appName = "Extra2D App";
std::string appVersion = "1.0.0";
std::string organization = "";
std::string configFile = "config.json";
/**
* @brief
* @return
*/
static AppConfig createDefault();
/**
* @brief
* @return true false
*/
bool validate() const;
/**
* @brief
*/
void reset();
/**
* @brief
* @param other
*/
void merge(const AppConfig& other);
/**
* @brief
* @return true
*/
bool isValid() const { return validate(); }
};
}

View File

@ -1,23 +0,0 @@
#pragma once
// 动态库导出宏
// 静态库时 E2D_API 为空
#ifndef E2D_BUILDING_DLL
#define E2D_API
#else
#if defined(_WIN32) || defined(__CYGWIN__)
#define E2D_API __declspec(dllexport)
#else
#define E2D_API __attribute__((visibility("default")))
#endif
#endif
// 模板类导出(不需要导出)
#define E2D_TEMPLATE_API
// 内联函数导出
#if defined(_WIN32) || defined(__CYGWIN__)
#define E2D_INLINE __forceinline
#else
#define E2D_INLINE inline
#endif

View File

@ -1,101 +0,0 @@
#pragma once
#include <extra2d/core/export.h>
#include <extra2d/core/module_meta.h>
#include <extra2d/core/types.h>
#include <initializer_list>
namespace extra2d {
/**
* @brief
*/
template<typename T>
class ModuleMeta : public ModuleMetaBase {
public:
using ModuleType = T;
const char* name_ = nullptr;
int priority_ = 0;
std::vector<const char*> dependencies_;
const char* getName() const override { return name_; }
int getPriority() const override { return priority_; }
std::vector<const char*> getDependencies() const override { return dependencies_; }
T* create() override {
return new T();
}
};
namespace detail {
/**
* @brief
*/
template<typename T>
struct ModuleAutoRegister {
ModuleMeta<T> meta;
ModuleAutoRegister(const char* name, int priority, std::initializer_list<const char*> deps) {
meta.name_ = name;
meta.priority_ = priority;
meta.dependencies_ = std::vector<const char*>(deps);
ModuleRegistry::instance().registerMeta(&meta);
}
};
} // namespace detail
} // namespace extra2d
// ============================================================================
// 模块定义宏 - 静态自动注册
// ============================================================================
/**
* @brief
*
*
*/
#define E2D_MODULE(ModuleClassName, priorityValue, ...) \
__attribute__((used)) \
static ::extra2d::detail::ModuleAutoRegister< ::extra2d::ModuleClassName> \
E2D_CONCAT(_e2d_auto_reg_, ModuleClassName)( \
#ModuleClassName, priorityValue, { __VA_ARGS__ });
/**
* @brief force_link
*
* DLL
*
* 使 cpp
* } // namespace extra2d
* E2D_MODULE_EXPORT(HelloModule, 1000)
*/
#define E2D_MODULE_EXPORT(ModuleClassName, priorityValue, ...) \
E2D_MODULE(ModuleClassName, priorityValue, __VA_ARGS__) \
extern "C" E2D_API void E2D_CONCAT(e2d_force_link_, ModuleClassName)() {}
/**
* @brief force_link
*/
#define E2D_DECLARE_FORCE_LINK(ModuleClassName) \
extern "C" void E2D_CONCAT(e2d_force_link_, ModuleClassName)()
/**
* @brief force_link
*/
#define E2D_CALL_FORCE_LINK(ModuleClassName) \
E2D_CONCAT(e2d_force_link_, ModuleClassName)()
/**
* @brief +
*
* main.cpp DLL
*/
#define E2D_FORCE_LINK(ModuleClassName) \
E2D_DECLARE_FORCE_LINK(ModuleClassName); \
E2D_CALL_FORCE_LINK(ModuleClassName)

View File

@ -1,141 +0,0 @@
#pragma once
#include <extra2d/core/export.h>
#include <extra2d/core/types.h>
#include <vector>
#include <string>
#include <unordered_map>
#include <memory>
#include <functional>
#include <algorithm>
namespace extra2d {
class Module;
/**
* @brief
*
*
*/
struct E2D_API ModuleMetaBase {
virtual ~ModuleMetaBase() = default;
/**
* @brief
*/
virtual const char* getName() const = 0;
/**
* @brief
*/
virtual int getPriority() const { return 0; }
/**
* @brief
*/
virtual std::vector<const char*> getDependencies() const { return {}; }
/**
* @brief
*/
virtual Module* create() = 0;
};
/**
* @brief
*
*
*/
class E2D_API ModuleRegistry {
public:
/**
* @brief
*/
static ModuleRegistry& instance();
/**
* @brief
* @param meta
*/
void registerMeta(ModuleMetaBase* meta);
/**
* @brief
*/
std::vector<ModuleMetaBase*> getAllMetas() const;
/**
* @brief
*/
ModuleMetaBase* getMeta(const char* name) const;
/**
* @brief
* @return true
*/
bool createAndInitAll();
/**
* @brief
*/
void destroyAll();
/**
* @brief
*/
template<typename T>
T* getModule() const {
for (const auto& [name, ptr] : instanceMap_) {
if (auto* derived = dynamic_cast<T*>(ptr)) {
return derived;
}
}
return nullptr;
}
/**
* @brief
*/
Module* getModule(const char* name) const;
/**
* @brief
*/
bool hasModule(const char* name) const;
/**
* @brief
*/
std::vector<Module*> getAllModules() const;
/**
* @brief
*/
bool isInitialized() const { return initialized_; }
private:
ModuleRegistry() = default;
~ModuleRegistry() = default;
ModuleRegistry(const ModuleRegistry&) = delete;
ModuleRegistry& operator=(const ModuleRegistry&) = delete;
/**
* @brief
*/
std::vector<ModuleMetaBase*> sortByDependency();
/**
* @brief
*/
bool hasCircularDependency() const;
std::vector<ModuleMetaBase*> metas_;
std::vector<std::unique_ptr<Module>> instances_;
std::unordered_map<std::string, Module*> instanceMap_;
bool initialized_ = false;
};
} // namespace extra2d

View File

@ -1,27 +1,26 @@
#pragma once #pragma once
#include <algorithm>
#include <extra2d/core/service_interface.h> #include <extra2d/core/service_interface.h>
#include <extra2d/core/types.h> #include <extra2d/core/types.h>
#include <functional>
#include <memory>
#include <mutex>
#include <typeindex>
#include <unordered_map> #include <unordered_map>
#include <vector> #include <vector>
#include <mutex>
#include <functional>
#include <typeindex>
#include <memory>
#include <algorithm>
namespace extra2d { namespace extra2d {
/** /**
* @brief * @brief
*/ */
template<typename T> template <typename T> using ServiceFactory = std::function<SharedPtr<T>()>;
using ServiceFactory = std::function<SharedPtr<T>()>;
/** /**
* @brief * @brief
* *
* *
* *
* - * -
* - * -
@ -31,222 +30,273 @@ using ServiceFactory = std::function<SharedPtr<T>()>;
*/ */
class ServiceLocator { class ServiceLocator {
public: public:
/** /**
* @brief * @brief
* @return * @return
*/ */
static ServiceLocator& instance(); static ServiceLocator &instance();
ServiceLocator(const ServiceLocator&) = delete; ServiceLocator(const ServiceLocator &) = delete;
ServiceLocator& operator=(const ServiceLocator&) = delete; ServiceLocator &operator=(const ServiceLocator &) = delete;
/** /**
* @brief * @brief
* @tparam T * @tparam T
* @param service * @param service
*/ */
template<typename T> template <typename T> void registerService(SharedPtr<T> service) {
void registerService(SharedPtr<T> service) { static_assert(std::is_base_of_v<IService, T>,
static_assert(std::is_base_of_v<IService, T>, "T must derive from IService");
"T must derive from IService");
std::lock_guard<std::mutex> lock(mutex_);
std::lock_guard<std::mutex> lock(mutex_); auto typeId = std::type_index(typeid(T));
auto typeId = std::type_index(typeid(T)); services_[typeId] = std::static_pointer_cast<IService>(service);
services_[typeId] = std::static_pointer_cast<IService>(service); orderedServices_.push_back(service);
orderedServices_.push_back(service); sortServices();
sortServices(); }
/**
* @brief
* @tparam T
* @param factory
*/
template <typename T> void registerFactory(ServiceFactory<T> factory) {
static_assert(std::is_base_of_v<IService, T>,
"T must derive from IService");
std::lock_guard<std::mutex> lock(mutex_);
auto typeId = std::type_index(typeid(T));
factories_[typeId] = [factory]() -> SharedPtr<IService> {
return std::static_pointer_cast<IService>(factory());
};
// 立即创建服务实例并添加到有序列表
auto service = factories_[typeId]();
services_[typeId] = service;
orderedServices_.push_back(service);
sortServices();
}
/**
* @brief
* @tparam T
* @return nullptr
*/
template <typename T> SharedPtr<T> getService() const {
static_assert(std::is_base_of_v<IService, T>,
"T must derive from IService");
std::lock_guard<std::mutex> lock(mutex_);
auto typeId = std::type_index(typeid(T));
auto it = services_.find(typeId);
if (it != services_.end()) {
return std::static_pointer_cast<T>(it->second);
} }
/** auto factoryIt = factories_.find(typeId);
* @brief if (factoryIt != factories_.end()) {
* @tparam T auto service = factoryIt->second();
* @param factory services_[typeId] = service;
*/ return std::static_pointer_cast<T>(service);
template<typename T>
void registerFactory(ServiceFactory<T> factory) {
static_assert(std::is_base_of_v<IService, T>,
"T must derive from IService");
std::lock_guard<std::mutex> lock(mutex_);
auto typeId = std::type_index(typeid(T));
factories_[typeId] = [factory]() -> SharedPtr<IService> {
return std::static_pointer_cast<IService>(factory());
};
} }
/** return nullptr;
* @brief }
* @tparam T
* @return nullptr /**
*/ * @brief
template<typename T> * @tparam T
SharedPtr<T> getService() const { * @return nullptr
static_assert(std::is_base_of_v<IService, T>, */
"T must derive from IService"); template <typename T> SharedPtr<T> tryGetService() const {
static_assert(std::is_base_of_v<IService, T>,
std::lock_guard<std::mutex> lock(mutex_); "T must derive from IService");
auto typeId = std::type_index(typeid(T));
std::lock_guard<std::mutex> lock(mutex_);
auto it = services_.find(typeId); auto typeId = std::type_index(typeid(T));
if (it != services_.end()) { auto it = services_.find(typeId);
return std::static_pointer_cast<T>(it->second); if (it != services_.end()) {
} return std::static_pointer_cast<T>(it->second);
}
auto factoryIt = factories_.find(typeId); return nullptr;
if (factoryIt != factories_.end()) { }
auto service = factoryIt->second();
services_[typeId] = service; /**
return std::static_pointer_cast<T>(service); * @brief
} * @tparam T
* @return true
return nullptr; */
template <typename T> bool hasService() const {
std::lock_guard<std::mutex> lock(mutex_);
auto typeId = std::type_index(typeid(T));
return services_.find(typeId) != services_.end() ||
factories_.find(typeId) != factories_.end();
}
/**
* @brief
* @tparam T
*/
template <typename T> void unregisterService() {
std::lock_guard<std::mutex> lock(mutex_);
auto typeId = std::type_index(typeid(T));
auto it = services_.find(typeId);
if (it != services_.end()) {
auto service = it->second;
services_.erase(it);
auto orderIt =
std::find(orderedServices_.begin(), orderedServices_.end(), service);
if (orderIt != orderedServices_.end()) {
orderedServices_.erase(orderIt);
}
} }
/** factories_.erase(typeId);
* @brief }
* @tparam T
* @return nullptr
*/
template<typename T>
SharedPtr<T> tryGetService() const {
static_assert(std::is_base_of_v<IService, T>,
"T must derive from IService");
std::lock_guard<std::mutex> lock(mutex_);
auto typeId = std::type_index(typeid(T));
auto it = services_.find(typeId);
if (it != services_.end()) {
return std::static_pointer_cast<T>(it->second);
}
return nullptr;
}
/** /**
* @brief * @brief
* @tparam T * @return true
* @return true */
*/ bool initializeAll();
template<typename T>
bool hasService() const {
std::lock_guard<std::mutex> lock(mutex_);
auto typeId = std::type_index(typeid(T));
return services_.find(typeId) != services_.end() ||
factories_.find(typeId) != factories_.end();
}
/** /**
* @brief * @brief
* @tparam T */
*/ void shutdownAll();
template<typename T>
void unregisterService() {
std::lock_guard<std::mutex> lock(mutex_);
auto typeId = std::type_index(typeid(T));
auto it = services_.find(typeId);
if (it != services_.end()) {
auto service = it->second;
services_.erase(it);
auto orderIt = std::find(orderedServices_.begin(),
orderedServices_.end(), service);
if (orderIt != orderedServices_.end()) {
orderedServices_.erase(orderIt);
}
}
factories_.erase(typeId);
}
/** /**
* @brief * @brief
* @return true * @param deltaTime
*/ */
bool initializeAll(); void updateAll(float deltaTime);
/** /**
* @brief * @brief
*/ */
void shutdownAll(); void pauseAll();
/** /**
* @brief * @brief
* @param deltaTime */
*/ void resumeAll();
void updateAll(float deltaTime);
/** /**
* @brief * @brief
*/ * @return
void pauseAll(); */
std::vector<SharedPtr<IService>> getAllServices() const;
/** /**
* @brief * @brief
*/ */
void resumeAll(); void clear();
/** /**
* @brief * @brief
* @return * @return
*/ */
std::vector<SharedPtr<IService>> getAllServices() const; size_t size() const { return services_.size(); }
/**
* @brief
*/
void clear();
/**
* @brief
* @return
*/
size_t size() const { return services_.size(); }
private: private:
ServiceLocator() = default; ServiceLocator() = default;
~ServiceLocator() = default; ~ServiceLocator() = default;
/** /**
* @brief * @brief
*/ */
void sortServices(); void sortServices();
mutable std::unordered_map<std::type_index, SharedPtr<IService>> services_; mutable std::unordered_map<std::type_index, SharedPtr<IService>> services_;
std::unordered_map<std::type_index, std::function<SharedPtr<IService>()>> factories_; std::unordered_map<std::type_index, std::function<SharedPtr<IService>()>>
std::vector<SharedPtr<IService>> orderedServices_; factories_;
mutable std::mutex mutex_; std::vector<SharedPtr<IService>> orderedServices_;
mutable std::mutex mutex_;
}; };
/** /**
* @brief * @brief
* *
*/ */
template<typename Interface, typename Implementation> template <typename Interface, typename Implementation> class ServiceRegistrar {
class ServiceRegistrar {
public: public:
explicit ServiceRegistrar(ServiceFactory<Interface> factory = nullptr) { explicit ServiceRegistrar(ServiceFactory<Interface> factory = nullptr) {
if (factory) { if (factory) {
ServiceLocator::instance().registerFactory<Interface>(factory); ServiceLocator::instance().registerFactory<Interface>(factory);
} else { } else {
ServiceLocator::instance().registerFactory<Interface>( ServiceLocator::instance().registerFactory<Interface>(
[]() -> SharedPtr<Interface> { []() -> SharedPtr<Interface> {
return makeShared<Implementation>(); return makeShared<Implementation>();
} });
);
}
} }
}
}; };
} /**
* @brief
* 使
*
*/
template <typename Interface, typename Implementation> struct ServiceAutoReg {
/**
* @brief 访
*/
static const bool registered;
#define E2D_REGISTER_SERVICE(Interface, Implementation) \ /**
namespace { \ * @brief
static ::extra2d::ServiceRegistrar<Interface, Implementation> \ * @return true
E2D_CONCAT(service_registrar_, __LINE__); \ */
} static bool doRegister() {
::extra2d::ServiceLocator::instance().registerFactory<Interface>(
[]() -> ::extra2d::SharedPtr<Interface> {
return ::extra2d::makeShared<Implementation>();
});
return true;
}
};
#define E2D_REGISTER_SERVICE_FACTORY(Interface, Factory) \ // 静态成员定义,在此处触发注册
namespace { \ template <typename Interface, typename Implementation>
static ::extra2d::ServiceRegistrar<Interface, Interface> \ const bool ServiceAutoReg<Interface, Implementation>::registered =
E2D_CONCAT(service_factory_registrar_, __LINE__)(Factory); \ ServiceAutoReg<Interface, Implementation>::doRegister();
/**
* @brief
*/
template <typename Interface> struct ServiceAutoRegFactory {
template <typename Factory> struct Impl {
static const bool registered;
static bool doRegister(Factory factory) {
::extra2d::ServiceLocator::instance().registerFactory<Interface>(factory);
return true;
} }
};
};
template <typename Interface>
template <typename Factory>
const bool ServiceAutoRegFactory<Interface>::Impl<Factory>::registered =
ServiceAutoRegFactory<Interface>::Impl<Factory>::doRegister(Factory{});
/**
* @brief
* 使
*
*/
#define E2D_AUTO_REGISTER_SERVICE(Interface, Implementation) \
static inline const bool E2D_CONCAT(_service_reg_, __LINE__) = \
ServiceAutoReg<Interface, Implementation>::registered
/**
* @brief
*/
#define E2D_AUTO_REGISTER_SERVICE_FACTORY(Interface, Factory) \
static inline const bool E2D_CONCAT(_service_factory_reg_, __LINE__) = \
ServiceAutoRegFactory<Interface>::Impl<Factory>::registered
} // namespace extra2d

View File

@ -0,0 +1,137 @@
#pragma once
#include <extra2d/core/service_interface.h>
#include <extra2d/core/service_locator.h>
#include <functional>
#include <vector>
#include <string>
namespace extra2d {
/**
* @brief
*/
struct ServiceRegistration {
std::string name;
ServicePriority priority;
std::function<SharedPtr<IService>()> factory;
bool enabled = true;
};
/**
* @brief
*
*/
class ServiceRegistry {
public:
/**
* @brief
* @return
*/
static ServiceRegistry& instance();
ServiceRegistry(const ServiceRegistry&) = delete;
ServiceRegistry& operator=(const ServiceRegistry&) = delete;
/**
* @brief
* @tparam T
* @tparam Impl
* @param name
* @param priority
*/
template<typename T, typename Impl>
void registerService(const std::string& name, ServicePriority priority) {
static_assert(std::is_base_of_v<IService, T>,
"T must derive from IService");
static_assert(std::is_base_of_v<T, Impl>,
"Impl must derive from T");
ServiceRegistration reg;
reg.name = name;
reg.priority = priority;
reg.factory = []() -> SharedPtr<IService> {
return std::static_pointer_cast<IService>(makeShared<Impl>());
};
registrations_.push_back(reg);
}
/**
* @brief
* @tparam T
* @param name
* @param priority
* @param factory
*/
template<typename T>
void registerServiceWithFactory(
const std::string& name,
ServicePriority priority,
std::function<SharedPtr<T>()> factory) {
static_assert(std::is_base_of_v<IService, T>,
"T must derive from IService");
ServiceRegistration reg;
reg.name = name;
reg.priority = priority;
reg.factory = [factory]() -> SharedPtr<IService> {
return std::static_pointer_cast<IService>(factory());
};
registrations_.push_back(reg);
}
/**
* @brief /
* @param name
* @param enabled
*/
void setServiceEnabled(const std::string& name, bool enabled);
/**
* @brief
* ServiceLocator
*/
void createAllServices();
/**
* @brief
* @return
*/
const std::vector<ServiceRegistration>& getRegistrations() const {
return registrations_;
}
/**
* @brief
*/
void clear() {
registrations_.clear();
}
private:
ServiceRegistry() = default;
~ServiceRegistry() = default;
std::vector<ServiceRegistration> registrations_;
};
/**
* @brief
* 使
*/
template<typename Interface, typename Implementation>
class AutoServiceRegistrar {
public:
AutoServiceRegistrar(const std::string& name, ServicePriority priority) {
ServiceRegistry::instance().registerService<Interface, Implementation>(
name, priority);
}
};
}
#define E2D_REGISTER_SERVICE_AUTO(Interface, Implementation, Name, Priority) \
namespace { \
static ::extra2d::AutoServiceRegistrar<Interface, Implementation> \
E2D_CONCAT(auto_service_registrar_, __LINE__)(Name, Priority); \
}

View File

@ -17,39 +17,39 @@ enum class EventType {
WindowClose, WindowClose,
WindowResize, WindowResize,
WindowFocus, WindowFocus,
WindowBlur, WindowLostFocus,
WindowMove, WindowMoved,
// 键盘事件 // 键盘事件
KeyPress, KeyPressed,
KeyRelease, KeyReleased,
KeyRepeat, KeyRepeat,
// 鼠标事件 // 鼠标事件
MousePress, MouseButtonPressed,
MouseRelease, MouseButtonReleased,
MouseMove, MouseMoved,
MouseScroll, MouseScrolled,
// UI 事件 // UI 事件
UIHoverEnter, UIHoverEnter,
UIHoverExit, UIHoverExit,
UIPress, UIPressed,
UIRelease, UIReleased,
UIClick, UIClicked,
// 游戏手柄事件 // 游戏手柄事件
GamepadConnect, GamepadConnected,
GamepadDisconnect, GamepadDisconnected,
GamepadPress, GamepadButtonPressed,
GamepadRelease, GamepadButtonReleased,
GamepadAxis, GamepadAxisMoved,
// 触摸事件 (移动端) // 触摸事件 (移动端)
TouchBegin, TouchBegan,
TouchMove, TouchMoved,
TouchEnd, TouchEnded,
TouchCancel, TouchCancelled,
// 自定义事件 // 自定义事件
Custom Custom
@ -61,13 +61,13 @@ enum class EventType {
struct KeyEvent { struct KeyEvent {
int keyCode; int keyCode;
int scancode; int scancode;
int mods; int mods; // 修饰键 (Shift, Ctrl, Alt, etc.)
}; };
// ============================================================================ // ============================================================================
// 鼠标事件数据 // 鼠标事件数据
// ============================================================================ // ============================================================================
struct MouseEvent { struct MouseButtonEvent {
int button; int button;
int mods; int mods;
Vec2 position; Vec2 position;
@ -99,7 +99,7 @@ struct WindowMoveEvent {
// ============================================================================ // ============================================================================
// 游戏手柄事件数据 // 游戏手柄事件数据
// ============================================================================ // ============================================================================
struct GamepadEvent { struct GamepadButtonEvent {
int gamepadId; int gamepadId;
int button; int button;
}; };
@ -134,33 +134,37 @@ struct Event {
double timestamp = 0.0; double timestamp = 0.0;
bool handled = false; bool handled = false;
std::variant<std::monostate, KeyEvent, MouseEvent, MouseMoveEvent, // 事件数据联合体
std::variant<std::monostate, KeyEvent, MouseButtonEvent, MouseMoveEvent,
MouseScrollEvent, WindowResizeEvent, WindowMoveEvent, MouseScrollEvent, WindowResizeEvent, WindowMoveEvent,
GamepadEvent, GamepadAxisEvent, TouchEvent, CustomEvent> GamepadButtonEvent, GamepadAxisEvent, TouchEvent, CustomEvent>
data; data;
// 便捷访问方法
bool isWindowEvent() const { bool isWindowEvent() const {
return type == EventType::WindowClose || type == EventType::WindowResize || return type == EventType::WindowClose || type == EventType::WindowResize ||
type == EventType::WindowFocus || type == EventType::WindowBlur || type == EventType::WindowFocus ||
type == EventType::WindowMove; type == EventType::WindowLostFocus || type == EventType::WindowMoved;
} }
bool isKeyboardEvent() const { bool isKeyboardEvent() const {
return type == EventType::KeyPress || type == EventType::KeyRelease || return type == EventType::KeyPressed || type == EventType::KeyReleased ||
type == EventType::KeyRepeat; type == EventType::KeyRepeat;
} }
bool isMouseEvent() const { bool isMouseEvent() const {
return type == EventType::MousePress || type == EventType::MouseRelease || return type == EventType::MouseButtonPressed ||
type == EventType::MouseMove || type == EventType::MouseScroll; type == EventType::MouseButtonReleased ||
type == EventType::MouseMoved || type == EventType::MouseScrolled;
} }
// 静态工厂方法
static Event createWindowResize(int width, int height); static Event createWindowResize(int width, int height);
static Event createWindowClose(); static Event createWindowClose();
static Event createKeyPress(int keyCode, int scancode, int mods); static Event createKeyPress(int keyCode, int scancode, int mods);
static Event createKeyRelease(int keyCode, int scancode, int mods); static Event createKeyRelease(int keyCode, int scancode, int mods);
static Event createMousePress(int button, int mods, const Vec2 &pos); static Event createMouseButtonPress(int button, int mods, const Vec2 &pos);
static Event createMouseRelease(int button, int mods, const Vec2 &pos); static Event createMouseButtonRelease(int button, int mods, const Vec2 &pos);
static Event createMouseMove(const Vec2 &pos, const Vec2 &delta); static Event createMouseMove(const Vec2 &pos, const Vec2 &delta);
static Event createMouseScroll(const Vec2 &offset, const Vec2 &pos); static Event createMouseScroll(const Vec2 &offset, const Vec2 &pos);
}; };

View File

@ -10,10 +10,7 @@
#include <extra2d/core/module.h> #include <extra2d/core/module.h>
#include <extra2d/core/registry.h> #include <extra2d/core/registry.h>
// Config // Config removed - app info now in Application class
#include <extra2d/config/app_config.h>
#include <extra2d/config/platform_config.h>
#include <extra2d/config/platform_detector.h>
// Platform // Platform
#include <extra2d/platform/iinput.h> #include <extra2d/platform/iinput.h>

View File

@ -0,0 +1,79 @@
#pragma once
#include <extra2d/graphics/core/render_backend.h>
#include <extra2d/core/smart_ptr.h>
namespace extra2d {
/**
* @brief
*/
enum class BackendType {
OpenGL, // OpenGL 4.x
Vulkan, // Vulkan 1.x
Metal, // Metal (macOS/iOS)
D3D11, // Direct3D 11
D3D12, // Direct3D 12
OpenGLES, // OpenGL ES (移动平台)
Count
};
/**
* @brief
*/
class BackendFactory {
public:
/**
* @brief
* @return
*/
static BackendFactory& getInstance();
/**
* @brief
* @param type
* @return
*/
UniquePtr<RenderBackend> createBackend(BackendType type);
/**
* @brief
* @return
*/
UniquePtr<RenderBackend> createDefaultBackend();
/**
* @brief
* @param type
* @return truefalse
*/
bool isBackendAvailable(BackendType type) const;
/**
* @brief
* @return
*/
BackendType getRecommendedBackend() const;
/**
* @brief
* @param type
* @return
*/
const char* getBackendName(BackendType type) const;
/**
* @brief
* @param name
* @return OpenGL
*/
BackendType parseBackendType(const char* name) const;
private:
BackendFactory() = default;
~BackendFactory() = default;
BackendFactory(const BackendFactory&) = delete;
BackendFactory& operator=(const BackendFactory&) = delete;
};
} // namespace extra2d

View File

@ -0,0 +1,83 @@
#pragma once
#include <extra2d/graphics/resources/buffer.h>
#include <glad/glad.h>
#include <cstddef>
#include <cstdint>
namespace extra2d {
// ============================================================================
// OpenGL 缓冲区实现
// ============================================================================
class GLBuffer : public Buffer {
public:
/**
* @brief
*/
GLBuffer();
/**
* @brief
*/
~GLBuffer() override;
/**
* @brief
* @param desc
* @return true
*/
bool init(const BufferDesc& desc);
/**
* @brief
*/
void shutdown();
// Buffer 接口实现
void bind() override;
void unbind() override;
void setData(const void* data, size_t size) override;
void updateData(const void* data, size_t offset, size_t size) override;
void* map() override;
void unmap() override;
size_t getSize() const override { return size_; }
BufferType getType() const override { return type_; }
BufferUsage getUsage() const override { return usage_; }
bool isValid() const override { return bufferID_ != 0; }
uintptr_t getNativeHandle() const override { return static_cast<uintptr_t>(bufferID_); }
/**
* @brief OpenGL ID
* @return ID
*/
GLuint getBufferID() const { return bufferID_; }
/**
* @brief OpenGL
* @return
*/
GLenum getTarget() const { return target_; }
private:
GLuint bufferID_ = 0;
GLenum target_ = GL_ARRAY_BUFFER;
size_t size_ = 0;
BufferType type_ = BufferType::Vertex;
BufferUsage usage_ = BufferUsage::Static;
GLenum glUsage_ = GL_STATIC_DRAW;
bool mapped_ = false;
void* mappedPtr_ = nullptr;
/**
* @brief 使 OpenGL
*/
static GLenum convertUsage(BufferUsage usage);
/**
* @brief OpenGL
*/
static GLenum convertType(BufferType type);
};
} // namespace extra2d

View File

@ -0,0 +1,139 @@
#pragma once
#include <glad/glad.h>
#include <cstdint>
#include <string>
namespace extra2d {
// ============================================================================
// OpenGL 版本信息
// ============================================================================
struct GLVersion {
int major = 0;
int minor = 0;
bool es = false; // 是否为 ES 版本
};
// ============================================================================
// OpenGL 上下文管理类
// ============================================================================
class GLContext {
public:
/**
* @brief GLContext
*/
static GLContext& get();
/**
* @brief OpenGL
* @return true
*/
bool init();
/**
* @brief OpenGL
*/
void shutdown();
/**
* @brief
* @return true
*/
bool isValid() const { return initialized_; }
/**
* @brief OpenGL
*/
const GLVersion& getVersion() const { return version_; }
/**
* @brief OpenGL
*/
std::string getVersionString() const;
/**
* @brief GPU
*/
std::string getVendor() const;
/**
* @brief GPU
*/
std::string getRenderer() const;
/**
* @brief
* @param extension
* @return true
*/
bool hasExtension(const std::string& extension) const;
/**
* @brief
*/
int getMaxTextureSize() const;
/**
* @brief
*/
int getMaxTextureUnits() const;
/**
* @brief
*/
int getMaxVertexAttribs() const;
/**
* @brief uniform
*/
int getMaxUniformBufferBindings() const;
/**
* @brief OpenGL ES
*/
bool isGLES() const { return version_.es; }
/**
* @brief VAO
*/
bool hasVAO() const;
/**
* @brief FBO
*/
bool hasFBO() const;
/**
* @brief Shader
*/
bool hasShader() const;
private:
GLContext() = default;
~GLContext() = default;
GLContext(const GLContext&) = delete;
GLContext& operator=(const GLContext&) = delete;
bool initialized_ = false;
GLVersion version_;
// 缓存的限制值
mutable int maxTextureSize_ = -1;
mutable int maxTextureUnits_ = -1;
mutable int maxVertexAttribs_ = -1;
mutable int maxUniformBufferBindings_ = -1;
/**
* @brief OpenGL
*/
void parseVersion();
/**
* @brief OpenGL
*/
bool loadExtensions();
};
} // namespace extra2d

View File

@ -0,0 +1,84 @@
#pragma once
#include <extra2d/graphics/backends/opengl/gl_texture.h>
#include <extra2d/graphics/texture/font.h>
#include <stb/stb_truetype.h>
#include <stb/stb_rect_pack.h>
#include <unordered_map>
#include <vector>
namespace extra2d {
// ============================================================================
// OpenGL 字体图集实现 (使用 STB 库)
// 使用 stb_rect_pack 进行动态矩形打包,支持动态缓存字形
// ============================================================================
class GLFontAtlas : public FontAtlas {
public:
GLFontAtlas(const std::string& filepath, int fontSize, bool useSDF = false);
~GLFontAtlas() override;
// FontAtlas 接口实现
const Glyph* getGlyph(char32_t codepoint) const override;
Texture* getTexture() const override { return texture_.get(); }
int getFontSize() const override { return fontSize_; }
float getAscent() const override { return ascent_; }
float getDescent() const override { return descent_; }
float getLineGap() const override { return lineGap_; }
float getLineHeight() const override { return lineHeight_; }
bool isSDF() const override { return useSDF_; }
Vec2 measureText(const std::string& text) override;
private:
// 字形数据内部结构
struct GlyphData {
float width;
float height;
float bearingX;
float bearingY;
float advance;
float u0, v0, u1, v1;
};
// 图集配置 - 增大尺寸以支持更多字符
static constexpr int ATLAS_WIDTH = 1024;
static constexpr int ATLAS_HEIGHT = 1024;
static constexpr int PADDING = 2; // 字形之间的间距
bool useSDF_;
int fontSize_;
Ptr<GLTexture> texture_;
std::unordered_map<char32_t, GlyphData> glyphs_;
float lineHeight_;
float ascent_;
float descent_;
float lineGap_;
// 字体数据
std::vector<unsigned char> fontData_;
stbtt_fontinfo fontInfo_;
float scale_;
// stb_rect_pack 上下文 - 持久化以支持增量打包
mutable stbrp_context packContext_;
mutable std::vector<stbrp_node> packNodes_;
// 预分配缓冲区,避免每次动态分配
mutable std::vector<uint8_t> glyphBitmapCache_;
mutable std::vector<uint8_t> glyphRgbaCache_;
// 初始化字体
bool initFont(const std::string& filepath);
// 创建空白图集纹理
void createAtlas();
// 缓存字形到图集
void cacheGlyph(char32_t codepoint);
// 更新图集纹理区域
void updateAtlas(int x, int y, int width, int height,
const std::vector<uint8_t>& data);
};
} // namespace extra2d

View File

@ -0,0 +1,105 @@
#pragma once
#include <extra2d/graphics/resources/framebuffer.h>
#include <glad/glad.h>
#include <array>
#include <cstdint>
namespace extra2d {
// ============================================================================
// OpenGL 帧缓冲实现
// ============================================================================
class GLFramebuffer : public Framebuffer {
public:
// 最大颜色附件数
static constexpr int MAX_COLOR_ATTACHMENTS = 8;
/**
* @brief
*/
GLFramebuffer();
/**
* @brief
*/
~GLFramebuffer() override;
/**
* @brief
* @param desc
* @return true
*/
bool init(const FramebufferDesc& desc);
/**
* @brief
*/
void shutdown();
// Framebuffer 接口实现
void bind() override;
void unbind() override;
void attachColorTexture(Ptr<Texture> texture, int attachment = 0) override;
void attachDepthTexture(Ptr<Texture> texture) override;
void attachDepthStencilTexture(Ptr<Texture> texture) override;
bool isComplete() override;
Ptr<Texture> getColorTexture(int attachment = 0) const override;
Ptr<Texture> getDepthTexture() const override;
int getWidth() const override { return width_; }
int getHeight() const override { return height_; }
Size getSize() const override { return Size(static_cast<float>(width_), static_cast<float>(height_)); }
bool isValid() const override { return fboID_ != 0; }
uintptr_t getNativeHandle() const override { return static_cast<uintptr_t>(fboID_); }
void clear(const Color& color, bool clearColor = true,
bool clearDepth = true, bool clearStencil = false) override;
void setViewport(int x, int y, int width, int height) override;
bool readPixels(int x, int y, int width, int height,
std::vector<uint8_t>& outData) override;
/**
* @brief OpenGL FBO ID
* @return FBO ID
*/
GLuint getFboID() const { return fboID_; }
/**
* @brief 便
* @param width
* @param height
* @param colorFormat
* @param depthFormat
* @return true
*/
bool createWithTextures(int width, int height,
PixelFormat colorFormat = PixelFormat::RGBA8,
PixelFormat depthFormat = PixelFormat::Depth24);
private:
GLuint fboID_ = 0;
int width_ = 0;
int height_ = 0;
int numColorAttachments_ = 1;
bool hasDepth_ = false;
bool hasStencil_ = false;
// 附件纹理
std::array<Ptr<Texture>, MAX_COLOR_ATTACHMENTS> colorTextures_;
Ptr<Texture> depthTexture_;
Ptr<Texture> depthStencilTexture_;
// 是否为内置纹理(需要自动清理)
bool hasInternalTextures_ = false;
/**
* @brief
*/
bool checkStatus();
/**
* @brief OpenGL
*/
static GLenum getColorAttachment(int index);
};
} // namespace extra2d

View File

@ -0,0 +1,131 @@
#pragma once
#include <extra2d/graphics/resources/pipeline.h>
#include <glad/glad.h>
#include <cstdint>
namespace extra2d {
// ============================================================================
// OpenGL 管线状态实现
// ============================================================================
class GLPipeline : public Pipeline {
public:
/**
* @brief
*/
GLPipeline();
/**
* @brief
*/
~GLPipeline() override;
/**
* @brief 线
* @param desc 线
* @return true
*/
bool init(const PipelineDesc& desc);
/**
* @brief 线
*/
void shutdown();
// Pipeline 接口实现
void bind() override;
void unbind() override;
void setBlendMode(BlendMode mode) override;
BlendMode getBlendMode() const override { return blendMode_; }
void setDepthTest(bool enabled) override;
void setDepthWrite(bool enabled) override;
void setDepthFunc(DepthFunc func) override;
void setCullMode(CullMode mode) override;
bool isValid() const override { return initialized_; }
uintptr_t getNativeHandle() const override { return 0; } // OpenGL 管线没有单一句柄
/**
* @brief
* @param x X坐标
* @param y Y坐标
* @param width
* @param height
*/
void setViewport(int x, int y, int width, int height);
/**
* @brief
* @param x X坐标
* @param y Y坐标
* @param width
* @param height
*/
void getViewport(int& x, int& y, int& width, int& height) const;
/**
* @brief
*/
void applyAllStates();
private:
bool initialized_ = false;
// 当前状态
BlendMode blendMode_ = BlendMode::Alpha;
bool blendEnabled_ = true;
bool depthTest_ = false;
bool depthWrite_ = false;
DepthFunc depthFunc_ = DepthFunc::Less;
CullMode cullMode_ = CullMode::None;
// 视口
int viewportX_ = 0;
int viewportY_ = 0;
int viewportWidth_ = 0;
int viewportHeight_ = 0;
// 状态缓存(避免冗余 GL 调用)
BlendMode cachedBlendMode_ = BlendMode::None;
bool cachedBlendEnabled_ = false;
bool cachedDepthTest_ = false;
bool cachedDepthWrite_ = false;
DepthFunc cachedDepthFunc_ = DepthFunc::Less;
CullMode cachedCullMode_ = CullMode::None;
int cachedViewportX_ = -1;
int cachedViewportY_ = -1;
int cachedViewportWidth_ = -1;
int cachedViewportHeight_ = -1;
/**
* @brief
*/
void applyBlendState();
/**
* @brief
*/
void applyDepthState();
/**
* @brief
*/
void applyCullState();
/**
* @brief OpenGL
*/
static void getBlendFactors(BlendMode mode, GLenum& srcFactor, GLenum& dstFactor);
/**
* @brief OpenGL
*/
static GLenum convertDepthFunc(DepthFunc func);
/**
* @brief OpenGL
*/
static GLenum convertCullMode(CullMode mode);
};
} // namespace extra2d

View File

@ -1,6 +1,9 @@
#pragma once #pragma once
#include <extra2d/graphics/opengl/gl_sprite_batch.h> #include <extra2d/graphics/backends/opengl/gl_buffer.h>
#include <extra2d/graphics/backends/opengl/gl_framebuffer.h>
#include <extra2d/graphics/backends/opengl/gl_pipeline.h>
#include <extra2d/graphics/backends/opengl/gl_sprite_batch.h>
#include <extra2d/graphics/core/render_backend.h> #include <extra2d/graphics/core/render_backend.h>
#include <extra2d/graphics/shader/shader_interface.h> #include <extra2d/graphics/shader/shader_interface.h>
@ -10,7 +13,10 @@
namespace extra2d { namespace extra2d {
// 前向声明
class IWindow; class IWindow;
class GLContext;
class GLFramebuffer;
// ============================================================================ // ============================================================================
// OpenGL 渲染器实现 // OpenGL 渲染器实现
@ -48,6 +54,7 @@ public:
void drawSprite(const Texture &texture, const Vec2 &position, void drawSprite(const Texture &texture, const Vec2 &position,
const Color &tint) override; const Color &tint) override;
void endSpriteBatch() override; void endSpriteBatch() override;
void flush() override;
void drawLine(const Vec2 &start, const Vec2 &end, const Color &color, void drawLine(const Vec2 &start, const Vec2 &end, const Color &color,
float width) override; float width) override;
@ -76,6 +83,42 @@ public:
Stats getStats() const override { return stats_; } Stats getStats() const override { return stats_; }
void resetStats() override; void resetStats() override;
// GLFramebuffer 相关方法
/**
* @brief
* @param desc
* @return
*/
Ptr<GLFramebuffer> createFramebuffer(const FramebufferDesc& desc);
/**
* @brief
* @param framebuffer nullptr
*/
void bindFramebuffer(GLFramebuffer* framebuffer);
/**
* @brief
*/
void unbindFramebuffer();
/**
* @brief
* @return
*/
Ptr<GLFramebuffer> getDefaultFramebuffer() const;
/**
* @brief
* @param color
* @param clearColor
* @param clearDepth
* @param clearStencil
*/
void clearFramebuffer(const Color& color, bool clearColor = true,
bool clearDepth = true, bool clearStencil = false);
private: private:
// 形状批处理常量 // 形状批处理常量
static constexpr size_t MAX_CIRCLE_SEGMENTS = 128; static constexpr size_t MAX_CIRCLE_SEGMENTS = 128;
@ -92,10 +135,10 @@ private:
GLSpriteBatch spriteBatch_; GLSpriteBatch spriteBatch_;
Ptr<IShader> shapeShader_; Ptr<IShader> shapeShader_;
GLuint shapeVao_; GLuint shapeVao_; // 形状 VAO手动管理用于顶点属性配置
GLuint shapeVbo_; GLBuffer shapeBuffer_; // 形状 VBO使用 GLBuffer 管理)
GLuint lineVao_; // 线条专用 VAO GLuint lineVao_; // 线条 VAO(手动管理,用于顶点属性配置)
GLuint lineVbo_; // 线条专用 VBO GLBuffer lineBuffer_; // 线条 VBO使用 GLBuffer 管理)
glm::mat4 viewProjection_; glm::mat4 viewProjection_;
std::vector<glm::mat4> transformStack_; std::vector<glm::mat4> transformStack_;
@ -112,15 +155,23 @@ private:
size_t lineVertexCount_ = 0; size_t lineVertexCount_ = 0;
float currentLineWidth_ = 1.0f; float currentLineWidth_ = 1.0f;
// OpenGL 状态缓存 // OpenGL 管线状态管理
BlendMode cachedBlendMode_ = BlendMode::None; GLPipeline pipeline_;
bool blendEnabled_ = false;
int cachedViewportX_ = 0; // 自动批处理状态
int cachedViewportY_ = 0; bool batchActive_ = false; // 批处理是否激活
int cachedViewportWidth_ = 0; bool autoBatchEnabled_ = true; // 是否启用自动批处理
int cachedViewportHeight_ = 0; const Texture* currentBatchTexture_ = nullptr; // 当前批处理的纹理
std::vector<SpriteData> pendingSprites_; // 待提交的精灵
static constexpr size_t MAX_BATCH_SPRITES = 1000; // 最大批处理精灵数
// 帧缓冲管理
mutable Ptr<GLFramebuffer> defaultFramebuffer_; // 默认帧缓冲(延迟创建)
GLFramebuffer* currentFramebuffer_ = nullptr; // 当前绑定的帧缓冲
void initShapeRendering(); void initShapeRendering();
void ensureBatchActive(); // 确保批处理已激活
void submitPendingSprites(); // 提交待处理的精灵
void flushShapeBatch(); void flushShapeBatch();
void flushLineBatch(); void flushLineBatch();
void addShapeVertex(float x, float y, const Color &color); void addShapeVertex(float x, float y, const Color &color);

View File

@ -0,0 +1,68 @@
#pragma once
#include <extra2d/graphics/backends/opengl/gl_buffer.h>
#include <extra2d/graphics/backends/opengl/gl_texture.h>
#include <extra2d/graphics/batch/sprite_batch.h>
#include <extra2d/graphics/shader/shader_interface.h>
#include <array>
#include <glad/glad.h>
#include <vector>
namespace extra2d {
// ============================================================================
// OpenGL 精灵批处理渲染器
// 使用 batch/sprite_batch 作为后端无关的批处理层
// ============================================================================
class GLSpriteBatch {
public:
GLSpriteBatch();
~GLSpriteBatch();
// 初始化/关闭
bool init();
void shutdown();
// 批处理生命周期
void begin(const glm::mat4& viewProjection);
void end();
// 绘制单个精灵
void draw(const Texture& texture, const SpriteData& data);
// 批量绘制(用于文本渲染优化)
void drawBatch(const Texture& texture, const std::vector<SpriteData>& sprites);
// 获取绘制调用次数
uint32_t getDrawCallCount() const { return drawCallCount_; }
private:
// OpenGL 对象
GLuint vao_;
GLBuffer vbo_; // 顶点缓冲区(动态)
GLBuffer ebo_; // 索引缓冲区(静态)
// 后端无关的批处理层
SpriteBatch batch_;
// 批次管理
struct Batch {
const GLTexture* texture;
size_t startVertex;
size_t vertexCount;
};
std::vector<Batch> batches_;
const GLTexture* currentTexture_;
// 着色器和矩阵
Ptr<IShader> shader_;
uint32_t drawCallCount_;
glm::mat4 viewProjection_;
// 内部方法
void flush();
void submitBatch();
};
} // namespace extra2d

View File

@ -0,0 +1,78 @@
#pragma once
#include <extra2d/graphics/core/render_backend.h>
namespace extra2d {
/**
* @brief Vulkan
*
* Vulkan后端应该包含的内容
* Vulkan上下文线
*/
class VulkanRenderer : public RenderBackend {
public:
VulkanRenderer();
~VulkanRenderer() override;
// RenderBackend 接口实现
bool init(IWindow* window) override;
void shutdown() override;
void beginFrame(const Color &clearColor) override;
void endFrame() override;
void setViewport(int x, int y, int width, int height) override;
void setVSync(bool enabled) override;
void setBlendMode(BlendMode mode) override;
void setViewProjection(const glm::mat4 &matrix) override;
void pushTransform(const glm::mat4 &transform) override;
void popTransform() override;
glm::mat4 getCurrentTransform() const override;
Ptr<Texture> createTexture(int width, int height, const uint8_t *pixels,
int channels) override;
Ptr<Texture> loadTexture(const std::string &filepath) override;
void beginSpriteBatch() override;
void drawSprite(const Texture &texture, const Rect &destRect,
const Rect &srcRect, const Color &tint, float rotation,
const Vec2 &anchor) override;
void drawSprite(const Texture &texture, const Vec2 &position,
const Color &tint) override;
void endSpriteBatch() override;
void drawLine(const Vec2 &start, const Vec2 &end, const Color &color,
float width) override;
void drawRect(const Rect &rect, const Color &color, float width) override;
void fillRect(const Rect &rect, const Color &color) override;
void drawCircle(const Vec2 &center, float radius, const Color &color,
int segments, float width) override;
void fillCircle(const Vec2 &center, float radius, const Color &color,
int segments) override;
void drawTriangle(const Vec2 &p1, const Vec2 &p2, const Vec2 &p3,
const Color &color, float width) override;
void fillTriangle(const Vec2 &p1, const Vec2 &p2, const Vec2 &p3,
const Color &color) override;
void drawPolygon(const std::vector<Vec2> &points, const Color &color,
float width) override;
void fillPolygon(const std::vector<Vec2> &points,
const Color &color) override;
Ptr<FontAtlas> createFontAtlas(const std::string &filepath, int fontSize,
bool useSDF = false) override;
void drawText(const FontAtlas &font, const std::string &text,
const Vec2 &position, const Color &color) override;
void drawText(const FontAtlas &font, const std::string &text, float x,
float y, const Color &color) override;
Stats getStats() const override { return stats_; }
void resetStats() override;
private:
Stats stats_;
bool initialized_ = false;
};
} // namespace extra2d

View File

@ -0,0 +1,157 @@
#pragma once
#include <extra2d/core/color.h>
#include <extra2d/core/math_types.h>
#include <extra2d/core/types.h>
#include <glm/mat4x4.hpp>
#include <cstdint>
#include <vector>
namespace extra2d {
// ============================================================================
// 形状顶点结构
// ============================================================================
struct ShapeVertex {
float x, y; // 位置
float r, g, b, a; // 颜色
ShapeVertex() = default;
ShapeVertex(float px, float py, const Color& c)
: x(px), y(py), r(c.r), g(c.g), b(c.b), a(c.a) {}
};
// ============================================================================
// 形状批处理抽象接口 - 后端无关
// ============================================================================
class ShapeBatch {
public:
virtual ~ShapeBatch() = default;
/**
* @brief
* @return true
*/
virtual bool init() = 0;
/**
* @brief
*/
virtual void shutdown() = 0;
/**
* @brief
* @param viewProjection
*/
virtual void begin(const glm::mat4& viewProjection) = 0;
/**
* @brief
*/
virtual void end() = 0;
/**
* @brief 线
* @param start
* @param end
* @param color
* @param width 线
*/
virtual void drawLine(const Vec2& start, const Vec2& end,
const Color& color, float width = 1.0f) = 0;
/**
* @brief
* @param rect
* @param color
* @param width
*/
virtual void drawRect(const Rect& rect, const Color& color,
float width = 1.0f) = 0;
/**
* @brief
* @param rect
* @param color
*/
virtual void fillRect(const Rect& rect, const Color& color) = 0;
/**
* @brief
* @param center
* @param radius
* @param color
* @param segments
* @param width
*/
virtual void drawCircle(const Vec2& center, float radius,
const Color& color, int segments = 32,
float width = 1.0f) = 0;
/**
* @brief
* @param center
* @param radius
* @param color
* @param segments
*/
virtual void fillCircle(const Vec2& center, float radius,
const Color& color, int segments = 32) = 0;
/**
* @brief
* @param p1 1
* @param p2 2
* @param p3 3
* @param color
* @param width
*/
virtual void drawTriangle(const Vec2& p1, const Vec2& p2, const Vec2& p3,
const Color& color, float width = 1.0f) = 0;
/**
* @brief
* @param p1 1
* @param p2 2
* @param p3 3
* @param color
*/
virtual void fillTriangle(const Vec2& p1, const Vec2& p2, const Vec2& p3,
const Color& color) = 0;
/**
* @brief
* @param points
* @param color
* @param width
*/
virtual void drawPolygon(const std::vector<Vec2>& points,
const Color& color, float width = 1.0f) = 0;
/**
* @brief
* @param points
* @param color
*/
virtual void fillPolygon(const std::vector<Vec2>& points,
const Color& color) = 0;
/**
* @brief
* @return
*/
virtual uint32_t getDrawCallCount() const = 0;
/**
* @brief
*/
virtual void resetDrawCallCount() = 0;
/**
* @brief
* @return true
*/
virtual bool isValid() const = 0;
};
} // namespace extra2d

View File

@ -0,0 +1,121 @@
#pragma once
#include <extra2d/core/types.h>
#include <extra2d/core/math_types.h>
#include <extra2d/core/color.h>
#include <extra2d/graphics/texture/texture.h>
#include <glm/mat4x4.hpp>
#include <array>
#include <vector>
#include <cstdint>
namespace extra2d {
// ============================================================================
// 三角函数查表 - 避免每帧计算 sin/cos
// ============================================================================
class TrigLookup {
public:
TrigLookup();
// 通过角度(0-360)获取 sin/cos
float sin(int angle) const;
float cos(int angle) const;
// 通过弧度获取 sin/cos
float sinRad(float rad) const;
float cosRad(float rad) const;
private:
static constexpr int TABLE_SIZE = 360 * 4; // 0.25度精度
std::array<float, TABLE_SIZE> sinTable_;
std::array<float, TABLE_SIZE> cosTable_;
};
// ============================================================================
// 精灵批次数据 - 后端无关
// ============================================================================
struct SpriteVertex {
Vec2 position;
Vec2 texCoord;
Color color;
};
struct SpriteData {
Vec2 position;
Vec2 size;
float rotation;
Vec2 pivot;
Color color;
const Texture* texture;
Rect uvRect;
};
// ============================================================================
// 通用精灵批处理 - 后端无关
// 负责:顶点生成、批次管理、三角函数查表
// ============================================================================
class SpriteBatch {
public:
static constexpr size_t MAX_SPRITES = 10000;
static constexpr size_t VERTICES_PER_SPRITE = 4;
static constexpr size_t INDICES_PER_SPRITE = 6;
static constexpr size_t MAX_VERTICES = MAX_SPRITES * VERTICES_PER_SPRITE;
static constexpr size_t MAX_INDICES = MAX_SPRITES * INDICES_PER_SPRITE;
SpriteBatch();
~SpriteBatch() = default;
// 开始批次
void begin(const glm::mat4& viewProjection);
// 结束批次 - 返回需要绘制的批次列表
void end();
// 绘制单个精灵
void draw(const SpriteData& sprite);
// 批量绘制 - 一次性处理多个精灵
void drawBatch(const std::vector<SpriteData>& sprites);
// 立即绘制 - 不缓存,直接提交
void drawImmediate(const SpriteData& sprite);
// 获取当前批次数据
const std::vector<SpriteVertex>& getVertices() const { return vertices_; }
const std::vector<uint16_t>& getIndices() const { return indices_; }
size_t getSpriteCount() const { return spriteCount_; }
// 检查是否需要刷新
bool needsFlush() const { return spriteCount_ >= MAX_SPRITES; }
// 清空批次
void clear();
private:
// 三角函数查表
TrigLookup trigLookup_;
// 顶点数据 - 使用固定大小数组避免动态分配
std::vector<SpriteVertex> vertices_;
std::vector<uint16_t> indices_;
size_t spriteCount_;
// 变换矩阵
glm::mat4 viewProjection_;
glm::mat4 cachedVP_;
bool vpDirty_;
// 生成索引
void generateIndices();
// 生成顶点
void generateVertices(const SpriteData& sprite, size_t vertexOffset);
// 刷新批次
void flush();
};
} // namespace extra2d

View File

@ -3,6 +3,7 @@
#include <extra2d/core/color.h> #include <extra2d/core/color.h>
#include <extra2d/core/math_types.h> #include <extra2d/core/math_types.h>
#include <extra2d/core/types.h> #include <extra2d/core/types.h>
#include <extra2d/graphics/resources/pipeline.h>
#include <glm/mat4x4.hpp> #include <glm/mat4x4.hpp>
namespace extra2d { namespace extra2d {
@ -24,15 +25,7 @@ enum class BackendType {
// D3D12 // D3D12
}; };
// ============================================================================ // BlendMode 定义在 pipeline.h 中
// 混合模式
// ============================================================================
enum class BlendMode {
None, // 不混合
Alpha, // 标准 Alpha 混合
Additive, // 加法混合
Multiply // 乘法混合
};
// ============================================================================ // ============================================================================
// 渲染后端抽象接口 // 渲染后端抽象接口
@ -78,6 +71,10 @@ public:
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// 精灵批渲染 // 精灵批渲染
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
/**
* @brief
* @note drawSprite/drawText
*/
virtual void beginSpriteBatch() = 0; virtual void beginSpriteBatch() = 0;
virtual void drawSprite(const Texture &texture, const Rect &destRect, virtual void drawSprite(const Texture &texture, const Rect &destRect,
const Rect &srcRect, const Color &tint, const Rect &srcRect, const Color &tint,
@ -86,6 +83,12 @@ public:
const Color &tint) = 0; const Color &tint) = 0;
virtual void endSpriteBatch() = 0; virtual void endSpriteBatch() = 0;
/**
* @brief
* @note
*/
virtual void flush() = 0;
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// 形状渲染 // 形状渲染
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------

View File

@ -3,10 +3,30 @@
#include <extra2d/core/module.h> #include <extra2d/core/module.h>
#include <extra2d/graphics/core/render_backend.h> #include <extra2d/graphics/core/render_backend.h>
#include <extra2d/platform/window_module.h> #include <extra2d/platform/window_module.h>
#include <functional>
#include <typeindex> #include <typeindex>
namespace extra2d { namespace extra2d {
/**
* @brief
*/
struct RenderCfg {
BackendType backend;
int targetFPS;
bool vsync;
int multisamples;
int priority;
RenderCfg()
: backend(BackendType::OpenGL)
, targetFPS(60)
, vsync(true)
, multisamples(0)
, priority(10)
{}
};
/** /**
* @brief * @brief
* *
@ -14,41 +34,22 @@ namespace extra2d {
class RenderModule : public Module { class RenderModule : public Module {
public: public:
/** /**
* @brief * @brief Lambda
* @param configFn
*/ */
struct Cfg { explicit RenderModule(std::function<void(RenderCfg&)> configFn);
BackendType backend;
int targetFPS;
bool vsync;
int multisamples;
int priority;
Cfg()
: backend(BackendType::OpenGL)
, targetFPS(60)
, vsync(true)
, multisamples(0)
, priority(10)
{}
};
/**
* @brief
* @param cfg
*/
explicit RenderModule(const Cfg& cfg = Cfg{});
/** /**
* @brief * @brief
*/ */
~RenderModule() override; ~RenderModule() override;
bool init() override; bool init() override;
void shutdown() override; void shutdown() override;
bool ok() const override { return initialized_; } bool ok() const override { return initialized_; }
const char* name() const override { return "render"; } const char* name() const override { return "render"; }
int priority() const override { return cfg_.priority; } int priority() const override { return cfg_.priority; }
/** /**
* @brief * @brief
* @return * @return
@ -56,7 +57,7 @@ public:
std::vector<std::type_index> deps() const override { std::vector<std::type_index> deps() const override {
return {std::type_index(typeid(WindowModule))}; return {std::type_index(typeid(WindowModule))};
} }
/** /**
* @brief * @brief
* @return * @return
@ -64,7 +65,7 @@ public:
RenderBackend* renderer() const { return renderer_.get(); } RenderBackend* renderer() const { return renderer_.get(); }
private: private:
Cfg cfg_; RenderCfg cfg_;
UniquePtr<RenderBackend> renderer_; UniquePtr<RenderBackend> renderer_;
bool initialized_ = false; bool initialized_ = false;
}; };

View File

@ -2,7 +2,7 @@
#include <extra2d/core/color.h> #include <extra2d/core/color.h>
#include <extra2d/core/types.h> #include <extra2d/core/types.h>
#include <extra2d/graphics/opengl/gl_texture.h> #include <extra2d/graphics/backends/opengl/gl_texture.h>
#include <extra2d/graphics/texture/texture.h> #include <extra2d/graphics/texture/texture.h>
#include <mutex> #include <mutex>

View File

@ -1,67 +0,0 @@
#pragma once
#include <extra2d/core/color.h>
#include <extra2d/core/math_types.h>
#include <extra2d/core/types.h>
#include <extra2d/graphics/texture/font.h>
#include <extra2d/graphics/opengl/gl_texture.h>
#include <extra2d/graphics/texture/texture.h>
#include <memory>
#include <stb/stb_rect_pack.h>
#include <stb/stb_truetype.h>
#include <unordered_map>
#include <vector>
namespace extra2d {
// ============================================================================
// OpenGL 字体图集实现 - 使用 stb_rect_pack 进行矩形打包
// ============================================================================
class GLFontAtlas : public FontAtlas {
public:
GLFontAtlas(const std::string &filepath, int fontSize, bool useSDF = false);
~GLFontAtlas();
// FontAtlas 接口实现
const Glyph *getGlyph(char32_t codepoint) const override;
Texture *getTexture() const override { return texture_.get(); }
int getFontSize() const override { return fontSize_; }
float getAscent() const override { return ascent_; }
float getDescent() const override { return descent_; }
float getLineGap() const override { return lineGap_; }
float getLineHeight() const override { return ascent_ - descent_ + lineGap_; }
Vec2 measureText(const std::string &text) override;
bool isSDF() const override { return useSDF_; }
private:
// 图集配置 - 增大尺寸以支持更多字符
static constexpr int ATLAS_WIDTH = 1024;
static constexpr int ATLAS_HEIGHT = 1024;
static constexpr int PADDING = 2; // 字形之间的间距
int fontSize_;
bool useSDF_;
mutable std::unique_ptr<GLTexture> texture_;
mutable std::unordered_map<char32_t, Glyph> glyphs_;
// stb_rect_pack 上下文
mutable stbrp_context packContext_;
mutable std::vector<stbrp_node> packNodes_;
mutable int currentY_;
std::vector<unsigned char> fontData_;
stbtt_fontinfo fontInfo_;
float scale_;
float ascent_;
float descent_;
float lineGap_;
// 预分配字形位图缓冲区,避免每次动态分配
mutable std::vector<uint8_t> glyphBitmapCache_;
mutable std::vector<uint8_t> glyphRgbaCache_;
void createAtlas();
void cacheGlyph(char32_t codepoint) const;
};
} // namespace extra2d

View File

@ -1,98 +0,0 @@
#pragma once
#include <array>
#include <extra2d/core/color.h>
#include <extra2d/core/math_types.h>
#include <extra2d/core/types.h>
#include <extra2d/graphics/opengl/gl_shader.h>
#include <extra2d/graphics/texture/texture.h>
#include <glm/mat4x4.hpp>
#include <vector>
#include <glad/glad.h>
namespace extra2d {
// ============================================================================
// OpenGL 精灵批渲染器 - 优化版本
// ============================================================================
class GLSpriteBatch {
public:
static constexpr size_t MAX_SPRITES = 10000;
static constexpr size_t VERTICES_PER_SPRITE = 4;
static constexpr size_t INDICES_PER_SPRITE = 6;
static constexpr size_t MAX_VERTICES = MAX_SPRITES * VERTICES_PER_SPRITE;
static constexpr size_t MAX_INDICES = MAX_SPRITES * INDICES_PER_SPRITE;
struct Vertex {
glm::vec2 position;
glm::vec2 texCoord;
glm::vec4 color;
};
struct SpriteData {
glm::vec2 position;
glm::vec2 size;
glm::vec2 texCoordMin;
glm::vec2 texCoordMax;
glm::vec4 color;
float rotation;
glm::vec2 anchor;
bool isSDF = false;
};
GLSpriteBatch();
~GLSpriteBatch();
bool init();
void shutdown();
void begin(const glm::mat4 &viewProjection);
void draw(const Texture &texture, const SpriteData &data);
void end();
// 批量绘制接口 - 用于自动批处理
void drawBatch(const Texture &texture,
const std::vector<SpriteData> &sprites);
// 立即绘制(不缓存)
void drawImmediate(const Texture &texture, const SpriteData &data);
// 统计
uint32_t getDrawCallCount() const { return drawCallCount_; }
uint32_t getSpriteCount() const { return spriteCount_; }
uint32_t getBatchCount() const { return batchCount_; }
// 检查是否需要刷新
bool needsFlush(const Texture &texture, bool isSDF) const;
private:
GLuint vao_;
GLuint vbo_;
GLuint ibo_;
GLShader shader_;
// 使用固定大小数组减少内存分配
std::array<Vertex, MAX_VERTICES> vertexBuffer_;
size_t vertexCount_;
const Texture *currentTexture_;
bool currentIsSDF_;
glm::mat4 viewProjection_;
// 缓存上一帧的 viewProjection避免重复设置
glm::mat4 cachedViewProjection_;
bool viewProjectionDirty_ = true;
uint32_t drawCallCount_;
uint32_t spriteCount_;
uint32_t batchCount_;
void flush();
void setupShader();
// 添加顶点到缓冲区
void addVertices(const SpriteData &data);
};
} // namespace extra2d

View File

@ -0,0 +1,111 @@
#pragma once
#include <extra2d/core/types.h>
#include <cstddef>
#include <cstdint>
namespace extra2d {
// ============================================================================
// 缓冲区类型枚举
// ============================================================================
enum class BufferType {
Vertex, // 顶点缓冲
Index, // 索引缓冲
Uniform // 统一缓冲
};
// ============================================================================
// 缓冲区使用模式枚举
// ============================================================================
enum class BufferUsage {
Static, // 静态数据,很少更新
Dynamic, // 动态数据,频繁更新
Stream // 流式数据,每帧更新
};
// ============================================================================
// 缓冲区描述结构
// ============================================================================
struct BufferDesc {
BufferType type = BufferType::Vertex;
BufferUsage usage = BufferUsage::Static;
size_t size = 0; // 缓冲区大小(字节)
const void* initialData = nullptr; // 初始数据
};
// ============================================================================
// 缓冲区抽象接口 - 渲染后端无关
// ============================================================================
class Buffer {
public:
virtual ~Buffer() = default;
/**
* @brief
*/
virtual void bind() = 0;
/**
* @brief
*/
virtual void unbind() = 0;
/**
* @brief
* @param data
* @param size
*/
virtual void setData(const void* data, size_t size) = 0;
/**
* @brief
* @param data
* @param offset
* @param size
*/
virtual void updateData(const void* data, size_t offset, size_t size) = 0;
/**
* @brief
* @return nullptr
*/
virtual void* map() = 0;
/**
* @brief
*/
virtual void unmap() = 0;
/**
* @brief
* @return
*/
virtual size_t getSize() const = 0;
/**
* @brief
* @return
*/
virtual BufferType getType() const = 0;
/**
* @brief 使
* @return 使
*/
virtual BufferUsage getUsage() const = 0;
/**
* @brief
* @return true
*/
virtual bool isValid() const = 0;
/**
* @brief
* @return
*/
virtual uintptr_t getNativeHandle() const = 0;
};
} // namespace extra2d

View File

@ -0,0 +1,131 @@
#pragma once
#include <extra2d/core/color.h>
#include <extra2d/core/math_types.h>
#include <extra2d/core/types.h>
#include <extra2d/graphics/texture/texture.h>
#include <string>
#include <unordered_map>
namespace extra2d {
// ============================================================================
// 字形信息结构
// ============================================================================
struct Glyph {
float width = 0; // 字形宽度
float height = 0; // 字形高度
float bearingX = 0; // 水平偏移
float bearingY = 0; // 垂直偏移(从基线到字形顶部)
float advance = 0; // 水平步进
float u0 = 0, v0 = 0; // 纹理坐标左下角
float u1 = 0, v1 = 0; // 纹理坐标右上角
};
// ============================================================================
// 字体图集描述结构
// ============================================================================
struct FontAtlasDesc {
std::string filepath; // 字体文件路径
int fontSize = 16; // 字体大小
bool useSDF = false; // 是否使用SDF渲染
int atlasSize = 512; // 图集大小
int padding = 2; // 字形间距
};
// ============================================================================
// 字体图集抽象接口 - 渲染后端无关
// ============================================================================
class FontAtlas {
public:
virtual ~FontAtlas() = default;
/**
* @brief
* @param desc
* @return true
*/
virtual bool init(const FontAtlasDesc& desc) = 0;
/**
* @brief
*/
virtual void shutdown() = 0;
/**
* @brief
* @param codepoint Unicode
* @return nullptr
*/
virtual const Glyph* getGlyph(char32_t codepoint) const = 0;
/**
* @brief
* @return
*/
virtual Ptr<Texture> getTexture() const = 0;
/**
* @brief
* @return
*/
virtual int getFontSize() const = 0;
/**
* @brief
* @return
*/
virtual float getLineHeight() const = 0;
/**
* @brief 线
* @return
*/
virtual float getAscent() const = 0;
/**
* @brief 线
* @return
*/
virtual float getDescent() const = 0;
/**
* @brief
* @param text
* @return
*/
virtual float measureText(const std::string& text) const = 0;
/**
* @brief
* @param text
* @return
*/
virtual Size measureTextSize(const std::string& text) const = 0;
/**
* @brief 使SDF渲染
* @return 使SDF返回 true
*/
virtual bool isSDF() const = 0;
/**
* @brief
* @return true
*/
virtual bool isValid() const = 0;
/**
* @brief
* @param text
* @return
*/
virtual int preloadGlyphs(const std::string& text) = 0;
/**
* @brief
*/
virtual void clearCache() = 0;
};
} // namespace extra2d

View File

@ -0,0 +1,140 @@
#pragma once
#include <extra2d/core/color.h>
#include <extra2d/core/types.h>
#include <extra2d/graphics/texture/texture.h>
#include <cstdint>
namespace extra2d {
// ============================================================================
// 帧缓冲描述结构
// ============================================================================
struct FramebufferDesc {
int width = 0; // 帧缓冲宽度
int height = 0; // 帧缓冲高度
int colorAttachments = 1; // 颜色附件数量
bool hasDepth = false; // 是否有深度附件
bool hasStencil = false; // 是否有模板附件
bool multisample = false; // 是否多重采样
int samples = 4; // 采样数(多重采样时有效)
};
// ============================================================================
// 帧缓冲抽象接口 - 渲染后端无关
// ============================================================================
class Framebuffer {
public:
virtual ~Framebuffer() = default;
/**
* @brief
*/
virtual void bind() = 0;
/**
* @brief
*/
virtual void unbind() = 0;
/**
* @brief
* @param texture
* @param attachment 0-7
*/
virtual void attachColorTexture(Ptr<Texture> texture, int attachment = 0) = 0;
/**
* @brief
* @param texture
*/
virtual void attachDepthTexture(Ptr<Texture> texture) = 0;
/**
* @brief
* @param texture
*/
virtual void attachDepthStencilTexture(Ptr<Texture> texture) = 0;
/**
* @brief
* @return true
*/
virtual bool isComplete() = 0;
/**
* @brief
* @param attachment
* @return
*/
virtual Ptr<Texture> getColorTexture(int attachment = 0) const = 0;
/**
* @brief
* @return
*/
virtual Ptr<Texture> getDepthTexture() const = 0;
/**
* @brief
* @return
*/
virtual int getWidth() const = 0;
/**
* @brief
* @return
*/
virtual int getHeight() const = 0;
/**
* @brief
* @return
*/
virtual Size getSize() const = 0;
/**
* @brief
* @return true
*/
virtual bool isValid() const = 0;
/**
* @brief
* @return
*/
virtual uintptr_t getNativeHandle() const = 0;
/**
* @brief
* @param color
* @param clearColor
* @param clearDepth
* @param clearStencil
*/
virtual void clear(const Color& color, bool clearColor = true,
bool clearDepth = true, bool clearStencil = false) = 0;
/**
* @brief
* @param x X坐标
* @param y Y坐标
* @param width
* @param height
*/
virtual void setViewport(int x, int y, int width, int height) = 0;
/**
* @brief
* @param x X坐标
* @param y Y坐标
* @param width
* @param height
* @param outData
* @return true
*/
virtual bool readPixels(int x, int y, int width, int height,
std::vector<uint8_t>& outData) = 0;
};
} // namespace extra2d

View File

@ -0,0 +1,162 @@
#pragma once
#include <extra2d/core/color.h>
#include <extra2d/core/types.h>
#include <cstdint>
namespace extra2d {
// ============================================================================
// 混合模式枚举
// ============================================================================
enum class BlendMode {
None, // 不混合
Alpha, // 标准 Alpha 混合
Additive, // 加法混合
Multiply // 乘法混合
};
// ============================================================================
// 深度测试函数枚举
// ============================================================================
enum class DepthFunc {
Never, // 永不通过
Less, // 小于
Equal, // 等于
LessEqual, // 小于等于
Greater, // 大于
NotEqual, // 不等于
GreaterEqual,// 大于等于
Always // 总是通过
};
// ============================================================================
// 裁剪模式枚举
// ============================================================================
enum class CullMode {
None, // 不裁剪
Front, // 裁剪正面
Back, // 裁剪背面
Both // 裁剪双面
};
// ============================================================================
// 顶点属性格式枚举
// ============================================================================
enum class VertexFormat {
Float1, // 1个float
Float2, // 2个float
Float3, // 3个float
Float4, // 4个float
Byte4, // 4个byte
UByte4, // 4个ubyte
Short2, // 2个short
Short4 // 4个short
};
// ============================================================================
// 顶点属性描述
// ============================================================================
struct VertexAttribute {
uint32_t location = 0; // 属性位置
VertexFormat format = VertexFormat::Float3; // 数据格式
uint32_t offset = 0; // 在顶点结构中的偏移
uint32_t stride = 0; // 顶点结构大小
bool normalized = false; // 是否归一化
VertexAttribute() = default;
VertexAttribute(uint32_t loc, VertexFormat fmt, uint32_t off, uint32_t str, bool norm = false)
: location(loc), format(fmt), offset(off), stride(str), normalized(norm) {}
};
// ============================================================================
// 管线描述结构
// ============================================================================
struct PipelineDesc {
// 混合状态
BlendMode blendMode = BlendMode::Alpha;
bool blendEnabled = true;
// 深度状态
bool depthTest = false;
bool depthWrite = false;
DepthFunc depthFunc = DepthFunc::Less;
// 裁剪状态
CullMode cullMode = CullMode::None;
// 顶点布局
std::vector<VertexAttribute> vertexAttributes;
// 着色器(由后端特定实现设置)
void* vertexShader = nullptr;
void* fragmentShader = nullptr;
};
// ============================================================================
// 渲染管线抽象接口 - 渲染后端无关
// ============================================================================
class Pipeline {
public:
virtual ~Pipeline() = default;
/**
* @brief 线
*/
virtual void bind() = 0;
/**
* @brief 线
*/
virtual void unbind() = 0;
/**
* @brief
* @param mode
*/
virtual void setBlendMode(BlendMode mode) = 0;
/**
* @brief
* @return
*/
virtual BlendMode getBlendMode() const = 0;
/**
* @brief
* @param enabled
*/
virtual void setDepthTest(bool enabled) = 0;
/**
* @brief
* @param enabled
*/
virtual void setDepthWrite(bool enabled) = 0;
/**
* @brief
* @param func
*/
virtual void setDepthFunc(DepthFunc func) = 0;
/**
* @brief
* @param mode
*/
virtual void setCullMode(CullMode mode) = 0;
/**
* @brief 线
* @return true
*/
virtual bool isValid() const = 0;
/**
* @brief
* @return
*/
virtual uintptr_t getNativeHandle() const = 0;
};
} // namespace extra2d

View File

@ -0,0 +1,134 @@
#pragma once
#include <extra2d/core/color.h>
#include <extra2d/core/types.h>
#include <glm/mat4x4.hpp>
#include <glm/vec2.hpp>
#include <glm/vec3.hpp>
#include <glm/vec4.hpp>
#include <string>
#include <vector>
namespace extra2d {
// ============================================================================
// 着色器类型枚举
// ============================================================================
enum class ShaderType {
Vertex, // 顶点着色器
Fragment, // 片段着色器
Geometry, // 几何着色器
Compute // 计算着色器
};
// ============================================================================
// 着色器描述结构
// ============================================================================
struct ShaderDesc {
std::string name; // 着色器名称
std::string vertexSource; // 顶点着色器源码
std::string fragmentSource; // 片段着色器源码
std::string geometrySource; // 几何着色器源码(可选)
std::vector<uint8_t> binaryData; // 预编译二进制数据(可选)
};
// ============================================================================
// 着色器抽象接口 - 渲染后端无关
// ============================================================================
class Shader {
public:
virtual ~Shader() = default;
/**
* @brief
*/
virtual void bind() = 0;
/**
* @brief
*/
virtual void unbind() = 0;
/**
* @brief uniform
* @param name uniform
* @param value
*/
virtual void setBool(const std::string& name, bool value) = 0;
/**
* @brief uniform
* @param name uniform
* @param value
*/
virtual void setInt(const std::string& name, int value) = 0;
/**
* @brief uniform
* @param name uniform
* @param value
*/
virtual void setFloat(const std::string& name, float value) = 0;
/**
* @brief uniform
* @param name uniform
* @param value
*/
virtual void setVec2(const std::string& name, const glm::vec2& value) = 0;
/**
* @brief uniform
* @param name uniform
* @param value
*/
virtual void setVec3(const std::string& name, const glm::vec3& value) = 0;
/**
* @brief uniform
* @param name uniform
* @param value
*/
virtual void setVec4(const std::string& name, const glm::vec4& value) = 0;
/**
* @brief 4x4 uniform
* @param name uniform
* @param value 4x4
*/
virtual void setMat4(const std::string& name, const glm::mat4& value) = 0;
/**
* @brief uniform
* @param name uniform
* @param color
*/
virtual void setColor(const std::string& name, const Color& color) = 0;
/**
* @brief
* @param name uniform
* @param slot
*/
virtual void setTexture(const std::string& name, int slot) = 0;
/**
* @brief
* @return
*/
virtual const std::string& getName() const = 0;
/**
* @brief
* @return true
*/
virtual bool isValid() const = 0;
/**
* @brief
* @return
*/
virtual uintptr_t getNativeHandle() const = 0;
};
} // namespace extra2d

View File

@ -4,7 +4,6 @@
#include <functional> #include <functional>
#include <string> #include <string>
#include <unordered_map> #include <unordered_map>
#include <unordered_set>
#include <vector> #include <vector>
#ifdef _WIN32 #ifdef _WIN32
@ -17,118 +16,113 @@ namespace extra2d {
// 文件变化事件 // 文件变化事件
// ============================================================================ // ============================================================================
struct FileChangeEvent { struct FileChangeEvent {
std::string filepath; std::string filepath;
enum class Type { enum class Type { Created, Modified, Deleted, Renamed } type;
Created,
Modified, uint64_t timestamp = 0;
Deleted,
Renamed
} type;
uint64_t timestamp = 0;
}; };
// ============================================================================ // ============================================================================
// 文件变化回调 // 文件变化回调
// ============================================================================ // ============================================================================
using FileChangeCallback = std::function<void(const FileChangeEvent&)>; using FileChangeCallback = std::function<void(const FileChangeEvent &)>;
// ============================================================================ // ============================================================================
// Shader热重载管理器 // Shader热重载管理器
// ============================================================================ // ============================================================================
class ShaderHotReloader { class ShaderHotReloader {
public: public:
/** /**
* @brief * @brief
* @return * @return
*/ */
static ShaderHotReloader& getInstance(); static ShaderHotReloader &getInstance();
/** /**
* @brief * @brief
* @return truefalse * @return truefalse
*/ */
bool init(); bool init();
/** /**
* @brief * @brief
*/ */
void shutdown(); void shutdown();
/** /**
* @brief Shader文件监视 * @brief Shader文件监视
* @param shaderName Shader名称 * @param shaderName Shader名称
* @param filePaths * @param filePaths
* @param callback * @param callback
*/ */
void watch(const std::string& shaderName, void watch(const std::string &shaderName,
const std::vector<std::string>& filePaths, const std::vector<std::string> &filePaths,
FileChangeCallback callback); FileChangeCallback callback);
/** /**
* @brief * @brief
* @param shaderName Shader名称 * @param shaderName Shader名称
*/ */
void unwatch(const std::string& shaderName); void unwatch(const std::string &shaderName);
/** /**
* @brief * @brief
*/ */
void update(); void update();
/** /**
* @brief / * @brief /
* @param enabled * @param enabled
*/ */
void setEnabled(bool enabled); void setEnabled(bool enabled);
/** /**
* @brief * @brief
* @return truefalse * @return truefalse
*/ */
bool isEnabled() const { return enabled_; } bool isEnabled() const { return enabled_; }
/** /**
* @brief * @brief
* @return truefalse * @return truefalse
*/ */
bool isInitialized() const { return initialized_; } bool isInitialized() const { return initialized_; }
private: private:
ShaderHotReloader() = default; ShaderHotReloader() = default;
~ShaderHotReloader() = default; ~ShaderHotReloader() = default;
ShaderHotReloader(const ShaderHotReloader&) = delete; ShaderHotReloader(const ShaderHotReloader &) = delete;
ShaderHotReloader& operator=(const ShaderHotReloader&) = delete; ShaderHotReloader &operator=(const ShaderHotReloader &) = delete;
bool enabled_ = false; bool enabled_ = false;
bool initialized_ = false; bool initialized_ = false;
struct WatchInfo { struct WatchInfo {
std::vector<std::string> filePaths; std::vector<std::string> filePaths;
FileChangeCallback callback; FileChangeCallback callback;
std::unordered_map<std::string, uint64_t> modifiedTimes; std::unordered_map<std::string, uint64_t> modifiedTimes;
}; };
std::unordered_map<std::string, WatchInfo> watchMap_; std::unordered_map<std::string, WatchInfo> watchMap_;
#ifdef _WIN32 #ifdef _WIN32
HANDLE watchHandle_ = nullptr; HANDLE watchHandle_ = nullptr;
std::vector<uint8_t> buffer_; std::vector<uint8_t> buffer_;
std::string watchDir_; std::string watchDir_;
bool watching_ = false; bool watching_ = false;
#endif #endif
/** /**
* @brief * @brief
*/ */
void pollChanges(); void pollChanges();
/** /**
* @brief * @brief
* @param filepath * @param filepath
* @return * @return
*/ */
static uint64_t getFileModifiedTime(const std::string& filepath); static uint64_t getFileModifiedTime(const std::string &filepath);
}; };
// 便捷宏 // 便捷宏

View File

@ -32,34 +32,136 @@ struct ShaderMetadata {
}; };
// ============================================================================ // ============================================================================
// ShaderLoader - Shader文件加载 // ShaderLoader接口 - 支持多种文件格式加载
// ============================================================================ // ============================================================================
class ShaderLoader { class IShaderLoader {
public:
virtual ~IShaderLoader() = default;
/**
* @brief Shader (.vert + .frag)
* @param name Shader名称
* @param vertPath
* @param fragPath
* @return
*/
virtual ShaderLoadResult loadFromSeparateFiles(
const std::string& name,
const std::string& vertPath,
const std::string& fragPath) = 0;
/**
* @brief Shader (.shader)
* @param path Shader文件路径
* @return
*/
virtual ShaderLoadResult loadFromCombinedFile(const std::string& path) = 0;
/**
* @brief Shader
* @param vertSource
* @param fragSource
* @return
*/
virtual ShaderLoadResult loadFromSource(
const std::string& vertSource,
const std::string& fragSource) = 0;
/**
* @brief Shader源码中的#include指令
* @param source
* @param baseDir
* @param outDependencies
* @return
*/
virtual std::string processIncludes(
const std::string& source,
const std::string& baseDir,
std::vector<std::string>& outDependencies) = 0;
/**
* @brief
* @param source
* @param defines
* @return
*/
virtual std::string applyDefines(
const std::string& source,
const std::vector<std::string>& defines) = 0;
/**
* @brief Shader元数据
* @param path Shader文件路径
* @return
*/
virtual ShaderMetadata getMetadata(const std::string& path) = 0;
};
// ============================================================================
// 默认ShaderLoader实现
// ============================================================================
class ShaderLoader : public IShaderLoader {
public: public:
ShaderLoader(); ShaderLoader();
~ShaderLoader() = default; ~ShaderLoader() override = default;
/**
* @brief Shader (.vert + .frag)
* @param name Shader名称
* @param vertPath
* @param fragPath
* @return
*/
ShaderLoadResult loadFromSeparateFiles( ShaderLoadResult loadFromSeparateFiles(
const std::string& name, const std::string& name,
const std::string& vertPath, const std::string& vertPath,
const std::string& fragPath); const std::string& fragPath) override;
ShaderLoadResult loadFromCombinedFile(const std::string& path); /**
* @brief Shader (.shader)
* @param path Shader文件路径
* @return
*/
ShaderLoadResult loadFromCombinedFile(const std::string& path) override;
/**
* @brief Shader
* @param vertSource
* @param fragSource
* @return
*/
ShaderLoadResult loadFromSource( ShaderLoadResult loadFromSource(
const std::string& vertSource, const std::string& vertSource,
const std::string& fragSource); const std::string& fragSource) override;
/**
* @brief Shader源码中的#include指令
* @param source
* @param baseDir
* @param outDependencies
* @return
*/
std::string processIncludes( std::string processIncludes(
const std::string& source, const std::string& source,
const std::string& baseDir, const std::string& baseDir,
std::vector<std::string>& outDependencies); std::vector<std::string>& outDependencies) override;
/**
* @brief
* @param source
* @param defines
* @return
*/
std::string applyDefines( std::string applyDefines(
const std::string& source, const std::string& source,
const std::vector<std::string>& defines); const std::vector<std::string>& defines) override;
ShaderMetadata getMetadata(const std::string& path); /**
* @brief Shader元数据
* @param path Shader文件路径
* @return
*/
ShaderMetadata getMetadata(const std::string& path) override;
/** /**
* @brief include搜索路径 * @brief include搜索路径

View File

@ -1,6 +1,5 @@
#pragma once #pragma once
#include <extra2d/config/platform_detector.h>
#include <extra2d/graphics/shader/shader_cache.h> #include <extra2d/graphics/shader/shader_cache.h>
#include <extra2d/graphics/shader/shader_hot_reloader.h> #include <extra2d/graphics/shader/shader_hot_reloader.h>
#include <extra2d/graphics/shader/shader_interface.h> #include <extra2d/graphics/shader/shader_interface.h>
@ -178,6 +177,14 @@ public:
*/ */
bool loadBuiltinShaders(); bool loadBuiltinShaders();
/**
* @brief JSON元数据文件加载Shader
* @param jsonPath JSON元数据文件路径
* @param name Shader名称
* @return Shader实例
*/
Ptr<IShader> loadFromMetadata(const std::string& jsonPath, const std::string& name);
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// 工具方法 // 工具方法
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------

View File

@ -4,7 +4,7 @@
#include <extra2d/core/math_types.h> #include <extra2d/core/math_types.h>
#include <extra2d/core/types.h> #include <extra2d/core/types.h>
#include <extra2d/graphics/texture/texture.h> #include <extra2d/graphics/texture/texture.h>
#include <extra2d/graphics/opengl/gl_texture.h> #include <extra2d/graphics/backends/opengl/gl_texture.h>
#include <string> #include <string>
#include <vector> #include <vector>
#include <unordered_map> #include <unordered_map>

View File

@ -5,7 +5,6 @@
#include <extra2d/graphics/texture/texture.h> #include <extra2d/graphics/texture/texture.h>
#include <extra2d/utils/logger.h> #include <extra2d/utils/logger.h>
#include <algorithm>
#include <atomic> #include <atomic>
#include <chrono> #include <chrono>
#include <cstdint> #include <cstdint>
@ -13,7 +12,6 @@
#include <mutex> #include <mutex>
#include <string> #include <string>
#include <unordered_map> #include <unordered_map>
#include <vector>
namespace extra2d { namespace extra2d {
@ -25,161 +23,153 @@ class RenderBackend;
// 纹理加载选项 // 纹理加载选项
// ============================================================================ // ============================================================================
struct TextureLoadOptions { struct TextureLoadOptions {
bool generateMipmaps = true; // 是否生成 mipmaps bool generateMipmaps = true; // 是否生成 mipmaps
bool sRGB = true; // 是否使用 sRGB 色彩空间 bool sRGB = true; // 是否使用 sRGB 色彩空间
bool premultiplyAlpha = false; // 是否预乘 Alpha bool premultiplyAlpha = false; // 是否预乘 Alpha
PixelFormat preferredFormat = PixelFormat::RGBA8; // 首选像素格式 PixelFormat preferredFormat = PixelFormat::RGBA8; // 首选像素格式
}; };
// ============================================================================ // ============================================================================
// 纹理键 - 用于唯一标识纹理缓存条目 // 纹理键 - 用于唯一标识纹理缓存条目
// ============================================================================ // ============================================================================
struct TextureKey { struct TextureKey {
std::string path; // 纹理文件路径 std::string path; // 纹理文件路径
Rect region; // 纹理区域(用于纹理图集) Rect region; // 纹理区域(用于纹理图集)
/** /**
* @brief * @brief
*/ */
TextureKey() = default; TextureKey() = default;
/** /**
* @brief * @brief
* @param p * @param p
*/ */
explicit TextureKey(const std::string& p) : path(p), region(Rect::Zero()) {} explicit TextureKey(const std::string &p) : path(p), region(Rect::Zero()) {}
/** /**
* @brief + * @brief +
* @param p * @param p
* @param r * @param r
*/ */
TextureKey(const std::string& p, const Rect& r) : path(p), region(r) {} TextureKey(const std::string &p, const Rect &r) : path(p), region(r) {}
/** /**
* @brief * @brief
* @param other TextureKey * @param other TextureKey
* @return * @return
*/ */
bool operator==(const TextureKey& other) const { bool operator==(const TextureKey &other) const {
return path == other.path && region == other.region; return path == other.path && region == other.region;
} }
/** /**
* @brief * @brief
* @param other TextureKey * @param other TextureKey
* @return * @return
*/ */
bool operator!=(const TextureKey& other) const { bool operator!=(const TextureKey &other) const { return !(*this == other); }
return !(*this == other);
}
}; };
// ============================================================================ // ============================================================================
// TextureKey 哈希函子 // TextureKey 哈希函子
// ============================================================================ // ============================================================================
struct TextureKeyHash { struct TextureKeyHash {
/** /**
* @brief TextureKey * @brief TextureKey
* @param key * @param key
* @return * @return
*/ */
size_t operator()(const TextureKey& key) const { size_t operator()(const TextureKey &key) const {
size_t h1 = std::hash<std::string>{}(key.path); size_t h1 = std::hash<std::string>{}(key.path);
size_t h2 = std::hash<float>{}(key.region.origin.x); size_t h2 = std::hash<float>{}(key.region.origin.x);
size_t h3 = std::hash<float>{}(key.region.origin.y); size_t h3 = std::hash<float>{}(key.region.origin.y);
size_t h4 = std::hash<float>{}(key.region.size.width); size_t h4 = std::hash<float>{}(key.region.size.width);
size_t h5 = std::hash<float>{}(key.region.size.height); size_t h5 = std::hash<float>{}(key.region.size.height);
// 组合哈希值 // 组合哈希值
size_t result = h1; size_t result = h1;
result ^= h2 + 0x9e3779b9 + (result << 6) + (result >> 2); result ^= h2 + 0x9e3779b9 + (result << 6) + (result >> 2);
result ^= h3 + 0x9e3779b9 + (result << 6) + (result >> 2); result ^= h3 + 0x9e3779b9 + (result << 6) + (result >> 2);
result ^= h4 + 0x9e3779b9 + (result << 6) + (result >> 2); result ^= h4 + 0x9e3779b9 + (result << 6) + (result >> 2);
result ^= h5 + 0x9e3779b9 + (result << 6) + (result >> 2); result ^= h5 + 0x9e3779b9 + (result << 6) + (result >> 2);
return result; return result;
} }
}; };
// ============================================================================ // ============================================================================
// 纹理池条目 // 纹理池条目
// ============================================================================ // ============================================================================
struct TexturePoolEntry { struct TexturePoolEntry {
Ptr<Texture> texture; // 纹理对象 Ptr<Texture> texture; // 纹理对象
mutable std::atomic<uint32_t> refCount; // 引用计数 mutable std::atomic<uint32_t> refCount; // 引用计数
TextureKey key; // 纹理键 TextureKey key; // 纹理键
size_t memorySize; // 内存占用(字节) size_t memorySize; // 内存占用(字节)
mutable uint64_t lastAccessTime; // 最后访问时间戳 mutable uint64_t lastAccessTime; // 最后访问时间戳
/** /**
* @brief * @brief
*/ */
TexturePoolEntry() TexturePoolEntry()
: texture(nullptr) : texture(nullptr), refCount(0), key(), memorySize(0), lastAccessTime(0) {
, refCount(0) }
, key()
, memorySize(0)
, lastAccessTime(0) {}
/** /**
* @brief * @brief
* @param tex * @param tex
* @param k * @param k
* @param memSize * @param memSize
*/ */
TexturePoolEntry(Ptr<Texture> tex, const TextureKey& k, size_t memSize) TexturePoolEntry(Ptr<Texture> tex, const TextureKey &k, size_t memSize)
: texture(tex) : texture(tex), refCount(1), key(k), memorySize(memSize),
, refCount(1) lastAccessTime(getCurrentTime()) {}
, key(k)
, memorySize(memSize)
, lastAccessTime(getCurrentTime()) {}
/** /**
* @brief * @brief
* @param other * @param other
*/ */
TexturePoolEntry(TexturePoolEntry&& other) noexcept TexturePoolEntry(TexturePoolEntry &&other) noexcept
: texture(std::move(other.texture)) : texture(std::move(other.texture)),
, refCount(other.refCount.load(std::memory_order_relaxed)) refCount(other.refCount.load(std::memory_order_relaxed)),
, key(std::move(other.key)) key(std::move(other.key)), memorySize(other.memorySize),
, memorySize(other.memorySize) lastAccessTime(other.lastAccessTime) {}
, lastAccessTime(other.lastAccessTime) {}
/** /**
* @brief * @brief
* @param other * @param other
* @return * @return
*/ */
TexturePoolEntry& operator=(TexturePoolEntry&& other) noexcept { TexturePoolEntry &operator=(TexturePoolEntry &&other) noexcept {
if (this != &other) { if (this != &other) {
texture = std::move(other.texture); texture = std::move(other.texture);
refCount.store(other.refCount.load(std::memory_order_relaxed), std::memory_order_relaxed); refCount.store(other.refCount.load(std::memory_order_relaxed),
key = std::move(other.key); std::memory_order_relaxed);
memorySize = other.memorySize; key = std::move(other.key);
lastAccessTime = other.lastAccessTime; memorySize = other.memorySize;
} lastAccessTime = other.lastAccessTime;
return *this;
} }
return *this;
}
// 禁止拷贝 // 禁止拷贝
TexturePoolEntry(const TexturePoolEntry&) = delete; TexturePoolEntry(const TexturePoolEntry &) = delete;
TexturePoolEntry& operator=(const TexturePoolEntry&) = delete; TexturePoolEntry &operator=(const TexturePoolEntry &) = delete;
/** /**
* @brief 访 * @brief 访
*/ */
void touch() const { lastAccessTime = getCurrentTime(); } void touch() const { lastAccessTime = getCurrentTime(); }
/** /**
* @brief * @brief
* @return * @return
*/ */
static uint64_t getCurrentTime() { static uint64_t getCurrentTime() {
auto now = std::chrono::steady_clock::now(); auto now = std::chrono::steady_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::milliseconds>( auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(
now.time_since_epoch()); now.time_since_epoch());
return static_cast<uint64_t>(duration.count()); return static_cast<uint64_t>(duration.count());
} }
}; };
// ============================================================================ // ============================================================================
@ -187,144 +177,143 @@ struct TexturePoolEntry {
// ============================================================================ // ============================================================================
class TextureRef { class TextureRef {
public: public:
/** /**
* @brief * @brief
*/ */
TextureRef() : texture_(nullptr), entry_(nullptr), mutex_(nullptr) {} TextureRef() : texture_(nullptr), entry_(nullptr), mutex_(nullptr) {}
/** /**
* @brief * @brief
* @param texture * @param texture
* @param entry * @param entry
* @param mutex * @param mutex
*/ */
TextureRef(Ptr<Texture> texture, TexturePoolEntry* entry, std::mutex* mutex) TextureRef(Ptr<Texture> texture, TexturePoolEntry *entry, std::mutex *mutex)
: texture_(texture), entry_(entry), mutex_(mutex) {} : texture_(texture), entry_(entry), mutex_(mutex) {}
/** /**
* @brief * @brief
* @param texture * @param texture
* @return * @return
*/ */
static TextureRef fromTexture(Ptr<Texture> texture) { static TextureRef fromTexture(Ptr<Texture> texture) {
return TextureRef(texture, nullptr, nullptr); return TextureRef(texture, nullptr, nullptr);
}
/**
* @brief
* @param other TextureRef
*/
TextureRef(const TextureRef &other)
: texture_(other.texture_), entry_(other.entry_), mutex_(other.mutex_) {
if (entry_ && entry_->refCount.load(std::memory_order_relaxed) > 0) {
entry_->refCount.fetch_add(1, std::memory_order_relaxed);
} }
}
/** /**
* @brief * @brief
* @param other TextureRef * @param other TextureRef
*/ */
TextureRef(const TextureRef& other) TextureRef(TextureRef &&other) noexcept
: texture_(other.texture_), entry_(other.entry_), mutex_(other.mutex_) { : texture_(std::move(other.texture_)), entry_(other.entry_),
if (entry_ && entry_->refCount.load(std::memory_order_relaxed) > 0) { mutex_(other.mutex_) {
entry_->refCount.fetch_add(1, std::memory_order_relaxed); other.entry_ = nullptr;
} other.mutex_ = nullptr;
}
/**
* @brief
*/
~TextureRef() { reset(); }
/**
* @brief
* @param other TextureRef
* @return
*/
TextureRef &operator=(const TextureRef &other) {
if (this != &other) {
reset();
texture_ = other.texture_;
entry_ = other.entry_;
mutex_ = other.mutex_;
if (entry_ && entry_->refCount.load(std::memory_order_relaxed) > 0) {
entry_->refCount.fetch_add(1, std::memory_order_relaxed);
}
} }
return *this;
}
/** /**
* @brief * @brief
* @param other TextureRef * @param other TextureRef
*/ * @return
TextureRef(TextureRef&& other) noexcept */
: texture_(std::move(other.texture_)) TextureRef &operator=(TextureRef &&other) noexcept {
, entry_(other.entry_) if (this != &other) {
, mutex_(other.mutex_) { reset();
other.entry_ = nullptr; texture_ = std::move(other.texture_);
other.mutex_ = nullptr; entry_ = other.entry_;
mutex_ = other.mutex_;
other.entry_ = nullptr;
other.mutex_ = nullptr;
} }
return *this;
}
/** /**
* @brief * @brief
*/ */
~TextureRef() { reset(); } void reset() {
if (entry_ && mutex_) {
/** std::lock_guard<std::mutex> lock(*mutex_);
* @brief if (entry_->refCount.load(std::memory_order_relaxed) > 0) {
* @param other TextureRef entry_->refCount.fetch_sub(1, std::memory_order_relaxed);
* @return }
*/
TextureRef& operator=(const TextureRef& other) {
if (this != &other) {
reset();
texture_ = other.texture_;
entry_ = other.entry_;
mutex_ = other.mutex_;
if (entry_ && entry_->refCount.load(std::memory_order_relaxed) > 0) {
entry_->refCount.fetch_add(1, std::memory_order_relaxed);
}
}
return *this;
} }
texture_.reset();
entry_ = nullptr;
mutex_ = nullptr;
}
/** /**
* @brief * @brief
* @param other TextureRef * @return
* @return */
*/ Texture *get() const { return texture_.get(); }
TextureRef& operator=(TextureRef&& other) noexcept {
if (this != &other) {
reset();
texture_ = std::move(other.texture_);
entry_ = other.entry_;
mutex_ = other.mutex_;
other.entry_ = nullptr;
other.mutex_ = nullptr;
}
return *this;
}
/** /**
* @brief * @brief
*/ * @return
void reset() { */
if (entry_ && mutex_) { Ptr<Texture> getPtr() const { return texture_; }
std::lock_guard<std::mutex> lock(*mutex_);
if (entry_->refCount.load(std::memory_order_relaxed) > 0) {
entry_->refCount.fetch_sub(1, std::memory_order_relaxed);
}
}
texture_.reset();
entry_ = nullptr;
mutex_ = nullptr;
}
/** /**
* @brief * @brief
* @return * @return
*/ */
Texture* get() const { return texture_.get(); } bool valid() const { return texture_ != nullptr; }
/** /**
* @brief * @brief
* @return */
*/ explicit operator bool() const { return valid(); }
Ptr<Texture> getPtr() const { return texture_; }
/** /**
* @brief * @brief
* @return */
*/ Texture *operator->() const { return texture_.get(); }
bool valid() const { return texture_ != nullptr; }
/** /**
* @brief * @brief
*/ */
explicit operator bool() const { return valid(); } Texture &operator*() const { return *texture_; }
/**
* @brief
*/
Texture* operator->() const { return texture_.get(); }
/**
* @brief
*/
Texture& operator*() const { return *texture_; }
private: private:
Ptr<Texture> texture_; Ptr<Texture> texture_;
TexturePoolEntry* entry_; TexturePoolEntry *entry_;
std::mutex* mutex_; std::mutex *mutex_;
}; };
// ============================================================================ // ============================================================================
@ -338,239 +327,235 @@ private:
// ============================================================================ // ============================================================================
class TexturePool { class TexturePool {
public: public:
// ======================================================================== // ========================================================================
// 统计信息 // 统计信息
// ======================================================================== // ========================================================================
struct Stats { struct Stats {
size_t textureCount = 0; // 纹理数量 size_t textureCount = 0; // 纹理数量
size_t memoryUsage = 0; // 内存使用量(字节) size_t memoryUsage = 0; // 内存使用量(字节)
size_t maxMemoryUsage = 0; // 最大内存使用量 size_t maxMemoryUsage = 0; // 最大内存使用量
size_t cacheHits = 0; // 缓存命中次数 size_t cacheHits = 0; // 缓存命中次数
size_t cacheMisses = 0; // 缓存未命中次数 size_t cacheMisses = 0; // 缓存未命中次数
size_t evictionCount = 0; // 淘汰次数 size_t evictionCount = 0; // 淘汰次数
}; };
// ======================================================================== // ========================================================================
// 构造和析构 // 构造和析构
// ======================================================================== // ========================================================================
/** /**
* @brief * @brief
*/ */
TexturePool(); TexturePool();
/** /**
* @brief * @brief
* @param scene * @param scene
* @param maxMemoryUsage 使0 * @param maxMemoryUsage 使0
*/ */
explicit TexturePool(Scene* scene, size_t maxMemoryUsage = 0); explicit TexturePool(Scene *scene, size_t maxMemoryUsage = 0);
/** /**
* @brief * @brief
*/ */
~TexturePool(); ~TexturePool();
// 禁止拷贝 // 禁止拷贝
TexturePool(const TexturePool&) = delete; TexturePool(const TexturePool &) = delete;
TexturePool& operator=(const TexturePool&) = delete; TexturePool &operator=(const TexturePool &) = delete;
/** /**
* @brief * @brief
* @param scene * @param scene
* @param maxMemoryUsage 使0 * @param maxMemoryUsage 使0
*/ */
void init(Scene* scene, size_t maxMemoryUsage = 0); void init(Scene *scene, size_t maxMemoryUsage = 0);
/** // ========================================================================
* @brief // 纹理加载
* @param backend // ========================================================================
*/
void setRenderBackend(RenderBackend* backend) { backend_ = backend; }
// ======================================================================== /**
// 纹理加载 * @brief
// ======================================================================== * @param path
* @param options
* @return
*/
TextureRef load(const std::string &path,
const TextureLoadOptions &options = TextureLoadOptions());
/** /**
* @brief * @brief
* @param path * @param path
* @param options * @param region
* @return * @param options
*/ * @return
TextureRef load(const std::string& path, */
const TextureLoadOptions& options = TextureLoadOptions()); TextureRef load(const std::string &path, const Rect &region,
const TextureLoadOptions &options = TextureLoadOptions());
/** /**
* @brief * @brief
* @param path * @param data
* @param region * @param width
* @param options * @param height
* @return * @param channels
*/ * @param key
TextureRef load(const std::string& path, const Rect& region, * @return
const TextureLoadOptions& options = TextureLoadOptions()); */
TextureRef loadFromMemory(const uint8_t *data, int width, int height,
int channels, const std::string &key);
/** /**
* @brief * @brief
* @param data * @param path
* @param width * @param options
* @param height * @return
* @param channels */
* @param key TextureRef
* @return getOrLoad(const std::string &path,
*/ const TextureLoadOptions &options = TextureLoadOptions());
TextureRef loadFromMemory(const uint8_t* data, int width, int height,
int channels, const std::string& key);
/** /**
* @brief * @brief
* @param path * @param path
* @param options * @param region
* @return * @param options
*/ * @return
TextureRef getOrLoad(const std::string& path, */
const TextureLoadOptions& options = TextureLoadOptions()); TextureRef
getOrLoad(const std::string &path, const Rect &region,
const TextureLoadOptions &options = TextureLoadOptions());
/** // ========================================================================
* @brief // 引用计数管理
* @param path // ========================================================================
* @param region
* @param options
* @return
*/
TextureRef getOrLoad(const std::string& path, const Rect& region,
const TextureLoadOptions& options = TextureLoadOptions());
// ======================================================================== /**
// 引用计数管理 * @brief
// ======================================================================== * @param key
* @return
*/
bool addRef(const TextureKey &key);
/** /**
* @brief * @brief
* @param key * @param key
* @return * @return
*/ */
bool addRef(const TextureKey& key); uint32_t release(const TextureKey &key);
/** /**
* @brief * @brief
* @param key * @param key
* @return * @return
*/ */
uint32_t release(const TextureKey& key); uint32_t getRefCount(const TextureKey &key) const;
/** // ========================================================================
* @brief // 缓存管理
* @param key // ========================================================================
* @return
*/
uint32_t getRefCount(const TextureKey& key) const;
// ======================================================================== /**
// 缓存管理 * @brief
// ======================================================================== * @param key
* @return
*/
bool isCached(const TextureKey &key) const;
/** /**
* @brief * @brief
* @param key * @param key
* @return * @return
*/ */
bool isCached(const TextureKey& key) const; bool removeFromCache(const TextureKey &key);
/** /**
* @brief * @brief 0
* @param key * @return
* @return */
*/ size_t collectGarbage();
bool removeFromCache(const TextureKey& key);
/** /**
* @brief 0 * @brief
* @return */
*/ void clear();
size_t collectGarbage();
/** // ========================================================================
* @brief // 内存管理
*/ // ========================================================================
void clear();
// ======================================================================== /**
// 内存管理 * @brief 使
// ======================================================================== * @return 使
*/
size_t getMemoryUsage() const;
/** /**
* @brief 使 * @brief 使
* @return 使 * @param maxMemory 使0
*/ */
size_t getMemoryUsage() const; void setMaxMemoryUsage(size_t maxMemory);
/** /**
* @brief 使 * @brief 使
* @param maxMemory 使0 * @return 使
*/ */
void setMaxMemoryUsage(size_t maxMemory); size_t getMaxMemoryUsage() const { return maxMemoryUsage_; }
/** /**
* @brief 使 * @brief LRU
* @return 使 * @param targetMemory 使
*/ * @return
size_t getMaxMemoryUsage() const { return maxMemoryUsage_; } */
size_t evictLRU(size_t targetMemory = 0);
/** // ========================================================================
* @brief LRU // 统计信息
* @param targetMemory 使 // ========================================================================
* @return
*/
size_t evictLRU(size_t targetMemory = 0);
// ======================================================================== /**
// 统计信息 * @brief
// ======================================================================== * @return
*/
Stats getStats() const;
/** /**
* @brief * @brief
* @return */
*/ void resetStats();
Stats getStats() const;
/**
* @brief
*/
void resetStats();
private: private:
/** /**
* @brief * @brief
* @param texture * @param texture
* @return * @return
*/ */
static size_t calculateTextureMemory(const Texture* texture); static size_t calculateTextureMemory(const Texture *texture);
/** /**
* @brief * @brief
* @return * @return
*/ */
bool needsEviction() const; bool needsEviction() const;
/** /**
* @brief * @brief
*/ */
void tryAutoEvict(); void tryAutoEvict();
Scene* scene_; // 场景指针 Scene *scene_; // 场景指针
RenderBackend* backend_ = nullptr; // 渲染后端 mutable std::mutex mutex_; // 互斥锁
mutable std::mutex mutex_; // 互斥锁 std::unordered_map<TextureKey, TexturePoolEntry, TextureKeyHash>
std::unordered_map<TextureKey, TexturePoolEntry, TextureKeyHash> cache_; // 纹理缓存 cache_; // 纹理缓存
size_t maxMemoryUsage_; // 最大内存使用量 size_t maxMemoryUsage_; // 最大内存使用量
size_t currentMemoryUsage_; // 当前内存使用量 size_t currentMemoryUsage_; // 当前内存使用量
// 统计信息 // 统计信息
mutable std::atomic<size_t> cacheHits_; mutable std::atomic<size_t> cacheHits_;
mutable std::atomic<size_t> cacheMisses_; mutable std::atomic<size_t> cacheMisses_;
mutable std::atomic<size_t> evictionCount_; mutable std::atomic<size_t> evictionCount_;
}; };
} // namespace extra2d } // namespace extra2d

View File

@ -1,71 +0,0 @@
#pragma once
#include <extra2d/core/module.h>
#include <extra2d/config/app_config.h>
#include <extra2d/config/config_manager.h>
#include <string>
namespace extra2d {
/**
* @brief
*
*/
class ConfigModule : public Module {
public:
/**
* @brief
*/
ConfigModule();
/**
* @brief
*/
~ConfigModule() override;
/**
* @brief
* @return
*/
const char* getName() const override { return "Config"; }
/**
* @brief
* @return
*/
int getPriority() const override { return 0; }
/**
* @brief
*/
void setupModule() override;
/**
* @brief
*/
void destroyModule() override;
/**
* @brief
* @param config
*/
void setAppConfig(const AppConfig& config) { appConfig_ = config; }
/**
* @brief
* @param path
*/
void setConfigPath(const std::string& path) { configPath_ = path; }
/**
* @brief
* @return
*/
ConfigManager& getManager() { return ConfigManager::instance(); }
private:
AppConfig appConfig_;
std::string configPath_;
};
} // namespace extra2d

View File

@ -1,83 +0,0 @@
#pragma once
#include <extra2d/core/module.h>
#include <extra2d/input/input_config.h>
#include <extra2d/platform/iinput.h>
#include <extra2d/platform/iwindow.h>
namespace extra2d {
/**
* @brief
*
*/
class InputModule : public Module {
public:
/**
* @brief
*/
InputModule();
/**
* @brief
*/
~InputModule() override;
/**
* @brief
* @return
*/
const char* getName() const override { return "Input"; }
/**
* @brief
* @return
*/
int getPriority() const override { return 30; }
/**
* @brief
*/
void setupModule() override;
/**
* @brief
*/
void destroyModule() override;
/**
* @brief
* @param ctx
*/
void onUpdate(UpdateContext& ctx) override;
/**
* @brief
* @param config
*/
void setInputConfig(const InputConfigData& config) { config_ = config; }
/**
* @brief
* @return
*/
IInput* getInput() const { return input_; }
/**
* @brief
* @param window
*/
void setWindow(IWindow* window);
private:
/**
* @brief 使
*/
void initializeWithWindow();
IWindow* window_ = nullptr;
IInput* input_ = nullptr;
InputConfigData config_;
};
} // namespace extra2d

View File

@ -1,143 +0,0 @@
#pragma once
#include <extra2d/core/module.h>
#include <extra2d/core/math_types.h>
#include <extra2d/graphics/render_backend.h>
#include <string>
namespace extra2d {
/**
* @brief
*/
struct RenderModuleConfig {
BackendType backend = BackendType::OpenGL;
bool vsync = true;
int targetFPS = 60;
int multisamples = 0;
bool sRGBFramebuffer = false;
int spriteBatchSize = 1000;
bool tripleBuffering = false;
Color clearColor{0.0f, 0.0f, 0.0f, 1.0f};
int maxTextureSize = 0;
int textureAnisotropy = 1;
bool wireframeMode = false;
bool depthTest = false;
bool blending = true;
bool dithering = false;
int maxRenderTargets = 1;
bool allowShaderHotReload = false;
std::string shaderCachePath;
/**
* @brief
*/
bool isMultisampleEnabled() const { return multisamples > 0; }
/**
* @brief
*/
bool isFPSCapped() const { return targetFPS > 0; }
/**
* @brief
* @return true
*/
bool validate() const {
if (targetFPS < 1 || targetFPS > 240) {
return false;
}
if (multisamples != 0 && multisamples != 2 && multisamples != 4 &&
multisamples != 8 && multisamples != 16) {
return false;
}
if (spriteBatchSize <= 0) {
return false;
}
return true;
}
};
/**
* @brief
*
*/
class RenderModule : public Module {
public:
/**
* @brief
*/
RenderModule();
/**
* @brief
*/
~RenderModule() override;
/**
* @brief
* @return
*/
const char* getName() const override { return "Render"; }
/**
* @brief
* @return
*/
int getPriority() const override { return 40; }
/**
* @brief
*/
void setupModule() override;
/**
* @brief
*/
void destroyModule() override;
/**
* @brief
* @param ctx
*/
void beforeRender(RenderContext& ctx) override;
/**
* @brief
* @param ctx
*/
void afterRender(RenderContext& ctx) override;
/**
* @brief
* @param config
*/
void setRenderConfig(const RenderModuleConfig& config) { config_ = config; }
/**
* @brief
* @param window
*/
void setWindow(IWindow* window);
/**
* @brief
* @return
*/
RenderBackend* getRenderer() const { return renderer_.get(); }
private:
/**
* @brief 使
*/
void initializeWithWindow();
IWindow* window_ = nullptr;
UniquePtr<RenderBackend> renderer_;
RenderModuleConfig config_;
};
} // namespace extra2d

View File

@ -1,83 +0,0 @@
#pragma once
#include <extra2d/core/module.h>
#include <extra2d/platform/window_config.h>
#include <extra2d/platform/iwindow.h>
namespace extra2d {
/**
* @brief
*
*/
class WindowModule : public Module {
public:
/**
* @brief
*/
WindowModule();
/**
* @brief
*/
~WindowModule() override;
/**
* @brief
* @return
*/
const char* getName() const override { return "Window"; }
/**
* @brief
* @return
*/
int getPriority() const override { return 20; }
/**
* @brief
*/
void setupModule() override;
/**
* @brief
*/
void destroyModule() override;
/**
* @brief
* @param config
*/
void setWindowConfig(const WindowConfigData& config) { windowConfig_ = config; }
/**
* @brief
* @return
*/
IWindow* getWindow() const { return window_.get(); }
private:
/**
* @brief SDL2
* @return true
*/
bool initSDL2();
/**
* @brief SDL2
*/
void shutdownSDL2();
/**
* @brief
* @param config
* @return true
*/
bool createWindow(const WindowConfigData& config);
bool sdl2Initialized_ = false;
WindowConfigData windowConfig_;
UniquePtr<IWindow> window_;
};
} // namespace extra2d

View File

@ -85,4 +85,4 @@ private:
} e2d_backend_reg_##name; \ } e2d_backend_reg_##name; \
} }
} } // namespace extra2d

View File

@ -0,0 +1,78 @@
#pragma once
#include <extra2d/core/module.h>
#include <extra2d/platform/iinput.h>
#include <extra2d/platform/window_module.h>
#include <functional>
#include <typeindex>
namespace extra2d {
/**
* @brief
*/
struct InputCfg {
float deadzone;
float mouseSensitivity;
bool enableVibration;
int maxGamepads;
int priority;
InputCfg()
: deadzone(0.15f)
, mouseSensitivity(1.0f)
, enableVibration(true)
, maxGamepads(4)
, priority(20)
{}
};
/**
* @brief
*
*/
class InputModule : public Module {
public:
/**
* @brief Lambda
* @param configFn
*/
explicit InputModule(std::function<void(InputCfg&)> configFn);
/**
* @brief
*/
~InputModule() override;
bool init() override;
void shutdown() override;
bool ok() const override { return initialized_; }
const char* name() const override { return "input"; }
int priority() const override { return cfg_.priority; }
/**
* @brief
* @return
*/
std::vector<std::type_index> deps() const override {
return {std::type_index(typeid(WindowModule))};
}
/**
* @brief
* @return
*/
IInput* input() const { return input_; }
/**
* @brief
*/
void update();
private:
InputCfg cfg_;
IInput* input_ = nullptr;
bool initialized_ = false;
};
} // namespace extra2d

View File

@ -1,195 +1,72 @@
#pragma once #pragma once
#include <SDL.h>
namespace extra2d { namespace extra2d {
/** /**
* @brief * @brief
*
* 使 SDL Scancode
* Scancode
*/ */
enum class Key : int { enum class Key : int {
None = 0, None = 0,
A, B, C, D, E, F, G, H, I, J, K, L, M,
// 字母键 N, O, P, Q, R, S, T, U, V, W, X, Y, Z,
A = SDL_SCANCODE_A, Num0, Num1, Num2, Num3, Num4,
B = SDL_SCANCODE_B, Num5, Num6, Num7, Num8, Num9,
C = SDL_SCANCODE_C, F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12,
D = SDL_SCANCODE_D, Space, Enter, Escape, Tab, Backspace,
E = SDL_SCANCODE_E, Insert, Delete, Home, End, PageUp, PageDown,
F = SDL_SCANCODE_F, Up, Down, Left, Right,
G = SDL_SCANCODE_G, LShift, RShift, LCtrl, RCtrl, LAlt, RAlt,
H = SDL_SCANCODE_H, CapsLock, NumLock, ScrollLock,
I = SDL_SCANCODE_I, Count
J = SDL_SCANCODE_J,
K = SDL_SCANCODE_K,
L = SDL_SCANCODE_L,
M = SDL_SCANCODE_M,
N = SDL_SCANCODE_N,
O = SDL_SCANCODE_O,
P = SDL_SCANCODE_P,
Q = SDL_SCANCODE_Q,
R = SDL_SCANCODE_R,
S = SDL_SCANCODE_S,
T = SDL_SCANCODE_T,
U = SDL_SCANCODE_U,
V = SDL_SCANCODE_V,
W = SDL_SCANCODE_W,
X = SDL_SCANCODE_X,
Y = SDL_SCANCODE_Y,
Z = SDL_SCANCODE_Z,
// 数字键(主键盘)
Num0 = SDL_SCANCODE_0,
Num1 = SDL_SCANCODE_1,
Num2 = SDL_SCANCODE_2,
Num3 = SDL_SCANCODE_3,
Num4 = SDL_SCANCODE_4,
Num5 = SDL_SCANCODE_5,
Num6 = SDL_SCANCODE_6,
Num7 = SDL_SCANCODE_7,
Num8 = SDL_SCANCODE_8,
Num9 = SDL_SCANCODE_9,
// 功能键
F1 = SDL_SCANCODE_F1,
F2 = SDL_SCANCODE_F2,
F3 = SDL_SCANCODE_F3,
F4 = SDL_SCANCODE_F4,
F5 = SDL_SCANCODE_F5,
F6 = SDL_SCANCODE_F6,
F7 = SDL_SCANCODE_F7,
F8 = SDL_SCANCODE_F8,
F9 = SDL_SCANCODE_F9,
F10 = SDL_SCANCODE_F10,
F11 = SDL_SCANCODE_F11,
F12 = SDL_SCANCODE_F12,
// 特殊键
Space = SDL_SCANCODE_SPACE,
Enter = SDL_SCANCODE_RETURN,
Escape = SDL_SCANCODE_ESCAPE,
Tab = SDL_SCANCODE_TAB,
Backspace = SDL_SCANCODE_BACKSPACE,
Insert = SDL_SCANCODE_INSERT,
Delete = SDL_SCANCODE_DELETE,
Home = SDL_SCANCODE_HOME,
End = SDL_SCANCODE_END,
PageUp = SDL_SCANCODE_PAGEUP,
PageDown = SDL_SCANCODE_PAGEDOWN,
// 方向键
Up = SDL_SCANCODE_UP,
Down = SDL_SCANCODE_DOWN,
Left = SDL_SCANCODE_LEFT,
Right = SDL_SCANCODE_RIGHT,
// 修饰键
LShift = SDL_SCANCODE_LSHIFT,
RShift = SDL_SCANCODE_RSHIFT,
LCtrl = SDL_SCANCODE_LCTRL,
RCtrl = SDL_SCANCODE_RCTRL,
LAlt = SDL_SCANCODE_LALT,
RAlt = SDL_SCANCODE_RALT,
// 锁定键
CapsLock = SDL_SCANCODE_CAPSLOCK,
NumLock = SDL_SCANCODE_NUMLOCKCLEAR,
ScrollLock = SDL_SCANCODE_SCROLLLOCK,
// 小键盘
KP0 = SDL_SCANCODE_KP_0,
KP1 = SDL_SCANCODE_KP_1,
KP2 = SDL_SCANCODE_KP_2,
KP3 = SDL_SCANCODE_KP_3,
KP4 = SDL_SCANCODE_KP_4,
KP5 = SDL_SCANCODE_KP_5,
KP6 = SDL_SCANCODE_KP_6,
KP7 = SDL_SCANCODE_KP_7,
KP8 = SDL_SCANCODE_KP_8,
KP9 = SDL_SCANCODE_KP_9,
KPPlus = SDL_SCANCODE_KP_PLUS,
KPMinus = SDL_SCANCODE_KP_MINUS,
KPMultiply = SDL_SCANCODE_KP_MULTIPLY,
KPDivide = SDL_SCANCODE_KP_DIVIDE,
KPEnter = SDL_SCANCODE_KP_ENTER,
KPPeriod = SDL_SCANCODE_KP_PERIOD,
// 符号键
Minus = SDL_SCANCODE_MINUS,
Equals = SDL_SCANCODE_EQUALS,
LeftBracket = SDL_SCANCODE_LEFTBRACKET,
RightBracket = SDL_SCANCODE_RIGHTBRACKET,
Backslash = SDL_SCANCODE_BACKSLASH,
Semicolon = SDL_SCANCODE_SEMICOLON,
Apostrophe = SDL_SCANCODE_APOSTROPHE,
Grave = SDL_SCANCODE_GRAVE,
Comma = SDL_SCANCODE_COMMA,
Period = SDL_SCANCODE_PERIOD,
Slash = SDL_SCANCODE_SLASH,
// 其他
PrintScreen = SDL_SCANCODE_PRINTSCREEN,
Pause = SDL_SCANCODE_PAUSE,
Count = SDL_NUM_SCANCODES
}; };
/** /**
* @brief * @brief
*
* 使 SDL
*/ */
enum class Mouse : int { enum class Mouse : int {
Left = SDL_BUTTON_LEFT, Left = 0,
Middle = SDL_BUTTON_MIDDLE, Right,
Right = SDL_BUTTON_RIGHT, Middle,
X1 = SDL_BUTTON_X1, X1,
X2 = SDL_BUTTON_X2, X2,
Count Count
}; };
/** /**
* @brief * @brief
*
* 使 SDL GameController
*/ */
enum class Gamepad : int { enum class Gamepad : int {
A = SDL_CONTROLLER_BUTTON_A, A = 0,
B = SDL_CONTROLLER_BUTTON_B, B,
X = SDL_CONTROLLER_BUTTON_X, X,
Y = SDL_CONTROLLER_BUTTON_Y, Y,
Back = SDL_CONTROLLER_BUTTON_BACK, LB,
Guide = SDL_CONTROLLER_BUTTON_GUIDE, RB,
Start = SDL_CONTROLLER_BUTTON_START, LT,
LStick = SDL_CONTROLLER_BUTTON_LEFTSTICK, RT,
RStick = SDL_CONTROLLER_BUTTON_RIGHTSTICK, Back,
LB = SDL_CONTROLLER_BUTTON_LEFTSHOULDER, Start,
RB = SDL_CONTROLLER_BUTTON_RIGHTSHOULDER, Guide,
DUp = SDL_CONTROLLER_BUTTON_DPAD_UP, LStick,
DDown = SDL_CONTROLLER_BUTTON_DPAD_DOWN, RStick,
DLeft = SDL_CONTROLLER_BUTTON_DPAD_LEFT, DUp,
DRight = SDL_CONTROLLER_BUTTON_DPAD_RIGHT, DDown,
LT = SDL_CONTROLLER_BUTTON_MAX, // 轴映射 DLeft,
RT, // 轴映射 DRight,
Count Count
}; };
/** /**
* @brief * @brief
*
* 使 SDL GameController
*/ */
enum class GamepadAxis : int { enum class GamepadAxis : int {
LeftX = SDL_CONTROLLER_AXIS_LEFTX, LeftX = 0,
LeftY = SDL_CONTROLLER_AXIS_LEFTY, LeftY,
RightX = SDL_CONTROLLER_AXIS_RIGHTX, RightX,
RightY = SDL_CONTROLLER_AXIS_RIGHTY, RightY,
LeftTrigger = SDL_CONTROLLER_AXIS_TRIGGERLEFT, LeftTrigger,
RightTrigger = SDL_CONTROLLER_AXIS_TRIGGERRIGHT, RightTrigger,
Count Count
}; };
} // namespace extra2d } // namespace extra2d

View File

@ -0,0 +1,64 @@
#pragma once
#include <extra2d/core/module.h>
#include <extra2d/platform/iwindow.h>
#include <extra2d/platform/window_config.h>
#include <functional>
#include <string>
namespace extra2d {
/**
* @brief
*/
struct WindowCfg {
std::string title;
int w;
int h;
WindowMode mode;
bool vsync;
int priority;
std::string backend;
WindowCfg()
: title("Extra2D"), w(1280), h(720), mode(WindowMode::Windowed),
vsync(true), priority(0), backend("sdl2") {}
};
/**
* @brief
*
*/
class WindowModule : public Module {
public:
/**
* @brief Lambda
* @param configFn
*/
explicit WindowModule(std::function<void(WindowCfg &)> configFn);
/**
* @brief
*/
~WindowModule() override;
bool init() override;
void shutdown() override;
bool ok() const override { return initialized_; }
const char *name() const override { return "window"; }
int priority() const override { return cfg_.priority; }
/**
* @brief
* @return
*/
IWindow *win() const { return win_.get(); }
private:
WindowCfg cfg_;
UniquePtr<IWindow> win_;
bool initialized_ = false;
bool sdlInited_ = false;
};
} // namespace extra2d

View File

@ -1,7 +1,6 @@
#pragma once #pragma once
#include <extra2d/core/color.h> #include <extra2d/core/color.h>
#include <extra2d/core/export.h>
#include <extra2d/core/math_types.h> #include <extra2d/core/math_types.h>
#include <extra2d/core/types.h> #include <extra2d/core/types.h>
#include <extra2d/event/event_dispatcher.h> #include <extra2d/event/event_dispatcher.h>
@ -20,7 +19,7 @@ struct RenderCommand;
// ============================================================================ // ============================================================================
// 节点基类 - 场景图的基础 // 节点基类 - 场景图的基础
// ============================================================================ // ============================================================================
class E2D_API Node : public std::enable_shared_from_this<Node> { class Node : public std::enable_shared_from_this<Node> {
public: public:
Node(); Node();
virtual ~Node(); virtual ~Node();
@ -152,31 +151,6 @@ public:
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
EventDispatcher &getEventDispatcher() { return eventDispatcher_; } EventDispatcher &getEventDispatcher() { return eventDispatcher_; }
/**
* @brief 便
* @param type
* @param callback
* @return ID
*/
ListenerId addListener(EventType type, EventDispatcher::EventCallback callback);
/**
* @brief
* @param id ID
*/
void removeListener(ListenerId id);
/**
* @brief
* @param type
*/
void removeAllListeners(EventType type);
/**
* @brief
*/
void removeAllListeners();
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------
// 内部方法 // 内部方法
// ------------------------------------------------------------------------ // ------------------------------------------------------------------------

View File

@ -1,7 +1,6 @@
#pragma once #pragma once
#include <extra2d/core/color.h> #include <extra2d/core/color.h>
#include <extra2d/core/export.h>
#include <extra2d/core/math_types.h> #include <extra2d/core/math_types.h>
#include <extra2d/scene/node.h> #include <extra2d/scene/node.h>
#include <vector> #include <vector>
@ -16,7 +15,7 @@ enum class ShapeType { Point, Line, Rect, Circle, Triangle, Polygon };
// ============================================================================ // ============================================================================
// 形状节点 - 用于绘制几何形状 // 形状节点 - 用于绘制几何形状
// ============================================================================ // ============================================================================
class E2D_API ShapeNode : public Node { class ShapeNode : public Node {
public: public:
ShapeNode(); ShapeNode();
~ShapeNode() override = default; ~ShapeNode() override = default;

View File

@ -26,7 +26,7 @@ public:
protected: protected:
void onTransitionStart() override; void onTransitionStart() override;
void renderContent(RenderBackend &renderer) override; void renderContent(RenderBackend &renderer) override;
void updateTransition(float dt); void updateTransition(float dt) override;
private: private:
int divisions_; int divisions_;

View File

@ -29,7 +29,7 @@ public:
protected: protected:
void onTransitionStart() override; void onTransitionStart() override;
void renderContent(RenderBackend &renderer) override; void renderContent(RenderBackend &renderer) override;
void updateTransition(float dt); void updateTransition(float dt) override;
private: private:
Axis axis_; Axis axis_;

View File

@ -24,7 +24,7 @@ public:
protected: protected:
void onTransitionStart() override; void onTransitionStart() override;
void renderContent(RenderBackend &renderer) override; void renderContent(RenderBackend &renderer) override;
void updateTransition(float dt); void updateTransition(float dt) override;
}; };
} // namespace extra2d } // namespace extra2d

View File

@ -27,7 +27,7 @@ public:
protected: protected:
void onTransitionStart() override; void onTransitionStart() override;
void renderContent(RenderBackend &renderer) override; void renderContent(RenderBackend &renderer) override;
void updateTransition(float dt); void updateTransition(float dt) override;
private: private:
TransitionDirection direction_; TransitionDirection direction_;

View File

@ -1,6 +1,7 @@
#pragma once #pragma once
#include <extra2d/core/service_interface.h> #include <extra2d/core/service_interface.h>
#include <extra2d/core/service_locator.h>
#include <extra2d/graphics/camera/camera.h> #include <extra2d/graphics/camera/camera.h>
#include <extra2d/graphics/camera/viewport_adapter.h> #include <extra2d/graphics/camera/viewport_adapter.h>
@ -106,6 +107,9 @@ public:
private: private:
Camera camera_; Camera camera_;
ViewportAdapter viewportAdapter_; ViewportAdapter viewportAdapter_;
// 服务注册元数据
E2D_AUTO_REGISTER_SERVICE(ICameraService, CameraService);
}; };
} }

View File

@ -1,6 +1,7 @@
#pragma once #pragma once
#include <extra2d/core/service_interface.h> #include <extra2d/core/service_interface.h>
#include <extra2d/core/service_locator.h>
#include <extra2d/event/event_dispatcher.h> #include <extra2d/event/event_dispatcher.h>
#include <extra2d/event/event_queue.h> #include <extra2d/event/event_queue.h>
@ -60,9 +61,17 @@ public:
size_t getTotalListenerCount() const override; size_t getTotalListenerCount() const override;
size_t getQueueSize() const override; size_t getQueueSize() const override;
EventQueue& getQueue() { return queue_; }
const EventQueue& getQueue() const { return queue_; }
EventDispatcher& getDispatcher() { return dispatcher_; }
const EventDispatcher& getDispatcher() const { return dispatcher_; }
private: private:
EventQueue queue_; EventQueue queue_;
EventDispatcher dispatcher_; EventDispatcher dispatcher_;
// 服务注册元数据
E2D_AUTO_REGISTER_SERVICE(IEventService, EventService);
}; };
} }

View File

@ -1,6 +1,7 @@
#pragma once #pragma once
#include <extra2d/core/service_interface.h> #include <extra2d/core/service_interface.h>
#include <extra2d/core/service_locator.h>
#include <extra2d/core/types.h> #include <extra2d/core/types.h>
#include <cstdarg> #include <cstdarg>
#include <string> #include <string>
@ -120,10 +121,13 @@ public:
private: private:
void output(LogLevel level, const char* msg); void output(LogLevel level, const char* msg);
const char* getLevelString(LogLevel level); const char* getLevelString(LogLevel level);
LogLevel level_; LogLevel level_;
class Impl; class Impl;
UniquePtr<Impl> impl_; UniquePtr<Impl> impl_;
// 服务注册元数据
E2D_AUTO_REGISTER_SERVICE(ILogger, ConsoleLogger);
}; };
} // namespace extra2d } // namespace extra2d

View File

@ -1,6 +1,7 @@
#pragma once #pragma once
#include <extra2d/core/service_interface.h> #include <extra2d/core/service_interface.h>
#include <extra2d/core/service_locator.h>
#include <extra2d/scene/scene_manager.h> #include <extra2d/scene/scene_manager.h>
namespace extra2d { namespace extra2d {
@ -86,6 +87,9 @@ public:
private: private:
SceneManager manager_; SceneManager manager_;
// 服务注册元数据
E2D_AUTO_REGISTER_SERVICE(ISceneService, SceneService);
}; };
} }

View File

@ -1,6 +1,7 @@
#pragma once #pragma once
#include <extra2d/core/service_interface.h> #include <extra2d/core/service_interface.h>
#include <extra2d/core/service_locator.h>
#include <extra2d/utils/timer.h> #include <extra2d/utils/timer.h>
namespace extra2d { namespace extra2d {
@ -48,6 +49,9 @@ public:
private: private:
TimerManager manager_; TimerManager manager_;
// 服务注册元数据
E2D_AUTO_REGISTER_SERVICE(ITimerService, TimerService);
}; };
} }

View File

@ -0,0 +1,10 @@
#version 300 es
precision highp float;
in vec4 v_color;
out vec4 fragColor;
void main() {
fragColor = v_color;
}

View File

@ -0,0 +1,14 @@
#version 300 es
precision highp float;
layout(location = 0) in vec2 a_position;
layout(location = 1) in vec4 a_color;
uniform mat4 u_viewProjection;
out vec4 v_color;
void main() {
gl_Position = u_viewProjection * vec4(a_position, 0.0, 1.0);
v_color = a_color;
}

View File

@ -0,0 +1,20 @@
#version 300 es
precision highp float;
in vec2 v_texCoord;
in vec4 v_color;
uniform sampler2D u_texture;
uniform float u_opacity;
out vec4 fragColor;
void main() {
vec4 texColor = texture(u_texture, v_texCoord);
fragColor = texColor * v_color;
fragColor.a *= u_opacity;
if (fragColor.a < 0.01) {
discard;
}
}

View File

@ -0,0 +1,18 @@
#version 300 es
precision highp float;
layout(location = 0) in vec2 a_position;
layout(location = 1) in vec2 a_texCoord;
layout(location = 2) in vec4 a_color;
uniform mat4 u_viewProjection;
uniform mat4 u_model;
out vec2 v_texCoord;
out vec4 v_color;
void main() {
gl_Position = u_viewProjection * u_model * vec4(a_position, 0.0, 1.0);
v_texCoord = a_texCoord;
v_color = a_color;
}

View File

@ -1,56 +0,0 @@
// ============================================
// Extra2D Combined Shader File
// Name: font
// Category: builtin
// Version: 1.0
// ============================================
#meta
{
"name": "font",
"category": "builtin",
"author": "Extra2D Team",
"description": "字体渲染Shader支持SDF"
}
#vertex
#version 300 es
precision highp float;
layout(location = 0) in vec2 a_position;
layout(location = 1) in vec2 a_texCoord;
layout(location = 2) in vec4 a_color;
uniform mat4 u_viewProjection;
uniform mat4 u_model;
out vec2 v_texCoord;
out vec4 v_color;
void main() {
gl_Position = u_viewProjection * u_model * vec4(a_position, 0.0, 1.0);
v_texCoord = a_texCoord;
v_color = a_color;
}
#fragment
#version 300 es
precision highp float;
in vec2 v_texCoord;
in vec4 v_color;
uniform sampler2D u_texture;
uniform float u_smoothing;
out vec4 fragColor;
void main() {
float dist = texture(u_texture, v_texCoord).r;
float alpha = smoothstep(0.5 - u_smoothing, 0.5 + u_smoothing, dist);
fragColor = vec4(v_color.rgb, v_color.a * alpha);
if (fragColor.a < 0.01) {
discard;
}
}

View File

@ -1,53 +0,0 @@
// ============================================
// Extra2D Combined Shader File
// Name: particle
// Category: builtin
// Version: 1.0
// ============================================
#meta
{
"name": "particle",
"category": "builtin",
"author": "Extra2D Team",
"description": "粒子渲染Shader"
}
#vertex
#version 300 es
precision highp float;
layout(location = 0) in vec2 a_position;
layout(location = 1) in vec2 a_texCoord;
layout(location = 2) in vec4 a_color;
uniform mat4 u_viewProjection;
out vec2 v_texCoord;
out vec4 v_color;
void main() {
gl_Position = u_viewProjection * vec4(a_position, 0.0, 1.0);
v_texCoord = a_texCoord;
v_color = a_color;
}
#fragment
#version 300 es
precision highp float;
in vec2 v_texCoord;
in vec4 v_color;
uniform sampler2D u_texture;
out vec4 fragColor;
void main() {
vec4 texColor = texture(u_texture, v_texCoord);
fragColor = texColor * v_color;
if (fragColor.a < 0.01) {
discard;
}
}

View File

@ -1,42 +0,0 @@
// ============================================
// Extra2D Combined Shader File
// Name: postprocess
// Category: builtin
// Version: 1.0
// ============================================
#meta
{
"name": "postprocess",
"category": "builtin",
"author": "Extra2D Team",
"description": "后处理基础Shader"
}
#vertex
#version 300 es
precision highp float;
layout(location = 0) in vec2 a_position;
layout(location = 1) in vec2 a_texCoord;
out vec2 v_texCoord;
void main() {
gl_Position = vec4(a_position, 0.0, 1.0);
v_texCoord = a_texCoord;
}
#fragment
#version 300 es
precision highp float;
in vec2 v_texCoord;
uniform sampler2D u_texture;
out vec4 fragColor;
void main() {
fragColor = texture(u_texture, v_texCoord);
}

View File

@ -1,42 +0,0 @@
// ============================================
// Extra2D Combined Shader File
// Name: shape
// Category: builtin
// Version: 1.0
// ============================================
#meta
{
"name": "shape",
"category": "builtin",
"author": "Extra2D Team",
"description": "形状渲染Shader支持顶点颜色批处理"
}
#vertex
#version 300 es
precision highp float;
layout(location = 0) in vec2 a_position;
layout(location = 1) in vec4 a_color;
uniform mat4 u_viewProjection;
out vec4 v_color;
void main() {
gl_Position = u_viewProjection * vec4(a_position, 0.0, 1.0);
v_color = a_color;
}
#fragment
#version 300 es
precision highp float;
in vec4 v_color;
out vec4 fragColor;
void main() {
fragColor = v_color;
}

View File

@ -1,61 +0,0 @@
// ============================================
// Extra2D Combined Shader File
// Name: sprite
// Category: builtin
// Version: 1.0
// ============================================
#meta
{
"name": "sprite",
"category": "builtin",
"author": "Extra2D Team",
"description": "标准2D精灵渲染Shader",
"uniforms": {
"u_viewProjection": { "type": "mat4", "description": "视图投影矩阵" },
"u_model": { "type": "mat4", "description": "模型矩阵" },
"u_opacity": { "type": "float", "default": 1.0, "description": "透明度" }
}
}
#vertex
#version 300 es
precision highp float;
layout(location = 0) in vec2 a_position;
layout(location = 1) in vec2 a_texCoord;
layout(location = 2) in vec4 a_color;
uniform mat4 u_viewProjection;
uniform mat4 u_model;
out vec2 v_texCoord;
out vec4 v_color;
void main() {
gl_Position = u_viewProjection * u_model * vec4(a_position, 0.0, 1.0);
v_texCoord = a_texCoord;
v_color = a_color;
}
#fragment
#version 300 es
precision highp float;
in vec2 v_texCoord;
in vec4 v_color;
uniform sampler2D u_texture;
uniform float u_opacity;
out vec4 fragColor;
void main() {
vec4 texColor = texture(u_texture, v_texCoord);
fragColor = texColor * v_color;
fragColor.a *= u_opacity;
if (fragColor.a < 0.01) {
discard;
}
}

View File

@ -1,71 +0,0 @@
// ============================================
// Extra2D Combined Shader File
// Name: blur
// Category: effects
// Version: 1.0
// ============================================
#meta
{
"name": "blur",
"category": "effects",
"author": "Extra2D Team",
"description": "模糊特效Shader"
}
#vertex
#version 300 es
precision highp float;
layout(location = 0) in vec2 a_position;
layout(location = 1) in vec2 a_texCoord;
layout(location = 2) in vec4 a_color;
uniform mat4 u_viewProjection;
uniform mat4 u_model;
out vec2 v_texCoord;
out vec4 v_color;
void main() {
gl_Position = u_viewProjection * u_model * vec4(a_position, 0.0, 1.0);
v_texCoord = a_texCoord;
v_color = a_color;
}
#fragment
#version 300 es
precision highp float;
in vec2 v_texCoord;
in vec4 v_color;
uniform sampler2D u_texture;
uniform float u_radius;
uniform vec2 u_textureSize;
uniform float u_opacity;
out vec4 fragColor;
void main() {
vec2 texel = u_radius / u_textureSize;
vec4 sum = vec4(0.0);
sum += texture(u_texture, v_texCoord + texel * vec2(-1.0, -1.0));
sum += texture(u_texture, v_texCoord + texel * vec2( 0.0, -1.0));
sum += texture(u_texture, v_texCoord + texel * vec2( 1.0, -1.0));
sum += texture(u_texture, v_texCoord + texel * vec2(-1.0, 0.0));
sum += texture(u_texture, v_texCoord + texel * vec2( 0.0, 0.0));
sum += texture(u_texture, v_texCoord + texel * vec2( 1.0, 0.0));
sum += texture(u_texture, v_texCoord + texel * vec2(-1.0, 1.0));
sum += texture(u_texture, v_texCoord + texel * vec2( 0.0, 1.0));
sum += texture(u_texture, v_texCoord + texel * vec2( 1.0, 1.0));
vec4 texColor = sum / 9.0;
fragColor = texColor * v_color;
fragColor.a *= u_opacity;
if (fragColor.a < 0.01) {
discard;
}
}

View File

@ -1,64 +0,0 @@
// ============================================
// Extra2D Combined Shader File
// Name: distortion
// Category: effects
// Version: 1.0
// ============================================
#meta
{
"name": "distortion",
"category": "effects",
"author": "Extra2D Team",
"description": "扭曲特效Shader"
}
#vertex
#version 300 es
precision highp float;
layout(location = 0) in vec2 a_position;
layout(location = 1) in vec2 a_texCoord;
layout(location = 2) in vec4 a_color;
uniform mat4 u_viewProjection;
uniform mat4 u_model;
out vec2 v_texCoord;
out vec4 v_color;
void main() {
gl_Position = u_viewProjection * u_model * vec4(a_position, 0.0, 1.0);
v_texCoord = a_texCoord;
v_color = a_color;
}
#fragment
#version 300 es
precision highp float;
in vec2 v_texCoord;
in vec4 v_color;
uniform sampler2D u_texture;
uniform float u_distortionAmount;
uniform float u_time;
uniform float u_timeScale;
out vec4 fragColor;
void main() {
vec2 uv = v_texCoord;
float t = u_time * u_timeScale;
float dx = sin(uv.y * 10.0 + t) * u_distortionAmount;
float dy = cos(uv.x * 10.0 + t) * u_distortionAmount;
uv += vec2(dx, dy);
vec4 texColor = texture(u_texture, uv);
fragColor = texColor * v_color;
if (fragColor.a < 0.01) {
discard;
}
}

View File

@ -1,61 +0,0 @@
// ============================================
// Extra2D Combined Shader File
// Name: grayscale
// Category: effects
// Version: 1.0
// ============================================
#meta
{
"name": "grayscale",
"category": "effects",
"author": "Extra2D Team",
"description": "灰度特效Shader"
}
#vertex
#version 300 es
precision highp float;
layout(location = 0) in vec2 a_position;
layout(location = 1) in vec2 a_texCoord;
layout(location = 2) in vec4 a_color;
uniform mat4 u_viewProjection;
uniform mat4 u_model;
out vec2 v_texCoord;
out vec4 v_color;
void main() {
gl_Position = u_viewProjection * u_model * vec4(a_position, 0.0, 1.0);
v_texCoord = a_texCoord;
v_color = a_color;
}
#fragment
#version 300 es
precision highp float;
in vec2 v_texCoord;
in vec4 v_color;
uniform sampler2D u_texture;
uniform float u_intensity;
uniform float u_opacity;
out vec4 fragColor;
void main() {
vec4 texColor = texture(u_texture, v_texCoord) * v_color;
float gray = dot(texColor.rgb, vec3(0.299, 0.587, 0.114));
texColor.rgb = mix(texColor.rgb, vec3(gray), u_intensity);
fragColor = texColor;
fragColor.a *= u_opacity;
if (fragColor.a < 0.01) {
discard;
}
}

View File

@ -1,77 +0,0 @@
// ============================================
// Extra2D Combined Shader File
// Name: grayscale_outline
// Category: effects
// Version: 1.0
// ============================================
#meta
{
"name": "grayscale_outline",
"category": "effects",
"author": "Extra2D Team",
"description": "灰度+描边组合效果Shader"
}
#vertex
#version 300 es
precision highp float;
layout(location = 0) in vec2 a_position;
layout(location = 1) in vec2 a_texCoord;
layout(location = 2) in vec4 a_color;
uniform mat4 u_viewProjection;
uniform mat4 u_model;
out vec2 v_texCoord;
out vec4 v_color;
void main() {
gl_Position = u_viewProjection * u_model * vec4(a_position, 0.0, 1.0);
v_texCoord = a_texCoord;
v_color = a_color;
}
#fragment
#version 300 es
precision highp float;
in vec2 v_texCoord;
in vec4 v_color;
uniform sampler2D u_texture;
uniform float u_grayIntensity;
uniform vec4 u_outlineColor;
uniform float u_thickness;
uniform vec2 u_textureSize;
uniform float u_opacity;
out vec4 fragColor;
void main() {
vec4 color = texture(u_texture, v_texCoord) * v_color;
float gray = dot(color.rgb, vec3(0.299, 0.587, 0.114));
color.rgb = mix(color.rgb, vec3(gray), u_grayIntensity);
float alpha = 0.0;
vec2 offset = u_thickness / u_textureSize;
alpha += texture(u_texture, v_texCoord + vec2(-offset.x, 0.0)).a;
alpha += texture(u_texture, v_texCoord + vec2(offset.x, 0.0)).a;
alpha += texture(u_texture, v_texCoord + vec2(0.0, -offset.y)).a;
alpha += texture(u_texture, v_texCoord + vec2(0.0, offset.y)).a;
if (color.a < 0.1 && alpha > 0.0) {
fragColor = u_outlineColor;
} else {
fragColor = color;
}
fragColor.a *= u_opacity;
if (fragColor.a < 0.01) {
discard;
}
}

View File

@ -1,60 +0,0 @@
// ============================================
// Extra2D Combined Shader File
// Name: invert
// Category: effects
// Version: 1.0
// ============================================
#meta
{
"name": "invert",
"category": "effects",
"author": "Extra2D Team",
"description": "反相特效Shader"
}
#vertex
#version 300 es
precision highp float;
layout(location = 0) in vec2 a_position;
layout(location = 1) in vec2 a_texCoord;
layout(location = 2) in vec4 a_color;
uniform mat4 u_viewProjection;
uniform mat4 u_model;
out vec2 v_texCoord;
out vec4 v_color;
void main() {
gl_Position = u_viewProjection * u_model * vec4(a_position, 0.0, 1.0);
v_texCoord = a_texCoord;
v_color = a_color;
}
#fragment
#version 300 es
precision highp float;
in vec2 v_texCoord;
in vec4 v_color;
uniform sampler2D u_texture;
uniform float u_strength;
uniform float u_opacity;
out vec4 fragColor;
void main() {
vec4 texColor = texture(u_texture, v_texCoord) * v_color;
vec3 inverted = vec3(1.0) - texColor.rgb;
texColor.rgb = mix(texColor.rgb, inverted, u_strength);
fragColor = texColor;
fragColor.a *= u_opacity;
if (fragColor.a < 0.01) {
discard;
}
}

View File

@ -1,70 +0,0 @@
// ============================================
// Extra2D Combined Shader File
// Name: outline
// Category: effects
// Version: 1.0
// ============================================
#meta
{
"name": "outline",
"category": "effects",
"author": "Extra2D Team",
"description": "描边特效Shader"
}
#vertex
#version 300 es
precision highp float;
layout(location = 0) in vec2 a_position;
layout(location = 1) in vec2 a_texCoord;
layout(location = 2) in vec4 a_color;
uniform mat4 u_viewProjection;
uniform mat4 u_model;
out vec2 v_texCoord;
out vec4 v_color;
void main() {
gl_Position = u_viewProjection * u_model * vec4(a_position, 0.0, 1.0);
v_texCoord = a_texCoord;
v_color = a_color;
}
#fragment
#version 300 es
precision highp float;
in vec2 v_texCoord;
in vec4 v_color;
uniform sampler2D u_texture;
uniform vec4 u_outlineColor;
uniform float u_thickness;
uniform vec2 u_textureSize;
out vec4 fragColor;
void main() {
vec4 color = texture(u_texture, v_texCoord);
float alpha = 0.0;
vec2 offset = u_thickness / u_textureSize;
alpha += texture(u_texture, v_texCoord + vec2(-offset.x, 0.0)).a;
alpha += texture(u_texture, v_texCoord + vec2(offset.x, 0.0)).a;
alpha += texture(u_texture, v_texCoord + vec2(0.0, -offset.y)).a;
alpha += texture(u_texture, v_texCoord + vec2(0.0, offset.y)).a;
if (color.a < 0.1 && alpha > 0.0) {
fragColor = u_outlineColor;
} else {
fragColor = color;
}
if (fragColor.a < 0.01) {
discard;
}
}

View File

@ -1,61 +0,0 @@
// ============================================
// Extra2D Combined Shader File
// Name: pixelate
// Category: effects
// Version: 1.0
// ============================================
#meta
{
"name": "pixelate",
"category": "effects",
"author": "Extra2D Team",
"description": "像素化特效Shader"
}
#vertex
#version 300 es
precision highp float;
layout(location = 0) in vec2 a_position;
layout(location = 1) in vec2 a_texCoord;
layout(location = 2) in vec4 a_color;
uniform mat4 u_viewProjection;
uniform mat4 u_model;
out vec2 v_texCoord;
out vec4 v_color;
void main() {
gl_Position = u_viewProjection * u_model * vec4(a_position, 0.0, 1.0);
v_texCoord = a_texCoord;
v_color = a_color;
}
#fragment
#version 300 es
precision highp float;
in vec2 v_texCoord;
in vec4 v_color;
uniform sampler2D u_texture;
uniform float u_pixelSize;
uniform vec2 u_textureSize;
uniform float u_opacity;
out vec4 fragColor;
void main() {
vec2 pixel = u_pixelSize / u_textureSize;
vec2 uv = floor(v_texCoord / pixel) * pixel + pixel * 0.5;
vec4 texColor = texture(u_texture, uv);
fragColor = texColor * v_color;
fragColor.a *= u_opacity;
if (fragColor.a < 0.01) {
discard;
}
}

View File

@ -1,66 +0,0 @@
// ============================================
// Extra2D Combined Shader File
// Name: pixelate_invert
// Category: effects
// Version: 1.0
// ============================================
#meta
{
"name": "pixelate_invert",
"category": "effects",
"author": "Extra2D Team",
"description": "像素化+反相组合效果Shader"
}
#vertex
#version 300 es
precision highp float;
layout(location = 0) in vec2 a_position;
layout(location = 1) in vec2 a_texCoord;
layout(location = 2) in vec4 a_color;
uniform mat4 u_viewProjection;
uniform mat4 u_model;
out vec2 v_texCoord;
out vec4 v_color;
void main() {
gl_Position = u_viewProjection * u_model * vec4(a_position, 0.0, 1.0);
v_texCoord = a_texCoord;
v_color = a_color;
}
#fragment
#version 300 es
precision highp float;
in vec2 v_texCoord;
in vec4 v_color;
uniform sampler2D u_texture;
uniform float u_pixelSize;
uniform vec2 u_textureSize;
uniform float u_invertStrength;
uniform float u_opacity;
out vec4 fragColor;
void main() {
vec2 pixel = u_pixelSize / u_textureSize;
vec2 uv = floor(v_texCoord / pixel) * pixel + pixel * 0.5;
vec4 color = texture(u_texture, uv) * v_color;
vec3 inverted = 1.0 - color.rgb;
color.rgb = mix(color.rgb, inverted, u_invertStrength);
fragColor = color;
fragColor.a *= u_opacity;
if (fragColor.a < 0.01) {
discard;
}
}

View File

@ -1,63 +0,0 @@
// ============================================
// Extra2D Combined Shader File
// Name: water
// Category: effects
// Version: 1.0
// ============================================
#meta
{
"name": "water",
"category": "effects",
"author": "Extra2D Team",
"description": "水波纹特效Shader"
}
#vertex
#version 300 es
precision highp float;
layout(location = 0) in vec2 a_position;
layout(location = 1) in vec2 a_texCoord;
layout(location = 2) in vec4 a_color;
uniform mat4 u_viewProjection;
uniform mat4 u_model;
out vec2 v_texCoord;
out vec4 v_color;
void main() {
gl_Position = u_viewProjection * u_model * vec4(a_position, 0.0, 1.0);
v_texCoord = a_texCoord;
v_color = a_color;
}
#fragment
#version 300 es
precision highp float;
in vec2 v_texCoord;
in vec4 v_color;
uniform sampler2D u_texture;
uniform float u_waveSpeed;
uniform float u_waveAmplitude;
uniform float u_waveFrequency;
uniform float u_time;
out vec4 fragColor;
void main() {
vec2 uv = v_texCoord;
float wave = sin(uv.y * u_waveFrequency + u_time * u_waveSpeed) * u_waveAmplitude;
uv.x += wave;
vec4 texColor = texture(u_texture, uv);
fragColor = texColor * v_color;
if (fragColor.a < 0.01) {
discard;
}
}

View File

@ -0,0 +1,20 @@
{
"name": "shape",
"category": "builtin",
"version": "1.0",
"description": "基本形状渲染Shader",
"uniforms": {
"u_viewProjection": { "type": "mat4", "description": "视图投影矩阵" }
},
"samplers": {},
"backends": {
"opengl": {
"vertex": "backends/opengl/builtin/shape.vert",
"fragment": "backends/opengl/builtin/shape.frag"
},
"vulkan": {
"vertex": "backends/vulkan/builtin/shape.vert.spv",
"fragment": "backends/vulkan/builtin/shape.frag.spv"
}
}
}

View File

@ -0,0 +1,24 @@
{
"name": "sprite",
"category": "builtin",
"version": "1.0",
"description": "标准2D精灵渲染Shader",
"uniforms": {
"u_viewProjection": { "type": "mat4", "description": "视图投影矩阵" },
"u_model": { "type": "mat4", "description": "模型矩阵" },
"u_opacity": { "type": "float", "default": 1.0, "description": "透明度" }
},
"samplers": {
"u_texture": { "binding": 0, "description": "纹理采样器" }
},
"backends": {
"opengl": {
"vertex": "backends/opengl/builtin/sprite.vert",
"fragment": "backends/opengl/builtin/sprite.frag"
},
"vulkan": {
"vertex": "backends/vulkan/builtin/sprite.vert.spv",
"fragment": "backends/vulkan/builtin/sprite.frag.spv"
}
}
}

View File

@ -1,268 +1,248 @@
#include <extra2d/app/application.h> #include <extra2d/app/application.h>
#include <extra2d/core/registry.h> #include <extra2d/core/registry.h>
#include <extra2d/platform/window_module.h>
#include <extra2d/platform/input_module.h>
#include <extra2d/graphics/core/render_module.h>
#include <extra2d/platform/iwindow.h>
#include <extra2d/platform/iinput.h>
#include <extra2d/graphics/core/render_backend.h> #include <extra2d/graphics/core/render_backend.h>
#include <extra2d/graphics/core/render_module.h>
#include <extra2d/graphics/memory/vram_manager.h>
#include <extra2d/platform/iinput.h>
#include <extra2d/platform/input_module.h>
#include <extra2d/platform/iwindow.h>
#include <extra2d/platform/window_module.h>
#include <extra2d/services/camera_service.h>
#include <extra2d/services/event_service.h>
#include <extra2d/services/logger_service.h>
#include <extra2d/services/scene_service.h> #include <extra2d/services/scene_service.h>
#include <extra2d/services/timer_service.h> #include <extra2d/services/timer_service.h>
#include <extra2d/services/event_service.h>
#include <extra2d/services/camera_service.h>
#include <extra2d/services/logger_service.h>
#include <extra2d/graphics/memory/vram_manager.h>
#include <chrono>
#include <thread>
namespace extra2d { namespace extra2d {
static double getTimeSeconds() { static double getTimeSeconds() {
#ifdef __SWITCH__ #ifdef __SWITCH__
struct timespec ts; struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts); clock_gettime(CLOCK_MONOTONIC, &ts);
return static_cast<double>(ts.tv_sec) + return static_cast<double>(ts.tv_sec) +
static_cast<double>(ts.tv_nsec) / 1000000000.0; static_cast<double>(ts.tv_nsec) / 1000000000.0;
#else #else
using namespace std::chrono; using namespace std::chrono;
auto now = steady_clock::now(); auto now = steady_clock::now();
auto duration = now.time_since_epoch(); auto duration = now.time_since_epoch();
return duration_cast<std::chrono::duration<double>>(duration).count(); return duration_cast<std::chrono::duration<double>>(duration).count();
#endif #endif
} }
Application& Application::get() { Application &Application::get() {
static Application instance; static Application instance;
return instance; return instance;
} }
Application::Application() { Application::Application() { Registry::instance().setApp(this); }
Registry::instance().setApp(this);
}
Application::~Application() { Application::~Application() {
if (initialized_) { if (initialized_) {
shutdown(); shutdown();
} }
} }
bool Application::init() { bool Application::init() {
AppConfig cfg; if (initialized_) {
return init(cfg);
}
bool Application::init(const AppConfig& config) {
if (initialized_) {
return true;
}
appConfig_ = config;
// 首先注册日志服务(模块初始化可能需要它)
auto& locator = ServiceLocator::instance();
if (!locator.hasService<ILogger>()) {
auto logger = makeShared<ConsoleLogger>();
locator.registerService(std::static_pointer_cast<ILogger>(logger));
}
// 初始化所有模块(拓扑排序)
if (!Registry::instance().init()) {
return false;
}
// 模块初始化完成后,注册其他核心服务
registerCoreServices();
initialized_ = true;
running_ = true;
return true; return true;
}
// 初始化所有模块(拓扑排序)
// 服务通过 E2D_AUTO_REGISTER_SERVICE 宏自动注册
if (!Registry::instance().init()) {
return false;
}
// 配置相机服务(需要窗口信息)
configureCameraService();
// 初始化所有服务
ServiceLocator::instance().initializeAll();
initialized_ = true;
running_ = true;
return true;
} }
void Application::registerCoreServices() { void Application::configureCameraService() {
auto& locator = ServiceLocator::instance(); auto *winMod = get<WindowModule>();
if (!winMod || !winMod->win()) {
if (!locator.hasService<ISceneService>()) { return;
auto service = makeShared<SceneService>(); }
locator.registerService(std::static_pointer_cast<ISceneService>(service));
} auto cameraService = ServiceLocator::instance().getService<ICameraService>();
if (!cameraService) {
if (!locator.hasService<ITimerService>()) { return;
auto service = makeShared<TimerService>(); }
locator.registerService(std::static_pointer_cast<ITimerService>(service));
} auto *win = winMod->win();
cameraService->setViewport(0, static_cast<float>(win->width()),
if (!locator.hasService<IEventService>()) { static_cast<float>(win->height()), 0);
auto service = makeShared<EventService>();
locator.registerService(std::static_pointer_cast<IEventService>(service)); ViewportConfig vpConfig;
} vpConfig.logicWidth = static_cast<float>(win->width());
vpConfig.logicHeight = static_cast<float>(win->height());
auto* winMod = get<WindowModule>(); vpConfig.mode = ViewportMode::AspectRatio;
if (winMod && winMod->win() && !locator.hasService<ICameraService>()) { cameraService->setViewportConfig(vpConfig);
auto cameraService = makeShared<CameraService>(); cameraService->updateViewport(win->width(), win->height());
auto* win = winMod->win();
cameraService->setViewport(0, static_cast<float>(win->width()), win->onResize([cameraService](int width, int height) {
static_cast<float>(win->height()), 0); cameraService->updateViewport(width, height);
ViewportConfig vpConfig; cameraService->applyViewportAdapter();
vpConfig.logicWidth = static_cast<float>(win->width()); });
vpConfig.logicHeight = static_cast<float>(win->height());
vpConfig.mode = ViewportMode::AspectRatio;
cameraService->setViewportConfig(vpConfig);
cameraService->updateViewport(win->width(), win->height());
locator.registerService(std::static_pointer_cast<ICameraService>(cameraService));
win->onResize([cameraService](int width, int height) {
cameraService->updateViewport(width, height);
cameraService->applyViewportAdapter();
});
}
locator.initializeAll();
} }
void Application::shutdown() { void Application::shutdown() {
if (!initialized_) return; if (!initialized_)
return;
VRAMMgr::get().printStats();
VRAMMgr::get().printStats();
ServiceLocator::instance().clear();
Registry::instance().shutdown(); ServiceLocator::instance().shutdownAll();
Registry::instance().clear(); ServiceLocator::instance().clear();
Registry::instance().shutdown();
initialized_ = false; Registry::instance().clear();
running_ = false;
initialized_ = false;
running_ = false;
} }
void Application::run() { void Application::run() {
if (!initialized_) return; if (!initialized_)
return;
auto* winMod = get<WindowModule>();
if (!winMod || !winMod->win()) return; auto *winMod = get<WindowModule>();
if (!winMod || !winMod->win())
lastFrameTime_ = getTimeSeconds(); return;
while (running_ && !winMod->win()->shouldClose()) { lastFrameTime_ = getTimeSeconds();
mainLoop();
} while (running_ && !winMod->win()->shouldClose()) {
mainLoop();
}
} }
void Application::quit() { void Application::quit() {
shouldQuit_ = true; shouldQuit_ = true;
running_ = false; running_ = false;
} }
void Application::pause() { void Application::pause() {
if (!paused_) { if (!paused_) {
paused_ = true; paused_ = true;
ServiceLocator::instance().pauseAll(); ServiceLocator::instance().pauseAll();
} }
} }
void Application::resume() { void Application::resume() {
if (paused_) { if (paused_) {
paused_ = false; paused_ = false;
ServiceLocator::instance().resumeAll(); ServiceLocator::instance().resumeAll();
lastFrameTime_ = getTimeSeconds(); lastFrameTime_ = getTimeSeconds();
} }
} }
void Application::mainLoop() { void Application::mainLoop() {
double currentTime = getTimeSeconds(); double currentTime = getTimeSeconds();
deltaTime_ = static_cast<float>(currentTime - lastFrameTime_); deltaTime_ = static_cast<float>(currentTime - lastFrameTime_);
lastFrameTime_ = currentTime; lastFrameTime_ = currentTime;
totalTime_ += deltaTime_; totalTime_ += deltaTime_;
frameCount_++; frameCount_++;
fpsTimer_ += deltaTime_; fpsTimer_ += deltaTime_;
if (fpsTimer_ >= 1.0f) { if (fpsTimer_ >= 1.0f) {
currentFps_ = frameCount_; currentFps_ = frameCount_;
frameCount_ = 0; frameCount_ = 0;
fpsTimer_ -= 1.0f; fpsTimer_ -= 1.0f;
} }
auto* winMod = get<WindowModule>(); auto *winMod = get<WindowModule>();
if (winMod && winMod->win()) { if (winMod && winMod->win()) {
winMod->win()->poll(); winMod->win()->poll();
} }
auto eventService = ServiceLocator::instance().getService<IEventService>(); auto eventService = ServiceLocator::instance().getService<IEventService>();
if (eventService) { if (eventService) {
eventService->processQueue(); eventService->processQueue();
} }
if (!paused_) { if (!paused_) {
update(); update();
} }
render(); render();
// 帧率限制 // 帧率限制
auto* renderMod = get<RenderModule>(); auto *renderMod = get<RenderModule>();
if (renderMod && renderMod->renderer()) { if (renderMod && renderMod->renderer()) {
// 这里可以添加帧率限制逻辑 // 这里可以添加帧率限制逻辑
} }
} }
void Application::update() { void Application::update() {
ServiceLocator::instance().updateAll(deltaTime_); ServiceLocator::instance().updateAll(deltaTime_);
auto* inputMod = get<InputModule>(); auto *inputMod = get<InputModule>();
if (inputMod) { if (inputMod) {
inputMod->update(); inputMod->update();
} }
} }
void Application::render() { void Application::render() {
auto* renderMod = get<RenderModule>(); auto *renderMod = get<RenderModule>();
if (!renderMod || !renderMod->renderer()) return; if (!renderMod || !renderMod->renderer())
return;
auto* renderer = renderMod->renderer();
auto* winMod = get<WindowModule>(); auto *renderer = renderMod->renderer();
if (!winMod || !winMod->win()) return; auto *winMod = get<WindowModule>();
if (!winMod || !winMod->win())
auto cameraService = ServiceLocator::instance().getService<ICameraService>(); return;
if (cameraService) {
const auto& vp = cameraService->getViewportResult().viewport; auto cameraService = ServiceLocator::instance().getService<ICameraService>();
renderer->setViewport( if (cameraService) {
static_cast<int>(vp.origin.x), static_cast<int>(vp.origin.y), const auto &vp = cameraService->getViewportResult().viewport;
static_cast<int>(vp.size.width), static_cast<int>(vp.size.height)); renderer->setViewport(
renderer->setViewProjection(cameraService->getViewProjectionMatrix()); static_cast<int>(vp.origin.x), static_cast<int>(vp.origin.y),
} else { static_cast<int>(vp.size.width), static_cast<int>(vp.size.height));
renderer->setViewport(0, 0, winMod->win()->width(), winMod->win()->height()); renderer->setViewProjection(cameraService->getViewProjectionMatrix());
} } else {
renderer->setViewport(0, 0, winMod->win()->width(),
auto sceneService = ServiceLocator::instance().getService<ISceneService>(); winMod->win()->height());
if (sceneService) { }
sceneService->render(*renderer);
} auto sceneService = ServiceLocator::instance().getService<ISceneService>();
if (sceneService) {
winMod->win()->swap(); sceneService->render(*renderer);
}
winMod->win()->swap();
} }
IWindow* Application::window() { IWindow *Application::window() {
auto* winMod = get<WindowModule>(); auto *winMod = get<WindowModule>();
return winMod ? winMod->win() : nullptr; return winMod ? winMod->win() : nullptr;
} }
RenderBackend* Application::renderer() { RenderBackend *Application::renderer() {
auto* renderMod = get<RenderModule>(); auto *renderMod = get<RenderModule>();
return renderMod ? renderMod->renderer() : nullptr; return renderMod ? renderMod->renderer() : nullptr;
} }
IInput* Application::input() { IInput *Application::input() {
auto* winMod = get<WindowModule>(); auto *winMod = get<WindowModule>();
return (winMod && winMod->win()) ? winMod->win()->input() : nullptr; return (winMod && winMod->win()) ? winMod->win()->input() : nullptr;
} }
void Application::enterScene(Ptr<Scene> scene) { void Application::enterScene(Ptr<Scene> scene) {
auto sceneService = ServiceLocator::instance().getService<ISceneService>(); auto sceneService = ServiceLocator::instance().getService<ISceneService>();
auto* winMod = get<WindowModule>(); auto *winMod = get<WindowModule>();
if (sceneService && scene && winMod && winMod->win()) { if (sceneService && scene && winMod && winMod->win()) {
scene->setViewportSize(static_cast<float>(winMod->win()->width()), scene->setViewportSize(static_cast<float>(winMod->win()->width()),
static_cast<float>(winMod->win()->height())); static_cast<float>(winMod->win()->height()));
sceneService->enterScene(scene); sceneService->enterScene(scene);
} }
} }
} // namespace extra2d } // namespace extra2d

View File

@ -1,56 +0,0 @@
#include <extra2d/config/app_config.h>
#include <extra2d/utils/logger.h>
namespace extra2d {
AppConfig AppConfig::createDefault() {
AppConfig config;
config.appName = "Extra2D App";
config.appVersion = "1.0.0";
config.organization = "";
config.configFile = "config.json";
return config;
}
bool AppConfig::validate() const {
if (appName.empty()) {
E2D_LOG_ERROR("Config validation failed: app name cannot be empty");
return false;
}
if (appVersion.empty()) {
E2D_LOG_ERROR("Config validation failed: app version cannot be empty");
return false;
}
if (configFile.empty()) {
E2D_LOG_ERROR("Config validation failed: config file cannot be empty");
return false;
}
return true;
}
void AppConfig::reset() {
*this = createDefault();
E2D_LOG_INFO("App config reset to defaults");
}
void AppConfig::merge(const AppConfig& other) {
if (other.appName != "Extra2D App") {
appName = other.appName;
}
if (other.appVersion != "1.0.0") {
appVersion = other.appVersion;
}
if (!other.organization.empty()) {
organization = other.organization;
}
if (other.configFile != "config.json") {
configFile = other.configFile;
}
E2D_LOG_INFO("Merged app config");
}
}

View File

@ -1,57 +0,0 @@
#include <extra2d/core/module.h>
namespace extra2d {
// ---------------------------------------------------------------------------
// ModuleContext 实现
// ---------------------------------------------------------------------------
ModuleContext::ModuleContext(std::vector<Module*>& modules)
: modules_(modules)
, index_(-1) {
}
void ModuleContext::next() {
index_++;
if (index_ < static_cast<int>(modules_.size())) {
handle(modules_[index_]);
}
}
// ---------------------------------------------------------------------------
// UpdateContext 实现
// ---------------------------------------------------------------------------
UpdateContext::UpdateContext(std::vector<Module*>& modules, float deltaTime)
: ModuleContext(modules)
, deltaTime_(deltaTime) {
}
void UpdateContext::handle(Module* m) {
m->onUpdate(*this);
}
// ---------------------------------------------------------------------------
// RenderContext 实现
// ---------------------------------------------------------------------------
RenderContext::RenderContext(std::vector<Module*>& modules, Phase phase)
: ModuleContext(modules)
, phase_(phase) {
}
void RenderContext::handle(Module* m) {
switch (phase_) {
case Phase::Before:
m->beforeRender(*this);
break;
case Phase::On:
m->onRender(*this);
break;
case Phase::After:
m->afterRender(*this);
break;
}
}
} // namespace extra2d

View File

@ -1,239 +0,0 @@
#include <extra2d/core/module.h>
#include <extra2d/core/module_macros.h>
#include <extra2d/core/module_meta.h>
#include <extra2d/utils/logger.h>
#include <algorithm>
#include <unordered_set>
namespace extra2d {
ModuleRegistry &ModuleRegistry::instance() {
static ModuleRegistry instance;
return instance;
}
void ModuleRegistry::registerMeta(ModuleMetaBase *meta) {
if (!meta)
return;
for (const auto *existing : metas_) {
if (existing && std::string(existing->getName()) == meta->getName()) {
return;
}
}
metas_.push_back(meta);
}
std::vector<ModuleMetaBase *> ModuleRegistry::getAllMetas() const {
return metas_;
}
ModuleMetaBase *ModuleRegistry::getMeta(const char *name) const {
if (!name)
return nullptr;
for (auto *meta : metas_) {
if (meta && std::string(meta->getName()) == name) {
return meta;
}
}
return nullptr;
}
Module *ModuleRegistry::getModule(const char *name) const {
auto it = instanceMap_.find(name ? name : "");
return it != instanceMap_.end() ? it->second : nullptr;
}
bool ModuleRegistry::hasModule(const char *name) const {
return instanceMap_.find(name ? name : "") != instanceMap_.end();
}
std::vector<Module *> ModuleRegistry::getAllModules() const {
std::vector<Module *> result;
result.reserve(instances_.size());
for (const auto &inst : instances_) {
result.push_back(inst.get());
}
return result;
}
bool ModuleRegistry::hasCircularDependency() const {
std::unordered_map<std::string, std::unordered_set<std::string>> graph;
for (auto *meta : metas_) {
if (!meta)
continue;
std::string name = meta->getName();
for (const auto *dep : meta->getDependencies()) {
graph[name].insert(dep);
}
}
std::unordered_set<std::string> visited;
std::unordered_set<std::string> recStack;
std::function<bool(const std::string &)> hasCycle =
[&](const std::string &node) -> bool {
visited.insert(node);
recStack.insert(node);
for (const auto &neighbor : graph[node]) {
if (visited.find(neighbor) == visited.end()) {
if (hasCycle(neighbor))
return true;
} else if (recStack.find(neighbor) != recStack.end()) {
return true;
}
}
recStack.erase(node);
return false;
};
for (auto *meta : metas_) {
if (!meta)
continue;
std::string name = meta->getName();
if (visited.find(name) == visited.end()) {
if (hasCycle(name))
return true;
}
}
return false;
}
std::vector<ModuleMetaBase *> ModuleRegistry::sortByDependency() {
if (hasCircularDependency()) {
E2D_LOG_ERROR("Circular dependency detected in modules!");
return {};
}
std::unordered_map<std::string, int> inDegree;
std::unordered_map<std::string, std::vector<std::string>> graph;
std::unordered_map<std::string, ModuleMetaBase *> nameToMeta;
for (auto *meta : metas_) {
if (!meta)
continue;
std::string name = meta->getName();
nameToMeta[name] = meta;
if (inDegree.find(name) == inDegree.end()) {
inDegree[name] = 0;
}
for (const auto *dep : meta->getDependencies()) {
std::string depName = dep;
graph[depName].push_back(name);
inDegree[name]++;
}
}
std::vector<ModuleMetaBase *> sorted;
std::vector<std::string> currentLevel;
for (const auto &[name, degree] : inDegree) {
if (degree == 0) {
currentLevel.push_back(name);
}
}
while (!currentLevel.empty()) {
std::sort(currentLevel.begin(), currentLevel.end(),
[&nameToMeta](const std::string &a, const std::string &b) {
auto *metaA = nameToMeta[a];
auto *metaB = nameToMeta[b];
if (!metaA || !metaB)
return a < b;
return metaA->getPriority() < metaB->getPriority();
});
std::vector<std::string> nextLevel;
for (const auto &name : currentLevel) {
if (nameToMeta.find(name) != nameToMeta.end()) {
sorted.push_back(nameToMeta[name]);
}
for (const auto &neighbor : graph[name]) {
inDegree[neighbor]--;
if (inDegree[neighbor] == 0) {
nextLevel.push_back(neighbor);
}
}
}
currentLevel = std::move(nextLevel);
}
return sorted;
}
bool ModuleRegistry::createAndInitAll() {
if (initialized_) {
return true;
}
// 动态库中模块已通过 E2D_MODULE 宏自动注册
// 无需手动调用 registerCoreModules()
E2D_LOG_INFO("ModuleRegistry: {} modules registered", metas_.size());
for (auto *meta : metas_) {
if (meta) {
E2D_LOG_INFO(" - {} (priority: {})", meta->getName(),
meta->getPriority());
}
}
auto sorted = sortByDependency();
if (sorted.empty() && !metas_.empty()) {
E2D_LOG_ERROR(
"Failed to sort modules by dependency - possible circular dependency");
return false;
}
for (auto *meta : sorted) {
if (!meta)
continue;
Module *instance = meta->create();
if (!instance) {
E2D_LOG_ERROR("Failed to create module: {}", meta->getName());
continue;
}
instance->setupModule();
instances_.emplace_back(instance);
instanceMap_[meta->getName()] = instance;
}
initialized_ = true;
return true;
}
void ModuleRegistry::destroyAll() {
if (!initialized_) {
return;
}
E2D_LOG_INFO("Destroying {} modules in reverse order", instances_.size());
// 先销毁所有模块(逆序)
for (auto it = instances_.rbegin(); it != instances_.rend(); ++it) {
if (*it) {
E2D_LOG_INFO("Destroying module: {}", (*it)->getName());
(*it)->destroyModule();
}
}
// 然后清理实例
instances_.clear();
instanceMap_.clear();
initialized_ = false;
}
} // namespace extra2d

View File

@ -0,0 +1,37 @@
#include <extra2d/core/service_registry.h>
namespace extra2d {
ServiceRegistry& ServiceRegistry::instance() {
static ServiceRegistry instance;
return instance;
}
void ServiceRegistry::setServiceEnabled(const std::string& name, bool enabled) {
for (auto& reg : registrations_) {
if (reg.name == name) {
reg.enabled = enabled;
break;
}
}
}
void ServiceRegistry::createAllServices() {
std::sort(registrations_.begin(), registrations_.end(),
[](const ServiceRegistration& a, const ServiceRegistration& b) {
return static_cast<int>(a.priority) < static_cast<int>(b.priority);
});
for (const auto& reg : registrations_) {
if (!reg.enabled) {
continue;
}
auto service = reg.factory();
if (service) {
ServiceLocator::instance().registerService<IService>(service);
}
}
}
}

View File

@ -2,6 +2,15 @@
namespace extra2d { namespace extra2d {
/**
* @brief
*
* Event对象
*
* @param width
* @param height
* @return Event对象
*/
Event Event::createWindowResize(int width, int height) { Event Event::createWindowResize(int width, int height) {
Event event; Event event;
event.type = EventType::WindowResize; event.type = EventType::WindowResize;
@ -9,50 +18,115 @@ Event Event::createWindowResize(int width, int height) {
return event; return event;
} }
/**
* @brief
*
* Event对象
*
* @return Event对象
*/
Event Event::createWindowClose() { Event Event::createWindowClose() {
Event event; Event event;
event.type = EventType::WindowClose; event.type = EventType::WindowClose;
return event; return event;
} }
/**
* @brief
*
* Event对象
*
* @param keyCode
* @param scancode
* @param mods ShiftCtrl等
* @return Event对象
*/
Event Event::createKeyPress(int keyCode, int scancode, int mods) { Event Event::createKeyPress(int keyCode, int scancode, int mods) {
Event event; Event event;
event.type = EventType::KeyPress; event.type = EventType::KeyPressed;
event.data = KeyEvent{keyCode, scancode, mods}; event.data = KeyEvent{keyCode, scancode, mods};
return event; return event;
} }
/**
* @brief
*
* Event对象
*
* @param keyCode
* @param scancode
* @param mods ShiftCtrl等
* @return Event对象
*/
Event Event::createKeyRelease(int keyCode, int scancode, int mods) { Event Event::createKeyRelease(int keyCode, int scancode, int mods) {
Event event; Event event;
event.type = EventType::KeyRelease; event.type = EventType::KeyReleased;
event.data = KeyEvent{keyCode, scancode, mods}; event.data = KeyEvent{keyCode, scancode, mods};
return event; return event;
} }
Event Event::createMousePress(int button, int mods, const Vec2 &pos) { /**
* @brief
*
* Event对象
*
* @param button
* @param mods
* @param pos
* @return Event对象
*/
Event Event::createMouseButtonPress(int button, int mods, const Vec2 &pos) {
Event event; Event event;
event.type = EventType::MousePress; event.type = EventType::MouseButtonPressed;
event.data = MouseEvent{button, mods, pos}; event.data = MouseButtonEvent{button, mods, pos};
return event; return event;
} }
Event Event::createMouseRelease(int button, int mods, const Vec2 &pos) { /**
* @brief
*
* Event对象
*
* @param button
* @param mods
* @param pos
* @return Event对象
*/
Event Event::createMouseButtonRelease(int button, int mods, const Vec2 &pos) {
Event event; Event event;
event.type = EventType::MouseRelease; event.type = EventType::MouseButtonReleased;
event.data = MouseEvent{button, mods, pos}; event.data = MouseButtonEvent{button, mods, pos};
return event; return event;
} }
/**
* @brief
*
* Event对象
*
* @param pos
* @param delta
* @return Event对象
*/
Event Event::createMouseMove(const Vec2 &pos, const Vec2 &delta) { Event Event::createMouseMove(const Vec2 &pos, const Vec2 &delta) {
Event event; Event event;
event.type = EventType::MouseMove; event.type = EventType::MouseMoved;
event.data = MouseMoveEvent{pos, delta}; event.data = MouseMoveEvent{pos, delta};
return event; return event;
} }
/**
* @brief
*
* Event对象
*
* @param offset
* @param pos
* @return Event对象
*/
Event Event::createMouseScroll(const Vec2 &offset, const Vec2 &pos) { Event Event::createMouseScroll(const Vec2 &offset, const Vec2 &pos) {
Event event; Event event;
event.type = EventType::MouseScroll; event.type = EventType::MouseScrolled;
event.data = MouseScrollEvent{offset, pos}; event.data = MouseScrollEvent{offset, pos};
return event; return event;
} }

View File

@ -0,0 +1,127 @@
#include <extra2d/graphics/backends/backend_factory.h>
// 条件编译包含对应后端实现
#ifdef E2D_BACKEND_OPENGL
#include <extra2d/graphics/backends/opengl/gl_renderer.h>
#endif
#ifdef E2D_BACKEND_VULKAN
#include <extra2d/graphics/backends/vulkan/vk_renderer.h>
#endif
#include <extra2d/utils/logger.h>
#include <cstring>
namespace extra2d {
BackendFactory& BackendFactory::getInstance() {
static BackendFactory instance;
return instance;
}
UniquePtr<RenderBackend> BackendFactory::createBackend(BackendType type) {
switch (type) {
#ifdef E2D_BACKEND_OPENGL
case BackendType::OpenGL:
E2D_LOG_INFO("Creating OpenGL render backend");
return makeUnique<GLRenderer>();
#endif
#ifdef E2D_BACKEND_VULKAN
case BackendType::Vulkan:
E2D_LOG_INFO("Creating Vulkan render backend");
return makeUnique<VulkanRenderer>();
#endif
default:
E2D_LOG_ERROR("Unsupported render backend type: {}", static_cast<int>(type));
return nullptr;
}
}
UniquePtr<RenderBackend> BackendFactory::createDefaultBackend() {
BackendType recommended = getRecommendedBackend();
return createBackend(recommended);
}
bool BackendFactory::isBackendAvailable(BackendType type) const {
switch (type) {
#ifdef E2D_BACKEND_OPENGL
case BackendType::OpenGL:
return true;
#endif
#ifdef E2D_BACKEND_VULKAN
case BackendType::Vulkan:
return true;
#endif
default:
return false;
}
}
BackendType BackendFactory::getRecommendedBackend() const {
// 平台特定的默认后端选择
// 优先级Vulkan > OpenGL
#ifdef E2D_BACKEND_VULKAN
return BackendType::Vulkan;
#endif
#ifdef E2D_BACKEND_OPENGL
return BackendType::OpenGL;
#endif
// 如果没有可用的后端返回OpenGL作为默认值
return BackendType::OpenGL;
}
const char* BackendFactory::getBackendName(BackendType type) const {
switch (type) {
case BackendType::OpenGL:
return "OpenGL";
case BackendType::Vulkan:
return "Vulkan";
case BackendType::Metal:
return "Metal";
case BackendType::D3D11:
return "D3D11";
case BackendType::D3D12:
return "D3D12";
case BackendType::OpenGLES:
return "OpenGL ES";
default:
return "Unknown";
}
}
BackendType BackendFactory::parseBackendType(const char* name) const {
if (!name) {
return BackendType::OpenGL;
}
if (std::strcmp(name, "opengl") == 0 || std::strcmp(name, "OpenGL") == 0) {
return BackendType::OpenGL;
}
if (std::strcmp(name, "vulkan") == 0 || std::strcmp(name, "Vulkan") == 0) {
return BackendType::Vulkan;
}
if (std::strcmp(name, "metal") == 0 || std::strcmp(name, "Metal") == 0) {
return BackendType::Metal;
}
if (std::strcmp(name, "d3d11") == 0 || std::strcmp(name, "D3D11") == 0) {
return BackendType::D3D11;
}
if (std::strcmp(name, "d3d12") == 0 || std::strcmp(name, "D3D12") == 0) {
return BackendType::D3D12;
}
if (std::strcmp(name, "opengles") == 0 || std::strcmp(name, "OpenGLES") == 0) {
return BackendType::OpenGLES;
}
E2D_LOG_WARN("Unknown backend type '{}', defaulting to OpenGL", name);
return BackendType::OpenGL;
}
} // namespace extra2d

View File

@ -0,0 +1,171 @@
#include <extra2d/graphics/backends/opengl/gl_buffer.h>
#include <extra2d/graphics/memory/vram_manager.h>
#include <extra2d/utils/logger.h>
#include <cstring>
namespace extra2d {
// ============================================================================
// GLBuffer 实现
// ============================================================================
GLBuffer::GLBuffer() = default;
GLBuffer::~GLBuffer() {
shutdown();
}
bool GLBuffer::init(const BufferDesc& desc) {
if (bufferID_ != 0) {
shutdown();
}
type_ = desc.type;
usage_ = desc.usage;
size_ = desc.size;
target_ = convertType(type_);
glUsage_ = convertUsage(usage_);
// 生成缓冲区
glGenBuffers(1, &bufferID_);
if (bufferID_ == 0) {
E2D_LOG_ERROR("Failed to generate OpenGL buffer");
return false;
}
// 绑定并分配缓冲区
glBindBuffer(target_, bufferID_);
glBufferData(target_, static_cast<GLsizeiptr>(size_), desc.initialData, glUsage_);
glBindBuffer(target_, 0);
// 追踪显存使用
VRAMMgr::get().allocBuffer(size_);
E2D_LOG_DEBUG("GLBuffer created: ID={}, Size={}, Type={}, Usage={}",
bufferID_, size_, static_cast<int>(type_), static_cast<int>(usage_));
return true;
}
void GLBuffer::shutdown() {
if (bufferID_ != 0) {
if (mapped_) {
unmap();
}
// 释放显存追踪
VRAMMgr::get().freeBuffer(size_);
glDeleteBuffers(1, &bufferID_);
E2D_LOG_DEBUG("GLBuffer destroyed: ID={}", bufferID_);
bufferID_ = 0;
}
size_ = 0;
mapped_ = false;
mappedPtr_ = nullptr;
}
void GLBuffer::bind() {
if (bufferID_ != 0) {
glBindBuffer(target_, bufferID_);
}
}
void GLBuffer::unbind() {
glBindBuffer(target_, 0);
}
void GLBuffer::setData(const void* data, size_t size) {
if (bufferID_ == 0) {
return;
}
bind();
// 如果大小相同,使用 glBufferSubData 更高效
if (size == size_) {
glBufferSubData(target_, 0, static_cast<GLsizeiptr>(size), data);
} else {
// 大小不同,重新分配
size_ = size;
glBufferData(target_, static_cast<GLsizeiptr>(size_), data, glUsage_);
}
unbind();
}
void GLBuffer::updateData(const void* data, size_t offset, size_t size) {
if (bufferID_ == 0 || data == nullptr || size == 0) {
return;
}
if (offset + size > size_) {
E2D_LOG_WARN("GLBuffer updateData out of bounds: offset={}, size={}, bufferSize={}",
offset, size, size_);
return;
}
bind();
glBufferSubData(target_, static_cast<GLintptr>(offset), static_cast<GLsizeiptr>(size), data);
unbind();
}
void* GLBuffer::map() {
if (bufferID_ == 0 || mapped_) {
return nullptr;
}
bind();
// 使用 glMapBufferRange 替代 glMapBuffer更现代且安全
GLbitfield access = GL_MAP_WRITE_BIT;
if (usage_ == BufferUsage::Dynamic || usage_ == BufferUsage::Stream) {
access |= GL_MAP_INVALIDATE_BUFFER_BIT; // 暗示驱动可以丢弃旧数据
}
mappedPtr_ = glMapBufferRange(target_, 0, static_cast<GLsizeiptr>(size_), access);
if (mappedPtr_) {
mapped_ = true;
} else {
E2D_LOG_ERROR("Failed to map GLBuffer");
}
return mappedPtr_;
}
void GLBuffer::unmap() {
if (!mapped_ || bufferID_ == 0) {
return;
}
glUnmapBuffer(target_);
mapped_ = false;
mappedPtr_ = nullptr;
unbind();
}
GLenum GLBuffer::convertUsage(BufferUsage usage) {
switch (usage) {
case BufferUsage::Static:
return GL_STATIC_DRAW;
case BufferUsage::Dynamic:
return GL_DYNAMIC_DRAW;
case BufferUsage::Stream:
return GL_STREAM_DRAW;
default:
return GL_STATIC_DRAW;
}
}
GLenum GLBuffer::convertType(BufferType type) {
switch (type) {
case BufferType::Vertex:
return GL_ARRAY_BUFFER;
case BufferType::Index:
return GL_ELEMENT_ARRAY_BUFFER;
case BufferType::Uniform:
return GL_UNIFORM_BUFFER;
default:
return GL_ARRAY_BUFFER;
}
}
} // namespace extra2d

View File

@ -0,0 +1,167 @@
#include <extra2d/graphics/backends/opengl/gl_context.h>
#include <extra2d/graphics/memory/gpu_context.h>
#include <extra2d/utils/logger.h>
#include <cstring>
#include <sstream>
namespace extra2d {
// ============================================================================
// GLContext 实现
// ============================================================================
GLContext& GLContext::get() {
static GLContext instance;
return instance;
}
bool GLContext::init() {
if (initialized_) {
return true;
}
// 解析 OpenGL 版本
parseVersion();
// 加载扩展GLAD 已在 glad.c 中完成)
if (!loadExtensions()) {
E2D_LOG_ERROR("Failed to load OpenGL extensions");
return false;
}
initialized_ = true;
// 标记 GPU 上下文为有效
GPUContext::get().markValid();
E2D_LOG_INFO("OpenGL Context initialized");
E2D_LOG_INFO(" Version: {}", getVersionString());
E2D_LOG_INFO(" Vendor: {}", getVendor());
E2D_LOG_INFO(" Renderer: {}", getRenderer());
E2D_LOG_INFO(" Max Texture Size: {}", getMaxTextureSize());
E2D_LOG_INFO(" Max Texture Units: {}", getMaxTextureUnits());
return true;
}
void GLContext::shutdown() {
// 标记 GPU 上下文为无效
GPUContext::get().markInvalid();
initialized_ = false;
version_ = GLVersion{};
maxTextureSize_ = -1;
maxTextureUnits_ = -1;
maxVertexAttribs_ = -1;
maxUniformBufferBindings_ = -1;
}
std::string GLContext::getVersionString() const {
const char* version = reinterpret_cast<const char*>(glGetString(GL_VERSION));
return version ? version : "Unknown";
}
std::string GLContext::getVendor() const {
const char* vendor = reinterpret_cast<const char*>(glGetString(GL_VENDOR));
return vendor ? vendor : "Unknown";
}
std::string GLContext::getRenderer() const {
const char* renderer = reinterpret_cast<const char*>(glGetString(GL_RENDERER));
return renderer ? renderer : "Unknown";
}
bool GLContext::hasExtension(const std::string& extension) const {
GLint numExtensions = 0;
glGetIntegerv(GL_NUM_EXTENSIONS, &numExtensions);
for (GLint i = 0; i < numExtensions; ++i) {
const char* ext = reinterpret_cast<const char*>(glGetStringi(GL_EXTENSIONS, i));
if (ext && extension == ext) {
return true;
}
}
return false;
}
int GLContext::getMaxTextureSize() const {
if (maxTextureSize_ < 0) {
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize_);
}
return maxTextureSize_;
}
int GLContext::getMaxTextureUnits() const {
if (maxTextureUnits_ < 0) {
glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &maxTextureUnits_);
}
return maxTextureUnits_;
}
int GLContext::getMaxVertexAttribs() const {
if (maxVertexAttribs_ < 0) {
glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &maxVertexAttribs_);
}
return maxVertexAttribs_;
}
int GLContext::getMaxUniformBufferBindings() const {
if (maxUniformBufferBindings_ < 0) {
glGetIntegerv(GL_MAX_UNIFORM_BUFFER_BINDINGS, &maxUniformBufferBindings_);
}
return maxUniformBufferBindings_;
}
bool GLContext::hasVAO() const {
// OpenGL 3.0+ 或 OpenGL ES 3.0+ 原生支持 VAO
if (version_.es) {
return version_.major >= 3;
}
return version_.major > 3 || (version_.major == 3 && version_.minor >= 0);
}
bool GLContext::hasFBO() const {
// OpenGL 3.0+ 或 OpenGL ES 2.0+ 原生支持 FBO
if (version_.es) {
return version_.major >= 2;
}
return version_.major >= 3;
}
bool GLContext::hasShader() const {
// OpenGL 2.0+ 或 OpenGL ES 2.0+ 原生支持 Shader
if (version_.es) {
return version_.major >= 2;
}
return version_.major >= 2;
}
void GLContext::parseVersion() {
const char* versionStr = reinterpret_cast<const char*>(glGetString(GL_VERSION));
if (!versionStr) {
version_ = GLVersion{0, 0, false};
return;
}
std::string version(versionStr);
// 检查是否为 OpenGL ES
if (version.find("OpenGL ES") != std::string::npos) {
version_.es = true;
// 解析 ES 版本号,格式如 "OpenGL ES 3.0"
std::sscanf(version.c_str(), "OpenGL ES %d.%d", &version_.major, &version_.minor);
} else {
version_.es = false;
// 解析桌面版本号,格式如 "3.3.0 NVIDIA"
std::sscanf(version.c_str(), "%d.%d", &version_.major, &version_.minor);
}
}
bool GLContext::loadExtensions() {
// GLAD 已经在 glad.c 中加载了所有扩展
// 这里可以添加额外的扩展检查
return true;
}
} // namespace extra2d

View File

@ -1,105 +1,151 @@
#include <extra2d/graphics/opengl/gl_font_atlas.h> #include <extra2d/graphics/backends/opengl/gl_font_atlas.h>
#include <extra2d/utils/logger.h> #include <extra2d/utils/logger.h>
#include <algorithm>
#include <cmath>
#include <cstring>
#include <fstream> #include <fstream>
// 在实现文件中定义 STB 实现
#define STB_TRUETYPE_IMPLEMENTATION #define STB_TRUETYPE_IMPLEMENTATION
#include <stb/stb_truetype.h> #include <stb/stb_truetype.h>
#define STB_RECT_PACK_IMPLEMENTATION
#include <algorithm>
#include <stb/stb_rect_pack.h>
#define STB_RECT_PACK_IMPLEMENTATION
#include <stb/stb_rect_pack.h>
namespace extra2d { namespace extra2d {
// ============================================================================ // ============================================================================
// 构造函数 - 初始化字体图集 // GLFontAtlas 构造函数
// 加载字体文件并初始化图集
// ============================================================================ // ============================================================================
/**
* @brief
* @param filepath
* @param fontSize
* @param useSDF 使
*/
GLFontAtlas::GLFontAtlas(const std::string &filepath, int fontSize, bool useSDF) GLFontAtlas::GLFontAtlas(const std::string &filepath, int fontSize, bool useSDF)
: fontSize_(fontSize), useSDF_(useSDF), currentY_(0), scale_(0.0f), : useSDF_(useSDF), fontSize_(fontSize), lineHeight_(0.0f), ascent_(0.0f),
ascent_(0.0f), descent_(0.0f), lineGap_(0.0f) { descent_(0.0f), lineGap_(0.0f), scale_(0.0f) {
// 加载字体文件 // 加载字体文件
std::ifstream file(filepath, std::ios::binary | std::ios::ate); if (!initFont(filepath)) {
if (!file.is_open()) { E2D_LOG_ERROR("Failed to initialize font: {}", filepath);
E2D_LOG_ERROR("Failed to load font: {}", filepath);
return;
}
std::streamsize size = file.tellg();
file.seekg(0, std::ios::beg);
fontData_.resize(size);
if (!file.read(reinterpret_cast<char *>(fontData_.data()), size)) {
E2D_LOG_ERROR("Failed to read font file: {}", filepath);
return;
}
// 初始化 stb_truetype
if (!stbtt_InitFont(&fontInfo_, fontData_.data(),
stbtt_GetFontOffsetForIndex(fontData_.data(), 0))) {
E2D_LOG_ERROR("Failed to init font: {}", filepath);
return; return;
} }
// 计算字体缩放比例和度量
scale_ = stbtt_ScaleForPixelHeight(&fontInfo_, static_cast<float>(fontSize_)); scale_ = stbtt_ScaleForPixelHeight(&fontInfo_, static_cast<float>(fontSize_));
int ascent, descent, lineGap; int ascent, descent, lineGap;
stbtt_GetFontVMetrics(&fontInfo_, &ascent, &descent, &lineGap); stbtt_GetFontVMetrics(&fontInfo_, &ascent, &descent, &lineGap);
ascent_ = static_cast<float>(ascent) * scale_; ascent_ = static_cast<float>(ascent) * scale_;
descent_ = static_cast<float>(descent) * scale_; descent_ = static_cast<float>(descent) * scale_;
lineGap_ = static_cast<float>(lineGap) * scale_; lineGap_ = static_cast<float>(lineGap) * scale_;
lineHeight_ = ascent_ - descent_ + lineGap_;
// 创建图集纹理和打包上下文
createAtlas(); createAtlas();
// 预加载常用 ASCII 字符
std::string asciiChars = " !\"#$%&'()*+,-./"
"0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`"
"abcdefghijklmnopqrstuvwxyz{|}~";
for (char c : asciiChars) {
char32_t codepoint = static_cast<char32_t>(static_cast<unsigned char>(c));
cacheGlyph(codepoint);
}
E2D_LOG_INFO("Font atlas created: {} ({}px, {}x{})", filepath, fontSize_,
ATLAS_WIDTH, ATLAS_HEIGHT);
} }
// ============================================================================ // ============================================================================
// 析构函数 // GLFontAtlas 析构函数
// ============================================================================ // ============================================================================
/** GLFontAtlas::~GLFontAtlas() {
* @brief // 智能指针自动管理纹理资源
*/ }
GLFontAtlas::~GLFontAtlas() = default;
// ============================================================================ // ============================================================================
// 获取字形 - 如果字形不存在则缓存它 // 获取字形信息 - 如果字形不存在则动态缓存
// ============================================================================ // ============================================================================
/**
* @brief
* @param codepoint Unicode码点
* @return nullptr
*/
const Glyph *GLFontAtlas::getGlyph(char32_t codepoint) const { const Glyph *GLFontAtlas::getGlyph(char32_t codepoint) const {
auto it = glyphs_.find(codepoint); auto it = glyphs_.find(codepoint);
if (it == glyphs_.end()) { if (it == glyphs_.end()) {
cacheGlyph(codepoint); // 动态缓存新字形
const_cast<GLFontAtlas *>(this)->cacheGlyph(codepoint);
it = glyphs_.find(codepoint); it = glyphs_.find(codepoint);
if (it == glyphs_.end()) {
return nullptr;
}
} }
return (it != glyphs_.end()) ? &it->second : nullptr;
// 返回静态存储的 Glyph 数据
static Glyph glyph;
const auto &data = it->second;
glyph.width = data.width;
glyph.height = data.height;
glyph.bearingX = data.bearingX;
glyph.bearingY = data.bearingY;
glyph.advance = data.advance;
glyph.u0 = data.u0;
glyph.v0 = data.v0;
glyph.u1 = data.u1;
glyph.v1 = data.v1;
return &glyph;
} }
// ============================================================================ // ============================================================================
// 测量文本尺寸 // 测量文本尺寸 - 支持多行文本
// ============================================================================ // ============================================================================
/**
* @brief
* @param text
* @return
*/
Vec2 GLFontAtlas::measureText(const std::string &text) { Vec2 GLFontAtlas::measureText(const std::string &text) {
float width = 0.0f; float width = 0.0f;
float height = getAscent() - getDescent(); float maxWidth = 0.0f;
float height = lineHeight_;
float currentWidth = 0.0f; float currentWidth = 0.0f;
for (char c : text) { for (size_t i = 0; i < text.length();) {
char32_t codepoint = static_cast<char32_t>(static_cast<unsigned char>(c)); // 处理 UTF-8 编码
char32_t codepoint = 0;
unsigned char c = static_cast<unsigned char>(text[i]);
if ((c & 0x80) == 0) {
// 单字节 ASCII
codepoint = c;
i++;
} else if ((c & 0xE0) == 0xC0) {
// 2字节 UTF-8
if (i + 1 >= text.length())
break;
codepoint =
((c & 0x1F) << 6) | (static_cast<unsigned char>(text[i + 1]) & 0x3F);
i += 2;
} else if ((c & 0xF0) == 0xE0) {
// 3字节 UTF-8
if (i + 2 >= text.length())
break;
codepoint = ((c & 0x0F) << 12) |
((static_cast<unsigned char>(text[i + 1]) & 0x3F) << 6) |
(static_cast<unsigned char>(text[i + 2]) & 0x3F);
i += 3;
} else if ((c & 0xF8) == 0xF0) {
// 4字节 UTF-8
if (i + 3 >= text.length())
break;
codepoint = ((c & 0x07) << 18) |
((static_cast<unsigned char>(text[i + 1]) & 0x3F) << 12) |
((static_cast<unsigned char>(text[i + 2]) & 0x3F) << 6) |
(static_cast<unsigned char>(text[i + 3]) & 0x3F);
i += 4;
} else {
// 无效的 UTF-8跳过
i++;
continue;
}
// 处理换行
if (codepoint == '\n') { if (codepoint == '\n') {
width = std::max(width, currentWidth); maxWidth = std::max(maxWidth, currentWidth);
currentWidth = 0.0f; currentWidth = 0.0f;
height += getLineHeight(); height += lineHeight_;
continue; continue;
} }
@ -109,25 +155,51 @@ Vec2 GLFontAtlas::measureText(const std::string &text) {
} }
} }
width = std::max(width, currentWidth); maxWidth = std::max(maxWidth, currentWidth);
return Vec2(width, height); return Vec2(maxWidth, height);
}
// ============================================================================
// 初始化字体 - 加载字体文件到内存
// ============================================================================
bool GLFontAtlas::initFont(const std::string &filepath) {
// 读取字体文件到内存
std::ifstream file(filepath, std::ios::binary | std::ios::ate);
if (!file.is_open()) {
E2D_LOG_ERROR("Failed to open font file: {}", filepath);
return false;
}
std::streamsize size = file.tellg();
file.seekg(0, std::ios::beg);
fontData_.resize(static_cast<size_t>(size));
if (!file.read(reinterpret_cast<char *>(fontData_.data()), size)) {
E2D_LOG_ERROR("Failed to read font file: {}", filepath);
return false;
}
// 初始化 STB 字体
if (!stbtt_InitFont(&fontInfo_, fontData_.data(), 0)) {
E2D_LOG_ERROR("Failed to initialize STB font: {}", filepath);
return false;
}
return true;
} }
// ============================================================================ // ============================================================================
// 创建图集纹理 - 初始化空白纹理和矩形打包上下文 // 创建图集纹理 - 初始化空白纹理和矩形打包上下文
// ============================================================================ // ============================================================================
/**
* @brief
*/
void GLFontAtlas::createAtlas() { void GLFontAtlas::createAtlas() {
// 统一使用 4 通道格式 // 统一使用 4 通道格式 (RGBA)
int channels = 4; int channels = 4;
std::vector<uint8_t> emptyData(ATLAS_WIDTH * ATLAS_HEIGHT * channels, 0); std::vector<uint8_t> emptyData(ATLAS_WIDTH * ATLAS_HEIGHT * channels, 0);
texture_ = std::make_unique<GLTexture>(ATLAS_WIDTH, ATLAS_HEIGHT, texture_ = std::make_unique<GLTexture>(ATLAS_WIDTH, ATLAS_HEIGHT,
emptyData.data(), channels); emptyData.data(), channels);
texture_->setFilter(true); texture_->setFilter(true);
// 初始化矩形打包上下文 // 初始化矩形打包上下文 - 持久化以支持增量打包
packNodes_.resize(ATLAS_WIDTH); packNodes_.resize(ATLAS_WIDTH);
stbrp_init_target(&packContext_, ATLAS_WIDTH, ATLAS_HEIGHT, packNodes_.data(), stbrp_init_target(&packContext_, ATLAS_WIDTH, ATLAS_HEIGHT, packNodes_.data(),
ATLAS_WIDTH); ATLAS_WIDTH);
@ -143,16 +215,19 @@ void GLFontAtlas::createAtlas() {
// 缓存字形 - 渲染字形到图集并存储信息 // 缓存字形 - 渲染字形到图集并存储信息
// 使用 stb_rect_pack 进行矩形打包 // 使用 stb_rect_pack 进行矩形打包
// ============================================================================ // ============================================================================
/** void GLFontAtlas::cacheGlyph(char32_t codepoint) {
* @brief // 检查是否已存在
* @param codepoint Unicode码点 if (glyphs_.find(codepoint) != glyphs_.end()) {
*/ return;
void GLFontAtlas::cacheGlyph(char32_t codepoint) const { }
int advance = 0;
stbtt_GetCodepointHMetrics(&fontInfo_, static_cast<int>(codepoint), &advance,
nullptr);
float advancePx = advance * scale_;
// 获取字形水平度量
int advance, leftSideBearing;
stbtt_GetCodepointHMetrics(&fontInfo_, static_cast<int>(codepoint), &advance,
&leftSideBearing);
float advancePx = static_cast<float>(advance) * scale_;
// SDF 渲染模式
if (useSDF_) { if (useSDF_) {
constexpr int SDF_PADDING = 8; constexpr int SDF_PADDING = 8;
constexpr unsigned char ONEDGE_VALUE = 128; constexpr unsigned char ONEDGE_VALUE = 128;
@ -162,15 +237,18 @@ void GLFontAtlas::cacheGlyph(char32_t codepoint) const {
unsigned char *sdf = stbtt_GetCodepointSDF( unsigned char *sdf = stbtt_GetCodepointSDF(
&fontInfo_, scale_, static_cast<int>(codepoint), SDF_PADDING, &fontInfo_, scale_, static_cast<int>(codepoint), SDF_PADDING,
ONEDGE_VALUE, PIXEL_DIST_SCALE, &w, &h, &xoff, &yoff); ONEDGE_VALUE, PIXEL_DIST_SCALE, &w, &h, &xoff, &yoff);
if (!sdf || w <= 0 || h <= 0) { if (!sdf || w <= 0 || h <= 0) {
if (sdf) if (sdf)
stbtt_FreeSDF(sdf, nullptr); stbtt_FreeSDF(sdf, nullptr);
Glyph glyph{}; // 创建空白字形(如空格)
glyph.advance = advancePx; GlyphData data{};
glyphs_[codepoint] = glyph; data.advance = advancePx;
glyphs_[codepoint] = data;
return; return;
} }
// 使用 stb_rect_pack 打包矩形
stbrp_rect rect; stbrp_rect rect;
rect.id = static_cast<int>(codepoint); rect.id = static_cast<int>(codepoint);
rect.w = w + PADDING * 2; rect.w = w + PADDING * 2;
@ -178,8 +256,7 @@ void GLFontAtlas::cacheGlyph(char32_t codepoint) const {
stbrp_pack_rects(&packContext_, &rect, 1); stbrp_pack_rects(&packContext_, &rect, 1);
if (!rect.was_packed) { if (!rect.was_packed) {
E2D_LOG_WARN("Font atlas is full, cannot cache codepoint: {}", E2D_LOG_WARN("Font atlas is full, cannot cache codepoint: {}", codepoint);
static_cast<int>(codepoint));
stbtt_FreeSDF(sdf, nullptr); stbtt_FreeSDF(sdf, nullptr);
return; return;
} }
@ -187,23 +264,25 @@ void GLFontAtlas::cacheGlyph(char32_t codepoint) const {
int atlasX = rect.x + PADDING; int atlasX = rect.x + PADDING;
int atlasY = rect.y + PADDING; int atlasY = rect.y + PADDING;
Glyph glyph; // 创建字形数据
glyph.width = static_cast<float>(w); GlyphData data;
glyph.height = static_cast<float>(h); data.width = static_cast<float>(w);
glyph.bearingX = static_cast<float>(xoff); data.height = static_cast<float>(h);
glyph.bearingY = static_cast<float>(yoff); data.bearingX = static_cast<float>(xoff);
glyph.advance = advancePx; data.bearingY = static_cast<float>(yoff);
data.advance = advancePx;
// 计算 UV 坐标
// stb_rect_pack 使用左上角为原点OpenGL纹理使用左下角为原点 // stb_rect_pack 使用左上角为原点OpenGL纹理使用左下角为原点
// 需要翻转V坐标 // 需要翻转V坐标
float v0 = static_cast<float>(atlasY) / ATLAS_HEIGHT; float v0 = static_cast<float>(atlasY) / ATLAS_HEIGHT;
float v1 = static_cast<float>(atlasY + h) / ATLAS_HEIGHT; float v1 = static_cast<float>(atlasY + h) / ATLAS_HEIGHT;
glyph.u0 = static_cast<float>(atlasX) / ATLAS_WIDTH; data.u0 = static_cast<float>(atlasX) / ATLAS_WIDTH;
glyph.v0 = 1.0f - v1; // 翻转V坐标 data.v0 = 1.0f - v1; // 翻转V坐标
glyph.u1 = static_cast<float>(atlasX + w) / ATLAS_WIDTH; data.u1 = static_cast<float>(atlasX + w) / ATLAS_WIDTH;
glyph.v1 = 1.0f - v0; // 翻转V坐标 data.v1 = 1.0f - v0; // 翻转V坐标
glyphs_[codepoint] = glyph; glyphs_[codepoint] = data;
// 将 SDF 单通道数据转换为 RGBA 格式(统一格式) // 将 SDF 单通道数据转换为 RGBA 格式(统一格式)
size_t pixelCount = static_cast<size_t>(w) * static_cast<size_t>(h); size_t pixelCount = static_cast<size_t>(w) * static_cast<size_t>(h);
@ -216,33 +295,33 @@ void GLFontAtlas::cacheGlyph(char32_t codepoint) const {
glyphRgbaCache_[i * 4 + 3] = alpha; // A - SDF 值存储在 Alpha 通道 glyphRgbaCache_[i * 4 + 3] = alpha; // A - SDF 值存储在 Alpha 通道
} }
// 直接设置像素对齐为 4无需查询当前状态 // 更新纹理 - OpenGL纹理坐标原点在左下角需要将Y坐标翻转
glBindTexture(GL_TEXTURE_2D, texture_->getTextureID()); updateAtlas(atlasX, ATLAS_HEIGHT - atlasY - h, w, h, glyphRgbaCache_);
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
// OpenGL纹理坐标原点在左下角需要将Y坐标翻转
glTexSubImage2D(GL_TEXTURE_2D, 0, atlasX, ATLAS_HEIGHT - atlasY - h, w, h,
GL_RGBA, GL_UNSIGNED_BYTE, glyphRgbaCache_.data());
stbtt_FreeSDF(sdf, nullptr); stbtt_FreeSDF(sdf, nullptr);
return; return;
} }
// 普通位图渲染模式
int x0 = 0, y0 = 0, x1 = 0, y1 = 0; int x0 = 0, y0 = 0, x1 = 0, y1 = 0;
stbtt_GetCodepointBitmapBox(&fontInfo_, static_cast<int>(codepoint), scale_, stbtt_GetCodepointBitmapBox(&fontInfo_, static_cast<int>(codepoint), scale_,
scale_, &x0, &y0, &x1, &y1); scale_, &x0, &y0, &x1, &y1);
int w = x1 - x0; int w = x1 - x0;
int h = y1 - y0; int h = y1 - y0;
int xoff = x0; int xoff = x0;
// y0 是相对于基线的偏移(通常为负值,表示在基线上方)
// bearingY 应该是字形顶部相对于基线的偏移
int yoff = y0; int yoff = y0;
if (w <= 0 || h <= 0) { if (w <= 0 || h <= 0) {
Glyph glyph{}; // 空白字符(如空格)
glyph.advance = advancePx; GlyphData data{};
glyphs_[codepoint] = glyph; data.advance = advancePx;
glyphs_[codepoint] = data;
return; return;
} }
// 使用预分配缓冲区 // 使用预分配缓冲区渲染字形
size_t pixelCount = static_cast<size_t>(w) * static_cast<size_t>(h); size_t pixelCount = static_cast<size_t>(w) * static_cast<size_t>(h);
glyphBitmapCache_.resize(pixelCount); glyphBitmapCache_.resize(pixelCount);
stbtt_MakeCodepointBitmap(&fontInfo_, glyphBitmapCache_.data(), w, h, w, stbtt_MakeCodepointBitmap(&fontInfo_, glyphBitmapCache_.data(), w, h, w,
@ -257,35 +336,32 @@ void GLFontAtlas::cacheGlyph(char32_t codepoint) const {
stbrp_pack_rects(&packContext_, &rect, 1); stbrp_pack_rects(&packContext_, &rect, 1);
if (!rect.was_packed) { if (!rect.was_packed) {
// 图集已满,无法缓存更多字形 E2D_LOG_WARN("Font atlas is full, cannot cache codepoint: {}", codepoint);
E2D_LOG_WARN("Font atlas is full, cannot cache codepoint: {}",
static_cast<int>(codepoint));
return; return;
} }
int atlasX = rect.x + PADDING; int atlasX = rect.x + PADDING;
int atlasY = rect.y + PADDING; int atlasY = rect.y + PADDING;
// 创建字形信息 // 创建字形数据
Glyph glyph; GlyphData data;
glyph.width = static_cast<float>(w); data.width = static_cast<float>(w);
glyph.height = static_cast<float>(h); data.height = static_cast<float>(h);
glyph.bearingX = static_cast<float>(xoff); data.bearingX = static_cast<float>(xoff);
glyph.bearingY = static_cast<float>(yoff); data.bearingY = static_cast<float>(yoff);
glyph.advance = advancePx; data.advance = advancePx;
// 计算纹理坐标(相对于图集) // 计算 UV 坐标
// stb_rect_pack 使用左上角为原点OpenGL纹理使用左下角为原点 // stb_rect_pack 使用左上角为原点OpenGL纹理使用左下角为原点
// 需要翻转V坐标 // 需要翻转V坐标
float v0 = static_cast<float>(atlasY) / ATLAS_HEIGHT; float v0 = static_cast<float>(atlasY) / ATLAS_HEIGHT;
float v1 = static_cast<float>(atlasY + h) / ATLAS_HEIGHT; float v1 = static_cast<float>(atlasY + h) / ATLAS_HEIGHT;
glyph.u0 = static_cast<float>(atlasX) / ATLAS_WIDTH; data.u0 = static_cast<float>(atlasX) / ATLAS_WIDTH;
glyph.v0 = 1.0f - v1; // 翻转V坐标 data.v0 = 1.0f - v1; // 翻转V坐标
glyph.u1 = static_cast<float>(atlasX + w) / ATLAS_WIDTH; data.u1 = static_cast<float>(atlasX + w) / ATLAS_WIDTH;
glyph.v1 = 1.0f - v0; // 翻转V坐标 data.v1 = 1.0f - v0; // 翻转V坐标
// 存储字形 glyphs_[codepoint] = data;
glyphs_[codepoint] = glyph;
// 将单通道字形数据转换为 RGBA 格式白色字形Alpha 通道存储灰度) // 将单通道字形数据转换为 RGBA 格式白色字形Alpha 通道存储灰度)
glyphRgbaCache_.resize(pixelCount * 4); glyphRgbaCache_.resize(pixelCount * 4);
@ -297,13 +373,21 @@ void GLFontAtlas::cacheGlyph(char32_t codepoint) const {
glyphRgbaCache_[i * 4 + 3] = alpha; // A glyphRgbaCache_[i * 4 + 3] = alpha; // A
} }
// 更新纹理 - 将字形数据上传到图集的指定位置 // 更新纹理 - OpenGL纹理坐标原点在左下角需要将Y坐标翻转
// 直接设置像素对齐为 4无需查询当前状态 updateAtlas(atlasX, ATLAS_HEIGHT - atlasY - h, w, h, glyphRgbaCache_);
glBindTexture(GL_TEXTURE_2D, texture_->getTextureID()); }
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
// OpenGL纹理坐标原点在左下角需要将Y坐标翻转 // ============================================================================
glTexSubImage2D(GL_TEXTURE_2D, 0, atlasX, ATLAS_HEIGHT - atlasY - h, w, h, // 更新图集纹理区域
GL_RGBA, GL_UNSIGNED_BYTE, glyphRgbaCache_.data()); // ============================================================================
void GLFontAtlas::updateAtlas(int x, int y, int width, int height,
const std::vector<uint8_t> &data) {
if (texture_) {
texture_->bind();
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, width, height, GL_RGBA,
GL_UNSIGNED_BYTE, data.data());
}
} }
} // namespace extra2d } // namespace extra2d

View File

@ -0,0 +1,268 @@
#include <extra2d/graphics/backends/opengl/gl_framebuffer.h>
#include <extra2d/graphics/backends/opengl/gl_texture.h>
#include <extra2d/utils/logger.h>
namespace extra2d {
// ============================================================================
// GLFramebuffer 实现
// ============================================================================
GLFramebuffer::GLFramebuffer() = default;
GLFramebuffer::~GLFramebuffer() {
shutdown();
}
bool GLFramebuffer::init(const FramebufferDesc& desc) {
if (fboID_ != 0) {
shutdown();
}
width_ = desc.width;
height_ = desc.height;
numColorAttachments_ = desc.colorAttachments;
hasDepth_ = desc.hasDepth;
hasStencil_ = desc.hasStencil;
// 限制颜色附件数
if (numColorAttachments_ > MAX_COLOR_ATTACHMENTS) {
numColorAttachments_ = MAX_COLOR_ATTACHMENTS;
}
// 生成 FBO
glGenFramebuffers(1, &fboID_);
if (fboID_ == 0) {
E2D_LOG_ERROR("Failed to generate OpenGL framebuffer");
return false;
}
E2D_LOG_DEBUG("GLFramebuffer created: ID={}, Size={}x{}, ColorAttachments={}",
fboID_, width_, height_, numColorAttachments_);
return true;
}
void GLFramebuffer::shutdown() {
if (fboID_ != 0) {
glDeleteFramebuffers(1, &fboID_);
E2D_LOG_DEBUG("GLFramebuffer destroyed: ID={}", fboID_);
fboID_ = 0;
}
// 清理纹理引用
for (auto& tex : colorTextures_) {
tex.reset();
}
depthTexture_.reset();
depthStencilTexture_.reset();
hasInternalTextures_ = false;
}
void GLFramebuffer::bind() {
if (fboID_ != 0) {
glBindFramebuffer(GL_FRAMEBUFFER, fboID_);
}
}
void GLFramebuffer::unbind() {
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
void GLFramebuffer::attachColorTexture(Ptr<Texture> texture, int attachment) {
if (fboID_ == 0 || !texture || attachment < 0 || attachment >= MAX_COLOR_ATTACHMENTS) {
return;
}
bind();
// 获取 OpenGL 纹理 ID
GLuint texID = static_cast<GLuint>(reinterpret_cast<uintptr_t>(texture->getNativeHandle()));
glFramebufferTexture2D(GL_FRAMEBUFFER, getColorAttachment(attachment),
GL_TEXTURE_2D, texID, 0);
colorTextures_[attachment] = texture;
unbind();
}
void GLFramebuffer::attachDepthTexture(Ptr<Texture> texture) {
if (fboID_ == 0 || !texture) {
return;
}
bind();
// 获取 OpenGL 纹理 ID
GLuint texID = static_cast<GLuint>(reinterpret_cast<uintptr_t>(texture->getNativeHandle()));
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
GL_TEXTURE_2D, texID, 0);
depthTexture_ = texture;
hasDepth_ = true;
hasStencil_ = false;
unbind();
}
void GLFramebuffer::attachDepthStencilTexture(Ptr<Texture> texture) {
if (fboID_ == 0 || !texture) {
return;
}
bind();
// 获取 OpenGL 纹理 ID
GLuint texID = static_cast<GLuint>(reinterpret_cast<uintptr_t>(texture->getNativeHandle()));
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT,
GL_TEXTURE_2D, texID, 0);
depthStencilTexture_ = texture;
hasDepth_ = true;
hasStencil_ = true;
unbind();
}
bool GLFramebuffer::isComplete() {
if (fboID_ == 0) {
return false;
}
bind();
bool complete = checkStatus();
unbind();
return complete;
}
Ptr<Texture> GLFramebuffer::getColorTexture(int attachment) const {
if (attachment >= 0 && attachment < MAX_COLOR_ATTACHMENTS) {
return colorTextures_[attachment];
}
return nullptr;
}
Ptr<Texture> GLFramebuffer::getDepthTexture() const {
return depthTexture_;
}
void GLFramebuffer::clear(const Color& color, bool clearColor,
bool clearDepth, bool clearStencil) {
if (fboID_ == 0) {
return;
}
bind();
GLbitfield mask = 0;
if (clearColor) {
mask |= GL_COLOR_BUFFER_BIT;
glClearColor(color.r, color.g, color.b, color.a);
}
if (clearDepth) {
mask |= GL_DEPTH_BUFFER_BIT;
glClearDepthf(1.0f);
}
if (clearStencil) {
mask |= GL_STENCIL_BUFFER_BIT;
glClearStencil(0);
}
if (mask != 0) {
glClear(mask);
}
unbind();
}
void GLFramebuffer::setViewport(int x, int y, int width, int height) {
glViewport(x, y, width, height);
}
bool GLFramebuffer::readPixels(int x, int y, int width, int height,
std::vector<uint8_t>& outData) {
if (fboID_ == 0 || width <= 0 || height <= 0) {
return false;
}
// 计算需要的缓冲区大小 (RGBA8)
size_t dataSize = width * height * 4;
outData.resize(dataSize);
bind();
glReadPixels(x, y, width, height, GL_RGBA, GL_UNSIGNED_BYTE, outData.data());
unbind();
return true;
}
bool GLFramebuffer::createWithTextures(int width, int height,
PixelFormat colorFormat,
PixelFormat depthFormat) {
FramebufferDesc desc;
desc.width = width;
desc.height = height;
desc.colorAttachments = 1;
desc.hasDepth = (depthFormat != PixelFormat::RGBA8);
desc.hasStencil = (depthFormat == PixelFormat::Depth24Stencil8);
if (!init(desc)) {
return false;
}
hasInternalTextures_ = true;
// 创建颜色纹理
// 注意:这里简化处理,实际应该通过纹理工厂创建
// 暂时返回 true实际纹理创建由调用者处理
return true;
}
bool GLFramebuffer::checkStatus() {
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
switch (status) {
case GL_FRAMEBUFFER_COMPLETE:
return true;
case GL_FRAMEBUFFER_UNDEFINED:
E2D_LOG_ERROR("Framebuffer incomplete: GL_FRAMEBUFFER_UNDEFINED");
break;
case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
E2D_LOG_ERROR("Framebuffer incomplete: GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT");
break;
case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
E2D_LOG_ERROR("Framebuffer incomplete: GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT");
break;
#ifndef GL_ES_VERSION_2_0
case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER:
E2D_LOG_ERROR("Framebuffer incomplete: GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER");
break;
case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER:
E2D_LOG_ERROR("Framebuffer incomplete: GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER");
break;
#endif
case GL_FRAMEBUFFER_UNSUPPORTED:
E2D_LOG_ERROR("Framebuffer incomplete: GL_FRAMEBUFFER_UNSUPPORTED");
break;
default:
E2D_LOG_ERROR("Framebuffer incomplete: Unknown error {}", status);
break;
}
return false;
}
GLenum GLFramebuffer::getColorAttachment(int index) {
return GL_COLOR_ATTACHMENT0 + index;
}
} // namespace extra2d

View File

@ -0,0 +1,222 @@
#include <extra2d/graphics/backends/opengl/gl_pipeline.h>
#include <extra2d/utils/logger.h>
namespace extra2d {
// ============================================================================
// GLPipeline 实现
// ============================================================================
GLPipeline::GLPipeline() = default;
GLPipeline::~GLPipeline() {
shutdown();
}
bool GLPipeline::init(const PipelineDesc& desc) {
if (initialized_) {
shutdown();
}
blendMode_ = desc.blendMode;
blendEnabled_ = desc.blendEnabled;
depthTest_ = desc.depthTest;
depthWrite_ = desc.depthWrite;
depthFunc_ = desc.depthFunc;
cullMode_ = desc.cullMode;
initialized_ = true;
E2D_LOG_DEBUG("GLPipeline initialized: blendMode={}, depthTest={}, cullMode={}",
static_cast<int>(blendMode_), depthTest_, static_cast<int>(cullMode_));
return true;
}
void GLPipeline::shutdown() {
initialized_ = false;
}
void GLPipeline::bind() {
if (!initialized_) {
return;
}
applyAllStates();
}
void GLPipeline::unbind() {
// OpenGL 不需要显式解绑管线
}
void GLPipeline::setBlendMode(BlendMode mode) {
blendMode_ = mode;
applyBlendState();
}
void GLPipeline::setDepthTest(bool enabled) {
depthTest_ = enabled;
applyDepthState();
}
void GLPipeline::setDepthWrite(bool enabled) {
depthWrite_ = enabled;
applyDepthState();
}
void GLPipeline::setDepthFunc(DepthFunc func) {
depthFunc_ = func;
applyDepthState();
}
void GLPipeline::setCullMode(CullMode mode) {
cullMode_ = mode;
applyCullState();
}
void GLPipeline::setViewport(int x, int y, int width, int height) {
viewportX_ = x;
viewportY_ = y;
viewportWidth_ = width;
viewportHeight_ = height;
// 检查缓存,避免冗余调用
if (x != cachedViewportX_ || y != cachedViewportY_ ||
width != cachedViewportWidth_ || height != cachedViewportHeight_) {
glViewport(x, y, width, height);
cachedViewportX_ = x;
cachedViewportY_ = y;
cachedViewportWidth_ = width;
cachedViewportHeight_ = height;
}
}
void GLPipeline::getViewport(int& x, int& y, int& width, int& height) const {
x = viewportX_;
y = viewportY_;
width = viewportWidth_;
height = viewportHeight_;
}
void GLPipeline::applyAllStates() {
applyBlendState();
applyDepthState();
applyCullState();
// 应用视口
if (viewportWidth_ > 0 && viewportHeight_ > 0) {
setViewport(viewportX_, viewportY_, viewportWidth_, viewportHeight_);
}
}
void GLPipeline::applyBlendState() {
// 检查是否需要启用/禁用混合
if (blendEnabled_ != cachedBlendEnabled_) {
if (blendEnabled_) {
glEnable(GL_BLEND);
} else {
glDisable(GL_BLEND);
}
cachedBlendEnabled_ = blendEnabled_;
}
// 如果禁用了混合,不需要设置混合函数
if (!blendEnabled_) {
return;
}
// 检查混合模式是否改变
if (blendMode_ != cachedBlendMode_) {
GLenum srcFactor, dstFactor;
getBlendFactors(blendMode_, srcFactor, dstFactor);
glBlendFunc(srcFactor, dstFactor);
cachedBlendMode_ = blendMode_;
}
}
void GLPipeline::applyDepthState() {
// 深度测试
if (depthTest_ != cachedDepthTest_) {
if (depthTest_) {
glEnable(GL_DEPTH_TEST);
} else {
glDisable(GL_DEPTH_TEST);
}
cachedDepthTest_ = depthTest_;
}
// 深度写入
if (depthWrite_ != cachedDepthWrite_) {
glDepthMask(depthWrite_ ? GL_TRUE : GL_FALSE);
cachedDepthWrite_ = depthWrite_;
}
// 深度函数
if (depthFunc_ != cachedDepthFunc_) {
glDepthFunc(convertDepthFunc(depthFunc_));
cachedDepthFunc_ = depthFunc_;
}
}
void GLPipeline::applyCullState() {
// 检查裁剪模式是否改变
if (cullMode_ != cachedCullMode_) {
if (cullMode_ == CullMode::None) {
glDisable(GL_CULL_FACE);
} else {
glEnable(GL_CULL_FACE);
glCullFace(convertCullMode(cullMode_));
}
cachedCullMode_ = cullMode_;
}
}
void GLPipeline::getBlendFactors(BlendMode mode, GLenum& srcFactor, GLenum& dstFactor) {
switch (mode) {
case BlendMode::None:
srcFactor = GL_ONE;
dstFactor = GL_ZERO;
break;
case BlendMode::Alpha:
srcFactor = GL_SRC_ALPHA;
dstFactor = GL_ONE_MINUS_SRC_ALPHA;
break;
case BlendMode::Additive:
srcFactor = GL_SRC_ALPHA;
dstFactor = GL_ONE;
break;
case BlendMode::Multiply:
srcFactor = GL_DST_COLOR;
dstFactor = GL_ONE_MINUS_SRC_ALPHA;
break;
default:
srcFactor = GL_SRC_ALPHA;
dstFactor = GL_ONE_MINUS_SRC_ALPHA;
break;
}
}
GLenum GLPipeline::convertDepthFunc(DepthFunc func) {
switch (func) {
case DepthFunc::Never: return GL_NEVER;
case DepthFunc::Less: return GL_LESS;
case DepthFunc::Equal: return GL_EQUAL;
case DepthFunc::LessEqual: return GL_LEQUAL;
case DepthFunc::Greater: return GL_GREATER;
case DepthFunc::NotEqual: return GL_NOTEQUAL;
case DepthFunc::GreaterEqual: return GL_GEQUAL;
case DepthFunc::Always: return GL_ALWAYS;
default: return GL_LESS;
}
}
GLenum GLPipeline::convertCullMode(CullMode mode) {
switch (mode) {
case CullMode::Front: return GL_FRONT;
case CullMode::Back: return GL_BACK;
case CullMode::Both: return GL_FRONT_AND_BACK;
default: return GL_BACK;
}
}
} // namespace extra2d

View File

@ -1,13 +1,15 @@
#include <SDL.h>
#include <algorithm> #include <algorithm>
#include <cmath> #include <cmath>
#include <cstring> #include <cstring>
#include <extra2d/graphics/backends/opengl/gl_context.h>
#include <extra2d/graphics/backends/opengl/gl_font_atlas.h>
#include <extra2d/graphics/backends/opengl/gl_framebuffer.h>
#include <extra2d/graphics/backends/opengl/gl_renderer.h>
#include <extra2d/graphics/backends/opengl/gl_texture.h>
#include <extra2d/graphics/batch/sprite_batch.h>
#include <extra2d/graphics/memory/gpu_context.h> #include <extra2d/graphics/memory/gpu_context.h>
#include <extra2d/graphics/opengl/gl_font_atlas.h>
#include <extra2d/graphics/opengl/gl_renderer.h>
#include <extra2d/graphics/opengl/gl_texture.h>
#include <extra2d/graphics/shader/shader_manager.h>
#include <extra2d/graphics/memory/vram_manager.h> #include <extra2d/graphics/memory/vram_manager.h>
#include <extra2d/graphics/shader/shader_manager.h>
#include <extra2d/platform/iwindow.h> #include <extra2d/platform/iwindow.h>
#include <extra2d/utils/logger.h> #include <extra2d/utils/logger.h>
#include <vector> #include <vector>
@ -17,30 +19,11 @@ namespace extra2d {
// VBO 初始大小(用于 VRAM 跟踪) // VBO 初始大小(用于 VRAM 跟踪)
static constexpr size_t SHAPE_VBO_SIZE = 1024 * sizeof(float); static constexpr size_t SHAPE_VBO_SIZE = 1024 * sizeof(float);
// ============================================================================
// BlendMode 查找表 - 编译期构建,运行时 O(1) 查找
// ============================================================================
struct BlendState {
bool enable;
GLenum srcFactor;
GLenum dstFactor;
};
static constexpr BlendState BLEND_STATES[] = {
{false, 0, 0}, // BlendMode::None
{true, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA}, // BlendMode::Alpha
{true, GL_SRC_ALPHA, GL_ONE}, // BlendMode::Additive
{true, GL_DST_COLOR, GL_ONE_MINUS_SRC_ALPHA} // BlendMode::Multiply
};
static constexpr size_t BLEND_STATE_COUNT =
sizeof(BLEND_STATES) / sizeof(BLEND_STATES[0]);
/** /**
* @brief OpenGL渲染器成员变量 * @brief OpenGL渲染器成员变量
*/ */
GLRenderer::GLRenderer() GLRenderer::GLRenderer()
: window_(nullptr), shapeVao_(0), shapeVbo_(0), lineVao_(0), lineVbo_(0), : window_(nullptr), shapeVao_(0), lineVao_(0),
vsync_(true), shapeVertexCount_(0), currentShapeMode_(GL_TRIANGLES), vsync_(true), shapeVertexCount_(0), currentShapeMode_(GL_TRIANGLES),
lineVertexCount_(0), currentLineWidth_(1.0f) { lineVertexCount_(0), currentLineWidth_(1.0f) {
resetStats(); resetStats();
@ -65,7 +48,11 @@ GLRenderer::~GLRenderer() { shutdown(); }
bool GLRenderer::init(IWindow *window) { bool GLRenderer::init(IWindow *window) {
window_ = window; window_ = window;
// Switch: GL 上下文已通过 SDL2 + EGL 初始化,无需 glewInit() // 初始化 OpenGL 上下文Switch 平台已通过 SDL2 + EGL 初始化GLContext 会处理兼容性)
if (!GLContext::get().init()) {
E2D_LOG_ERROR("Failed to initialize OpenGL context");
return false;
}
// 初始化精灵批渲染器 // 初始化精灵批渲染器
if (!spriteBatch_.init()) { if (!spriteBatch_.init()) {
@ -76,16 +63,24 @@ bool GLRenderer::init(IWindow *window) {
// 初始化形状渲染 // 初始化形状渲染
initShapeRendering(); initShapeRendering();
// 设置 OpenGL 状态 // 初始化管线状态管理
glEnable(GL_BLEND); PipelineDesc pipelineDesc;
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); pipelineDesc.blendMode = BlendMode::Alpha;
pipelineDesc.depthTest = false;
pipelineDesc.depthWrite = false;
if (!pipeline_.init(pipelineDesc)) {
E2D_LOG_ERROR("Failed to initialize GLPipeline");
return false;
}
// 应用初始管线状态
pipeline_.applyAllStates();
// 标记 GPU 上下文为有效 // 标记 GPU 上下文为有效
GPUContext::get().markValid(); GPUContext::get().markValid();
E2D_LOG_INFO("OpenGL Renderer initialized"); E2D_LOG_INFO("OpenGL Renderer initialized");
E2D_LOG_INFO("OpenGL Version: {}", E2D_LOG_INFO("OpenGL Version: {}", GLContext::get().getVersionString());
reinterpret_cast<const char *>(glGetString(GL_VERSION)));
return true; return true;
} }
@ -100,24 +95,22 @@ void GLRenderer::shutdown() {
spriteBatch_.shutdown(); spriteBatch_.shutdown();
if (lineVbo_ != 0) { // 关闭 GLBuffer自动释放 VBO
glDeleteBuffers(1, &lineVbo_); lineBuffer_.shutdown();
VRAMMgr::get().freeBuffer(MAX_LINE_VERTICES * sizeof(ShapeVertex)); shapeBuffer_.shutdown();
lineVbo_ = 0;
} // 删除 VAOVAO 仍然手动管理)
if (lineVao_ != 0) { if (lineVao_ != 0) {
glDeleteVertexArrays(1, &lineVao_); glDeleteVertexArrays(1, &lineVao_);
lineVao_ = 0; lineVao_ = 0;
} }
if (shapeVbo_ != 0) {
glDeleteBuffers(1, &shapeVbo_);
VRAMMgr::get().freeBuffer(MAX_SHAPE_VERTICES * sizeof(ShapeVertex));
shapeVbo_ = 0;
}
if (shapeVao_ != 0) { if (shapeVao_ != 0) {
glDeleteVertexArrays(1, &shapeVao_); glDeleteVertexArrays(1, &shapeVao_);
shapeVao_ = 0; shapeVao_ = 0;
} }
// 关闭 OpenGL 上下文
GLContext::get().shutdown();
} }
/** /**
@ -125,6 +118,9 @@ void GLRenderer::shutdown() {
* @param clearColor * @param clearColor
*/ */
void GLRenderer::beginFrame(const Color &clearColor) { void GLRenderer::beginFrame(const Color &clearColor) {
// 应用管线状态
pipeline_.applyAllStates();
glClearColor(clearColor.r, clearColor.g, clearColor.b, clearColor.a); glClearColor(clearColor.r, clearColor.g, clearColor.b, clearColor.a);
glClear(GL_COLOR_BUFFER_BIT); glClear(GL_COLOR_BUFFER_BIT);
resetStats(); resetStats();
@ -134,6 +130,10 @@ void GLRenderer::beginFrame(const Color &clearColor) {
* @brief * @brief
*/ */
void GLRenderer::endFrame() { void GLRenderer::endFrame() {
// 刷新所有待处理的精灵批次(自动批处理)
if (autoBatchEnabled_ && batchActive_) {
flush();
}
// 刷新所有待处理的形状批次 // 刷新所有待处理的形状批次
flushShapeBatch(); flushShapeBatch();
// 刷新所有待处理的线条批次 // 刷新所有待处理的线条批次
@ -148,7 +148,8 @@ void GLRenderer::endFrame() {
* @param height * @param height
*/ */
void GLRenderer::setViewport(int x, int y, int width, int height) { void GLRenderer::setViewport(int x, int y, int width, int height) {
glViewport(x, y, width, height); // 使用 GLPipeline 管理视口状态
pipeline_.setViewport(x, y, width, height);
} }
/** /**
@ -157,8 +158,10 @@ void GLRenderer::setViewport(int x, int y, int width, int height) {
*/ */
void GLRenderer::setVSync(bool enabled) { void GLRenderer::setVSync(bool enabled) {
vsync_ = enabled; vsync_ = enabled;
// 使用 SDL2 设置交换间隔 // 通过窗口接口设置垂直同步
SDL_GL_SetSwapInterval(enabled ? 1 : 0); if (window_) {
window_->setVSync(enabled);
}
} }
/** /**
@ -166,31 +169,8 @@ void GLRenderer::setVSync(bool enabled) {
* @param mode * @param mode
*/ */
void GLRenderer::setBlendMode(BlendMode mode) { void GLRenderer::setBlendMode(BlendMode mode) {
// 状态缓存检查,避免冗余 GL 调用 // 使用 GLPipeline 管理混合状态
if (cachedBlendMode_ == mode) { pipeline_.setBlendMode(mode);
return;
}
cachedBlendMode_ = mode;
// 使用查找表替代 switch
size_t index = static_cast<size_t>(mode);
if (index >= BLEND_STATE_COUNT) {
index = 0;
}
const BlendState &state = BLEND_STATES[index];
if (state.enable) {
if (!blendEnabled_) {
glEnable(GL_BLEND);
blendEnabled_ = true;
}
glBlendFunc(state.srcFactor, state.dstFactor);
} else {
if (blendEnabled_) {
glDisable(GL_BLEND);
blendEnabled_ = false;
}
}
} }
/** /**
@ -256,9 +236,45 @@ Ptr<Texture> GLRenderer::loadTexture(const std::string &filepath) {
} }
/** /**
* @brief * @brief 使
*/ */
void GLRenderer::beginSpriteBatch() { spriteBatch_.begin(viewProjection_); } void GLRenderer::ensureBatchActive() {
if (!batchActive_) {
spriteBatch_.begin(viewProjection_);
batchActive_ = true;
currentBatchTexture_ = nullptr;
pendingSprites_.clear();
}
}
/**
* @brief 使
*/
void GLRenderer::submitPendingSprites() {
if (pendingSprites_.empty()) {
return;
}
// 提交所有待处理的精灵
spriteBatch_.drawBatch(*currentBatchTexture_, pendingSprites_);
pendingSprites_.clear();
currentBatchTexture_ = nullptr;
}
/**
* @brief
* @note drawSprite/drawText
*/
void GLRenderer::beginSpriteBatch() {
// 如果自动批处理已激活,先提交
if (autoBatchEnabled_ && batchActive_) {
flush();
}
// 禁用自动批处理,进入手动模式
autoBatchEnabled_ = false;
spriteBatch_.begin(viewProjection_);
batchActive_ = true;
}
/** /**
* @brief * @brief
@ -272,29 +288,65 @@ void GLRenderer::beginSpriteBatch() { spriteBatch_.begin(viewProjection_); }
void GLRenderer::drawSprite(const Texture &texture, const Rect &destRect, void GLRenderer::drawSprite(const Texture &texture, const Rect &destRect,
const Rect &srcRect, const Color &tint, const Rect &srcRect, const Color &tint,
float rotation, const Vec2 &anchor) { float rotation, const Vec2 &anchor) {
GLSpriteBatch::SpriteData data; // 自动批处理模式
data.position = glm::vec2(destRect.origin.x, destRect.origin.y); if (autoBatchEnabled_) {
data.size = glm::vec2(destRect.size.width, destRect.size.height); ensureBatchActive();
Texture *tex = const_cast<Texture *>(&texture); // 如果纹理变化或缓冲区满,先提交当前批次
float texW = static_cast<float>(tex->getWidth()); if (currentBatchTexture_ != &texture ||
float texH = static_cast<float>(tex->getHeight()); pendingSprites_.size() >= MAX_BATCH_SPRITES) {
submitPendingSprites();
currentBatchTexture_ = &texture;
}
// 纹理坐标计算 // 创建精灵数据
float u1 = srcRect.origin.x / texW; SpriteData data;
float u2 = (srcRect.origin.x + srcRect.size.width) / texW; data.position = Vec2(destRect.origin.x, destRect.origin.y);
float v1 = srcRect.origin.y / texH; data.size = Vec2(destRect.size.width, destRect.size.height);
float v2 = (srcRect.origin.y + srcRect.size.height) / texH;
data.texCoordMin = glm::vec2(glm::min(u1, u2), glm::min(v1, v2)); Texture *tex = const_cast<Texture *>(&texture);
data.texCoordMax = glm::vec2(glm::max(u1, u2), glm::max(v1, v2)); float texW = static_cast<float>(tex->getWidth());
float texH = static_cast<float>(tex->getHeight());
data.color = glm::vec4(tint.r, tint.g, tint.b, tint.a); // 纹理坐标计算
data.rotation = rotation * 3.14159f / 180.0f; float u1 = srcRect.origin.x / texW;
data.anchor = glm::vec2(anchor.x, anchor.y); float u2 = (srcRect.origin.x + srcRect.size.width) / texW;
data.isSDF = false; float v1 = srcRect.origin.y / texH;
float v2 = (srcRect.origin.y + srcRect.size.height) / texH;
spriteBatch_.draw(texture, data); data.uvRect = Rect(Vec2(glm::min(u1, u2), glm::min(v1, v2)),
Size(glm::abs(u2 - u1), glm::abs(v2 - v1)));
data.color = tint;
data.rotation = rotation * 3.14159f / 180.0f;
data.pivot = Vec2(anchor.x, anchor.y);
// 添加到待处理列表
pendingSprites_.push_back(data);
} else {
// 手动批处理模式
SpriteData data;
data.position = Vec2(destRect.origin.x, destRect.origin.y);
data.size = Vec2(destRect.size.width, destRect.size.height);
Texture *tex = const_cast<Texture *>(&texture);
float texW = static_cast<float>(tex->getWidth());
float texH = static_cast<float>(tex->getHeight());
float u1 = srcRect.origin.x / texW;
float u2 = (srcRect.origin.x + srcRect.size.width) / texW;
float v1 = srcRect.origin.y / texH;
float v2 = (srcRect.origin.y + srcRect.size.height) / texH;
data.uvRect = Rect(Vec2(glm::min(u1, u2), glm::min(v1, v2)),
Size(glm::abs(u2 - u1), glm::abs(v2 - v1)));
data.color = tint;
data.rotation = rotation * 3.14159f / 180.0f;
data.pivot = Vec2(anchor.x, anchor.y);
spriteBatch_.draw(texture, data);
}
} }
/** /**
@ -313,11 +365,34 @@ void GLRenderer::drawSprite(const Texture &texture, const Vec2 &position,
} }
/** /**
* @brief * @brief
* @note
*/ */
void GLRenderer::endSpriteBatch() { void GLRenderer::endSpriteBatch() {
spriteBatch_.end(); if (autoBatchEnabled_) {
stats_.drawCalls += spriteBatch_.getDrawCallCount(); // 自动模式下,只是标记批处理结束
flush();
} else {
// 手动模式下,提交批处理并恢复自动模式
spriteBatch_.end();
stats_.drawCalls += spriteBatch_.getDrawCallCount();
batchActive_ = false;
autoBatchEnabled_ = true;
}
}
/**
* @brief
* @note
*/
void GLRenderer::flush() {
if (autoBatchEnabled_ && batchActive_) {
submitPendingSprites();
spriteBatch_.end();
stats_.drawCalls += spriteBatch_.getDrawCallCount();
batchActive_ = false;
currentBatchTexture_ = nullptr;
}
} }
/** /**
@ -578,13 +653,38 @@ void GLRenderer::drawText(const FontAtlas &font, const std::string &text,
float cursorY = y; float cursorY = y;
float baselineY = cursorY + font.getAscent(); float baselineY = cursorY + font.getAscent();
// 确保批处理已激活(自动批处理)
if (autoBatchEnabled_) {
ensureBatchActive();
}
// 检查纹理变化,如果纹理不同则先提交当前批次
if (autoBatchEnabled_ && currentBatchTexture_ != nullptr &&
currentBatchTexture_ != font.getTexture()) {
submitPendingSprites();
}
if (autoBatchEnabled_) {
currentBatchTexture_ = font.getTexture();
}
// 收集所有字符数据用于批处理 // 收集所有字符数据用于批处理
std::vector<GLSpriteBatch::SpriteData> sprites; std::vector<SpriteData> sprites;
sprites.reserve(text.size()); // 预分配空间 sprites.reserve(text.size()); // 预分配空间
for (char c : text) { for (char c : text) {
char32_t codepoint = static_cast<char32_t>(static_cast<unsigned char>(c)); char32_t codepoint = static_cast<char32_t>(static_cast<unsigned char>(c));
if (codepoint == '\n') { if (codepoint == '\n') {
// 换行时,将当前行添加到待处理列表
if (!sprites.empty()) {
if (autoBatchEnabled_) {
pendingSprites_.insert(pendingSprites_.end(), sprites.begin(),
sprites.end());
} else {
// 手动模式直接提交
spriteBatch_.drawBatch(*font.getTexture(), sprites);
}
sprites.clear();
}
cursorX = x; cursorX = x;
cursorY += font.getLineHeight(); cursorY += font.getLineHeight();
baselineY = cursorY + font.getAscent(); baselineY = cursorY + font.getAscent();
@ -596,30 +696,50 @@ void GLRenderer::drawText(const FontAtlas &font, const std::string &text,
float penX = cursorX; float penX = cursorX;
cursorX += glyph->advance; cursorX += glyph->advance;
if (glyph->width <= 0.0f || glyph->height <= 0.0f) { // 使用 epsilon 比较浮点数,避免精度问题
constexpr float EPSILON = 0.001f;
if (glyph->width < EPSILON || glyph->height < EPSILON) {
continue; continue;
} }
// 计算字形位置
// bearingX: 水平偏移(从左边缘到字形左边缘)
// bearingY: 垂直偏移(从基线到字形顶部,通常为负值)
float xPos = penX + glyph->bearingX; float xPos = penX + glyph->bearingX;
float yPos = baselineY + glyph->bearingY; float yPos = baselineY + glyph->bearingY;
GLSpriteBatch::SpriteData data; SpriteData data;
data.position = glm::vec2(xPos, yPos); // 设置精灵中心位置(精灵批处理使用中心点)
data.size = glm::vec2(glyph->width, glyph->height); data.position =
data.texCoordMin = glm::vec2(glyph->u0, glyph->v0); Vec2(xPos + glyph->width * 0.5f, yPos + glyph->height * 0.5f);
data.texCoordMax = glm::vec2(glyph->u1, glyph->v1); data.size = Vec2(glyph->width, glyph->height);
data.color = glm::vec4(color.r, color.g, color.b, color.a); data.uvRect = Rect(Vec2(glyph->u0, glyph->v0),
Size(glyph->u1 - glyph->u0, glyph->v1 - glyph->v0));
data.color = color;
data.rotation = 0.0f; data.rotation = 0.0f;
data.anchor = glm::vec2(0.0f, 0.0f); // pivot (0.5, 0.5) 表示中心点,这样 position 就是精灵中心
data.isSDF = font.isSDF(); data.pivot = Vec2(0.5f, 0.5f);
sprites.push_back(data); sprites.push_back(data);
// 自动批处理:如果缓冲区满,先提交当前批次
if (autoBatchEnabled_ && sprites.size() >= MAX_BATCH_SPRITES) {
pendingSprites_.insert(pendingSprites_.end(), sprites.begin(),
sprites.end());
sprites.clear();
}
} }
} }
// 使用批处理绘制所有字符 // 提交剩余的字符
if (!sprites.empty()) { if (!sprites.empty()) {
spriteBatch_.drawBatch(*font.getTexture(), sprites); if (autoBatchEnabled_) {
pendingSprites_.insert(pendingSprites_.end(), sprites.begin(),
sprites.end());
} else {
// 手动模式下直接提交
spriteBatch_.drawBatch(*font.getTexture(), sprites);
}
} }
} }
@ -641,14 +761,19 @@ void GLRenderer::initShapeRendering() {
} }
} }
// 创建形状 VAO 和 VBO // 初始化形状 GLBuffer使用 Dynamic 使用模式,因为每帧都会更新)
BufferDesc shapeBufferDesc;
shapeBufferDesc.type = BufferType::Vertex;
shapeBufferDesc.usage = BufferUsage::Dynamic;
shapeBufferDesc.size = MAX_SHAPE_VERTICES * sizeof(ShapeVertex);
if (!shapeBuffer_.init(shapeBufferDesc)) {
E2D_LOG_ERROR("Failed to initialize shape buffer");
}
// 创建形状 VAO手动管理用于顶点属性配置
glGenVertexArrays(1, &shapeVao_); glGenVertexArrays(1, &shapeVao_);
glGenBuffers(1, &shapeVbo_);
glBindVertexArray(shapeVao_); glBindVertexArray(shapeVao_);
glBindBuffer(GL_ARRAY_BUFFER, shapeVbo_); shapeBuffer_.bind();
glBufferData(GL_ARRAY_BUFFER, MAX_SHAPE_VERTICES * sizeof(ShapeVertex),
nullptr, GL_DYNAMIC_DRAW);
// 位置属性 (location = 0) // 位置属性 (location = 0)
glEnableVertexAttribArray(0); glEnableVertexAttribArray(0);
@ -662,14 +787,19 @@ void GLRenderer::initShapeRendering() {
glBindVertexArray(0); glBindVertexArray(0);
// 创建线条专用 VAO 和 VBO // 初始化线条 GLBuffer使用 Dynamic 使用模式,因为每帧都会更新)
BufferDesc lineBufferDesc;
lineBufferDesc.type = BufferType::Vertex;
lineBufferDesc.usage = BufferUsage::Dynamic;
lineBufferDesc.size = MAX_LINE_VERTICES * sizeof(ShapeVertex);
if (!lineBuffer_.init(lineBufferDesc)) {
E2D_LOG_ERROR("Failed to initialize line buffer");
}
// 创建线条 VAO手动管理用于顶点属性配置
glGenVertexArrays(1, &lineVao_); glGenVertexArrays(1, &lineVao_);
glGenBuffers(1, &lineVbo_);
glBindVertexArray(lineVao_); glBindVertexArray(lineVao_);
glBindBuffer(GL_ARRAY_BUFFER, lineVbo_); lineBuffer_.bind();
glBufferData(GL_ARRAY_BUFFER, MAX_LINE_VERTICES * sizeof(ShapeVertex),
nullptr, GL_DYNAMIC_DRAW);
// 位置属性 (location = 0) // 位置属性 (location = 0)
glEnableVertexAttribArray(0); glEnableVertexAttribArray(0);
@ -682,10 +812,6 @@ void GLRenderer::initShapeRendering() {
reinterpret_cast<void *>(offsetof(ShapeVertex, r))); reinterpret_cast<void *>(offsetof(ShapeVertex, r)));
glBindVertexArray(0); glBindVertexArray(0);
// VRAM 跟踪
VRAMMgr::get().allocBuffer(MAX_SHAPE_VERTICES * sizeof(ShapeVertex));
VRAMMgr::get().allocBuffer(MAX_LINE_VERTICES * sizeof(ShapeVertex));
} }
/** /**
@ -765,9 +891,9 @@ void GLRenderer::flushShapeBatch() {
shapeShader_->setMat4("u_viewProjection", viewProjection_); shapeShader_->setMat4("u_viewProjection", viewProjection_);
} }
glBindBuffer(GL_ARRAY_BUFFER, shapeVbo_); // 使用 GLBuffer::updateData() 更新缓冲区数据
glBufferSubData(GL_ARRAY_BUFFER, 0, shapeVertexCount_ * sizeof(ShapeVertex), shapeBuffer_.updateData(shapeVertexCache_.data(), 0,
shapeVertexCache_.data()); shapeVertexCount_ * sizeof(ShapeVertex));
glBindVertexArray(shapeVao_); glBindVertexArray(shapeVao_);
glDrawArrays(currentShapeMode_, 0, static_cast<GLsizei>(shapeVertexCount_)); glDrawArrays(currentShapeMode_, 0, static_cast<GLsizei>(shapeVertexCount_));
@ -794,9 +920,9 @@ void GLRenderer::flushLineBatch() {
shapeShader_->setMat4("u_viewProjection", viewProjection_); shapeShader_->setMat4("u_viewProjection", viewProjection_);
} }
glBindBuffer(GL_ARRAY_BUFFER, lineVbo_); // 使用 GLBuffer::updateData() 更新缓冲区数据
glBufferSubData(GL_ARRAY_BUFFER, 0, lineVertexCount_ * sizeof(ShapeVertex), lineBuffer_.updateData(lineVertexCache_.data(), 0,
lineVertexCache_.data()); lineVertexCount_ * sizeof(ShapeVertex));
glBindVertexArray(lineVao_); glBindVertexArray(lineVao_);
glDrawArrays(GL_LINES, 0, static_cast<GLsizei>(lineVertexCount_)); glDrawArrays(GL_LINES, 0, static_cast<GLsizei>(lineVertexCount_));
@ -806,4 +932,90 @@ void GLRenderer::flushLineBatch() {
lineVertexCount_ = 0; lineVertexCount_ = 0;
} }
/**
* @brief
* @param desc
* @return
*/
Ptr<GLFramebuffer> GLRenderer::createFramebuffer(const FramebufferDesc& desc) {
auto framebuffer = makePtr<GLFramebuffer>();
if (!framebuffer->init(desc)) {
E2D_LOG_ERROR("Failed to create framebuffer");
return nullptr;
}
return framebuffer;
}
/**
* @brief
* @param framebuffer nullptr
*/
void GLRenderer::bindFramebuffer(GLFramebuffer* framebuffer) {
// 先刷新所有待处理的渲染批次
flush();
flushShapeBatch();
flushLineBatch();
if (framebuffer == nullptr) {
// 绑定默认帧缓冲ID 为 0
glBindFramebuffer(GL_FRAMEBUFFER, 0);
currentFramebuffer_ = nullptr;
E2D_LOG_TRACE("Bound default framebuffer (0)");
} else {
// 绑定自定义帧缓冲
framebuffer->bind();
currentFramebuffer_ = framebuffer;
E2D_LOG_TRACE("Bound custom framebuffer (ID: {})", framebuffer->getFboID());
}
}
/**
* @brief
*/
void GLRenderer::unbindFramebuffer() {
bindFramebuffer(nullptr);
}
/**
* @brief
* @return
*/
Ptr<GLFramebuffer> GLRenderer::getDefaultFramebuffer() const {
if (!defaultFramebuffer_) {
// 延迟创建默认帧缓冲对象代表系统默认帧缓冲ID 为 0
defaultFramebuffer_ = makePtr<GLFramebuffer>();
// 注意:默认帧缓冲不需要显式初始化,它的 FBO ID 为 0
}
return defaultFramebuffer_;
}
/**
* @brief
* @param color
* @param clearColor
* @param clearDepth
* @param clearStencil
*/
void GLRenderer::clearFramebuffer(const Color& color, bool clearColor,
bool clearDepth, bool clearStencil) {
GLbitfield mask = 0;
if (clearColor) {
glClearColor(color.r, color.g, color.b, color.a);
mask |= GL_COLOR_BUFFER_BIT;
}
if (clearDepth) {
mask |= GL_DEPTH_BUFFER_BIT;
}
if (clearStencil) {
mask |= GL_STENCIL_BUFFER_BIT;
}
if (mask != 0) {
glClear(mask);
}
}
} // namespace extra2d } // namespace extra2d

View File

@ -1,4 +1,4 @@
#include <extra2d/graphics/opengl/gl_shader.h> #include <extra2d/graphics/backends/opengl/gl_shader.h>
#include <extra2d/utils/logger.h> #include <extra2d/utils/logger.h>
namespace extra2d { namespace extra2d {

View File

@ -0,0 +1,202 @@
#include <extra2d/graphics/backends/opengl/gl_sprite_batch.h>
#include <extra2d/graphics/backends/opengl/gl_texture.h>
#include <extra2d/graphics/shader/shader_manager.h>
#include <extra2d/utils/logger.h>
namespace extra2d {
GLSpriteBatch::GLSpriteBatch()
: vao_(0), currentTexture_(nullptr), drawCallCount_(0) {}
GLSpriteBatch::~GLSpriteBatch() { shutdown(); }
bool GLSpriteBatch::init() {
// 从ShaderManager获取精灵着色器
shader_ = ShaderManager::getInstance().getBuiltin("sprite");
if (!shader_) {
E2D_LOG_ERROR("Failed to get builtin sprite shader");
return false;
}
// 创建 VAO
glGenVertexArrays(1, &vao_);
glBindVertexArray(vao_);
// 初始化 VBO顶点缓冲区- 动态使用模式
BufferDesc vboDesc;
vboDesc.type = BufferType::Vertex;
vboDesc.usage = BufferUsage::Dynamic;
vboDesc.size = SpriteBatch::MAX_VERTICES * sizeof(SpriteVertex);
vboDesc.initialData = nullptr;
if (!vbo_.init(vboDesc)) {
E2D_LOG_ERROR("Failed to initialize sprite batch VBO");
return false;
}
vbo_.bind();
// 设置顶点属性
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(SpriteVertex),
reinterpret_cast<void *>(offsetof(SpriteVertex, position)));
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(SpriteVertex),
reinterpret_cast<void *>(offsetof(SpriteVertex, texCoord)));
glEnableVertexAttribArray(2);
glVertexAttribPointer(2, 4, GL_FLOAT, GL_FALSE, sizeof(SpriteVertex),
reinterpret_cast<void *>(offsetof(SpriteVertex, color)));
// 初始化 EBO索引缓冲区- 静态使用模式
BufferDesc eboDesc;
eboDesc.type = BufferType::Index;
eboDesc.usage = BufferUsage::Static;
eboDesc.size = batch_.getIndices().size() * sizeof(uint16_t);
eboDesc.initialData = batch_.getIndices().data();
if (!ebo_.init(eboDesc)) {
E2D_LOG_ERROR("Failed to initialize sprite batch EBO");
return false;
}
ebo_.bind();
glBindVertexArray(0);
return true;
}
void GLSpriteBatch::shutdown() {
// 使用 GLBuffer::shutdown() 释放缓冲区资源
vbo_.shutdown();
ebo_.shutdown();
if (vao_ != 0) {
glDeleteVertexArrays(1, &vao_);
vao_ = 0;
}
}
void GLSpriteBatch::begin(const glm::mat4 &viewProjection) {
batch_.begin(viewProjection);
batches_.clear();
currentTexture_ = nullptr;
drawCallCount_ = 0;
// 保存 viewProjection 矩阵供后续使用
viewProjection_ = viewProjection;
// 绑定 VAO 和缓冲区
glBindVertexArray(vao_);
vbo_.bind();
ebo_.bind();
}
void GLSpriteBatch::end() {
if (batch_.getSpriteCount() > 0) {
flush();
}
// 解绑缓冲区
vbo_.unbind();
ebo_.unbind();
glBindVertexArray(0);
}
void GLSpriteBatch::draw(const Texture &texture, const SpriteData &data) {
const GLTexture *glTex = dynamic_cast<const GLTexture *>(&texture);
if (!glTex) {
E2D_LOG_WARN("Invalid texture type for sprite batch");
return;
}
// 如果纹理改变或批次已满,先提交当前批次
if (currentTexture_ != glTex || batch_.needsFlush()) {
if (batch_.getSpriteCount() > 0) {
submitBatch();
}
currentTexture_ = glTex;
}
// 使用 batch 层生成顶点
batch_.draw(data);
}
void GLSpriteBatch::drawBatch(const Texture &texture,
const std::vector<SpriteData> &sprites) {
const GLTexture *glTex = dynamic_cast<const GLTexture *>(&texture);
if (!glTex) {
E2D_LOG_WARN("Invalid texture type for sprite batch");
return;
}
// 批量处理精灵
for (const auto &data : sprites) {
// 如果纹理改变或批次已满,先提交当前批次
if (currentTexture_ != glTex || batch_.needsFlush()) {
if (batch_.getSpriteCount() > 0) {
submitBatch();
}
currentTexture_ = glTex;
}
// 使用 batch 层生成顶点
batch_.draw(data);
}
}
void GLSpriteBatch::submitBatch() {
if (batch_.getSpriteCount() == 0) {
return;
}
// 记录批次信息
Batch batchInfo;
batchInfo.texture = currentTexture_;
batchInfo.startVertex = 0; // 每次提交都是新的缓冲区
batchInfo.vertexCount = batch_.getSpriteCount() * 4;
batches_.push_back(batchInfo);
// 绑定着色器并设置uniform
if (shader_) {
shader_->bind();
// 设置视图投影矩阵
shader_->setMat4("u_viewProjection", viewProjection_);
// 设置模型矩阵为单位矩阵(精灵位置已经在顶点生成时计算)
glm::mat4 model(1.0f);
shader_->setMat4("u_model", model);
// 设置纹理采样器
shader_->setInt("u_texture", 0);
// 设置透明度
shader_->setFloat("u_opacity", 1.0f);
}
// 上传顶点数据 - 使用 orphaning 策略优化动态缓冲区
// 通过传入 nullptr 进行 orphaning告诉驱动器可以丢弃旧缓冲区并分配新内存
// 这样可以避免 GPU 等待,提高性能
size_t vertexDataSize = batch_.getVertices().size() * sizeof(SpriteVertex);
vbo_.setData(nullptr, vertexDataSize); // orphaning
vbo_.updateData(batch_.getVertices().data(), 0, vertexDataSize);
// 绘制
currentTexture_->bind(0);
size_t indexCount = batch_.getSpriteCount() * 6;
glDrawElements(GL_TRIANGLES, static_cast<GLsizei>(indexCount),
GL_UNSIGNED_SHORT, nullptr);
drawCallCount_++;
// 清空 batch 层,准备下一批
batch_.clear();
}
void GLSpriteBatch::flush() {
// 提交最后的批次
if (batch_.getSpriteCount() > 0) {
submitBatch();
}
// 重置状态
batches_.clear();
currentTexture_ = nullptr;
}
} // namespace extra2d

View File

@ -1,4 +1,4 @@
#include <extra2d/graphics/opengl/gl_texture.h> #include <extra2d/graphics/backends/opengl/gl_texture.h>
#include <extra2d/graphics/memory/gpu_context.h> #include <extra2d/graphics/memory/gpu_context.h>
#include <extra2d/graphics/memory/vram_manager.h> #include <extra2d/graphics/memory/vram_manager.h>
#define STB_IMAGE_IMPLEMENTATION #define STB_IMAGE_IMPLEMENTATION

View File

@ -0,0 +1,150 @@
#include <extra2d/graphics/backends/vulkan/vk_renderer.h>
#include <extra2d/utils/logger.h>
namespace extra2d {
VulkanRenderer::VulkanRenderer() = default;
VulkanRenderer::~VulkanRenderer() {
shutdown();
}
bool VulkanRenderer::init(IWindow* window) {
E2D_LOG_WARN("Vulkan renderer is not fully implemented yet");
initialized_ = true;
return true;
}
void VulkanRenderer::shutdown() {
initialized_ = false;
}
void VulkanRenderer::beginFrame(const Color &clearColor) {
// TODO: 实现Vulkan帧开始
}
void VulkanRenderer::endFrame() {
// TODO: 实现Vulkan帧结束
}
void VulkanRenderer::setViewport(int x, int y, int width, int height) {
// TODO: 实现视口设置
}
void VulkanRenderer::setVSync(bool enabled) {
// TODO: 实现垂直同步设置
}
void VulkanRenderer::setBlendMode(BlendMode mode) {
// TODO: 实现混合模式设置
}
void VulkanRenderer::setViewProjection(const glm::mat4 &matrix) {
// TODO: 实现视图投影矩阵设置
}
void VulkanRenderer::pushTransform(const glm::mat4 &transform) {
// TODO: 实现变换矩阵入栈
}
void VulkanRenderer::popTransform() {
// TODO: 实现变换矩阵出栈
}
glm::mat4 VulkanRenderer::getCurrentTransform() const {
return glm::mat4(1.0f);
}
Ptr<Texture> VulkanRenderer::createTexture(int width, int height, const uint8_t *pixels, int channels) {
// TODO: 实现Vulkan纹理创建
return nullptr;
}
Ptr<Texture> VulkanRenderer::loadTexture(const std::string &filepath) {
// TODO: 实现Vulkan纹理加载
return nullptr;
}
void VulkanRenderer::beginSpriteBatch() {
// TODO: 实现精灵批处理开始
}
void VulkanRenderer::drawSprite(const Texture &texture, const Rect &destRect,
const Rect &srcRect, const Color &tint, float rotation,
const Vec2 &anchor) {
// TODO: 实现精灵绘制
}
void VulkanRenderer::drawSprite(const Texture &texture, const Vec2 &position,
const Color &tint) {
// TODO: 实现简化精灵绘制
}
void VulkanRenderer::endSpriteBatch() {
// TODO: 实现精灵批处理结束
}
void VulkanRenderer::drawLine(const Vec2 &start, const Vec2 &end, const Color &color,
float width) {
// TODO: 实现线条绘制
}
void VulkanRenderer::drawRect(const Rect &rect, const Color &color, float width) {
// TODO: 实现矩形边框绘制
}
void VulkanRenderer::fillRect(const Rect &rect, const Color &color) {
// TODO: 实现矩形填充
}
void VulkanRenderer::drawCircle(const Vec2 &center, float radius, const Color &color,
int segments, float width) {
// TODO: 实现圆形边框绘制
}
void VulkanRenderer::fillCircle(const Vec2 &center, float radius, const Color &color,
int segments) {
// TODO: 实现圆形填充
}
void VulkanRenderer::drawTriangle(const Vec2 &p1, const Vec2 &p2, const Vec2 &p3,
const Color &color, float width) {
// TODO: 实现三角形边框绘制
}
void VulkanRenderer::fillTriangle(const Vec2 &p1, const Vec2 &p2, const Vec2 &p3,
const Color &color) {
// TODO: 实现三角形填充
}
void VulkanRenderer::drawPolygon(const std::vector<Vec2> &points, const Color &color,
float width) {
// TODO: 实现多边形边框绘制
}
void VulkanRenderer::fillPolygon(const std::vector<Vec2> &points,
const Color &color) {
// TODO: 实现多边形填充
}
Ptr<FontAtlas> VulkanRenderer::createFontAtlas(const std::string &filepath, int fontSize,
bool useSDF) {
// TODO: 实现字体图集创建
return nullptr;
}
void VulkanRenderer::drawText(const FontAtlas &font, const std::string &text,
const Vec2 &position, const Color &color) {
// TODO: 实现文本绘制
}
void VulkanRenderer::drawText(const FontAtlas &font, const std::string &text, float x,
float y, const Color &color) {
// TODO: 实现文本绘制
}
void VulkanRenderer::resetStats() {
stats_ = Stats{};
}
} // namespace extra2d

View File

@ -0,0 +1,72 @@
#include <extra2d/graphics/batch/shape_batch.h>
namespace extra2d {
// ============================================================================
// ShapeBatch 基础实现(后端无关部分)
// ============================================================================
// 这里可以添加后端无关的工具函数
// 例如:计算圆形的顶点、三角化多边形等
// 计算圆形顶点
void calculateCircleVertices(std::vector<Vec2>& outVertices,
const Vec2& center, float radius,
int segments, bool fill) {
outVertices.clear();
outVertices.reserve(fill ? segments + 1 : segments);
if (fill) {
// 填充圆形:中心点 + 边缘点
outVertices.push_back(center);
for (int i = 0; i <= segments; ++i) {
float angle = 2.0f * 3.14159265359f * static_cast<float>(i) / static_cast<float>(segments);
outVertices.emplace_back(
center.x + radius * cosf(angle),
center.y + radius * sinf(angle)
);
}
} else {
// 圆形边框:只保留边缘点
for (int i = 0; i < segments; ++i) {
float angle = 2.0f * 3.14159265359f * static_cast<float>(i) / static_cast<float>(segments);
outVertices.emplace_back(
center.x + radius * cosf(angle),
center.y + radius * sinf(angle)
);
}
}
}
// 计算矩形顶点
void calculateRectVertices(std::vector<Vec2>& outVertices, const Rect& rect) {
outVertices.clear();
outVertices.reserve(4);
float x1 = rect.origin.x;
float y1 = rect.origin.y;
float x2 = rect.origin.x + rect.size.width;
float y2 = rect.origin.y + rect.size.height;
outVertices.emplace_back(x1, y1); // 左上
outVertices.emplace_back(x2, y1); // 右上
outVertices.emplace_back(x2, y2); // 右下
outVertices.emplace_back(x1, y2); // 左下
}
// 简单的多边形三角化(扇形三角化,适用于凸多边形)
void triangulatePolygon(std::vector<uint16_t>& outIndices, int vertexCount) {
outIndices.clear();
if (vertexCount < 3) {
return;
}
// 扇形三角化:以第一个顶点为扇形中心
for (int i = 1; i < vertexCount - 1; ++i) {
outIndices.push_back(0);
outIndices.push_back(i);
outIndices.push_back(i + 1);
}
}
} // namespace extra2d

View File

@ -0,0 +1,205 @@
#include <extra2d/graphics/batch/sprite_batch.h>
#include <extra2d/utils/logger.h>
#include <cmath>
#include <cstring>
namespace extra2d {
// ============================================================================
// TrigLookup 实现 - 三角函数查表
// ============================================================================
TrigLookup::TrigLookup() {
constexpr float PI = 3.14159265359f;
constexpr float DEG2RAD = PI / 180.0f;
for (int i = 0; i < TABLE_SIZE; ++i) {
float angle = static_cast<float>(i) * (360.0f / TABLE_SIZE) * DEG2RAD;
sinTable_[i] = std::sin(angle);
cosTable_[i] = std::cos(angle);
}
}
float TrigLookup::sin(int angle) const {
// 规范化角度到 0-360
angle = ((angle % 360) + 360) % 360;
return sinTable_[angle * 4];
}
float TrigLookup::cos(int angle) const {
// 规范化角度到 0-360
angle = ((angle % 360) + 360) % 360;
return cosTable_[angle * 4];
}
float TrigLookup::sinRad(float rad) const {
constexpr float RAD2DEG = 180.0f / 3.14159265359f;
int angle = static_cast<int>(rad * RAD2DEG);
return sin(angle);
}
float TrigLookup::cosRad(float rad) const {
constexpr float RAD2DEG = 180.0f / 3.14159265359f;
int angle = static_cast<int>(rad * RAD2DEG);
return cos(angle);
}
// ============================================================================
// SpriteBatch 实现
// ============================================================================
SpriteBatch::SpriteBatch()
: spriteCount_(0)
, vpDirty_(true) {
// 预分配顶点缓冲区
vertices_.reserve(MAX_VERTICES);
indices_.reserve(MAX_INDICES);
// 生成静态索引缓冲区
generateIndices();
}
void SpriteBatch::generateIndices() {
indices_.clear();
for (size_t i = 0; i < MAX_SPRITES; ++i) {
uint16_t base = static_cast<uint16_t>(i * 4);
// 两个三角形: (0,1,2) 和 (0,2,3)
indices_.push_back(base + 0);
indices_.push_back(base + 1);
indices_.push_back(base + 2);
indices_.push_back(base + 0);
indices_.push_back(base + 2);
indices_.push_back(base + 3);
}
}
void SpriteBatch::begin(const glm::mat4& viewProjection) {
viewProjection_ = viewProjection;
vpDirty_ = true;
spriteCount_ = 0;
vertices_.clear();
}
void SpriteBatch::end() {
// 批次结束,数据已准备好供后端使用
}
void SpriteBatch::draw(const SpriteData& sprite) {
if (spriteCount_ >= MAX_SPRITES) {
// 缓冲区已满,需要刷新
flush();
}
generateVertices(sprite, spriteCount_ * VERTICES_PER_SPRITE);
spriteCount_++;
}
void SpriteBatch::drawBatch(const std::vector<SpriteData>& sprites) {
size_t index = 0;
while (index < sprites.size()) {
// 计算剩余空间
size_t remainingSpace = MAX_SPRITES - spriteCount_;
size_t batchSize = std::min(remainingSpace, sprites.size() - index);
// 批量生成顶点
for (size_t i = 0; i < batchSize; ++i) {
generateVertices(sprites[index + i], spriteCount_ * VERTICES_PER_SPRITE);
spriteCount_++;
}
index += batchSize;
// 如果缓冲区已满,需要刷新(由后端处理)
if (spriteCount_ >= MAX_SPRITES && index < sprites.size()) {
// 通知后端刷新,然后继续
// 注意:这里只是准备数据,实际 GPU 提交由后端决定
break;
}
}
}
void SpriteBatch::drawImmediate(const SpriteData& sprite) {
// 立即绘制模式:清空当前批次,只绘制这一个精灵
clear();
draw(sprite);
}
void SpriteBatch::generateVertices(const SpriteData& sprite, size_t vertexOffset) {
// 确保顶点缓冲区足够
if (vertices_.size() < vertexOffset + VERTICES_PER_SPRITE) {
vertices_.resize(vertexOffset + VERTICES_PER_SPRITE);
}
// 计算旋转(使用查表)
float c = 1.0f;
float s = 0.0f;
if (sprite.rotation != 0.0f) {
c = trigLookup_.cosRad(sprite.rotation);
s = trigLookup_.sinRad(sprite.rotation);
}
// 计算精灵的四个角(相对于中心点)
float halfWidth = sprite.size.x * 0.5f;
float halfHeight = sprite.size.y * 0.5f;
// 考虑 pivot 偏移
float pivotOffsetX = (sprite.pivot.x - 0.5f) * sprite.size.x;
float pivotOffsetY = (sprite.pivot.y - 0.5f) * sprite.size.y;
// 四个角的本地坐标
Vec2 localCorners[4] = {
Vec2(-halfWidth - pivotOffsetX, -halfHeight - pivotOffsetY), // 左下
Vec2( halfWidth - pivotOffsetX, -halfHeight - pivotOffsetY), // 右下
Vec2( halfWidth - pivotOffsetX, halfHeight - pivotOffsetY), // 右上
Vec2(-halfWidth - pivotOffsetX, halfHeight - pivotOffsetY) // 左上
};
// 应用旋转和平移
Vec2 worldPos = sprite.position;
for (int i = 0; i < 4; ++i) {
Vec2 rotated;
if (sprite.rotation != 0.0f) {
rotated.x = localCorners[i].x * c - localCorners[i].y * s;
rotated.y = localCorners[i].x * s + localCorners[i].y * c;
} else {
rotated = localCorners[i];
}
vertices_[vertexOffset + i].position = worldPos + rotated;
}
// 设置纹理坐标
// uvRect.origin = (u0, v0) - 左下
// uvRect.size = (width, height) - 从左上到右下的尺寸
float u0 = sprite.uvRect.origin.x;
float v0 = sprite.uvRect.origin.y;
float u1 = u0 + sprite.uvRect.size.width;
float v1 = v0 + sprite.uvRect.size.height;
// 顶点顺序: 左下, 右下, 右上, 左上
// 注意: 在 gl_font_atlas 中 v0 > v1 (因为翻转了V坐标)
// 所以 v0 对应底部v1 对应顶部
vertices_[vertexOffset + 0].texCoord = Vec2(u0, v0); // 左下
vertices_[vertexOffset + 1].texCoord = Vec2(u1, v0); // 右下
vertices_[vertexOffset + 2].texCoord = Vec2(u1, v1); // 右上
vertices_[vertexOffset + 3].texCoord = Vec2(u0, v1); // 左上
// 设置颜色
for (int i = 0; i < 4; ++i) {
vertices_[vertexOffset + i].color = sprite.color;
}
}
void SpriteBatch::flush() {
// 标记需要刷新 - 实际刷新由后端处理
// 这里只是重置计数器,让后端知道需要提交当前批次
spriteCount_ = 0;
vertices_.clear();
}
void SpriteBatch::clear() {
spriteCount_ = 0;
vertices_.clear();
}
} // namespace extra2d

View File

@ -1,4 +1,4 @@
#include <extra2d/graphics/opengl/gl_renderer.h> #include <extra2d/graphics/backends/opengl/gl_renderer.h>
#include <extra2d/graphics/core/render_backend.h> #include <extra2d/graphics/core/render_backend.h>
namespace extra2d { namespace extra2d {

View File

@ -1,17 +1,19 @@
#include <extra2d/graphics/core/render_module.h> #include <extra2d/graphics/core/render_module.h>
#include <extra2d/graphics/opengl/gl_renderer.h> #include <extra2d/graphics/backends/opengl/gl_renderer.h>
#include <extra2d/graphics/opengl/gl_shader.h> #include <extra2d/graphics/backends/opengl/gl_shader.h>
#include <extra2d/graphics/shader/shader_manager.h> #include <extra2d/graphics/shader/shader_manager.h>
#include <extra2d/core/registry.h> #include <extra2d/core/registry.h>
#include <extra2d/core/service_locator.h> #include <extra2d/core/service_locator.h>
#include <extra2d/app/application.h> #include <extra2d/app/application.h>
#include <extra2d/platform/window_module.h> #include <extra2d/platform/window_module.h>
#include <extra2d/utils/logger.h> #include <extra2d/utils/logger.h>
#include <SDL.h> #include <filesystem>
namespace extra2d { namespace extra2d {
RenderModule::RenderModule(const Cfg& cfg) : cfg_(cfg) {} RenderModule::RenderModule(std::function<void(RenderCfg&)> configFn) {
configFn(cfg_);
}
RenderModule::~RenderModule() { RenderModule::~RenderModule() {
if (initialized_) { if (initialized_) {
@ -20,13 +22,12 @@ RenderModule::~RenderModule() {
} }
static std::string getExecutableDir() { static std::string getExecutableDir() {
char* basePath = SDL_GetBasePath(); try {
if (basePath) { auto currentPath = std::filesystem::current_path();
std::string path(basePath); return currentPath.string() + "/";
SDL_free(basePath); } catch (...) {
return path; return "./";
} }
return "./";
} }
bool RenderModule::init() { bool RenderModule::init() {

Some files were not shown because too many files have changed in this diff Show More