Compare commits
17 Commits
d3a8c6c979
...
583e866861
| Author | SHA1 | Date |
|---|---|---|
|
|
583e866861 | |
|
|
edd47a890a | |
|
|
69606230da | |
|
|
6babd376c9 | |
|
|
2748e80dea | |
|
|
c8a6ea19e3 | |
|
|
32e12b8c99 | |
|
|
6b4ce69657 | |
|
|
30b677f192 | |
|
|
89fb955eb0 | |
|
|
f1cf6a6d85 | |
|
|
a4276e4376 | |
|
|
d2660a86bb | |
|
|
61dea772f7 | |
|
|
4f02ad0e39 | |
|
|
4b1de5e36a | |
|
|
9f83b8fde5 |
86
CLAUDE.md
86
CLAUDE.md
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(); }
|
||||
};
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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)
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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); \
|
||||
}
|
||||
|
|
@ -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);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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 可用返回true,否则返回false
|
||||
*/
|
||||
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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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);
|
||||
|
|
@ -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
|
||||
|
|
@ -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 ¢er, float radius, const Color &color,
|
||||
int segments, float width) override;
|
||||
void fillCircle(const Vec2 ¢er, 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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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;
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 形状渲染
|
||||
// ------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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);
|
||||
};
|
||||
|
||||
// 便捷宏
|
||||
|
|
|
|||
|
|
@ -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搜索路径
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 工具方法
|
||||
// ------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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 ®ion,
|
||||
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 ®ion,
|
||||
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_; // 当前内存使用量
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -85,4 +85,4 @@ private:
|
|||
} e2d_backend_reg_##name; \
|
||||
}
|
||||
|
||||
}
|
||||
} // namespace extra2d
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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();
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 内部方法
|
||||
// ------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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_;
|
||||
|
|
|
|||
|
|
@ -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_;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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_;
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
#version 300 es
|
||||
precision highp float;
|
||||
|
||||
in vec4 v_color;
|
||||
|
||||
out vec4 fragColor;
|
||||
|
||||
void main() {
|
||||
fragColor = v_color;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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()));
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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 修饰键状态(如Shift、Ctrl等)
|
||||
* @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 修饰键状态(如Shift、Ctrl等)
|
||||
* @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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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();
|
||||
|
||||
// 删除 VAO(VAO 仍然手动管理)
|
||||
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
|
||||
|
|
@ -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 {
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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 ¢er, float radius, const Color &color,
|
||||
int segments, float width) {
|
||||
// TODO: 实现圆形边框绘制
|
||||
}
|
||||
|
||||
void VulkanRenderer::fillCircle(const Vec2 ¢er, 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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
Loading…
Reference in New Issue