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
#include <extra2d/core/types.h>
#include <extra2d/core/module.h>
#include <extra2d/core/registry.h>
#include <extra2d/core/service_locator.h>
#include <extra2d/config/app_config.h>
#include <extra2d/core/types.h>
#include <string>
namespace extra2d {
@ -21,10 +20,17 @@ class InputModule;
*/
class Application {
public:
static Application& get();
static Application &get();
Application(const Application&) = delete;
Application& operator=(const Application&) = delete;
Application(const Application &) = delete;
Application &operator=(const Application &) = delete;
/**
* @brief
*/
std::string appName = "Extra2D App";
std::string appVersion = "1.0.0";
std::string organization = "";
/**
* @brief
@ -32,8 +38,7 @@ public:
* @tparam Args
* @return
*/
template<typename T, typename... Args>
T* use(Args&&... args) {
template <typename T, typename... Args> T *use(Args &&...args) {
return Registry::instance().use<T>(std::forward<Args>(args)...);
}
@ -42,10 +47,7 @@ public:
* @tparam T
* @return
*/
template<typename T>
T* get() const {
return Registry::instance().get<T>();
}
template <typename T> T *get() const { return Registry::instance().get<T>(); }
/**
* @brief
@ -53,13 +55,6 @@ public:
*/
bool init();
/**
* @brief
* @param config
* @return true
*/
bool init(const AppConfig& config);
/**
* @brief
*/
@ -92,19 +87,19 @@ public:
* @brief
* @return
*/
IWindow* window();
IWindow *window();
/**
* @brief
* @return
*/
RenderBackend* renderer();
RenderBackend *renderer();
/**
* @brief
* @return
*/
IInput* input();
IInput *input();
/**
* @brief
@ -123,7 +118,7 @@ private:
void mainLoop();
void update();
void render();
void registerCoreServices();
void configureCameraService();
bool initialized_ = false;
bool running_ = false;
@ -136,8 +131,6 @@ private:
int frameCount_ = 0;
float fpsTimer_ = 0.0f;
int currentFps_ = 0;
AppConfig appConfig_;
};
} // 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,22 +1,21 @@
#pragma once
#include <algorithm>
#include <extra2d/core/service_interface.h>
#include <extra2d/core/types.h>
#include <functional>
#include <memory>
#include <mutex>
#include <typeindex>
#include <unordered_map>
#include <vector>
#include <mutex>
#include <functional>
#include <typeindex>
#include <memory>
#include <algorithm>
namespace extra2d {
/**
* @brief
*/
template<typename T>
using ServiceFactory = std::function<SharedPtr<T>()>;
template <typename T> using ServiceFactory = std::function<SharedPtr<T>()>;
/**
* @brief
@ -35,18 +34,17 @@ public:
* @brief
* @return
*/
static ServiceLocator& instance();
static ServiceLocator &instance();
ServiceLocator(const ServiceLocator&) = delete;
ServiceLocator& operator=(const ServiceLocator&) = delete;
ServiceLocator(const ServiceLocator &) = delete;
ServiceLocator &operator=(const ServiceLocator &) = delete;
/**
* @brief
* @tparam T
* @param service
*/
template<typename T>
void registerService(SharedPtr<T> service) {
template <typename T> void registerService(SharedPtr<T> service) {
static_assert(std::is_base_of_v<IService, T>,
"T must derive from IService");
@ -62,8 +60,7 @@ public:
* @tparam T
* @param factory
*/
template<typename T>
void registerFactory(ServiceFactory<T> factory) {
template <typename T> void registerFactory(ServiceFactory<T> factory) {
static_assert(std::is_base_of_v<IService, T>,
"T must derive from IService");
@ -72,6 +69,12 @@ public:
factories_[typeId] = [factory]() -> SharedPtr<IService> {
return std::static_pointer_cast<IService>(factory());
};
// 立即创建服务实例并添加到有序列表
auto service = factories_[typeId]();
services_[typeId] = service;
orderedServices_.push_back(service);
sortServices();
}
/**
@ -79,8 +82,7 @@ public:
* @tparam T
* @return nullptr
*/
template<typename T>
SharedPtr<T> getService() const {
template <typename T> SharedPtr<T> getService() const {
static_assert(std::is_base_of_v<IService, T>,
"T must derive from IService");
@ -107,8 +109,7 @@ public:
* @tparam T
* @return nullptr
*/
template<typename T>
SharedPtr<T> tryGetService() const {
template <typename T> SharedPtr<T> tryGetService() const {
static_assert(std::is_base_of_v<IService, T>,
"T must derive from IService");
@ -126,8 +127,7 @@ public:
* @tparam T
* @return true
*/
template<typename T>
bool hasService() const {
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() ||
@ -138,8 +138,7 @@ public:
* @brief
* @tparam T
*/
template<typename T>
void unregisterService() {
template <typename T> void unregisterService() {
std::lock_guard<std::mutex> lock(mutex_);
auto typeId = std::type_index(typeid(T));
@ -148,8 +147,8 @@ public:
auto service = it->second;
services_.erase(it);
auto orderIt = std::find(orderedServices_.begin(),
orderedServices_.end(), service);
auto orderIt =
std::find(orderedServices_.begin(), orderedServices_.end(), service);
if (orderIt != orderedServices_.end()) {
orderedServices_.erase(orderIt);
}
@ -212,7 +211,8 @@ private:
void sortServices();
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>()>>
factories_;
std::vector<SharedPtr<IService>> orderedServices_;
mutable std::mutex mutex_;
};
@ -221,8 +221,7 @@ private:
* @brief
*
*/
template<typename Interface, typename Implementation>
class ServiceRegistrar {
template <typename Interface, typename Implementation> class ServiceRegistrar {
public:
explicit ServiceRegistrar(ServiceFactory<Interface> factory = nullptr) {
if (factory) {
@ -231,22 +230,73 @@ public:
ServiceLocator::instance().registerFactory<Interface>(
[]() -> SharedPtr<Interface> {
return makeShared<Implementation>();
}
);
});
}
}
};
}
/**
* @brief
* 使
*
*/
template <typename Interface, typename Implementation> struct ServiceAutoReg {
/**
* @brief 访
*/
static const bool registered;
#define E2D_REGISTER_SERVICE(Interface, Implementation) \
namespace { \
static ::extra2d::ServiceRegistrar<Interface, Implementation> \
E2D_CONCAT(service_registrar_, __LINE__); \
/**
* @brief
* @return true
*/
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 { \
static ::extra2d::ServiceRegistrar<Interface, Interface> \
E2D_CONCAT(service_factory_registrar_, __LINE__)(Factory); \
// 静态成员定义,在此处触发注册
template <typename Interface, typename Implementation>
const bool ServiceAutoReg<Interface, Implementation>::registered =
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,
WindowResize,
WindowFocus,
WindowBlur,
WindowMove,
WindowLostFocus,
WindowMoved,
// 键盘事件
KeyPress,
KeyRelease,
KeyPressed,
KeyReleased,
KeyRepeat,
// 鼠标事件
MousePress,
MouseRelease,
MouseMove,
MouseScroll,
MouseButtonPressed,
MouseButtonReleased,
MouseMoved,
MouseScrolled,
// UI 事件
UIHoverEnter,
UIHoverExit,
UIPress,
UIRelease,
UIClick,
UIPressed,
UIReleased,
UIClicked,
// 游戏手柄事件
GamepadConnect,
GamepadDisconnect,
GamepadPress,
GamepadRelease,
GamepadAxis,
GamepadConnected,
GamepadDisconnected,
GamepadButtonPressed,
GamepadButtonReleased,
GamepadAxisMoved,
// 触摸事件 (移动端)
TouchBegin,
TouchMove,
TouchEnd,
TouchCancel,
TouchBegan,
TouchMoved,
TouchEnded,
TouchCancelled,
// 自定义事件
Custom
@ -61,13 +61,13 @@ enum class EventType {
struct KeyEvent {
int keyCode;
int scancode;
int mods;
int mods; // 修饰键 (Shift, Ctrl, Alt, etc.)
};
// ============================================================================
// 鼠标事件数据
// ============================================================================
struct MouseEvent {
struct MouseButtonEvent {
int button;
int mods;
Vec2 position;
@ -99,7 +99,7 @@ struct WindowMoveEvent {
// ============================================================================
// 游戏手柄事件数据
// ============================================================================
struct GamepadEvent {
struct GamepadButtonEvent {
int gamepadId;
int button;
};
@ -134,33 +134,37 @@ struct Event {
double timestamp = 0.0;
bool handled = false;
std::variant<std::monostate, KeyEvent, MouseEvent, MouseMoveEvent,
// 事件数据联合体
std::variant<std::monostate, KeyEvent, MouseButtonEvent, MouseMoveEvent,
MouseScrollEvent, WindowResizeEvent, WindowMoveEvent,
GamepadEvent, GamepadAxisEvent, TouchEvent, CustomEvent>
GamepadButtonEvent, GamepadAxisEvent, TouchEvent, CustomEvent>
data;
// 便捷访问方法
bool isWindowEvent() const {
return type == EventType::WindowClose || type == EventType::WindowResize ||
type == EventType::WindowFocus || type == EventType::WindowBlur ||
type == EventType::WindowMove;
type == EventType::WindowFocus ||
type == EventType::WindowLostFocus || type == EventType::WindowMoved;
}
bool isKeyboardEvent() const {
return type == EventType::KeyPress || type == EventType::KeyRelease ||
return type == EventType::KeyPressed || type == EventType::KeyReleased ||
type == EventType::KeyRepeat;
}
bool isMouseEvent() const {
return type == EventType::MousePress || type == EventType::MouseRelease ||
type == EventType::MouseMove || type == EventType::MouseScroll;
return type == EventType::MouseButtonPressed ||
type == EventType::MouseButtonReleased ||
type == EventType::MouseMoved || type == EventType::MouseScrolled;
}
// 静态工厂方法
static Event createWindowResize(int width, int height);
static Event createWindowClose();
static Event createKeyPress(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 createMouseRelease(int button, int mods, const Vec2 &pos);
static Event createMouseButtonPress(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 createMouseScroll(const Vec2 &offset, const Vec2 &pos);
};

View File

@ -10,10 +10,7 @@
#include <extra2d/core/module.h>
#include <extra2d/core/registry.h>
// Config
#include <extra2d/config/app_config.h>
#include <extra2d/config/platform_config.h>
#include <extra2d/config/platform_detector.h>
// Config removed - app info now in Application class
// Platform
#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
#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/shader/shader_interface.h>
@ -10,7 +13,10 @@
namespace extra2d {
// 前向声明
class IWindow;
class GLContext;
class GLFramebuffer;
// ============================================================================
// OpenGL 渲染器实现
@ -48,6 +54,7 @@ public:
void drawSprite(const Texture &texture, const Vec2 &position,
const Color &tint) override;
void endSpriteBatch() override;
void flush() override;
void drawLine(const Vec2 &start, const Vec2 &end, const Color &color,
float width) override;
@ -76,6 +83,42 @@ public:
Stats getStats() const override { return stats_; }
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:
// 形状批处理常量
static constexpr size_t MAX_CIRCLE_SEGMENTS = 128;
@ -92,10 +135,10 @@ private:
GLSpriteBatch spriteBatch_;
Ptr<IShader> shapeShader_;
GLuint shapeVao_;
GLuint shapeVbo_;
GLuint lineVao_; // 线条专用 VAO
GLuint lineVbo_; // 线条专用 VBO
GLuint shapeVao_; // 形状 VAO手动管理用于顶点属性配置
GLBuffer shapeBuffer_; // 形状 VBO使用 GLBuffer 管理)
GLuint lineVao_; // 线条 VAO(手动管理,用于顶点属性配置)
GLBuffer lineBuffer_; // 线条 VBO使用 GLBuffer 管理)
glm::mat4 viewProjection_;
std::vector<glm::mat4> transformStack_;
@ -112,15 +155,23 @@ private:
size_t lineVertexCount_ = 0;
float currentLineWidth_ = 1.0f;
// OpenGL 状态缓存
BlendMode cachedBlendMode_ = BlendMode::None;
bool blendEnabled_ = false;
int cachedViewportX_ = 0;
int cachedViewportY_ = 0;
int cachedViewportWidth_ = 0;
int cachedViewportHeight_ = 0;
// OpenGL 管线状态管理
GLPipeline pipeline_;
// 自动批处理状态
bool batchActive_ = false; // 批处理是否激活
bool autoBatchEnabled_ = true; // 是否启用自动批处理
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 ensureBatchActive(); // 确保批处理已激活
void submitPendingSprites(); // 提交待处理的精灵
void flushShapeBatch();
void flushLineBatch();
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/math_types.h>
#include <extra2d/core/types.h>
#include <extra2d/graphics/resources/pipeline.h>
#include <glm/mat4x4.hpp>
namespace extra2d {
@ -24,15 +25,7 @@ enum class BackendType {
// D3D12
};
// ============================================================================
// 混合模式
// ============================================================================
enum class BlendMode {
None, // 不混合
Alpha, // 标准 Alpha 混合
Additive, // 加法混合
Multiply // 乘法混合
};
// BlendMode 定义在 pipeline.h 中
// ============================================================================
// 渲染后端抽象接口
@ -78,6 +71,10 @@ public:
// ------------------------------------------------------------------------
// 精灵批渲染
// ------------------------------------------------------------------------
/**
* @brief
* @note drawSprite/drawText
*/
virtual void beginSpriteBatch() = 0;
virtual void drawSprite(const Texture &texture, const Rect &destRect,
const Rect &srcRect, const Color &tint,
@ -86,6 +83,12 @@ public:
const Color &tint) = 0;
virtual void endSpriteBatch() = 0;
/**
* @brief
* @note
*/
virtual void flush() = 0;
// ------------------------------------------------------------------------
// 形状渲染
// ------------------------------------------------------------------------

View File

@ -3,10 +3,30 @@
#include <extra2d/core/module.h>
#include <extra2d/graphics/core/render_backend.h>
#include <extra2d/platform/window_module.h>
#include <functional>
#include <typeindex>
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
*
@ -14,29 +34,10 @@ namespace extra2d {
class RenderModule : public Module {
public:
/**
* @brief
* @brief Lambda
* @param configFn
*/
struct Cfg {
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{});
explicit RenderModule(std::function<void(RenderCfg&)> configFn);
/**
* @brief
@ -64,7 +65,7 @@ public:
RenderBackend* renderer() const { return renderer_.get(); }
private:
Cfg cfg_;
RenderCfg cfg_;
UniquePtr<RenderBackend> renderer_;
bool initialized_ = false;
};

View File

@ -2,7 +2,7 @@
#include <extra2d/core/color.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 <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 <string>
#include <unordered_map>
#include <unordered_set>
#include <vector>
#ifdef _WIN32
@ -19,12 +18,7 @@ namespace extra2d {
struct FileChangeEvent {
std::string filepath;
enum class Type {
Created,
Modified,
Deleted,
Renamed
} type;
enum class Type { Created, Modified, Deleted, Renamed } type;
uint64_t timestamp = 0;
};
@ -32,7 +26,7 @@ struct FileChangeEvent {
// ============================================================================
// 文件变化回调
// ============================================================================
using FileChangeCallback = std::function<void(const FileChangeEvent&)>;
using FileChangeCallback = std::function<void(const FileChangeEvent &)>;
// ============================================================================
// Shader热重载管理器
@ -43,7 +37,7 @@ public:
* @brief
* @return
*/
static ShaderHotReloader& getInstance();
static ShaderHotReloader &getInstance();
/**
* @brief
@ -62,15 +56,15 @@ public:
* @param filePaths
* @param callback
*/
void watch(const std::string& shaderName,
const std::vector<std::string>& filePaths,
void watch(const std::string &shaderName,
const std::vector<std::string> &filePaths,
FileChangeCallback callback);
/**
* @brief
* @param shaderName Shader名称
*/
void unwatch(const std::string& shaderName);
void unwatch(const std::string &shaderName);
/**
* @brief
@ -98,8 +92,8 @@ public:
private:
ShaderHotReloader() = default;
~ShaderHotReloader() = default;
ShaderHotReloader(const ShaderHotReloader&) = delete;
ShaderHotReloader& operator=(const ShaderHotReloader&) = delete;
ShaderHotReloader(const ShaderHotReloader &) = delete;
ShaderHotReloader &operator=(const ShaderHotReloader &) = delete;
bool enabled_ = false;
bool initialized_ = false;
@ -128,7 +122,7 @@ private:
* @param filepath
* @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:
ShaderLoader();
~ShaderLoader() = default;
~ShaderLoader() override = default;
/**
* @brief Shader (.vert + .frag)
* @param name Shader名称
* @param vertPath
* @param fragPath
* @return
*/
ShaderLoadResult loadFromSeparateFiles(
const std::string& name,
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(
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(
const std::string& source,
const std::string& baseDir,
std::vector<std::string>& outDependencies);
std::vector<std::string>& outDependencies) override;
/**
* @brief
* @param source
* @param defines
* @return
*/
std::string applyDefines(
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搜索路径

View File

@ -1,6 +1,5 @@
#pragma once
#include <extra2d/config/platform_detector.h>
#include <extra2d/graphics/shader/shader_cache.h>
#include <extra2d/graphics/shader/shader_hot_reloader.h>
#include <extra2d/graphics/shader/shader_interface.h>
@ -178,6 +177,14 @@ public:
*/
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/types.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 <vector>
#include <unordered_map>

View File

@ -5,7 +5,6 @@
#include <extra2d/graphics/texture/texture.h>
#include <extra2d/utils/logger.h>
#include <algorithm>
#include <atomic>
#include <chrono>
#include <cstdint>
@ -13,7 +12,6 @@
#include <mutex>
#include <string>
#include <unordered_map>
#include <vector>
namespace extra2d {
@ -47,21 +45,21 @@ struct TextureKey {
* @brief
* @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 +
* @param p
* @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
* @param other TextureKey
* @return
*/
bool operator==(const TextureKey& other) const {
bool operator==(const TextureKey &other) const {
return path == other.path && region == other.region;
}
@ -70,9 +68,7 @@ struct TextureKey {
* @param other TextureKey
* @return
*/
bool operator!=(const TextureKey& other) const {
return !(*this == other);
}
bool operator!=(const TextureKey &other) const { return !(*this == other); }
};
// ============================================================================
@ -84,7 +80,7 @@ struct TextureKeyHash {
* @param key
* @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 h2 = std::hash<float>{}(key.region.origin.x);
size_t h3 = std::hash<float>{}(key.region.origin.y);
@ -115,11 +111,8 @@ struct TexturePoolEntry {
* @brief
*/
TexturePoolEntry()
: texture(nullptr)
, refCount(0)
, key()
, memorySize(0)
, lastAccessTime(0) {}
: texture(nullptr), refCount(0), key(), memorySize(0), lastAccessTime(0) {
}
/**
* @brief
@ -127,33 +120,30 @@ struct TexturePoolEntry {
* @param k
* @param memSize
*/
TexturePoolEntry(Ptr<Texture> tex, const TextureKey& k, size_t memSize)
: texture(tex)
, refCount(1)
, key(k)
, memorySize(memSize)
, lastAccessTime(getCurrentTime()) {}
TexturePoolEntry(Ptr<Texture> tex, const TextureKey &k, size_t memSize)
: texture(tex), refCount(1), key(k), memorySize(memSize),
lastAccessTime(getCurrentTime()) {}
/**
* @brief
* @param other
*/
TexturePoolEntry(TexturePoolEntry&& other) noexcept
: texture(std::move(other.texture))
, refCount(other.refCount.load(std::memory_order_relaxed))
, key(std::move(other.key))
, memorySize(other.memorySize)
, lastAccessTime(other.lastAccessTime) {}
TexturePoolEntry(TexturePoolEntry &&other) noexcept
: texture(std::move(other.texture)),
refCount(other.refCount.load(std::memory_order_relaxed)),
key(std::move(other.key)), memorySize(other.memorySize),
lastAccessTime(other.lastAccessTime) {}
/**
* @brief
* @param other
* @return
*/
TexturePoolEntry& operator=(TexturePoolEntry&& other) noexcept {
TexturePoolEntry &operator=(TexturePoolEntry &&other) noexcept {
if (this != &other) {
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),
std::memory_order_relaxed);
key = std::move(other.key);
memorySize = other.memorySize;
lastAccessTime = other.lastAccessTime;
@ -162,8 +152,8 @@ struct TexturePoolEntry {
}
// 禁止拷贝
TexturePoolEntry(const TexturePoolEntry&) = delete;
TexturePoolEntry& operator=(const TexturePoolEntry&) = delete;
TexturePoolEntry(const TexturePoolEntry &) = delete;
TexturePoolEntry &operator=(const TexturePoolEntry &) = delete;
/**
* @brief 访
@ -198,7 +188,7 @@ public:
* @param entry
* @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) {}
/**
@ -214,7 +204,7 @@ public:
* @brief
* @param other TextureRef
*/
TextureRef(const TextureRef& other)
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);
@ -225,10 +215,9 @@ public:
* @brief
* @param other TextureRef
*/
TextureRef(TextureRef&& other) noexcept
: texture_(std::move(other.texture_))
, entry_(other.entry_)
, mutex_(other.mutex_) {
TextureRef(TextureRef &&other) noexcept
: texture_(std::move(other.texture_)), entry_(other.entry_),
mutex_(other.mutex_) {
other.entry_ = nullptr;
other.mutex_ = nullptr;
}
@ -243,7 +232,7 @@ public:
* @param other TextureRef
* @return
*/
TextureRef& operator=(const TextureRef& other) {
TextureRef &operator=(const TextureRef &other) {
if (this != &other) {
reset();
texture_ = other.texture_;
@ -261,7 +250,7 @@ public:
* @param other TextureRef
* @return
*/
TextureRef& operator=(TextureRef&& other) noexcept {
TextureRef &operator=(TextureRef &&other) noexcept {
if (this != &other) {
reset();
texture_ = std::move(other.texture_);
@ -292,7 +281,7 @@ public:
* @brief
* @return
*/
Texture* get() const { return texture_.get(); }
Texture *get() const { return texture_.get(); }
/**
* @brief
@ -314,17 +303,17 @@ public:
/**
* @brief
*/
Texture* operator->() const { return texture_.get(); }
Texture *operator->() const { return texture_.get(); }
/**
* @brief
*/
Texture& operator*() const { return *texture_; }
Texture &operator*() const { return *texture_; }
private:
Ptr<Texture> texture_;
TexturePoolEntry* entry_;
std::mutex* mutex_;
TexturePoolEntry *entry_;
std::mutex *mutex_;
};
// ============================================================================
@ -364,7 +353,7 @@ public:
* @param scene
* @param maxMemoryUsage 使0
*/
explicit TexturePool(Scene* scene, size_t maxMemoryUsage = 0);
explicit TexturePool(Scene *scene, size_t maxMemoryUsage = 0);
/**
* @brief
@ -372,21 +361,15 @@ public:
~TexturePool();
// 禁止拷贝
TexturePool(const TexturePool&) = delete;
TexturePool& operator=(const TexturePool&) = delete;
TexturePool(const TexturePool &) = delete;
TexturePool &operator=(const TexturePool &) = delete;
/**
* @brief
* @param scene
* @param maxMemoryUsage 使0
*/
void init(Scene* scene, size_t maxMemoryUsage = 0);
/**
* @brief
* @param backend
*/
void setRenderBackend(RenderBackend* backend) { backend_ = backend; }
void init(Scene *scene, size_t maxMemoryUsage = 0);
// ========================================================================
// 纹理加载
@ -398,8 +381,8 @@ public:
* @param options
* @return
*/
TextureRef load(const std::string& path,
const TextureLoadOptions& options = TextureLoadOptions());
TextureRef load(const std::string &path,
const TextureLoadOptions &options = TextureLoadOptions());
/**
* @brief
@ -408,8 +391,8 @@ public:
* @param options
* @return
*/
TextureRef load(const std::string& path, const Rect& region,
const TextureLoadOptions& options = TextureLoadOptions());
TextureRef load(const std::string &path, const Rect &region,
const TextureLoadOptions &options = TextureLoadOptions());
/**
* @brief
@ -420,8 +403,8 @@ public:
* @param key
* @return
*/
TextureRef loadFromMemory(const uint8_t* data, int width, int height,
int channels, const std::string& key);
TextureRef loadFromMemory(const uint8_t *data, int width, int height,
int channels, const std::string &key);
/**
* @brief
@ -429,8 +412,9 @@ public:
* @param options
* @return
*/
TextureRef getOrLoad(const std::string& path,
const TextureLoadOptions& options = TextureLoadOptions());
TextureRef
getOrLoad(const std::string &path,
const TextureLoadOptions &options = TextureLoadOptions());
/**
* @brief
@ -439,8 +423,9 @@ public:
* @param options
* @return
*/
TextureRef getOrLoad(const std::string& path, const Rect& region,
const TextureLoadOptions& options = TextureLoadOptions());
TextureRef
getOrLoad(const std::string &path, const Rect &region,
const TextureLoadOptions &options = TextureLoadOptions());
// ========================================================================
// 引用计数管理
@ -451,21 +436,21 @@ public:
* @param key
* @return
*/
bool addRef(const TextureKey& key);
bool addRef(const TextureKey &key);
/**
* @brief
* @param key
* @return
*/
uint32_t release(const TextureKey& key);
uint32_t release(const TextureKey &key);
/**
* @brief
* @param key
* @return
*/
uint32_t getRefCount(const TextureKey& key) const;
uint32_t getRefCount(const TextureKey &key) const;
// ========================================================================
// 缓存管理
@ -476,14 +461,14 @@ public:
* @param key
* @return
*/
bool isCached(const TextureKey& key) const;
bool isCached(const TextureKey &key) const;
/**
* @brief
* @param key
* @return
*/
bool removeFromCache(const TextureKey& key);
bool removeFromCache(const TextureKey &key);
/**
* @brief 0
@ -546,7 +531,7 @@ private:
* @param texture
* @return
*/
static size_t calculateTextureMemory(const Texture* texture);
static size_t calculateTextureMemory(const Texture *texture);
/**
* @brief
@ -559,10 +544,10 @@ private:
*/
void tryAutoEvict();
Scene* scene_; // 场景指针
RenderBackend* backend_ = nullptr; // 渲染后端
Scene *scene_; // 场景指针
mutable std::mutex mutex_; // 互斥锁
std::unordered_map<TextureKey, TexturePoolEntry, TextureKeyHash> cache_; // 纹理缓存
std::unordered_map<TextureKey, TexturePoolEntry, TextureKeyHash>
cache_; // 纹理缓存
size_t maxMemoryUsage_; // 最大内存使用量
size_t currentMemoryUsage_; // 当前内存使用量

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; \
}
}
} // 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,194 +1,71 @@
#pragma once
#include <SDL.h>
namespace extra2d {
/**
* @brief
*
* 使 SDL Scancode
* Scancode
* @brief
*/
enum class Key : int {
None = 0,
// 字母键
A = SDL_SCANCODE_A,
B = SDL_SCANCODE_B,
C = SDL_SCANCODE_C,
D = SDL_SCANCODE_D,
E = SDL_SCANCODE_E,
F = SDL_SCANCODE_F,
G = SDL_SCANCODE_G,
H = SDL_SCANCODE_H,
I = SDL_SCANCODE_I,
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
A, B, C, D, E, F, G, H, I, J, K, L, M,
N, O, P, Q, R, S, T, U, V, W, X, Y, Z,
Num0, Num1, Num2, Num3, Num4,
Num5, Num6, Num7, Num8, Num9,
F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12,
Space, Enter, Escape, Tab, Backspace,
Insert, Delete, Home, End, PageUp, PageDown,
Up, Down, Left, Right,
LShift, RShift, LCtrl, RCtrl, LAlt, RAlt,
CapsLock, NumLock, ScrollLock,
Count
};
/**
* @brief
*
* 使 SDL
* @brief
*/
enum class Mouse : int {
Left = SDL_BUTTON_LEFT,
Middle = SDL_BUTTON_MIDDLE,
Right = SDL_BUTTON_RIGHT,
X1 = SDL_BUTTON_X1,
X2 = SDL_BUTTON_X2,
Left = 0,
Right,
Middle,
X1,
X2,
Count
};
/**
* @brief
*
* 使 SDL GameController
* @brief
*/
enum class Gamepad : int {
A = SDL_CONTROLLER_BUTTON_A,
B = SDL_CONTROLLER_BUTTON_B,
X = SDL_CONTROLLER_BUTTON_X,
Y = SDL_CONTROLLER_BUTTON_Y,
Back = SDL_CONTROLLER_BUTTON_BACK,
Guide = SDL_CONTROLLER_BUTTON_GUIDE,
Start = SDL_CONTROLLER_BUTTON_START,
LStick = SDL_CONTROLLER_BUTTON_LEFTSTICK,
RStick = SDL_CONTROLLER_BUTTON_RIGHTSTICK,
LB = SDL_CONTROLLER_BUTTON_LEFTSHOULDER,
RB = SDL_CONTROLLER_BUTTON_RIGHTSHOULDER,
DUp = SDL_CONTROLLER_BUTTON_DPAD_UP,
DDown = SDL_CONTROLLER_BUTTON_DPAD_DOWN,
DLeft = SDL_CONTROLLER_BUTTON_DPAD_LEFT,
DRight = SDL_CONTROLLER_BUTTON_DPAD_RIGHT,
LT = SDL_CONTROLLER_BUTTON_MAX, // 轴映射
RT, // 轴映射
A = 0,
B,
X,
Y,
LB,
RB,
LT,
RT,
Back,
Start,
Guide,
LStick,
RStick,
DUp,
DDown,
DLeft,
DRight,
Count
};
/**
* @brief
*
* 使 SDL GameController
* @brief
*/
enum class GamepadAxis : int {
LeftX = SDL_CONTROLLER_AXIS_LEFTX,
LeftY = SDL_CONTROLLER_AXIS_LEFTY,
RightX = SDL_CONTROLLER_AXIS_RIGHTX,
RightY = SDL_CONTROLLER_AXIS_RIGHTY,
LeftTrigger = SDL_CONTROLLER_AXIS_TRIGGERLEFT,
RightTrigger = SDL_CONTROLLER_AXIS_TRIGGERRIGHT,
LeftX = 0,
LeftY,
RightX,
RightY,
LeftTrigger,
RightTrigger,
Count
};

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
#include <extra2d/core/color.h>
#include <extra2d/core/export.h>
#include <extra2d/core/math_types.h>
#include <extra2d/core/types.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:
Node();
virtual ~Node();
@ -152,31 +151,6 @@ public:
// ------------------------------------------------------------------------
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
#include <extra2d/core/color.h>
#include <extra2d/core/export.h>
#include <extra2d/core/math_types.h>
#include <extra2d/scene/node.h>
#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:
ShapeNode();
~ShapeNode() override = default;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,7 @@
#pragma once
#include <extra2d/core/service_interface.h>
#include <extra2d/core/service_locator.h>
#include <extra2d/event/event_dispatcher.h>
#include <extra2d/event/event_queue.h>
@ -60,9 +61,17 @@ public:
size_t getTotalListenerCount() 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:
EventQueue queue_;
EventDispatcher dispatcher_;
// 服务注册元数据
E2D_AUTO_REGISTER_SERVICE(IEventService, EventService);
};
}

View File

@ -1,6 +1,7 @@
#pragma once
#include <extra2d/core/service_interface.h>
#include <extra2d/core/service_locator.h>
#include <extra2d/core/types.h>
#include <cstdarg>
#include <string>
@ -124,6 +125,9 @@ private:
LogLevel level_;
class Impl;
UniquePtr<Impl> impl_;
// 服务注册元数据
E2D_AUTO_REGISTER_SERVICE(ILogger, ConsoleLogger);
};
} // namespace extra2d

View File

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

View File

@ -1,6 +1,7 @@
#pragma once
#include <extra2d/core/service_interface.h>
#include <extra2d/core/service_locator.h>
#include <extra2d/utils/timer.h>
namespace extra2d {
@ -48,6 +49,9 @@ public:
private:
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,20 +1,18 @@
#include <extra2d/app/application.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_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/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 {
@ -32,14 +30,12 @@ static double getTimeSeconds() {
#endif
}
Application& Application::get() {
Application &Application::get() {
static Application instance;
return instance;
}
Application::Application() {
Registry::instance().setApp(this);
}
Application::Application() { Registry::instance().setApp(this); }
Application::~Application() {
if (initialized_) {
@ -48,83 +44,62 @@ Application::~Application() {
}
bool Application::init() {
AppConfig cfg;
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));
}
// 初始化所有模块(拓扑排序)
// 服务通过 E2D_AUTO_REGISTER_SERVICE 宏自动注册
if (!Registry::instance().init()) {
return false;
}
// 模块初始化完成后,注册其他核心服务
registerCoreServices();
// 配置相机服务(需要窗口信息)
configureCameraService();
// 初始化所有服务
ServiceLocator::instance().initializeAll();
initialized_ = true;
running_ = true;
return true;
}
void Application::registerCoreServices() {
auto& locator = ServiceLocator::instance();
if (!locator.hasService<ISceneService>()) {
auto service = makeShared<SceneService>();
locator.registerService(std::static_pointer_cast<ISceneService>(service));
void Application::configureCameraService() {
auto *winMod = get<WindowModule>();
if (!winMod || !winMod->win()) {
return;
}
if (!locator.hasService<ITimerService>()) {
auto service = makeShared<TimerService>();
locator.registerService(std::static_pointer_cast<ITimerService>(service));
auto cameraService = ServiceLocator::instance().getService<ICameraService>();
if (!cameraService) {
return;
}
if (!locator.hasService<IEventService>()) {
auto service = makeShared<EventService>();
locator.registerService(std::static_pointer_cast<IEventService>(service));
}
auto* winMod = get<WindowModule>();
if (winMod && winMod->win() && !locator.hasService<ICameraService>()) {
auto cameraService = makeShared<CameraService>();
auto* win = winMod->win();
auto *win = winMod->win();
cameraService->setViewport(0, static_cast<float>(win->width()),
static_cast<float>(win->height()), 0);
ViewportConfig vpConfig;
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() {
if (!initialized_) return;
if (!initialized_)
return;
VRAMMgr::get().printStats();
ServiceLocator::instance().shutdownAll();
ServiceLocator::instance().clear();
Registry::instance().shutdown();
Registry::instance().clear();
@ -134,10 +109,12 @@ void Application::shutdown() {
}
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())
return;
lastFrameTime_ = getTimeSeconds();
@ -181,7 +158,7 @@ void Application::mainLoop() {
fpsTimer_ -= 1.0f;
}
auto* winMod = get<WindowModule>();
auto *winMod = get<WindowModule>();
if (winMod && winMod->win()) {
winMod->win()->poll();
}
@ -198,7 +175,7 @@ void Application::mainLoop() {
render();
// 帧率限制
auto* renderMod = get<RenderModule>();
auto *renderMod = get<RenderModule>();
if (renderMod && renderMod->renderer()) {
// 这里可以添加帧率限制逻辑
}
@ -207,29 +184,32 @@ void Application::mainLoop() {
void Application::update() {
ServiceLocator::instance().updateAll(deltaTime_);
auto* inputMod = get<InputModule>();
auto *inputMod = get<InputModule>();
if (inputMod) {
inputMod->update();
}
}
void Application::render() {
auto* renderMod = get<RenderModule>();
if (!renderMod || !renderMod->renderer()) return;
auto *renderMod = get<RenderModule>();
if (!renderMod || !renderMod->renderer())
return;
auto* renderer = renderMod->renderer();
auto* winMod = get<WindowModule>();
if (!winMod || !winMod->win()) return;
auto *renderer = renderMod->renderer();
auto *winMod = get<WindowModule>();
if (!winMod || !winMod->win())
return;
auto cameraService = ServiceLocator::instance().getService<ICameraService>();
if (cameraService) {
const auto& vp = cameraService->getViewportResult().viewport;
const auto &vp = cameraService->getViewportResult().viewport;
renderer->setViewport(
static_cast<int>(vp.origin.x), static_cast<int>(vp.origin.y),
static_cast<int>(vp.size.width), static_cast<int>(vp.size.height));
renderer->setViewProjection(cameraService->getViewProjectionMatrix());
} else {
renderer->setViewport(0, 0, winMod->win()->width(), winMod->win()->height());
renderer->setViewport(0, 0, winMod->win()->width(),
winMod->win()->height());
}
auto sceneService = ServiceLocator::instance().getService<ISceneService>();
@ -240,24 +220,24 @@ void Application::render() {
winMod->win()->swap();
}
IWindow* Application::window() {
auto* winMod = get<WindowModule>();
IWindow *Application::window() {
auto *winMod = get<WindowModule>();
return winMod ? winMod->win() : nullptr;
}
RenderBackend* Application::renderer() {
auto* renderMod = get<RenderModule>();
RenderBackend *Application::renderer() {
auto *renderMod = get<RenderModule>();
return renderMod ? renderMod->renderer() : nullptr;
}
IInput* Application::input() {
auto* winMod = get<WindowModule>();
IInput *Application::input() {
auto *winMod = get<WindowModule>();
return (winMod && winMod->win()) ? winMod->win()->input() : nullptr;
}
void Application::enterScene(Ptr<Scene> scene) {
auto sceneService = ServiceLocator::instance().getService<ISceneService>();
auto* winMod = get<WindowModule>();
auto *winMod = get<WindowModule>();
if (sceneService && scene && winMod && winMod->win()) {
scene->setViewportSize(static_cast<float>(winMod->win()->width()),
static_cast<float>(winMod->win()->height()));

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 {
/**
* @brief
*
* Event对象
*
* @param width
* @param height
* @return Event对象
*/
Event Event::createWindowResize(int width, int height) {
Event event;
event.type = EventType::WindowResize;
@ -9,50 +18,115 @@ Event Event::createWindowResize(int width, int height) {
return event;
}
/**
* @brief
*
* Event对象
*
* @return Event对象
*/
Event Event::createWindowClose() {
Event event;
event.type = EventType::WindowClose;
return event;
}
/**
* @brief
*
* Event对象
*
* @param keyCode
* @param scancode
* @param mods ShiftCtrl等
* @return Event对象
*/
Event Event::createKeyPress(int keyCode, int scancode, int mods) {
Event event;
event.type = EventType::KeyPress;
event.type = EventType::KeyPressed;
event.data = KeyEvent{keyCode, scancode, mods};
return event;
}
/**
* @brief
*
* Event对象
*
* @param keyCode
* @param scancode
* @param mods ShiftCtrl等
* @return Event对象
*/
Event Event::createKeyRelease(int keyCode, int scancode, int mods) {
Event event;
event.type = EventType::KeyRelease;
event.type = EventType::KeyReleased;
event.data = KeyEvent{keyCode, scancode, mods};
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.type = EventType::MousePress;
event.data = MouseEvent{button, mods, pos};
event.type = EventType::MouseButtonPressed;
event.data = MouseButtonEvent{button, mods, pos};
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.type = EventType::MouseRelease;
event.data = MouseEvent{button, mods, pos};
event.type = EventType::MouseButtonReleased;
event.data = MouseButtonEvent{button, mods, pos};
return event;
}
/**
* @brief
*
* Event对象
*
* @param pos
* @param delta
* @return Event对象
*/
Event Event::createMouseMove(const Vec2 &pos, const Vec2 &delta) {
Event event;
event.type = EventType::MouseMove;
event.type = EventType::MouseMoved;
event.data = MouseMoveEvent{pos, delta};
return event;
}
/**
* @brief
*
* Event对象
*
* @param offset
* @param pos
* @return Event对象
*/
Event Event::createMouseScroll(const Vec2 &offset, const Vec2 &pos) {
Event event;
event.type = EventType::MouseScroll;
event.type = EventType::MouseScrolled;
event.data = MouseScrollEvent{offset, pos};
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 <algorithm>
#include <cmath>
#include <cstring>
#include <fstream>
// 在实现文件中定义 STB 实现
#define STB_TRUETYPE_IMPLEMENTATION
#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 {
// ============================================================================
// 构造函数 - 初始化字体图集
// GLFontAtlas 构造函数
// 加载字体文件并初始化图集
// ============================================================================
/**
* @brief
* @param filepath
* @param fontSize
* @param useSDF 使
*/
GLFontAtlas::GLFontAtlas(const std::string &filepath, int fontSize, bool useSDF)
: fontSize_(fontSize), useSDF_(useSDF), currentY_(0), scale_(0.0f),
ascent_(0.0f), descent_(0.0f), lineGap_(0.0f) {
: useSDF_(useSDF), fontSize_(fontSize), lineHeight_(0.0f), ascent_(0.0f),
descent_(0.0f), lineGap_(0.0f), scale_(0.0f) {
// 加载字体文件
std::ifstream file(filepath, std::ios::binary | std::ios::ate);
if (!file.is_open()) {
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);
if (!initFont(filepath)) {
E2D_LOG_ERROR("Failed to initialize font: {}", filepath);
return;
}
// 计算字体缩放比例和度量
scale_ = stbtt_ScaleForPixelHeight(&fontInfo_, static_cast<float>(fontSize_));
int ascent, descent, lineGap;
stbtt_GetFontVMetrics(&fontInfo_, &ascent, &descent, &lineGap);
ascent_ = static_cast<float>(ascent) * scale_;
descent_ = static_cast<float>(descent) * scale_;
lineGap_ = static_cast<float>(lineGap) * scale_;
lineHeight_ = ascent_ - descent_ + lineGap_;
// 创建图集纹理和打包上下文
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 析构函数
// ============================================================================
/**
* @brief
*/
GLFontAtlas::~GLFontAtlas() = default;
GLFontAtlas::~GLFontAtlas() {
// 智能指针自动管理纹理资源
}
// ============================================================================
// 获取字形 - 如果字形不存在则缓存它
// 获取字形信息 - 如果字形不存在则动态缓存
// ============================================================================
/**
* @brief
* @param codepoint Unicode码点
* @return nullptr
*/
const Glyph *GLFontAtlas::getGlyph(char32_t codepoint) const {
auto it = glyphs_.find(codepoint);
if (it == glyphs_.end()) {
cacheGlyph(codepoint);
// 动态缓存新字形
const_cast<GLFontAtlas *>(this)->cacheGlyph(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) {
float width = 0.0f;
float height = getAscent() - getDescent();
float maxWidth = 0.0f;
float height = lineHeight_;
float currentWidth = 0.0f;
for (char c : text) {
char32_t codepoint = static_cast<char32_t>(static_cast<unsigned char>(c));
for (size_t i = 0; i < text.length();) {
// 处理 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') {
width = std::max(width, currentWidth);
maxWidth = std::max(maxWidth, currentWidth);
currentWidth = 0.0f;
height += getLineHeight();
height += lineHeight_;
continue;
}
@ -109,25 +155,51 @@ Vec2 GLFontAtlas::measureText(const std::string &text) {
}
}
width = std::max(width, currentWidth);
return Vec2(width, height);
maxWidth = std::max(maxWidth, currentWidth);
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() {
// 统一使用 4 通道格式
// 统一使用 4 通道格式 (RGBA)
int channels = 4;
std::vector<uint8_t> emptyData(ATLAS_WIDTH * ATLAS_HEIGHT * channels, 0);
texture_ = std::make_unique<GLTexture>(ATLAS_WIDTH, ATLAS_HEIGHT,
emptyData.data(), channels);
texture_->setFilter(true);
// 初始化矩形打包上下文
// 初始化矩形打包上下文 - 持久化以支持增量打包
packNodes_.resize(ATLAS_WIDTH);
stbrp_init_target(&packContext_, ATLAS_WIDTH, ATLAS_HEIGHT, packNodes_.data(),
ATLAS_WIDTH);
@ -143,16 +215,19 @@ void GLFontAtlas::createAtlas() {
// 缓存字形 - 渲染字形到图集并存储信息
// 使用 stb_rect_pack 进行矩形打包
// ============================================================================
/**
* @brief
* @param codepoint Unicode码点
*/
void GLFontAtlas::cacheGlyph(char32_t codepoint) const {
int advance = 0;
stbtt_GetCodepointHMetrics(&fontInfo_, static_cast<int>(codepoint), &advance,
nullptr);
float advancePx = advance * scale_;
void GLFontAtlas::cacheGlyph(char32_t codepoint) {
// 检查是否已存在
if (glyphs_.find(codepoint) != glyphs_.end()) {
return;
}
// 获取字形水平度量
int advance, leftSideBearing;
stbtt_GetCodepointHMetrics(&fontInfo_, static_cast<int>(codepoint), &advance,
&leftSideBearing);
float advancePx = static_cast<float>(advance) * scale_;
// SDF 渲染模式
if (useSDF_) {
constexpr int SDF_PADDING = 8;
constexpr unsigned char ONEDGE_VALUE = 128;
@ -162,15 +237,18 @@ void GLFontAtlas::cacheGlyph(char32_t codepoint) const {
unsigned char *sdf = stbtt_GetCodepointSDF(
&fontInfo_, scale_, static_cast<int>(codepoint), SDF_PADDING,
ONEDGE_VALUE, PIXEL_DIST_SCALE, &w, &h, &xoff, &yoff);
if (!sdf || w <= 0 || h <= 0) {
if (sdf)
stbtt_FreeSDF(sdf, nullptr);
Glyph glyph{};
glyph.advance = advancePx;
glyphs_[codepoint] = glyph;
// 创建空白字形(如空格)
GlyphData data{};
data.advance = advancePx;
glyphs_[codepoint] = data;
return;
}
// 使用 stb_rect_pack 打包矩形
stbrp_rect rect;
rect.id = static_cast<int>(codepoint);
rect.w = w + PADDING * 2;
@ -178,8 +256,7 @@ void GLFontAtlas::cacheGlyph(char32_t codepoint) const {
stbrp_pack_rects(&packContext_, &rect, 1);
if (!rect.was_packed) {
E2D_LOG_WARN("Font atlas is full, cannot cache codepoint: {}",
static_cast<int>(codepoint));
E2D_LOG_WARN("Font atlas is full, cannot cache codepoint: {}", codepoint);
stbtt_FreeSDF(sdf, nullptr);
return;
}
@ -187,23 +264,25 @@ void GLFontAtlas::cacheGlyph(char32_t codepoint) const {
int atlasX = rect.x + PADDING;
int atlasY = rect.y + PADDING;
Glyph glyph;
glyph.width = static_cast<float>(w);
glyph.height = static_cast<float>(h);
glyph.bearingX = static_cast<float>(xoff);
glyph.bearingY = static_cast<float>(yoff);
glyph.advance = advancePx;
// 创建字形数据
GlyphData data;
data.width = static_cast<float>(w);
data.height = static_cast<float>(h);
data.bearingX = static_cast<float>(xoff);
data.bearingY = static_cast<float>(yoff);
data.advance = advancePx;
// 计算 UV 坐标
// stb_rect_pack 使用左上角为原点OpenGL纹理使用左下角为原点
// 需要翻转V坐标
float v0 = static_cast<float>(atlasY) / ATLAS_HEIGHT;
float v1 = static_cast<float>(atlasY + h) / ATLAS_HEIGHT;
glyph.u0 = static_cast<float>(atlasX) / ATLAS_WIDTH;
glyph.v0 = 1.0f - v1; // 翻转V坐标
glyph.u1 = static_cast<float>(atlasX + w) / ATLAS_WIDTH;
glyph.v1 = 1.0f - v0; // 翻转V坐标
data.u0 = static_cast<float>(atlasX) / ATLAS_WIDTH;
data.v0 = 1.0f - v1; // 翻转V坐标
data.u1 = static_cast<float>(atlasX + w) / ATLAS_WIDTH;
data.v1 = 1.0f - v0; // 翻转V坐标
glyphs_[codepoint] = glyph;
glyphs_[codepoint] = data;
// 将 SDF 单通道数据转换为 RGBA 格式(统一格式)
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 通道
}
// 直接设置像素对齐为 4无需查询当前状态
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());
// 更新纹理 - OpenGL纹理坐标原点在左下角需要将Y坐标翻转
updateAtlas(atlasX, ATLAS_HEIGHT - atlasY - h, w, h, glyphRgbaCache_);
stbtt_FreeSDF(sdf, nullptr);
return;
}
// 普通位图渲染模式
int x0 = 0, y0 = 0, x1 = 0, y1 = 0;
stbtt_GetCodepointBitmapBox(&fontInfo_, static_cast<int>(codepoint), scale_,
scale_, &x0, &y0, &x1, &y1);
int w = x1 - x0;
int h = y1 - y0;
int xoff = x0;
// y0 是相对于基线的偏移(通常为负值,表示在基线上方)
// bearingY 应该是字形顶部相对于基线的偏移
int yoff = y0;
if (w <= 0 || h <= 0) {
Glyph glyph{};
glyph.advance = advancePx;
glyphs_[codepoint] = glyph;
// 空白字符(如空格)
GlyphData data{};
data.advance = advancePx;
glyphs_[codepoint] = data;
return;
}
// 使用预分配缓冲区
// 使用预分配缓冲区渲染字形
size_t pixelCount = static_cast<size_t>(w) * static_cast<size_t>(h);
glyphBitmapCache_.resize(pixelCount);
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);
if (!rect.was_packed) {
// 图集已满,无法缓存更多字形
E2D_LOG_WARN("Font atlas is full, cannot cache codepoint: {}",
static_cast<int>(codepoint));
E2D_LOG_WARN("Font atlas is full, cannot cache codepoint: {}", codepoint);
return;
}
int atlasX = rect.x + PADDING;
int atlasY = rect.y + PADDING;
// 创建字形信息
Glyph glyph;
glyph.width = static_cast<float>(w);
glyph.height = static_cast<float>(h);
glyph.bearingX = static_cast<float>(xoff);
glyph.bearingY = static_cast<float>(yoff);
glyph.advance = advancePx;
// 创建字形数据
GlyphData data;
data.width = static_cast<float>(w);
data.height = static_cast<float>(h);
data.bearingX = static_cast<float>(xoff);
data.bearingY = static_cast<float>(yoff);
data.advance = advancePx;
// 计算纹理坐标(相对于图集)
// 计算 UV 坐标
// stb_rect_pack 使用左上角为原点OpenGL纹理使用左下角为原点
// 需要翻转V坐标
float v0 = static_cast<float>(atlasY) / ATLAS_HEIGHT;
float v1 = static_cast<float>(atlasY + h) / ATLAS_HEIGHT;
glyph.u0 = static_cast<float>(atlasX) / ATLAS_WIDTH;
glyph.v0 = 1.0f - v1; // 翻转V坐标
glyph.u1 = static_cast<float>(atlasX + w) / ATLAS_WIDTH;
glyph.v1 = 1.0f - v0; // 翻转V坐标
data.u0 = static_cast<float>(atlasX) / ATLAS_WIDTH;
data.v0 = 1.0f - v1; // 翻转V坐标
data.u1 = static_cast<float>(atlasX + w) / ATLAS_WIDTH;
data.v1 = 1.0f - v0; // 翻转V坐标
// 存储字形
glyphs_[codepoint] = glyph;
glyphs_[codepoint] = data;
// 将单通道字形数据转换为 RGBA 格式白色字形Alpha 通道存储灰度)
glyphRgbaCache_.resize(pixelCount * 4);
@ -297,13 +373,21 @@ void GLFontAtlas::cacheGlyph(char32_t codepoint) const {
glyphRgbaCache_[i * 4 + 3] = alpha; // A
}
// 更新纹理 - 将字形数据上传到图集的指定位置
// 直接设置像素对齐为 4无需查询当前状态
glBindTexture(GL_TEXTURE_2D, texture_->getTextureID());
// 更新纹理 - OpenGL纹理坐标原点在左下角需要将Y坐标翻转
updateAtlas(atlasX, ATLAS_HEIGHT - atlasY - h, w, h, glyphRgbaCache_);
}
// ============================================================================
// 更新图集纹理区域
// ============================================================================
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);
// OpenGL纹理坐标原点在左下角需要将Y坐标翻转
glTexSubImage2D(GL_TEXTURE_2D, 0, atlasX, ATLAS_HEIGHT - atlasY - h, w, h,
GL_RGBA, GL_UNSIGNED_BYTE, glyphRgbaCache_.data());
glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, width, height, GL_RGBA,
GL_UNSIGNED_BYTE, data.data());
}
}
} // 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 <cmath>
#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/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/shader/shader_manager.h>
#include <extra2d/platform/iwindow.h>
#include <extra2d/utils/logger.h>
#include <vector>
@ -17,30 +19,11 @@ namespace extra2d {
// VBO 初始大小(用于 VRAM 跟踪)
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渲染器成员变量
*/
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),
lineVertexCount_(0), currentLineWidth_(1.0f) {
resetStats();
@ -65,7 +48,11 @@ GLRenderer::~GLRenderer() { shutdown(); }
bool GLRenderer::init(IWindow *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()) {
@ -76,16 +63,24 @@ bool GLRenderer::init(IWindow *window) {
// 初始化形状渲染
initShapeRendering();
// 设置 OpenGL 状态
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
// 初始化管线状态管理
PipelineDesc pipelineDesc;
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 上下文为有效
GPUContext::get().markValid();
E2D_LOG_INFO("OpenGL Renderer initialized");
E2D_LOG_INFO("OpenGL Version: {}",
reinterpret_cast<const char *>(glGetString(GL_VERSION)));
E2D_LOG_INFO("OpenGL Version: {}", GLContext::get().getVersionString());
return true;
}
@ -100,24 +95,22 @@ void GLRenderer::shutdown() {
spriteBatch_.shutdown();
if (lineVbo_ != 0) {
glDeleteBuffers(1, &lineVbo_);
VRAMMgr::get().freeBuffer(MAX_LINE_VERTICES * sizeof(ShapeVertex));
lineVbo_ = 0;
}
// 关闭 GLBuffer自动释放 VBO
lineBuffer_.shutdown();
shapeBuffer_.shutdown();
// 删除 VAOVAO 仍然手动管理)
if (lineVao_ != 0) {
glDeleteVertexArrays(1, &lineVao_);
lineVao_ = 0;
}
if (shapeVbo_ != 0) {
glDeleteBuffers(1, &shapeVbo_);
VRAMMgr::get().freeBuffer(MAX_SHAPE_VERTICES * sizeof(ShapeVertex));
shapeVbo_ = 0;
}
if (shapeVao_ != 0) {
glDeleteVertexArrays(1, &shapeVao_);
shapeVao_ = 0;
}
// 关闭 OpenGL 上下文
GLContext::get().shutdown();
}
/**
@ -125,6 +118,9 @@ void GLRenderer::shutdown() {
* @param clearColor
*/
void GLRenderer::beginFrame(const Color &clearColor) {
// 应用管线状态
pipeline_.applyAllStates();
glClearColor(clearColor.r, clearColor.g, clearColor.b, clearColor.a);
glClear(GL_COLOR_BUFFER_BIT);
resetStats();
@ -134,6 +130,10 @@ void GLRenderer::beginFrame(const Color &clearColor) {
* @brief
*/
void GLRenderer::endFrame() {
// 刷新所有待处理的精灵批次(自动批处理)
if (autoBatchEnabled_ && batchActive_) {
flush();
}
// 刷新所有待处理的形状批次
flushShapeBatch();
// 刷新所有待处理的线条批次
@ -148,7 +148,8 @@ void GLRenderer::endFrame() {
* @param 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) {
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
*/
void GLRenderer::setBlendMode(BlendMode mode) {
// 状态缓存检查,避免冗余 GL 调用
if (cachedBlendMode_ == 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;
}
}
// 使用 GLPipeline 管理混合状态
pipeline_.setBlendMode(mode);
}
/**
@ -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
@ -272,9 +288,21 @@ void GLRenderer::beginSpriteBatch() { spriteBatch_.begin(viewProjection_); }
void GLRenderer::drawSprite(const Texture &texture, const Rect &destRect,
const Rect &srcRect, const Color &tint,
float rotation, const Vec2 &anchor) {
GLSpriteBatch::SpriteData data;
data.position = glm::vec2(destRect.origin.x, destRect.origin.y);
data.size = glm::vec2(destRect.size.width, destRect.size.height);
// 自动批处理模式
if (autoBatchEnabled_) {
ensureBatchActive();
// 如果纹理变化或缓冲区满,先提交当前批次
if (currentBatchTexture_ != &texture ||
pendingSprites_.size() >= MAX_BATCH_SPRITES) {
submitPendingSprites();
currentBatchTexture_ = &texture;
}
// 创建精灵数据
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());
@ -286,15 +314,39 @@ void GLRenderer::drawSprite(const Texture &texture, const Rect &destRect,
float v1 = srcRect.origin.y / texH;
float v2 = (srcRect.origin.y + srcRect.size.height) / texH;
data.texCoordMin = glm::vec2(glm::min(u1, u2), glm::min(v1, v2));
data.texCoordMax = glm::vec2(glm::max(u1, u2), glm::max(v1, v2));
data.uvRect = Rect(Vec2(glm::min(u1, u2), glm::min(v1, v2)),
Size(glm::abs(u2 - u1), glm::abs(v2 - v1)));
data.color = glm::vec4(tint.r, tint.g, tint.b, tint.a);
data.color = tint;
data.rotation = rotation * 3.14159f / 180.0f;
data.anchor = glm::vec2(anchor.x, anchor.y);
data.isSDF = false;
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() {
if (autoBatchEnabled_) {
// 自动模式下,只是标记批处理结束
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 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()); // 预分配空间
for (char c : text) {
char32_t codepoint = static_cast<char32_t>(static_cast<unsigned char>(c));
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;
cursorY += font.getLineHeight();
baselineY = cursorY + font.getAscent();
@ -596,31 +696,51 @@ void GLRenderer::drawText(const FontAtlas &font, const std::string &text,
float penX = cursorX;
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;
}
// 计算字形位置
// bearingX: 水平偏移(从左边缘到字形左边缘)
// bearingY: 垂直偏移(从基线到字形顶部,通常为负值)
float xPos = penX + glyph->bearingX;
float yPos = baselineY + glyph->bearingY;
GLSpriteBatch::SpriteData data;
data.position = glm::vec2(xPos, yPos);
data.size = glm::vec2(glyph->width, glyph->height);
data.texCoordMin = glm::vec2(glyph->u0, glyph->v0);
data.texCoordMax = glm::vec2(glyph->u1, glyph->v1);
data.color = glm::vec4(color.r, color.g, color.b, color.a);
SpriteData data;
// 设置精灵中心位置(精灵批处理使用中心点)
data.position =
Vec2(xPos + glyph->width * 0.5f, yPos + glyph->height * 0.5f);
data.size = Vec2(glyph->width, glyph->height);
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.anchor = glm::vec2(0.0f, 0.0f);
data.isSDF = font.isSDF();
// pivot (0.5, 0.5) 表示中心点,这样 position 就是精灵中心
data.pivot = Vec2(0.5f, 0.5f);
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 (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_);
glGenBuffers(1, &shapeVbo_);
glBindVertexArray(shapeVao_);
glBindBuffer(GL_ARRAY_BUFFER, shapeVbo_);
glBufferData(GL_ARRAY_BUFFER, MAX_SHAPE_VERTICES * sizeof(ShapeVertex),
nullptr, GL_DYNAMIC_DRAW);
shapeBuffer_.bind();
// 位置属性 (location = 0)
glEnableVertexAttribArray(0);
@ -662,14 +787,19 @@ void GLRenderer::initShapeRendering() {
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_);
glGenBuffers(1, &lineVbo_);
glBindVertexArray(lineVao_);
glBindBuffer(GL_ARRAY_BUFFER, lineVbo_);
glBufferData(GL_ARRAY_BUFFER, MAX_LINE_VERTICES * sizeof(ShapeVertex),
nullptr, GL_DYNAMIC_DRAW);
lineBuffer_.bind();
// 位置属性 (location = 0)
glEnableVertexAttribArray(0);
@ -682,10 +812,6 @@ void GLRenderer::initShapeRendering() {
reinterpret_cast<void *>(offsetof(ShapeVertex, r)));
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_);
}
glBindBuffer(GL_ARRAY_BUFFER, shapeVbo_);
glBufferSubData(GL_ARRAY_BUFFER, 0, shapeVertexCount_ * sizeof(ShapeVertex),
shapeVertexCache_.data());
// 使用 GLBuffer::updateData() 更新缓冲区数据
shapeBuffer_.updateData(shapeVertexCache_.data(), 0,
shapeVertexCount_ * sizeof(ShapeVertex));
glBindVertexArray(shapeVao_);
glDrawArrays(currentShapeMode_, 0, static_cast<GLsizei>(shapeVertexCount_));
@ -794,9 +920,9 @@ void GLRenderer::flushLineBatch() {
shapeShader_->setMat4("u_viewProjection", viewProjection_);
}
glBindBuffer(GL_ARRAY_BUFFER, lineVbo_);
glBufferSubData(GL_ARRAY_BUFFER, 0, lineVertexCount_ * sizeof(ShapeVertex),
lineVertexCache_.data());
// 使用 GLBuffer::updateData() 更新缓冲区数据
lineBuffer_.updateData(lineVertexCache_.data(), 0,
lineVertexCount_ * sizeof(ShapeVertex));
glBindVertexArray(lineVao_);
glDrawArrays(GL_LINES, 0, static_cast<GLsizei>(lineVertexCount_));
@ -806,4 +932,90 @@ void GLRenderer::flushLineBatch() {
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

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>
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/vram_manager.h>
#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>
namespace extra2d {

View File

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

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