Compare commits

...

17 Commits

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

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

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

View File

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

View File

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

View File

@ -1,87 +0,0 @@
#pragma once
#include <extra2d/core/types.h>
#include <string>
namespace extra2d {
/**
* @file platform_config.h
* @brief
*
*
* IModuleConfig::applyPlatformConstraints()
*/
/**
* @brief
*/
enum class PlatformType {
Auto,
Windows,
Switch,
Linux,
macOS
};
/**
* @brief
*/
struct PlatformCapabilities {
bool supportsWindowed = true;
bool supportsFullscreen = true;
bool supportsBorderless = true;
bool supportsCursor = true;
bool supportsCursorHide = true;
bool supportsDPIAwareness = true;
bool supportsVSync = true;
bool supportsMultiMonitor = true;
bool supportsClipboard = true;
bool supportsGamepad = true;
bool supportsTouch = false;
bool supportsKeyboard = true;
bool supportsMouse = true;
bool supportsResize = true;
bool supportsHighDPI = true;
int maxTextureSize = 16384;
int preferredScreenWidth = 1920;
int preferredScreenHeight = 1080;
float defaultDPI = 96.0f;
bool hasWindowSupport() const { return supportsWindowed || supportsFullscreen || supportsBorderless; }
bool hasInputSupport() const { return supportsKeyboard || supportsMouse || supportsGamepad || supportsTouch; }
bool isDesktop() const { return supportsKeyboard && supportsMouse && supportsWindowed; }
bool isConsole() const { return !supportsWindowed && supportsGamepad; }
};
/**
* @brief
*/
class PlatformConfig {
public:
virtual ~PlatformConfig() = default;
virtual PlatformType platformType() const = 0;
virtual const char* platformName() const = 0;
virtual const PlatformCapabilities& capabilities() const = 0;
virtual int getRecommendedWidth() const = 0;
virtual int getRecommendedHeight() const = 0;
virtual bool isResolutionSupported(int width, int height) const = 0;
};
/**
* @brief
* @param type Auto
* @return
*/
UniquePtr<PlatformConfig> createPlatformConfig(PlatformType type = PlatformType::Auto);
/**
* @brief
* @param type
* @return
*/
const char* getPlatformTypeName(PlatformType type);
}

View File

@ -1,210 +0,0 @@
#pragma once
#include <extra2d/config/app_config.h>
#include <extra2d/config/platform_config.h>
#include <extra2d/core/types.h>
#include <string>
namespace extra2d {
// ============================================================================
// 平台检测器工具类
// ============================================================================
class PlatformDetector {
public:
/**
* @brief
* @return
*/
static PlatformType detect();
/**
* @brief
* @return "Windows", "Linux", "macOS", "Switch"
*/
static const char* platformName();
/**
* @brief
* @param type
* @return
*/
static const char* platformName(PlatformType type);
/**
* @brief
* @return true
*/
static bool isDesktopPlatform();
/**
* @brief
* @return true
*/
static bool isConsolePlatform();
/**
* @brief
* @return true
*/
static bool isMobilePlatform();
/**
* @brief
* @return
*/
static PlatformCapabilities capabilities();
/**
* @brief
* @param type
* @return
*/
static PlatformCapabilities capabilities(PlatformType type);
/**
* @brief
* @return
*/
static AppConfig platformDefaults();
/**
* @brief
* @param type
* @return
*/
static AppConfig platformDefaults(PlatformType type);
/**
* @brief
* @param width
* @param height
*/
static void getRecommendedResolution(int& width, int& height);
/**
* @brief DPI
* @return DPI
*/
static float getDefaultDPI();
/**
* @brief
* @param feature
* @return true
*/
static bool supportsFeature(const std::string& feature);
/**
* @brief
* @return MB 0
*/
static int getSystemMemoryMB();
/**
* @brief CPU
* @return CPU
*/
static int getCPUCoreCount();
/**
* @brief 线
* @return true
*/
static bool supportsMultithreadedRendering();
/**
* @brief
* @param appName
* @return
*/
static std::string getConfigPath(const std::string& appName);
/**
* @brief
* @param appName
* @return
*/
static std::string getSavePath(const std::string& appName);
/**
* @brief
* @param appName
* @return
*/
static std::string getCachePath(const std::string& appName);
/**
* @brief
* @param appName
* @return
*/
static std::string getLogPath(const std::string& appName);
/**
* @brief Shader
* Switch平台使用romfs使
* @param appName
* @return
*/
static std::string getResourcePath(const std::string& appName = "");
/**
* @brief Shader路径
* @param appName
* @return Shader目录路径
*/
static std::string getShaderPath(const std::string& appName = "");
/**
* @brief Shader缓存路径
* Switch平台使用sdmc使
* @param appName
* @return Shader缓存目录路径
*/
static std::string getShaderCachePath(const std::string& appName = "");
/**
* @brief 使romfs
* @return 使romfs返回true
*/
static bool usesRomfs();
/**
* @brief
* Switch平台不支持热重载romfs只读
* @return true
*/
static bool supportsHotReload();
/**
* @brief
* @return true
*/
static bool isLittleEndian();
/**
* @brief
* @return true
*/
static bool isBigEndian();
/**
* @brief
* @return
*/
static std::string getPlatformSummary();
private:
static PlatformCapabilities getWindowsCapabilities();
static PlatformCapabilities getLinuxCapabilities();
static PlatformCapabilities getMacOSCapabilities();
static PlatformCapabilities getSwitchCapabilities();
static AppConfig getWindowsDefaults();
static AppConfig getLinuxDefaults();
static AppConfig getMacOSDefaults();
static AppConfig getSwitchDefaults();
};
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,10 +3,30 @@
#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
*
@ -14,41 +34,22 @@ namespace extra2d {
class InputModule : public Module {
public:
/**
* @brief
* @brief Lambda
* @param configFn
*/
struct Cfg {
float deadzone;
float mouseSensitivity;
bool enableVibration;
int maxGamepads;
int priority;
Cfg()
: deadzone(0.15f)
, mouseSensitivity(1.0f)
, enableVibration(true)
, maxGamepads(4)
, priority(20)
{}
};
/**
* @brief
* @param cfg
*/
explicit InputModule(const Cfg& cfg = Cfg{});
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
@ -56,20 +57,20 @@ public:
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:
Cfg cfg_;
InputCfg cfg_;
IInput* input_ = nullptr;
bool initialized_ = false;
};

View File

@ -1,67 +1,64 @@
#pragma once
#include <extra2d/core/module.h>
#include <extra2d/platform/window_config.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
*/
struct Cfg {
std::string title;
int w;
int h;
WindowMode mode;
bool vsync;
int priority;
std::string backend;
Cfg()
: title("Extra2D")
, w(1280)
, h(720)
, mode(WindowMode::Windowed)
, vsync(true)
, priority(0)
, backend("sdl2") {}
};
/**
* @brief
* @param cfg
*/
explicit WindowModule(const Cfg& cfg = Cfg());
/**
* @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(); }
/**
* @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:
Cfg cfg_;
UniquePtr<IWindow> win_;
bool initialized_ = false;
bool sdlInited_ = false;
WindowCfg cfg_;
UniquePtr<IWindow> win_;
bool initialized_ = false;
bool sdlInited_ = false;
};
} // namespace extra2d

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,7 @@
#pragma once
#include <extra2d/core/service_interface.h>
#include <extra2d/core/service_locator.h>
#include <extra2d/event/event_dispatcher.h>
#include <extra2d/event/event_queue.h>
@ -68,6 +69,9 @@ public:
private:
EventQueue queue_;
EventDispatcher dispatcher_;
// 服务注册元数据
E2D_AUTO_REGISTER_SERVICE(IEventService, EventService);
};
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,268 +1,248 @@
#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 {
static double getTimeSeconds() {
#ifdef __SWITCH__
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return static_cast<double>(ts.tv_sec) +
static_cast<double>(ts.tv_nsec) / 1000000000.0;
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
return static_cast<double>(ts.tv_sec) +
static_cast<double>(ts.tv_nsec) / 1000000000.0;
#else
using namespace std::chrono;
auto now = steady_clock::now();
auto duration = now.time_since_epoch();
return duration_cast<std::chrono::duration<double>>(duration).count();
using namespace std::chrono;
auto now = steady_clock::now();
auto duration = now.time_since_epoch();
return duration_cast<std::chrono::duration<double>>(duration).count();
#endif
}
Application& Application::get() {
static Application instance;
return instance;
Application &Application::get() {
static Application instance;
return instance;
}
Application::Application() {
Registry::instance().setApp(this);
}
Application::Application() { Registry::instance().setApp(this); }
Application::~Application() {
if (initialized_) {
shutdown();
}
if (initialized_) {
shutdown();
}
}
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));
}
// 初始化所有模块(拓扑排序)
if (!Registry::instance().init()) {
return false;
}
// 模块初始化完成后,注册其他核心服务
registerCoreServices();
initialized_ = true;
running_ = true;
if (initialized_) {
return true;
}
// 初始化所有模块(拓扑排序)
// 服务通过 E2D_AUTO_REGISTER_SERVICE 宏自动注册
if (!Registry::instance().init()) {
return false;
}
// 配置相机服务(需要窗口信息)
configureCameraService();
// 初始化所有服务
ServiceLocator::instance().initializeAll();
initialized_ = true;
running_ = true;
return true;
}
void Application::registerCoreServices() {
auto& locator = ServiceLocator::instance();
if (!locator.hasService<ISceneService>()) {
auto service = makeShared<SceneService>();
locator.registerService(std::static_pointer_cast<ISceneService>(service));
}
if (!locator.hasService<ITimerService>()) {
auto service = makeShared<TimerService>();
locator.registerService(std::static_pointer_cast<ITimerService>(service));
}
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();
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::configureCameraService() {
auto *winMod = get<WindowModule>();
if (!winMod || !winMod->win()) {
return;
}
auto cameraService = ServiceLocator::instance().getService<ICameraService>();
if (!cameraService) {
return;
}
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());
win->onResize([cameraService](int width, int height) {
cameraService->updateViewport(width, height);
cameraService->applyViewportAdapter();
});
}
void Application::shutdown() {
if (!initialized_) return;
VRAMMgr::get().printStats();
ServiceLocator::instance().clear();
Registry::instance().shutdown();
Registry::instance().clear();
initialized_ = false;
running_ = false;
if (!initialized_)
return;
VRAMMgr::get().printStats();
ServiceLocator::instance().shutdownAll();
ServiceLocator::instance().clear();
Registry::instance().shutdown();
Registry::instance().clear();
initialized_ = false;
running_ = false;
}
void Application::run() {
if (!initialized_) return;
auto* winMod = get<WindowModule>();
if (!winMod || !winMod->win()) return;
lastFrameTime_ = getTimeSeconds();
while (running_ && !winMod->win()->shouldClose()) {
mainLoop();
}
if (!initialized_)
return;
auto *winMod = get<WindowModule>();
if (!winMod || !winMod->win())
return;
lastFrameTime_ = getTimeSeconds();
while (running_ && !winMod->win()->shouldClose()) {
mainLoop();
}
}
void Application::quit() {
shouldQuit_ = true;
running_ = false;
shouldQuit_ = true;
running_ = false;
}
void Application::pause() {
if (!paused_) {
paused_ = true;
ServiceLocator::instance().pauseAll();
}
if (!paused_) {
paused_ = true;
ServiceLocator::instance().pauseAll();
}
}
void Application::resume() {
if (paused_) {
paused_ = false;
ServiceLocator::instance().resumeAll();
lastFrameTime_ = getTimeSeconds();
}
if (paused_) {
paused_ = false;
ServiceLocator::instance().resumeAll();
lastFrameTime_ = getTimeSeconds();
}
}
void Application::mainLoop() {
double currentTime = getTimeSeconds();
deltaTime_ = static_cast<float>(currentTime - lastFrameTime_);
lastFrameTime_ = currentTime;
totalTime_ += deltaTime_;
frameCount_++;
fpsTimer_ += deltaTime_;
if (fpsTimer_ >= 1.0f) {
currentFps_ = frameCount_;
frameCount_ = 0;
fpsTimer_ -= 1.0f;
}
auto* winMod = get<WindowModule>();
if (winMod && winMod->win()) {
winMod->win()->poll();
}
auto eventService = ServiceLocator::instance().getService<IEventService>();
if (eventService) {
eventService->processQueue();
}
if (!paused_) {
update();
}
render();
// 帧率限制
auto* renderMod = get<RenderModule>();
if (renderMod && renderMod->renderer()) {
// 这里可以添加帧率限制逻辑
}
double currentTime = getTimeSeconds();
deltaTime_ = static_cast<float>(currentTime - lastFrameTime_);
lastFrameTime_ = currentTime;
totalTime_ += deltaTime_;
frameCount_++;
fpsTimer_ += deltaTime_;
if (fpsTimer_ >= 1.0f) {
currentFps_ = frameCount_;
frameCount_ = 0;
fpsTimer_ -= 1.0f;
}
auto *winMod = get<WindowModule>();
if (winMod && winMod->win()) {
winMod->win()->poll();
}
auto eventService = ServiceLocator::instance().getService<IEventService>();
if (eventService) {
eventService->processQueue();
}
if (!paused_) {
update();
}
render();
// 帧率限制
auto *renderMod = get<RenderModule>();
if (renderMod && renderMod->renderer()) {
// 这里可以添加帧率限制逻辑
}
}
void Application::update() {
ServiceLocator::instance().updateAll(deltaTime_);
auto* inputMod = get<InputModule>();
if (inputMod) {
inputMod->update();
}
ServiceLocator::instance().updateAll(deltaTime_);
auto *inputMod = get<InputModule>();
if (inputMod) {
inputMod->update();
}
}
void Application::render() {
auto* renderMod = get<RenderModule>();
if (!renderMod || !renderMod->renderer()) 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;
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());
}
auto sceneService = ServiceLocator::instance().getService<ISceneService>();
if (sceneService) {
sceneService->render(*renderer);
}
winMod->win()->swap();
auto *renderMod = get<RenderModule>();
if (!renderMod || !renderMod->renderer())
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;
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());
}
auto sceneService = ServiceLocator::instance().getService<ISceneService>();
if (sceneService) {
sceneService->render(*renderer);
}
winMod->win()->swap();
}
IWindow* Application::window() {
auto* winMod = get<WindowModule>();
return winMod ? winMod->win() : nullptr;
IWindow *Application::window() {
auto *winMod = get<WindowModule>();
return winMod ? winMod->win() : nullptr;
}
RenderBackend* Application::renderer() {
auto* renderMod = get<RenderModule>();
return renderMod ? renderMod->renderer() : nullptr;
RenderBackend *Application::renderer() {
auto *renderMod = get<RenderModule>();
return renderMod ? renderMod->renderer() : nullptr;
}
IInput* Application::input() {
auto* winMod = get<WindowModule>();
return (winMod && winMod->win()) ? winMod->win()->input() : nullptr;
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>();
if (sceneService && scene && winMod && winMod->win()) {
scene->setViewportSize(static_cast<float>(winMod->win()->width()),
static_cast<float>(winMod->win()->height()));
sceneService->enterScene(scene);
}
auto sceneService = ServiceLocator::instance().getService<ISceneService>();
auto *winMod = get<WindowModule>();
if (sceneService && scene && winMod && winMod->win()) {
scene->setViewportSize(static_cast<float>(winMod->win()->width()),
static_cast<float>(winMod->win()->height()));
sceneService->enterScene(scene);
}
}
} // namespace extra2d

View File

@ -1,60 +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";
config.targetPlatform = PlatformType::Auto;
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;
}
if (other.targetPlatform != PlatformType::Auto) {
targetPlatform = other.targetPlatform;
}
E2D_LOG_INFO("Merged app config");
}
}

View File

@ -1,224 +0,0 @@
#include <extra2d/config/app_config.h>
#include <extra2d/config/platform_config.h>
#include <extra2d/utils/logger.h>
#ifdef _WIN32
#include <windows.h>
#endif
#ifdef __SWITCH__
#include <switch.h>
#endif
namespace extra2d {
namespace {
class WindowsPlatformConfig : public PlatformConfig {
public:
WindowsPlatformConfig() {
caps_.supportsWindowed = true;
caps_.supportsFullscreen = true;
caps_.supportsBorderless = true;
caps_.supportsCursor = true;
caps_.supportsCursorHide = true;
caps_.supportsDPIAwareness = true;
caps_.supportsVSync = true;
caps_.supportsMultiMonitor = true;
caps_.supportsClipboard = true;
caps_.supportsGamepad = true;
caps_.supportsTouch = false;
caps_.supportsKeyboard = true;
caps_.supportsMouse = true;
caps_.supportsResize = true;
caps_.supportsHighDPI = true;
caps_.maxTextureSize = 16384;
caps_.preferredScreenWidth = 1920;
caps_.preferredScreenHeight = 1080;
caps_.defaultDPI = 96.0f;
}
PlatformType platformType() const override { return PlatformType::Windows; }
const char *platformName() const override { return "Windows"; }
const PlatformCapabilities &capabilities() const override { return caps_; }
int getRecommendedWidth() const override { return 1920; }
int getRecommendedHeight() const override { return 1080; }
bool isResolutionSupported(int width, int height) const override {
return width >= 320 && height >= 240 && width <= caps_.maxTextureSize &&
height <= caps_.maxTextureSize;
}
private:
PlatformCapabilities caps_;
};
class LinuxPlatformConfig : public PlatformConfig {
public:
LinuxPlatformConfig() {
caps_.supportsWindowed = true;
caps_.supportsFullscreen = true;
caps_.supportsBorderless = true;
caps_.supportsCursor = true;
caps_.supportsCursorHide = true;
caps_.supportsDPIAwareness = true;
caps_.supportsVSync = true;
caps_.supportsMultiMonitor = true;
caps_.supportsClipboard = true;
caps_.supportsGamepad = true;
caps_.supportsTouch = false;
caps_.supportsKeyboard = true;
caps_.supportsMouse = true;
caps_.supportsResize = true;
caps_.supportsHighDPI = true;
caps_.maxTextureSize = 16384;
caps_.preferredScreenWidth = 1920;
caps_.preferredScreenHeight = 1080;
caps_.defaultDPI = 96.0f;
}
PlatformType platformType() const override { return PlatformType::Linux; }
const char *platformName() const override { return "Linux"; }
const PlatformCapabilities &capabilities() const override { return caps_; }
int getRecommendedWidth() const override { return 1920; }
int getRecommendedHeight() const override { return 1080; }
bool isResolutionSupported(int width, int height) const override {
return width >= 320 && height >= 240;
}
private:
PlatformCapabilities caps_;
};
class MacOSPlatformConfig : public PlatformConfig {
public:
MacOSPlatformConfig() {
caps_.supportsWindowed = true;
caps_.supportsFullscreen = true;
caps_.supportsBorderless = true;
caps_.supportsCursor = true;
caps_.supportsCursorHide = true;
caps_.supportsDPIAwareness = true;
caps_.supportsVSync = true;
caps_.supportsMultiMonitor = true;
caps_.supportsClipboard = true;
caps_.supportsGamepad = true;
caps_.supportsTouch = false;
caps_.supportsKeyboard = true;
caps_.supportsMouse = true;
caps_.supportsResize = true;
caps_.supportsHighDPI = true;
caps_.maxTextureSize = 16384;
caps_.preferredScreenWidth = 1920;
caps_.preferredScreenHeight = 1080;
caps_.defaultDPI = 144.0f;
}
PlatformType platformType() const override { return PlatformType::macOS; }
const char *platformName() const override { return "macOS"; }
const PlatformCapabilities &capabilities() const override { return caps_; }
int getRecommendedWidth() const override { return 1920; }
int getRecommendedHeight() const override { return 1080; }
bool isResolutionSupported(int width, int height) const override {
return width >= 320 && height >= 240;
}
private:
PlatformCapabilities caps_;
};
class SwitchPlatformConfig : public PlatformConfig {
public:
SwitchPlatformConfig() {
caps_.supportsWindowed = false;
caps_.supportsFullscreen = true;
caps_.supportsBorderless = false;
caps_.supportsCursor = false;
caps_.supportsCursorHide = false;
caps_.supportsDPIAwareness = false;
caps_.supportsVSync = true;
caps_.supportsMultiMonitor = false;
caps_.supportsClipboard = false;
caps_.supportsGamepad = true;
caps_.supportsTouch = true;
caps_.supportsKeyboard = false;
caps_.supportsMouse = false;
caps_.supportsResize = false;
caps_.supportsHighDPI = false;
caps_.maxTextureSize = 8192;
caps_.preferredScreenWidth = 1920;
caps_.preferredScreenHeight = 1080;
caps_.defaultDPI = 96.0f;
}
PlatformType platformType() const override { return PlatformType::Switch; }
const char *platformName() const override { return "Nintendo Switch"; }
const PlatformCapabilities &capabilities() const override { return caps_; }
int getRecommendedWidth() const override { return 1920; }
int getRecommendedHeight() const override { return 1080; }
bool isResolutionSupported(int width, int height) const override {
return (width == 1920 && height == 1080) ||
(width == 1280 && height == 720);
}
private:
PlatformCapabilities caps_;
};
}
UniquePtr<PlatformConfig> createPlatformConfig(PlatformType type) {
if (type == PlatformType::Auto) {
#ifdef _WIN32
type = PlatformType::Windows;
#elif defined(__SWITCH__)
type = PlatformType::Switch;
#elif defined(__linux__)
type = PlatformType::Linux;
#elif defined(__APPLE__)
type = PlatformType::macOS;
#else
type = PlatformType::Windows;
#endif
}
switch (type) {
case PlatformType::Windows:
E2D_LOG_INFO("Creating Windows platform config");
return makeUnique<WindowsPlatformConfig>();
case PlatformType::Switch:
E2D_LOG_INFO("Creating Nintendo Switch platform config");
return makeUnique<SwitchPlatformConfig>();
case PlatformType::Linux:
E2D_LOG_INFO("Creating Linux platform config");
return makeUnique<LinuxPlatformConfig>();
case PlatformType::macOS:
E2D_LOG_INFO("Creating macOS platform config");
return makeUnique<MacOSPlatformConfig>();
default:
E2D_LOG_WARN("Unknown platform type, defaulting to Windows");
return makeUnique<WindowsPlatformConfig>();
}
}
const char *getPlatformTypeName(PlatformType type) {
switch (type) {
case PlatformType::Auto:
return "Auto";
case PlatformType::Windows:
return "Windows";
case PlatformType::Switch:
return "Switch";
case PlatformType::Linux:
return "Linux";
case PlatformType::macOS:
return "macOS";
default:
return "Unknown";
}
}
}

View File

@ -1,678 +0,0 @@
#include <extra2d/config/platform_detector.h>
#include <extra2d/config/platform_config.h>
#include <extra2d/utils/logger.h>
#ifdef _WIN32
#include <windows.h>
#include <shlobj.h>
#include <psapi.h>
#elif defined(__linux__)
#include <sys/sysinfo.h>
#include <unistd.h>
#include <pwd.h>
#include <cstdlib>
#elif defined(__APPLE__)
#include <sys/sysctl.h>
#include <unistd.h>
#include <pwd.h>
#include <cstdlib>
#endif
#ifdef __SWITCH__
#include <switch.h>
#endif
namespace extra2d {
/**
* @brief
* 使
* @return
*/
PlatformType PlatformDetector::detect() {
#ifdef _WIN32
return PlatformType::Windows;
#elif defined(__SWITCH__)
return PlatformType::Switch;
#elif defined(__linux__)
return PlatformType::Linux;
#elif defined(__APPLE__)
return PlatformType::macOS;
#else
return PlatformType::Windows;
#endif
}
/**
* @brief
* @return "Windows", "Linux", "macOS", "Switch"
*/
const char* PlatformDetector::platformName() {
return platformName(detect());
}
/**
* @brief
* @param type
* @return
*/
const char* PlatformDetector::platformName(PlatformType type) {
switch (type) {
case PlatformType::Windows: return "Windows";
case PlatformType::Switch: return "Nintendo Switch";
case PlatformType::Linux: return "Linux";
case PlatformType::macOS: return "macOS";
case PlatformType::Auto: return "Auto";
default: return "Unknown";
}
}
/**
* @brief
* @return true
*/
bool PlatformDetector::isDesktopPlatform() {
PlatformType type = detect();
return type == PlatformType::Windows ||
type == PlatformType::Linux ||
type == PlatformType::macOS;
}
/**
* @brief
* @return true
*/
bool PlatformDetector::isConsolePlatform() {
return detect() == PlatformType::Switch;
}
/**
* @brief
* @return true
*/
bool PlatformDetector::isMobilePlatform() {
return false;
}
/**
* @brief
* @return
*/
PlatformCapabilities PlatformDetector::capabilities() {
return capabilities(detect());
}
/**
* @brief
* @param type
* @return
*/
PlatformCapabilities PlatformDetector::capabilities(PlatformType type) {
switch (type) {
case PlatformType::Windows:
return getWindowsCapabilities();
case PlatformType::Switch:
return getSwitchCapabilities();
case PlatformType::Linux:
return getLinuxCapabilities();
case PlatformType::macOS:
return getMacOSCapabilities();
default:
return getWindowsCapabilities();
}
}
/**
* @brief
* @return
*/
AppConfig PlatformDetector::platformDefaults() {
return platformDefaults(detect());
}
/**
* @brief
* @param type
* @return
*/
AppConfig PlatformDetector::platformDefaults(PlatformType type) {
switch (type) {
case PlatformType::Windows:
return getWindowsDefaults();
case PlatformType::Switch:
return getSwitchDefaults();
case PlatformType::Linux:
return getLinuxDefaults();
case PlatformType::macOS:
return getMacOSDefaults();
default:
return AppConfig::createDefault();
}
}
/**
* @brief
* @param width
* @param height
*/
void PlatformDetector::getRecommendedResolution(int& width, int& height) {
PlatformCapabilities caps = capabilities();
width = caps.preferredScreenWidth;
height = caps.preferredScreenHeight;
}
/**
* @brief DPI
* @return DPI
*/
float PlatformDetector::getDefaultDPI() {
return capabilities().defaultDPI;
}
/**
* @brief
* @param feature
* @return true
*/
bool PlatformDetector::supportsFeature(const std::string& feature) {
PlatformCapabilities caps = capabilities();
if (feature == "windowed") return caps.supportsWindowed;
if (feature == "fullscreen") return caps.supportsFullscreen;
if (feature == "borderless") return caps.supportsBorderless;
if (feature == "cursor") return caps.supportsCursor;
if (feature == "cursor_hide") return caps.supportsCursorHide;
if (feature == "dpi_awareness") return caps.supportsDPIAwareness;
if (feature == "vsync") return caps.supportsVSync;
if (feature == "multi_monitor") return caps.supportsMultiMonitor;
if (feature == "clipboard") return caps.supportsClipboard;
if (feature == "gamepad") return caps.supportsGamepad;
if (feature == "touch") return caps.supportsTouch;
if (feature == "keyboard") return caps.supportsKeyboard;
if (feature == "mouse") return caps.supportsMouse;
if (feature == "resize") return caps.supportsResize;
if (feature == "high_dpi") return caps.supportsHighDPI;
return false;
}
/**
* @brief
* @return MB 0
*/
int PlatformDetector::getSystemMemoryMB() {
#ifdef _WIN32
MEMORYSTATUSEX status;
status.dwLength = sizeof(status);
if (GlobalMemoryStatusEx(&status)) {
return static_cast<int>(status.ullTotalPhys / (1024 * 1024));
}
return 0;
#elif defined(__SWITCH__)
return 4096;
#elif defined(__linux__)
struct sysinfo info;
if (sysinfo(&info) == 0) {
return static_cast<int>(info.totalram * info.mem_unit / (1024 * 1024));
}
return 0;
#elif defined(__APPLE__)
int mib[2] = {CTL_HW, HW_MEMSIZE};
int64_t memSize = 0;
size_t length = sizeof(memSize);
if (sysctl(mib, 2, &memSize, &length, nullptr, 0) == 0) {
return static_cast<int>(memSize / (1024 * 1024));
}
return 0;
#else
return 0;
#endif
}
/**
* @brief CPU
* @return CPU
*/
int PlatformDetector::getCPUCoreCount() {
#ifdef _WIN32
SYSTEM_INFO sysinfo;
GetSystemInfo(&sysinfo);
return static_cast<int>(sysinfo.dwNumberOfProcessors);
#elif defined(__SWITCH__)
return 4;
#elif defined(__linux__) || defined(__APPLE__)
long cores = sysconf(_SC_NPROCESSORS_ONLN);
return static_cast<int>(cores > 0 ? cores : 1);
#else
return 1;
#endif
}
/**
* @brief 线
* @return true
*/
bool PlatformDetector::supportsMultithreadedRendering() {
#ifdef __SWITCH__
return false;
#else
return getCPUCoreCount() >= 2;
#endif
}
/**
* @brief
* @param appName
* @return
*/
std::string PlatformDetector::getConfigPath(const std::string& appName) {
#ifdef _WIN32
char path[MAX_PATH];
if (SUCCEEDED(SHGetFolderPathA(nullptr, CSIDL_APPDATA, nullptr, 0, path))) {
return std::string(path) + "\\" + appName + "\\config";
}
return ".\\config";
#elif defined(__SWITCH__)
return "sdmc:/config/" + appName;
#elif defined(__linux__)
const char* configHome = getenv("XDG_CONFIG_HOME");
if (configHome && configHome[0] != '\0') {
return std::string(configHome) + "/" + appName;
}
const char* home = getenv("HOME");
if (!home) {
struct passwd* pwd = getpwuid(getuid());
if (pwd) home = pwd->pw_dir;
}
if (home) {
return std::string(home) + "/.config/" + appName;
}
return "./config";
#elif defined(__APPLE__)
const char* home = getenv("HOME");
if (!home) {
struct passwd* pwd = getpwuid(getuid());
if (pwd) home = pwd->pw_dir;
}
if (home) {
return std::string(home) + "/Library/Application Support/" + appName + "/config";
}
return "./config";
#else
return "./config";
#endif
}
/**
* @brief
* @param appName
* @return
*/
std::string PlatformDetector::getSavePath(const std::string& appName) {
#ifdef _WIN32
char path[MAX_PATH];
if (SUCCEEDED(SHGetFolderPathA(nullptr, CSIDL_APPDATA, nullptr, 0, path))) {
return std::string(path) + "\\" + appName + "\\saves";
}
return ".\\saves";
#elif defined(__SWITCH__)
return "sdmc:/saves/" + appName;
#elif defined(__linux__)
const char* dataHome = getenv("XDG_DATA_HOME");
if (dataHome && dataHome[0] != '\0') {
return std::string(dataHome) + "/" + appName + "/saves";
}
const char* home = getenv("HOME");
if (!home) {
struct passwd* pwd = getpwuid(getuid());
if (pwd) home = pwd->pw_dir;
}
if (home) {
return std::string(home) + "/.local/share/" + appName + "/saves";
}
return "./saves";
#elif defined(__APPLE__)
const char* home = getenv("HOME");
if (!home) {
struct passwd* pwd = getpwuid(getuid());
if (pwd) home = pwd->pw_dir;
}
if (home) {
return std::string(home) + "/Library/Application Support/" + appName + "/saves";
}
return "./saves";
#else
return "./saves";
#endif
}
/**
* @brief
* @param appName
* @return
*/
std::string PlatformDetector::getCachePath(const std::string& appName) {
#ifdef _WIN32
char path[MAX_PATH];
if (SUCCEEDED(SHGetFolderPathA(nullptr, CSIDL_LOCAL_APPDATA, nullptr, 0, path))) {
return std::string(path) + "\\" + appName + "\\cache";
}
return ".\\cache";
#elif defined(__SWITCH__)
return "sdmc:/cache/" + appName;
#elif defined(__linux__)
const char* cacheHome = getenv("XDG_CACHE_HOME");
if (cacheHome && cacheHome[0] != '\0') {
return std::string(cacheHome) + "/" + appName;
}
const char* home = getenv("HOME");
if (!home) {
struct passwd* pwd = getpwuid(getuid());
if (pwd) home = pwd->pw_dir;
}
if (home) {
return std::string(home) + "/.cache/" + appName;
}
return "./cache";
#elif defined(__APPLE__)
const char* home = getenv("HOME");
if (!home) {
struct passwd* pwd = getpwuid(getuid());
if (pwd) home = pwd->pw_dir;
}
if (home) {
return std::string(home) + "/Library/Caches/" + appName;
}
return "./cache";
#else
return "./cache";
#endif
}
/**
* @brief
* @param appName
* @return
*/
std::string PlatformDetector::getLogPath(const std::string& appName) {
#ifdef _WIN32
char path[MAX_PATH];
if (SUCCEEDED(SHGetFolderPathA(nullptr, CSIDL_LOCAL_APPDATA, nullptr, 0, path))) {
return std::string(path) + "\\" + appName + "\\logs";
}
return ".\\logs";
#elif defined(__SWITCH__)
return "sdmc:/logs/" + appName;
#elif defined(__linux__)
const char* cacheHome = getenv("XDG_CACHE_HOME");
if (cacheHome && cacheHome[0] != '\0') {
return std::string(cacheHome) + "/" + appName + "/logs";
}
const char* home = getenv("HOME");
if (!home) {
struct passwd* pwd = getpwuid(getuid());
if (pwd) home = pwd->pw_dir;
}
if (home) {
return std::string(home) + "/.cache/" + appName + "/logs";
}
return "./logs";
#elif defined(__APPLE__)
const char* home = getenv("HOME");
if (!home) {
struct passwd* pwd = getpwuid(getuid());
if (pwd) home = pwd->pw_dir;
}
if (home) {
return std::string(home) + "/Library/Logs/" + appName;
}
return "./logs";
#else
return "./logs";
#endif
}
/**
* @brief Shader
* Switch平台使用romfs使
* @param appName
* @return
*/
std::string PlatformDetector::getResourcePath(const std::string& appName) {
#ifdef __SWITCH__
(void)appName;
return "romfs:/";
#else
(void)appName;
return "./resources/";
#endif
}
/**
* @brief Shader路径
* @param appName
* @return Shader目录路径
*/
std::string PlatformDetector::getShaderPath(const std::string& appName) {
#ifdef __SWITCH__
(void)appName;
return "romfs:/shaders/";
#else
(void)appName;
return "./shaders/";
#endif
}
/**
* @brief Shader缓存路径
* Switch平台使用sdmc使
* @param appName
* @return Shader缓存目录路径
*/
std::string PlatformDetector::getShaderCachePath(const std::string& appName) {
#ifdef __SWITCH__
std::string name = appName.empty() ? "extra2d" : appName;
return "sdmc:/cache/" + name + "/shaders/";
#else
return getCachePath(appName.empty() ? "extra2d" : appName) + "/shaders/";
#endif
}
/**
* @brief 使romfs
* @return 使romfs返回true
*/
bool PlatformDetector::usesRomfs() {
#ifdef __SWITCH__
return true;
#else
return false;
#endif
}
/**
* @brief
* Switch平台不支持热重载romfs只读
* @return true
*/
bool PlatformDetector::supportsHotReload() {
#ifdef __SWITCH__
return false;
#else
return true;
#endif
}
/**
* @brief
* @return true
*/
bool PlatformDetector::isLittleEndian() {
union {
uint32_t i;
char c[4];
} test = {0x01020304};
return test.c[0] == 0x04;
}
/**
* @brief
* @return true
*/
bool PlatformDetector::isBigEndian() {
return !isLittleEndian();
}
/**
* @brief
* @return
*/
std::string PlatformDetector::getPlatformSummary() {
std::string summary;
summary += "Platform: ";
summary += platformName();
summary += "\n";
summary += "Memory: ";
summary += std::to_string(getSystemMemoryMB());
summary += " MB\n";
summary += "CPU Cores: ";
summary += std::to_string(getCPUCoreCount());
summary += "\n";
summary += "Endianness: ";
summary += isLittleEndian() ? "Little Endian" : "Big Endian";
summary += "\n";
summary += "Desktop Platform: ";
summary += isDesktopPlatform() ? "Yes" : "No";
summary += "\n";
summary += "Console Platform: ";
summary += isConsolePlatform() ? "Yes" : "No";
summary += "\n";
summary += "Recommended Resolution: ";
int width, height;
getRecommendedResolution(width, height);
summary += std::to_string(width);
summary += "x";
summary += std::to_string(height);
summary += "\n";
summary += "Default DPI: ";
summary += std::to_string(static_cast<int>(getDefaultDPI()));
return summary;
}
PlatformCapabilities PlatformDetector::getWindowsCapabilities() {
PlatformCapabilities caps;
caps.supportsWindowed = true;
caps.supportsFullscreen = true;
caps.supportsBorderless = true;
caps.supportsCursor = true;
caps.supportsCursorHide = true;
caps.supportsDPIAwareness = true;
caps.supportsVSync = true;
caps.supportsMultiMonitor = true;
caps.supportsClipboard = true;
caps.supportsGamepad = true;
caps.supportsTouch = false;
caps.supportsKeyboard = true;
caps.supportsMouse = true;
caps.supportsResize = true;
caps.supportsHighDPI = true;
caps.maxTextureSize = 16384;
caps.preferredScreenWidth = 1920;
caps.preferredScreenHeight = 1080;
caps.defaultDPI = 96.0f;
return caps;
}
PlatformCapabilities PlatformDetector::getLinuxCapabilities() {
PlatformCapabilities caps;
caps.supportsWindowed = true;
caps.supportsFullscreen = true;
caps.supportsBorderless = true;
caps.supportsCursor = true;
caps.supportsCursorHide = true;
caps.supportsDPIAwareness = true;
caps.supportsVSync = true;
caps.supportsMultiMonitor = true;
caps.supportsClipboard = true;
caps.supportsGamepad = true;
caps.supportsTouch = false;
caps.supportsKeyboard = true;
caps.supportsMouse = true;
caps.supportsResize = true;
caps.supportsHighDPI = true;
caps.maxTextureSize = 16384;
caps.preferredScreenWidth = 1920;
caps.preferredScreenHeight = 1080;
caps.defaultDPI = 96.0f;
return caps;
}
PlatformCapabilities PlatformDetector::getMacOSCapabilities() {
PlatformCapabilities caps;
caps.supportsWindowed = true;
caps.supportsFullscreen = true;
caps.supportsBorderless = true;
caps.supportsCursor = true;
caps.supportsCursorHide = true;
caps.supportsDPIAwareness = true;
caps.supportsVSync = true;
caps.supportsMultiMonitor = true;
caps.supportsClipboard = true;
caps.supportsGamepad = true;
caps.supportsTouch = false;
caps.supportsKeyboard = true;
caps.supportsMouse = true;
caps.supportsResize = true;
caps.supportsHighDPI = true;
caps.maxTextureSize = 16384;
caps.preferredScreenWidth = 1920;
caps.preferredScreenHeight = 1080;
caps.defaultDPI = 144.0f;
return caps;
}
PlatformCapabilities PlatformDetector::getSwitchCapabilities() {
PlatformCapabilities caps;
caps.supportsWindowed = false;
caps.supportsFullscreen = true;
caps.supportsBorderless = false;
caps.supportsCursor = false;
caps.supportsCursorHide = false;
caps.supportsDPIAwareness = false;
caps.supportsVSync = true;
caps.supportsMultiMonitor = false;
caps.supportsClipboard = false;
caps.supportsGamepad = true;
caps.supportsTouch = true;
caps.supportsKeyboard = false;
caps.supportsMouse = false;
caps.supportsResize = false;
caps.supportsHighDPI = false;
caps.maxTextureSize = 8192;
caps.preferredScreenWidth = 1920;
caps.preferredScreenHeight = 1080;
caps.defaultDPI = 96.0f;
return caps;
}
AppConfig PlatformDetector::getWindowsDefaults() {
AppConfig config = AppConfig::createDefault();
return config;
}
AppConfig PlatformDetector::getLinuxDefaults() {
AppConfig config = AppConfig::createDefault();
return config;
}
AppConfig PlatformDetector::getMacOSDefaults() {
AppConfig config = AppConfig::createDefault();
return config;
}
AppConfig PlatformDetector::getSwitchDefaults() {
AppConfig config = AppConfig::createDefault();
return config;
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,5 @@
#include <glad/glad.h>
#include <extra2d/graphics/opengl/gl_texture.h>
#include <extra2d/graphics/backends/opengl/gl_texture.h>
#include <extra2d/graphics/core/render_target.h>
#include <extra2d/utils/logger.h>

View File

@ -1,428 +0,0 @@
#include <cstring>
#include <extra2d/graphics/opengl/gl_sprite_batch.h>
#include <extra2d/utils/logger.h>
#include <glm/gtc/matrix_transform.hpp>
namespace extra2d {
// ============================================================================
// 三角函数查表 - 避免每帧重复计算 sin/cos
// ============================================================================
/**
* @brief sin/cos
*/
class TrigLookup {
public:
static constexpr size_t TABLE_SIZE = 360 * 4; // 0.25度精度
static constexpr float INDEX_SCALE = 4.0f; // 每度4个采样点
static constexpr float RAD_TO_INDEX = INDEX_SCALE * 180.0f / 3.14159265359f;
/**
* @brief sin值
* @param radians
* @return sin值
*/
static float sinRad(float radians) {
return table_.sinTable[normalizeIndexRad(radians)];
}
/**
* @brief cos值
* @param radians
* @return cos值
*/
static float cosRad(float radians) {
return table_.cosTable[normalizeIndexRad(radians)];
}
private:
struct Tables {
std::array<float, TABLE_SIZE> sinTable;
std::array<float, TABLE_SIZE> cosTable;
Tables() {
for (size_t i = 0; i < TABLE_SIZE; ++i) {
float angle = static_cast<float>(i) / INDEX_SCALE * 3.14159265359f / 180.0f;
sinTable[i] = std::sin(angle);
cosTable[i] = std::cos(angle);
}
}
};
static size_t normalizeIndexRad(float radians) {
int idx = static_cast<int>(radians * RAD_TO_INDEX) % static_cast<int>(TABLE_SIZE);
if (idx < 0) {
idx += static_cast<int>(TABLE_SIZE);
}
return static_cast<size_t>(idx);
}
static const Tables table_;
};
const TrigLookup::Tables TrigLookup::table_;
// 静态索引生成函数
/**
* @brief
* @return
*/
static const std::array<GLuint, GLSpriteBatch::MAX_INDICES>& getIndices() {
static std::array<GLuint, GLSpriteBatch::MAX_INDICES> indices = []() {
std::array<GLuint, GLSpriteBatch::MAX_INDICES> arr{};
for (size_t i = 0; i < GLSpriteBatch::MAX_SPRITES; ++i) {
GLuint base = static_cast<GLuint>(i * GLSpriteBatch::VERTICES_PER_SPRITE);
arr[i * GLSpriteBatch::INDICES_PER_SPRITE + 0] = base + 0;
arr[i * GLSpriteBatch::INDICES_PER_SPRITE + 1] = base + 1;
arr[i * GLSpriteBatch::INDICES_PER_SPRITE + 2] = base + 2;
arr[i * GLSpriteBatch::INDICES_PER_SPRITE + 3] = base + 0;
arr[i * GLSpriteBatch::INDICES_PER_SPRITE + 4] = base + 2;
arr[i * GLSpriteBatch::INDICES_PER_SPRITE + 5] = base + 3;
}
return arr;
}();
return indices;
}
// 顶点着色器 (GLES 3.2)
static const char *SPRITE_VERTEX_SHADER = R"(
#version 300 es
precision highp float;
layout(location = 0) in vec2 aPosition;
layout(location = 1) in vec2 aTexCoord;
layout(location = 2) in vec4 aColor;
uniform mat4 uViewProjection;
out vec2 vTexCoord;
out vec4 vColor;
void main() {
gl_Position = uViewProjection * vec4(aPosition, 0.0, 1.0);
vTexCoord = aTexCoord;
vColor = aColor;
}
)";
// 片段着色器 (GLES 3.2)
// SDF 常量硬编码ONEDGE_VALUE=128/255=0.502, PIXEL_DIST_SCALE=255/64=3.98
// SDF 值存储在 Alpha 通道
static const char *SPRITE_FRAGMENT_SHADER = R"(
#version 300 es
precision highp float;
in vec2 vTexCoord;
in vec4 vColor;
uniform sampler2D uTexture;
uniform int uUseSDF;
out vec4 fragColor;
void main() {
if (uUseSDF == 1) {
float dist = texture(uTexture, vTexCoord).a;
float sd = (dist - 0.502) * 3.98;
float w = fwidth(sd);
float alpha = smoothstep(-w, w, sd);
fragColor = vec4(vColor.rgb, vColor.a * alpha);
} else {
fragColor = texture(uTexture, vTexCoord) * vColor;
}
}
)";
/**
* @brief
*/
GLSpriteBatch::GLSpriteBatch()
: vao_(0), vbo_(0), ibo_(0), vertexCount_(0), currentTexture_(nullptr),
currentIsSDF_(false), drawCallCount_(0), spriteCount_(0), batchCount_(0) {
}
/**
* @brief shutdown释放资源
*/
GLSpriteBatch::~GLSpriteBatch() { shutdown(); }
/**
* @brief VAOVBOIBO和编译着色器
* @return truefalse
*/
bool GLSpriteBatch::init() {
// 创建并编译着色器
if (!shader_.compileFromSource(SPRITE_VERTEX_SHADER,
SPRITE_FRAGMENT_SHADER)) {
E2D_LOG_ERROR("Failed to compile sprite batch shader");
return false;
}
// 生成 VAO、VBO、IBO
glGenVertexArrays(1, &vao_);
glGenBuffers(1, &vbo_);
glGenBuffers(1, &ibo_);
glBindVertexArray(vao_);
// 设置 VBO - 使用动态绘制模式
glBindBuffer(GL_ARRAY_BUFFER, vbo_);
glBufferData(GL_ARRAY_BUFFER, MAX_VERTICES * sizeof(Vertex), nullptr,
GL_DYNAMIC_DRAW);
// 设置顶点属性
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex),
(void *)offsetof(Vertex, position));
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex),
(void *)offsetof(Vertex, texCoord));
glEnableVertexAttribArray(2);
glVertexAttribPointer(2, 4, GL_FLOAT, GL_FALSE, sizeof(Vertex),
(void *)offsetof(Vertex, color));
// 使用编译期生成的静态索引缓冲区
const auto& indices = getIndices();
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, ibo_);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, indices.size() * sizeof(GLuint),
indices.data(), GL_STATIC_DRAW);
glBindVertexArray(0);
E2D_LOG_INFO("GLSpriteBatch initialized with capacity for {} sprites",
MAX_SPRITES);
return true;
}
/**
* @brief OpenGL资源
*/
void GLSpriteBatch::shutdown() {
if (vao_ != 0) {
glDeleteVertexArrays(1, &vao_);
vao_ = 0;
}
if (vbo_ != 0) {
glDeleteBuffers(1, &vbo_);
vbo_ = 0;
}
if (ibo_ != 0) {
glDeleteBuffers(1, &ibo_);
ibo_ = 0;
}
}
/**
* @brief
* @param viewProjection
*/
void GLSpriteBatch::begin(const glm::mat4 &viewProjection) {
viewProjection_ = viewProjection;
vertexCount_ = 0;
currentTexture_ = nullptr;
currentIsSDF_ = false;
drawCallCount_ = 0;
spriteCount_ = 0;
batchCount_ = 0;
}
/**
* @brief
* @param texture
* @param isSDF SDF渲染
* @return truefalse
*/
bool GLSpriteBatch::needsFlush(const Texture &texture, bool isSDF) const {
if (currentTexture_ == nullptr) {
return false;
}
// 检查是否需要刷新纹理改变、SDF 状态改变或缓冲区已满
return (currentTexture_ != &texture) || (currentIsSDF_ != isSDF) ||
(vertexCount_ + VERTICES_PER_SPRITE > MAX_VERTICES);
}
/**
* @brief
* @param data
*/
void GLSpriteBatch::addVertices(const SpriteData &data) {
// 计算锚点偏移
float anchorOffsetX = data.size.x * data.anchor.x;
float anchorOffsetY = data.size.y * data.anchor.y;
// 使用三角函数查表替代 cosf/sinf
float cosR = TrigLookup::cosRad(data.rotation);
float sinR = TrigLookup::sinRad(data.rotation);
glm::vec4 color(data.color.r, data.color.g, data.color.b, data.color.a);
// 直接计算变换后的位置
float rx0 = -anchorOffsetX;
float ry0 = -anchorOffsetY;
float rx1 = data.size.x - anchorOffsetX;
float ry1 = data.size.y - anchorOffsetY;
// 预计算旋转后的偏移
float cosRx0 = rx0 * cosR, sinRx0 = rx0 * sinR;
float cosRx1 = rx1 * cosR, sinRx1 = rx1 * sinR;
float cosRy0 = ry0 * cosR, sinRy0 = ry0 * sinR;
float cosRy1 = ry1 * cosR, sinRy1 = ry1 * sinR;
// v0: (0, 0) -> (rx0, ry0)
vertexBuffer_[vertexCount_++] = {
glm::vec2(data.position.x + cosRx0 - sinRy0, data.position.y + sinRx0 + cosRy0),
glm::vec2(data.texCoordMin.x, data.texCoordMin.y),
color
};
// v1: (size.x, 0) -> (rx1, ry0)
vertexBuffer_[vertexCount_++] = {
glm::vec2(data.position.x + cosRx1 - sinRy0, data.position.y + sinRx1 + cosRy0),
glm::vec2(data.texCoordMax.x, data.texCoordMin.y),
color
};
// v2: (size.x, size.y) -> (rx1, ry1)
vertexBuffer_[vertexCount_++] = {
glm::vec2(data.position.x + cosRx1 - sinRy1, data.position.y + sinRx1 + cosRy1),
glm::vec2(data.texCoordMax.x, data.texCoordMax.y),
color
};
// v3: (0, size.y) -> (rx0, ry1)
vertexBuffer_[vertexCount_++] = {
glm::vec2(data.position.x + cosRx0 - sinRy1, data.position.y + sinRx0 + cosRy1),
glm::vec2(data.texCoordMin.x, data.texCoordMax.y),
color
};
}
/**
* @brief
* @param texture
* @param data
*/
void GLSpriteBatch::draw(const Texture &texture, const SpriteData &data) {
// 如果需要刷新,先提交当前批次
if (needsFlush(texture, data.isSDF)) {
flush();
}
currentTexture_ = &texture;
currentIsSDF_ = data.isSDF;
addVertices(data);
spriteCount_++;
}
/**
* @brief
* @param texture
* @param sprites
*/
void GLSpriteBatch::drawBatch(const Texture &texture,
const std::vector<SpriteData> &sprites) {
if (sprites.empty()) {
return;
}
// 如果当前有未提交的批次且纹理不同,先刷新
if (currentTexture_ != nullptr && currentTexture_ != &texture) {
flush();
}
currentTexture_ = &texture;
currentIsSDF_ = sprites[0].isSDF; // 假设批量中的精灵 SDF 状态一致
// 分批处理,避免超过缓冲区大小
size_t index = 0;
while (index < sprites.size()) {
size_t remainingSpace = (MAX_VERTICES - vertexCount_) / VERTICES_PER_SPRITE;
size_t batchSize = std::min(sprites.size() - index, remainingSpace);
for (size_t i = 0; i < batchSize; ++i) {
addVertices(sprites[index + i]);
spriteCount_++;
}
index += batchSize;
// 如果还有更多精灵,刷新当前批次
if (index < sprites.size()) {
flush();
}
}
batchCount_++;
}
/**
* @brief
* @param texture
* @param data
*/
void GLSpriteBatch::drawImmediate(const Texture &texture,
const SpriteData &data) {
// 立即绘制,不缓存 - 用于需要立即显示的情况
flush(); // 先提交当前批次
currentTexture_ = &texture;
currentIsSDF_ = data.isSDF;
addVertices(data);
spriteCount_++;
flush(); // 立即提交
}
/**
* @brief
*/
void GLSpriteBatch::end() {
if (vertexCount_ > 0) {
flush();
}
}
/**
* @brief OpenGL绘制调用
*/
void GLSpriteBatch::flush() {
if (vertexCount_ == 0 || currentTexture_ == nullptr) {
return;
}
// 绑定纹理
GLuint texID = static_cast<GLuint>(
reinterpret_cast<uintptr_t>(currentTexture_->getNativeHandle()));
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texID);
// 使用着色器
shader_.bind();
shader_.setMat4("uViewProjection", viewProjection_);
shader_.setInt("uTexture", 0);
shader_.setInt("uUseSDF", currentIsSDF_ ? 1 : 0);
// SDF 常量已硬编码到着色器中
// 更新 VBO 数据 - 只更新实际使用的部分
glBindBuffer(GL_ARRAY_BUFFER, vbo_);
glBufferSubData(GL_ARRAY_BUFFER, 0, vertexCount_ * sizeof(Vertex),
vertexBuffer_.data());
// 绘制
glBindVertexArray(vao_);
GLsizei indexCount = static_cast<GLsizei>(
(vertexCount_ / VERTICES_PER_SPRITE) * INDICES_PER_SPRITE);
glDrawElements(GL_TRIANGLES, indexCount, GL_UNSIGNED_INT, nullptr);
drawCallCount_++;
batchCount_++;
// 重置状态
vertexCount_ = 0;
currentTexture_ = nullptr;
currentIsSDF_ = false;
}
} // namespace extra2d

View File

@ -1,6 +1,10 @@
#include <extra2d/graphics/shader/shader_manager.h>
#include <extra2d/utils/logger.h>
#include <nlohmann/json.hpp>
namespace nl = nlohmann;
namespace extra2d {
/**
@ -20,15 +24,20 @@ ShaderManager& ShaderManager::getInstance() {
* @return truefalse
*/
bool ShaderManager::init(Ptr<IShaderFactory> factory, const std::string& appName) {
std::string shaderDir = PlatformDetector::getShaderPath(appName);
std::string cacheDir = PlatformDetector::getShaderCachePath(appName);
hotReloadSupported_ = PlatformDetector::supportsHotReload();
E2D_LOG_INFO("Platform: {} (HotReload: {})",
PlatformDetector::platformName(),
// 使用相对路径作为Shader目录
std::string shaderDir = "shaders/";
std::string cacheDir = "cache/shaders/";
// 非Switch平台支持热重载
#ifndef __SWITCH__
hotReloadSupported_ = true;
#else
hotReloadSupported_ = false;
#endif
E2D_LOG_INFO("ShaderManager init (HotReload: {})",
hotReloadSupported_ ? "supported" : "not supported");
return init(shaderDir, cacheDir, factory);
}
@ -56,7 +65,12 @@ bool ShaderManager::init(const std::string& shaderDir,
cacheDir_ = cacheDir;
factory_ = factory;
hotReloadSupported_ = PlatformDetector::supportsHotReload();
// 非Switch平台支持热重载
#ifndef __SWITCH__
hotReloadSupported_ = true;
#else
hotReloadSupported_ = false;
#endif
#ifdef __SWITCH__
if (!ShaderCache::getInstance().init(cacheDir_)) {
@ -440,10 +454,76 @@ Ptr<IShader> ShaderManager::getBuiltin(const std::string& name) {
return shader;
}
// 尝试从新的多后端JSON元数据加载
std::string jsonPath = shaderDir_ + "shared/builtin/" + name + ".json";
if (loader_.fileExists(jsonPath)) {
return loadFromMetadata(jsonPath, name);
}
// 回退到旧的组合文件格式
std::string path = shaderDir_ + "builtin/" + name + ".shader";
return loadFromCombinedFile(path);
}
/**
* @brief JSON元数据文件加载Shader
* @param jsonPath JSON元数据文件路径
* @param name Shader名称
* @return Shader实例
*/
Ptr<IShader> ShaderManager::loadFromMetadata(const std::string& jsonPath, const std::string& name) {
if (!initialized_) {
E2D_LOG_ERROR("ShaderManager not initialized");
return nullptr;
}
// 检查是否已加载
auto it = shaders_.find(name);
if (it != shaders_.end()) {
return it->second.shader;
}
// 读取JSON文件
std::string jsonContent = loader_.readFile(jsonPath);
if (jsonContent.empty()) {
E2D_LOG_ERROR("Failed to read shader metadata: {}", jsonPath);
return nullptr;
}
try {
// 使用nlohmann/json解析
nl::json j = nl::json::parse(jsonContent);
// 获取OpenGL后端路径
if (!j.contains("backends") || !j["backends"].contains("opengl")) {
E2D_LOG_ERROR("No OpenGL backend found in shader metadata: {}", jsonPath);
return nullptr;
}
auto& opengl = j["backends"]["opengl"];
if (!opengl.contains("vertex") || !opengl.contains("fragment")) {
E2D_LOG_ERROR("Missing vertex or fragment path in shader metadata: {}", jsonPath);
return nullptr;
}
std::string vertRelativePath = opengl["vertex"].get<std::string>();
std::string fragRelativePath = opengl["fragment"].get<std::string>();
// 构建完整路径
std::string vertPath = shaderDir_ + vertRelativePath;
std::string fragPath = shaderDir_ + fragRelativePath;
E2D_LOG_DEBUG("Loading shader from metadata: {} -> vert: {}, frag: {}", name, vertPath, fragPath);
// 使用分离文件加载
return loadFromFiles(name, vertPath, fragPath);
} catch (const nl::json::exception& e) {
E2D_LOG_ERROR("Failed to parse shader metadata {}: {}", jsonPath, e.what());
return nullptr;
}
}
/**
* @brief Shader
* @return truefalse
@ -465,17 +545,27 @@ bool ShaderManager::loadBuiltinShaders() {
};
for (const char* name : builtinNames) {
std::string path = shaderDir_ + "builtin/" + name + ".shader";
// 首先尝试新的多后端JSON格式
std::string jsonPath = shaderDir_ + "shared/builtin/" + name + ".json";
std::string shaderName = std::string("builtin_") + name;
if (!loadFromCombinedFile(path)) {
E2D_LOG_ERROR("Failed to load builtin {} shader from: {}", name, path);
Ptr<IShader> shader = nullptr;
if (loader_.fileExists(jsonPath)) {
shader = loadFromMetadata(jsonPath, name);
} else {
// 回退到旧的组合文件格式
std::string path = shaderDir_ + "builtin/" + name + ".shader";
shader = loadFromCombinedFile(path);
}
if (!shader) {
E2D_LOG_ERROR("Failed to load builtin {} shader", name);
allSuccess = false;
} else {
// 同时注册带 builtin_ 前缀的名称
auto it = shaders_.find(name);
if (it != shaders_.end()) {
shaders_[shaderName] = it->second;
shaders_.erase(it);
}
}
}

View File

@ -0,0 +1,28 @@
#include "glfw_window.h"
#include "glfw_input.h"
#include <extra2d/platform/backend_factory.h>
namespace extra2d {
namespace {
static bool s_glfwBackendRegistered = false;
}
void initGLFWBackend() {
if (s_glfwBackendRegistered) {
return;
}
s_glfwBackendRegistered = true;
BackendFactory::reg(
"glfw",
[]() -> UniquePtr<IWindow> {
return makeUnique<GLFWWindow>();
},
[]() -> UniquePtr<IInput> {
return makeUnique<GLFWInput>();
}
);
}
} // namespace extra2d

View File

@ -0,0 +1,426 @@
#include "glfw_input.h"
#include <extra2d/utils/logger.h>
#include <cmath>
namespace extra2d {
// GLFW 按键到引擎按键的映射
static Key glfwToKey(int glfwKey) {
switch (glfwKey) {
// 字母键
case GLFW_KEY_A: return Key::A;
case GLFW_KEY_B: return Key::B;
case GLFW_KEY_C: return Key::C;
case GLFW_KEY_D: return Key::D;
case GLFW_KEY_E: return Key::E;
case GLFW_KEY_F: return Key::F;
case GLFW_KEY_G: return Key::G;
case GLFW_KEY_H: return Key::H;
case GLFW_KEY_I: return Key::I;
case GLFW_KEY_J: return Key::J;
case GLFW_KEY_K: return Key::K;
case GLFW_KEY_L: return Key::L;
case GLFW_KEY_M: return Key::M;
case GLFW_KEY_N: return Key::N;
case GLFW_KEY_O: return Key::O;
case GLFW_KEY_P: return Key::P;
case GLFW_KEY_Q: return Key::Q;
case GLFW_KEY_R: return Key::R;
case GLFW_KEY_S: return Key::S;
case GLFW_KEY_T: return Key::T;
case GLFW_KEY_U: return Key::U;
case GLFW_KEY_V: return Key::V;
case GLFW_KEY_W: return Key::W;
case GLFW_KEY_X: return Key::X;
case GLFW_KEY_Y: return Key::Y;
case GLFW_KEY_Z: return Key::Z;
// 数字键
case GLFW_KEY_0: return Key::Num0;
case GLFW_KEY_1: return Key::Num1;
case GLFW_KEY_2: return Key::Num2;
case GLFW_KEY_3: return Key::Num3;
case GLFW_KEY_4: return Key::Num4;
case GLFW_KEY_5: return Key::Num5;
case GLFW_KEY_6: return Key::Num6;
case GLFW_KEY_7: return Key::Num7;
case GLFW_KEY_8: return Key::Num8;
case GLFW_KEY_9: return Key::Num9;
// 功能键
case GLFW_KEY_F1: return Key::F1;
case GLFW_KEY_F2: return Key::F2;
case GLFW_KEY_F3: return Key::F3;
case GLFW_KEY_F4: return Key::F4;
case GLFW_KEY_F5: return Key::F5;
case GLFW_KEY_F6: return Key::F6;
case GLFW_KEY_F7: return Key::F7;
case GLFW_KEY_F8: return Key::F8;
case GLFW_KEY_F9: return Key::F9;
case GLFW_KEY_F10: return Key::F10;
case GLFW_KEY_F11: return Key::F11;
case GLFW_KEY_F12: return Key::F12;
// 特殊键
case GLFW_KEY_SPACE: return Key::Space;
case GLFW_KEY_ENTER: return Key::Enter;
case GLFW_KEY_ESCAPE: return Key::Escape;
case GLFW_KEY_TAB: return Key::Tab;
case GLFW_KEY_BACKSPACE: return Key::Backspace;
case GLFW_KEY_INSERT: return Key::Insert;
case GLFW_KEY_DELETE: return Key::Delete;
case GLFW_KEY_HOME: return Key::Home;
case GLFW_KEY_END: return Key::End;
case GLFW_KEY_PAGE_UP: return Key::PageUp;
case GLFW_KEY_PAGE_DOWN: return Key::PageDown;
// 方向键
case GLFW_KEY_UP: return Key::Up;
case GLFW_KEY_DOWN: return Key::Down;
case GLFW_KEY_LEFT: return Key::Left;
case GLFW_KEY_RIGHT: return Key::Right;
// 修饰键
case GLFW_KEY_LEFT_SHIFT: return Key::LShift;
case GLFW_KEY_RIGHT_SHIFT: return Key::RShift;
case GLFW_KEY_LEFT_CONTROL: return Key::LCtrl;
case GLFW_KEY_RIGHT_CONTROL: return Key::RCtrl;
case GLFW_KEY_LEFT_ALT: return Key::LAlt;
case GLFW_KEY_RIGHT_ALT: return Key::RAlt;
// 锁定键
case GLFW_KEY_CAPS_LOCK: return Key::CapsLock;
case GLFW_KEY_NUM_LOCK: return Key::NumLock;
case GLFW_KEY_SCROLL_LOCK: return Key::ScrollLock;
default: return Key::None;
}
}
GLFWInput::GLFWInput() {
keyCurrent_.fill(false);
keyPrevious_.fill(false);
mouseCurrent_.fill(false);
mousePrevious_.fill(false);
gamepadCurrent_.fill(false);
gamepadPrevious_.fill(false);
}
GLFWInput::~GLFWInput() {
shutdown();
}
void GLFWInput::init() {
E2D_LOG_INFO("GLFWInput initialized");
openGamepad();
}
void GLFWInput::shutdown() {
closeGamepad();
E2D_LOG_INFO("GLFWInput shutdown");
}
void GLFWInput::update() {
// 保存上一帧状态
keyPrevious_ = keyCurrent_;
mousePrevious_ = mouseCurrent_;
gamepadPrevious_ = gamepadCurrent_;
// 重置增量
scrollDelta_ = 0.0f;
mouseDelta_ = Vec2{0.0f, 0.0f};
// 更新游戏手柄
updateGamepad();
// 更新键盘状态(通过轮询 GLFW
if (window_) {
for (int i = GLFW_KEY_SPACE; i <= GLFW_KEY_LAST; ++i) {
Key key = glfwToKey(i);
if (key != Key::None) {
int state = glfwGetKey(window_, i);
keyCurrent_[static_cast<size_t>(key)] = (state == GLFW_PRESS);
}
}
// 更新鼠标按钮状态
for (int i = 0; i < static_cast<int>(Mouse::Count); ++i) {
int glfwButton = GLFW_MOUSE_BUTTON_1 + i;
if (glfwButton <= GLFW_MOUSE_BUTTON_LAST) {
int state = glfwGetMouseButton(window_, glfwButton);
mouseCurrent_[i] = (state == GLFW_PRESS);
}
}
// 获取鼠标位置
double x, y;
glfwGetCursorPos(window_, &x, &y);
mousePos_ = Vec2{static_cast<float>(x), static_cast<float>(y)};
}
}
bool GLFWInput::down(Key key) const {
size_t idx = static_cast<size_t>(key);
if (idx < keyCurrent_.size()) {
return keyCurrent_[idx];
}
return false;
}
bool GLFWInput::pressed(Key key) const {
size_t idx = static_cast<size_t>(key);
if (idx < keyCurrent_.size()) {
return keyCurrent_[idx] && !keyPrevious_[idx];
}
return false;
}
bool GLFWInput::released(Key key) const {
size_t idx = static_cast<size_t>(key);
if (idx < keyCurrent_.size()) {
return !keyCurrent_[idx] && keyPrevious_[idx];
}
return false;
}
bool GLFWInput::down(Mouse btn) const {
size_t idx = static_cast<size_t>(btn);
if (idx < mouseCurrent_.size()) {
return mouseCurrent_[idx];
}
return false;
}
bool GLFWInput::pressed(Mouse btn) const {
size_t idx = static_cast<size_t>(btn);
if (idx < mouseCurrent_.size()) {
return mouseCurrent_[idx] && !mousePrevious_[idx];
}
return false;
}
bool GLFWInput::released(Mouse btn) const {
size_t idx = static_cast<size_t>(btn);
if (idx < mouseCurrent_.size()) {
return !mouseCurrent_[idx] && mousePrevious_[idx];
}
return false;
}
Vec2 GLFWInput::mouse() const {
return mousePos_;
}
Vec2 GLFWInput::mouseDelta() const {
return mouseDelta_;
}
float GLFWInput::scroll() const {
return scroll_;
}
float GLFWInput::scrollDelta() const {
return scrollDelta_;
}
void GLFWInput::setMouse(const Vec2& pos) {
if (window_) {
glfwSetCursorPos(window_, pos.x, pos.y);
}
}
bool GLFWInput::gamepad() const {
return gamepadId_ != -1;
}
bool GLFWInput::down(Gamepad btn) const {
size_t idx = static_cast<size_t>(btn);
if (idx < gamepadCurrent_.size()) {
return gamepadCurrent_[idx];
}
return false;
}
bool GLFWInput::pressed(Gamepad btn) const {
size_t idx = static_cast<size_t>(btn);
if (idx < gamepadCurrent_.size()) {
return gamepadCurrent_[idx] && !gamepadPrevious_[idx];
}
return false;
}
bool GLFWInput::released(Gamepad btn) const {
size_t idx = static_cast<size_t>(btn);
if (idx < gamepadCurrent_.size()) {
return !gamepadCurrent_[idx] && gamepadPrevious_[idx];
}
return false;
}
Vec2 GLFWInput::leftStick() const {
return leftStick_;
}
Vec2 GLFWInput::rightStick() const {
return rightStick_;
}
float GLFWInput::leftTrigger() const {
return leftTrigger_;
}
float GLFWInput::rightTrigger() const {
return rightTrigger_;
}
void GLFWInput::vibrate(float left, float right) {
// GLFW 本身不支持震动,需要平台特定的代码
// 这里可以扩展为使用平台特定的 API
(void)left;
(void)right;
}
bool GLFWInput::touching() const {
return false;
}
int GLFWInput::touchCount() const {
return 0;
}
Vec2 GLFWInput::touch(int index) const {
(void)index;
return Vec2{0.0f, 0.0f};
}
TouchPoint GLFWInput::touchPoint(int index) const {
(void)index;
return TouchPoint{};
}
// 事件处理函数
void GLFWInput::handleKeyEvent(int key, int scancode, int action, int mods) {
(void)scancode;
(void)mods;
Key eKey = glfwToKey(key);
if (eKey != Key::None) {
size_t idx = static_cast<size_t>(eKey);
if (action == GLFW_PRESS) {
keyCurrent_[idx] = true;
} else if (action == GLFW_RELEASE) {
keyCurrent_[idx] = false;
}
}
}
void GLFWInput::handleMouseButtonEvent(int button, int action, int mods) {
(void)mods;
if (button >= GLFW_MOUSE_BUTTON_1 && button <= GLFW_MOUSE_BUTTON_LAST) {
size_t idx = static_cast<size_t>(button - GLFW_MOUSE_BUTTON_1);
if (idx < mouseCurrent_.size()) {
if (action == GLFW_PRESS) {
mouseCurrent_[idx] = true;
} else if (action == GLFW_RELEASE) {
mouseCurrent_[idx] = false;
}
}
}
}
void GLFWInput::handleCursorPosEvent(double xpos, double ypos) {
Vec2 newPos{static_cast<float>(xpos), static_cast<float>(ypos)};
mouseDelta_ = newPos - mousePos_;
mousePos_ = newPos;
}
void GLFWInput::handleScrollEvent(double xoffset, double yoffset) {
(void)xoffset;
scroll_ += static_cast<float>(yoffset);
scrollDelta_ += static_cast<float>(yoffset);
}
void GLFWInput::handleJoystickEvent(int jid, int event) {
if (event == GLFW_CONNECTED) {
E2D_LOG_INFO("Gamepad connected: {}", jid);
if (gamepadId_ == -1) {
openGamepad();
}
} else if (event == GLFW_DISCONNECTED) {
if (jid == gamepadId_) {
E2D_LOG_INFO("Gamepad disconnected: {}", jid);
closeGamepad();
}
}
}
void GLFWInput::updateGamepad() {
if (gamepadId_ == -1) {
return;
}
GLFWgamepadstate state;
if (!glfwGetGamepadState(gamepadId_, &state)) {
return;
}
// 更新按钮状态
gamepadCurrent_[static_cast<size_t>(Gamepad::A)] = state.buttons[GLFW_GAMEPAD_BUTTON_A] == GLFW_PRESS;
gamepadCurrent_[static_cast<size_t>(Gamepad::B)] = state.buttons[GLFW_GAMEPAD_BUTTON_B] == GLFW_PRESS;
gamepadCurrent_[static_cast<size_t>(Gamepad::X)] = state.buttons[GLFW_GAMEPAD_BUTTON_X] == GLFW_PRESS;
gamepadCurrent_[static_cast<size_t>(Gamepad::Y)] = state.buttons[GLFW_GAMEPAD_BUTTON_Y] == GLFW_PRESS;
gamepadCurrent_[static_cast<size_t>(Gamepad::Back)] = state.buttons[GLFW_GAMEPAD_BUTTON_BACK] == GLFW_PRESS;
gamepadCurrent_[static_cast<size_t>(Gamepad::Start)] = state.buttons[GLFW_GAMEPAD_BUTTON_START] == GLFW_PRESS;
gamepadCurrent_[static_cast<size_t>(Gamepad::LStick)] = state.buttons[GLFW_GAMEPAD_BUTTON_LEFT_THUMB] == GLFW_PRESS;
gamepadCurrent_[static_cast<size_t>(Gamepad::RStick)] = state.buttons[GLFW_GAMEPAD_BUTTON_RIGHT_THUMB] == GLFW_PRESS;
gamepadCurrent_[static_cast<size_t>(Gamepad::LB)] = state.buttons[GLFW_GAMEPAD_BUTTON_LEFT_BUMPER] == GLFW_PRESS;
gamepadCurrent_[static_cast<size_t>(Gamepad::RB)] = state.buttons[GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER] == GLFW_PRESS;
gamepadCurrent_[static_cast<size_t>(Gamepad::DUp)] = state.buttons[GLFW_GAMEPAD_BUTTON_DPAD_UP] == GLFW_PRESS;
gamepadCurrent_[static_cast<size_t>(Gamepad::DDown)] = state.buttons[GLFW_GAMEPAD_BUTTON_DPAD_DOWN] == GLFW_PRESS;
gamepadCurrent_[static_cast<size_t>(Gamepad::DLeft)] = state.buttons[GLFW_GAMEPAD_BUTTON_DPAD_LEFT] == GLFW_PRESS;
gamepadCurrent_[static_cast<size_t>(Gamepad::DRight)] = state.buttons[GLFW_GAMEPAD_BUTTON_DPAD_RIGHT] == GLFW_PRESS;
gamepadCurrent_[static_cast<size_t>(Gamepad::Guide)] = state.buttons[GLFW_GAMEPAD_BUTTON_GUIDE] == GLFW_PRESS;
// 更新摇杆值(应用死区)
leftStick_.x = applyDeadzone(state.axes[GLFW_GAMEPAD_AXIS_LEFT_X]);
leftStick_.y = applyDeadzone(state.axes[GLFW_GAMEPAD_AXIS_LEFT_Y]);
rightStick_.x = applyDeadzone(state.axes[GLFW_GAMEPAD_AXIS_RIGHT_X]);
rightStick_.y = applyDeadzone(state.axes[GLFW_GAMEPAD_AXIS_RIGHT_Y]);
// 更新扳机值(范围 [0, 1]
leftTrigger_ = (state.axes[GLFW_GAMEPAD_AXIS_LEFT_TRIGGER] + 1.0f) * 0.5f;
rightTrigger_ = (state.axes[GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER] + 1.0f) * 0.5f;
}
void GLFWInput::openGamepad() {
for (int jid = GLFW_JOYSTICK_1; jid <= GLFW_JOYSTICK_LAST; ++jid) {
if (glfwJoystickPresent(jid) && glfwJoystickIsGamepad(jid)) {
gamepadId_ = jid;
E2D_LOG_INFO("Gamepad opened: {}", glfwGetGamepadName(jid));
return;
}
}
}
void GLFWInput::closeGamepad() {
if (gamepadId_ != -1) {
gamepadId_ = -1;
gamepadCurrent_.fill(false);
gamepadPrevious_.fill(false);
leftStick_ = Vec2{0.0f, 0.0f};
rightStick_ = Vec2{0.0f, 0.0f};
leftTrigger_ = 0.0f;
rightTrigger_ = 0.0f;
}
}
float GLFWInput::applyDeadzone(float value) const {
if (std::abs(value) < deadzone_) {
return 0.0f;
}
float sign = value >= 0.0f ? 1.0f : -1.0f;
return sign * (std::abs(value) - deadzone_) / (1.0f - deadzone_);
}
} // namespace extra2d

View File

@ -0,0 +1,95 @@
#pragma once
#include <extra2d/platform/iinput.h>
#include <GLFW/glfw3.h>
#include <array>
namespace extra2d {
/**
* @brief GLFW
*/
class GLFWInput : public IInput {
public:
GLFWInput();
~GLFWInput() override;
void init() override;
void shutdown() override;
void update() override;
// Keyboard
bool down(Key key) const override;
bool pressed(Key key) const override;
bool released(Key key) const override;
// Mouse
bool down(Mouse btn) const override;
bool pressed(Mouse btn) const override;
bool released(Mouse btn) const override;
Vec2 mouse() const override;
Vec2 mouseDelta() const override;
float scroll() const override;
float scrollDelta() const override;
void setMouse(const Vec2& pos) override;
// Gamepad
bool gamepad() const override;
bool down(Gamepad btn) const override;
bool pressed(Gamepad btn) const override;
bool released(Gamepad btn) const override;
Vec2 leftStick() const override;
Vec2 rightStick() const override;
float leftTrigger() const override;
float rightTrigger() const override;
void vibrate(float left, float right) override;
// Touch
bool touching() const override;
int touchCount() const override;
Vec2 touch(int index) const override;
TouchPoint touchPoint(int index) const override;
// GLFW specific
void handleKeyEvent(int key, int scancode, int action, int mods);
void handleMouseButtonEvent(int button, int action, int mods);
void handleCursorPosEvent(double xpos, double ypos);
void handleScrollEvent(double xoffset, double yoffset);
void handleJoystickEvent(int jid, int event);
void setWindow(GLFWwindow* window) { window_ = window; }
private:
void updateGamepad();
void openGamepad();
void closeGamepad();
float applyDeadzone(float value) const;
// Keyboard state
std::array<bool, static_cast<size_t>(Key::Count)> keyCurrent_;
std::array<bool, static_cast<size_t>(Key::Count)> keyPrevious_;
// Mouse state
std::array<bool, static_cast<size_t>(Mouse::Count)> mouseCurrent_;
std::array<bool, static_cast<size_t>(Mouse::Count)> mousePrevious_;
Vec2 mousePos_;
Vec2 mouseDelta_;
float scroll_ = 0.0f;
float scrollDelta_ = 0.0f;
// Gamepad state
std::array<bool, static_cast<size_t>(Gamepad::Count)> gamepadCurrent_;
std::array<bool, static_cast<size_t>(Gamepad::Count)> gamepadPrevious_;
Vec2 leftStick_;
Vec2 rightStick_;
float leftTrigger_ = 0.0f;
float rightTrigger_ = 0.0f;
int gamepadId_ = -1;
float deadzone_ = 0.15f;
GLFWwindow* window_ = nullptr;
};
} // namespace extra2d

View File

@ -0,0 +1,479 @@
#include "glfw_window.h"
#include "glfw_input.h"
#include <extra2d/utils/logger.h>
#include <glad/glad.h>
namespace extra2d {
GLFWWindow::GLFWWindow() {}
GLFWWindow::~GLFWWindow() {
destroy();
}
bool GLFWWindow::create(const WindowConfigData& cfg) {
if (!initGLFW()) {
return false;
}
// 设置 OpenGL ES 3.2 上下文
glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API);
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_DOUBLEBUFFER, GLFW_TRUE);
glfwWindowHint(GLFW_DEPTH_BITS, 24);
glfwWindowHint(GLFW_STENCIL_BITS, 8);
#ifdef __SWITCH__
// Switch 平台强制全屏
glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
glfwWindowHint(GLFW_DECORATED, GLFW_FALSE);
fullscreen_ = true;
#else
// 桌面平台配置
if (cfg.resizable) {
glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE);
} else {
glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
}
if (!cfg.decorated) {
glfwWindowHint(GLFW_DECORATED, GLFW_FALSE);
}
if (cfg.isFullscreen()) {
glfwWindowHint(GLFW_DECORATED, GLFW_FALSE);
fullscreen_ = true;
}
#endif
if (cfg.multisamples > 0) {
glfwWindowHint(GLFW_SAMPLES, cfg.multisamples);
}
// 创建窗口
GLFWmonitor* monitor = nullptr;
#ifdef __SWITCH__
monitor = glfwGetPrimaryMonitor();
#endif
if (fullscreen_ && !monitor) {
monitor = glfwGetPrimaryMonitor();
}
glfwWindow_ = glfwCreateWindow(
cfg.width, cfg.height,
cfg.title.c_str(),
monitor,
nullptr
);
if (!glfwWindow_) {
E2D_LOG_ERROR("Failed to create GLFW window");
deinitGLFW();
return false;
}
// 窗口居中(非全屏模式下)
#ifndef __SWITCH__
if (!fullscreen_ && !monitor) {
GLFWmonitor* primaryMonitor = glfwGetPrimaryMonitor();
if (primaryMonitor) {
const GLFWvidmode* mode = glfwGetVideoMode(primaryMonitor);
if (mode) {
int screenWidth = mode->width;
int screenHeight = mode->height;
int windowX = (screenWidth - cfg.width) / 2;
int windowY = (screenHeight - cfg.height) / 2;
glfwSetWindowPos(glfwWindow_, windowX, windowY);
}
}
}
#endif
glfwMakeContextCurrent(glfwWindow_);
// 初始化 GLAD
if (!gladLoadGLES2Loader((GLADloadproc)glfwGetProcAddress)) {
E2D_LOG_ERROR("Failed to initialize GLAD GLES2");
glfwDestroyWindow(glfwWindow_);
glfwWindow_ = nullptr;
deinitGLFW();
return false;
}
// 设置垂直同步
glfwSwapInterval(cfg.vsync ? 1 : 0);
vsync_ = cfg.vsync;
// 获取实际窗口大小
glfwGetWindowSize(glfwWindow_, &width_, &height_);
updateContentScale();
// 设置回调函数
glfwSetWindowUserPointer(glfwWindow_, this);
glfwSetFramebufferSizeCallback(glfwWindow_, framebufferSizeCallback);
glfwSetWindowCloseCallback(glfwWindow_, windowCloseCallback);
glfwSetWindowFocusCallback(glfwWindow_, windowFocusCallback);
glfwSetWindowIconifyCallback(glfwWindow_, windowIconifyCallback);
glfwSetCursorPosCallback(glfwWindow_, cursorPosCallback);
glfwSetMouseButtonCallback(glfwWindow_, mouseButtonCallback);
glfwSetScrollCallback(glfwWindow_, scrollCallback);
glfwSetKeyCallback(glfwWindow_, keyCallback);
glfwSetJoystickCallback(joystickCallback);
// 创建输入系统
input_ = makeUnique<GLFWInput>();
input_->setWindow(glfwWindow_);
input_->init();
E2D_LOG_INFO("GLFW window created: {}x{}", width_, height_);
E2D_LOG_INFO(" Platform: OpenGL ES 3.2");
return true;
}
void GLFWWindow::destroy() {
if (input_) {
input_->shutdown();
input_.reset();
}
if (glfwWindow_) {
glfwDestroyWindow(glfwWindow_);
glfwWindow_ = nullptr;
}
deinitGLFW();
}
void GLFWWindow::poll() {
if (!glfwWindow_) return;
if (input_) {
input_->update();
}
glfwPollEvents();
}
void GLFWWindow::swap() {
if (glfwWindow_) {
glfwSwapBuffers(glfwWindow_);
}
}
bool GLFWWindow::shouldClose() const {
if (!glfwWindow_) return true;
return shouldClose_ || glfwWindowShouldClose(glfwWindow_);
}
void GLFWWindow::close() {
shouldClose_ = true;
if (glfwWindow_) {
glfwSetWindowShouldClose(glfwWindow_, GLFW_TRUE);
}
}
void GLFWWindow::setTitle(const std::string& title) {
if (glfwWindow_) {
glfwSetWindowTitle(glfwWindow_, title.c_str());
}
}
void GLFWWindow::setSize(int w, int h) {
if (glfwWindow_) {
glfwSetWindowSize(glfwWindow_, w, h);
width_ = w;
height_ = h;
}
}
void GLFWWindow::setPos(int x, int y) {
#ifndef __SWITCH__
if (glfwWindow_) {
glfwSetWindowPos(glfwWindow_, x, y);
}
#else
(void)x;
(void)y;
#endif
}
void GLFWWindow::setFullscreen(bool fs) {
#ifndef __SWITCH__
if (!glfwWindow_) return;
if (fs == fullscreen_) return;
if (fs) {
GLFWmonitor* monitor = glfwGetPrimaryMonitor();
const GLFWvidmode* mode = glfwGetVideoMode(monitor);
glfwSetWindowMonitor(glfwWindow_, monitor, 0, 0, mode->width, mode->height, mode->refreshRate);
} else {
glfwSetWindowMonitor(glfwWindow_, nullptr, 100, 100, 1280, 720, 0);
}
fullscreen_ = fs;
glfwGetWindowSize(glfwWindow_, &width_, &height_);
updateContentScale();
#else
(void)fs;
#endif
}
void GLFWWindow::setVSync(bool vsync) {
if (glfwWindow_) {
glfwSwapInterval(vsync ? 1 : 0);
vsync_ = vsync;
}
}
void GLFWWindow::setVisible(bool visible) {
#ifndef __SWITCH__
if (glfwWindow_) {
if (visible) {
glfwShowWindow(glfwWindow_);
} else {
glfwHideWindow(glfwWindow_);
}
}
#else
(void)visible;
#endif
}
int GLFWWindow::width() const {
return width_;
}
int GLFWWindow::height() const {
return height_;
}
Size GLFWWindow::size() const {
return Size(static_cast<float>(width_), static_cast<float>(height_));
}
Vec2 GLFWWindow::pos() const {
int x = 0, y = 0;
#ifndef __SWITCH__
if (glfwWindow_) {
glfwGetWindowPos(glfwWindow_, &x, &y);
}
#endif
return Vec2(static_cast<float>(x), static_cast<float>(y));
}
bool GLFWWindow::fullscreen() const {
return fullscreen_;
}
bool GLFWWindow::vsync() const {
return vsync_;
}
bool GLFWWindow::focused() const {
return focused_;
}
bool GLFWWindow::minimized() const {
return minimized_;
}
float GLFWWindow::scaleX() const {
return scaleX_;
}
float GLFWWindow::scaleY() const {
return scaleY_;
}
void GLFWWindow::setCursor(Cursor cursor) {
#ifndef __SWITCH__
if (!glfwWindow_) return;
if (cursor == Cursor::Hidden) {
glfwSetInputMode(glfwWindow_, GLFW_CURSOR, GLFW_CURSOR_HIDDEN);
return;
}
glfwSetInputMode(glfwWindow_, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
GLFWcursor* glfwCursor = nullptr;
switch (cursor) {
case Cursor::Arrow:
glfwCursor = glfwCreateStandardCursor(GLFW_ARROW_CURSOR);
break;
case Cursor::IBeam:
glfwCursor = glfwCreateStandardCursor(GLFW_IBEAM_CURSOR);
break;
case Cursor::Crosshair:
glfwCursor = glfwCreateStandardCursor(GLFW_CROSSHAIR_CURSOR);
break;
case Cursor::Hand:
glfwCursor = glfwCreateStandardCursor(GLFW_HAND_CURSOR);
break;
case Cursor::HResize:
glfwCursor = glfwCreateStandardCursor(GLFW_HRESIZE_CURSOR);
break;
case Cursor::VResize:
glfwCursor = glfwCreateStandardCursor(GLFW_VRESIZE_CURSOR);
break;
default:
glfwCursor = glfwCreateStandardCursor(GLFW_ARROW_CURSOR);
break;
}
if (glfwCursor) {
glfwSetCursor(glfwWindow_, glfwCursor);
}
#else
(void)cursor;
#endif
}
void GLFWWindow::showCursor(bool show) {
#ifndef __SWITCH__
if (glfwWindow_) {
glfwSetInputMode(glfwWindow_, GLFW_CURSOR, show ? GLFW_CURSOR_NORMAL : GLFW_CURSOR_HIDDEN);
cursorVisible_ = show;
}
#else
(void)show;
#endif
}
void GLFWWindow::lockCursor(bool lock) {
#ifndef __SWITCH__
if (glfwWindow_) {
glfwSetInputMode(glfwWindow_, GLFW_CURSOR, lock ? GLFW_CURSOR_DISABLED : GLFW_CURSOR_NORMAL);
cursorLocked_ = lock;
}
#else
(void)lock;
#endif
}
IInput* GLFWWindow::input() const {
return input_.get();
}
void GLFWWindow::onResize(ResizeCb cb) {
resizeCb_ = cb;
}
void GLFWWindow::onClose(CloseCb cb) {
closeCb_ = cb;
}
void GLFWWindow::onFocus(FocusCb cb) {
focusCb_ = cb;
}
void* GLFWWindow::native() const {
return glfwWindow_;
}
bool GLFWWindow::initGLFW() {
static int glfwInitCount = 0;
if (glfwInitCount == 0) {
if (!glfwInit()) {
E2D_LOG_ERROR("Failed to initialize GLFW");
return false;
}
glfwInitCount++;
}
return true;
}
void GLFWWindow::deinitGLFW() {
static int glfwInitCount = 1;
glfwInitCount--;
if (glfwInitCount == 0) {
glfwTerminate();
}
}
void GLFWWindow::updateContentScale() {
if (glfwWindow_) {
int fbWidth, fbHeight;
glfwGetFramebufferSize(glfwWindow_, &fbWidth, &fbHeight);
scaleX_ = fbWidth > 0 ? static_cast<float>(fbWidth) / width_ : 1.0f;
scaleY_ = fbHeight > 0 ? static_cast<float>(fbHeight) / height_ : 1.0f;
}
}
// 静态回调函数
void GLFWWindow::framebufferSizeCallback(GLFWwindow* window, int width, int height) {
GLFWWindow* self = static_cast<GLFWWindow*>(glfwGetWindowUserPointer(window));
if (self) {
self->width_ = width;
self->height_ = height;
self->updateContentScale();
if (self->resizeCb_) {
self->resizeCb_(width, height);
}
}
}
void GLFWWindow::windowCloseCallback(GLFWwindow* window) {
GLFWWindow* self = static_cast<GLFWWindow*>(glfwGetWindowUserPointer(window));
if (self) {
self->shouldClose_ = true;
if (self->closeCb_) {
self->closeCb_();
}
}
}
void GLFWWindow::windowFocusCallback(GLFWwindow* window, int focused) {
GLFWWindow* self = static_cast<GLFWWindow*>(glfwGetWindowUserPointer(window));
if (self) {
self->focused_ = (focused == GLFW_TRUE);
if (self->focusCb_) {
self->focusCb_(self->focused_);
}
}
}
void GLFWWindow::windowIconifyCallback(GLFWwindow* window, int iconified) {
GLFWWindow* self = static_cast<GLFWWindow*>(glfwGetWindowUserPointer(window));
if (self) {
self->minimized_ = (iconified == GLFW_TRUE);
}
}
void GLFWWindow::cursorPosCallback(GLFWwindow* window, double xpos, double ypos) {
GLFWWindow* self = static_cast<GLFWWindow*>(glfwGetWindowUserPointer(window));
if (self && self->input_) {
self->input_->handleCursorPosEvent(xpos, ypos);
}
}
void GLFWWindow::mouseButtonCallback(GLFWwindow* window, int button, int action, int mods) {
GLFWWindow* self = static_cast<GLFWWindow*>(glfwGetWindowUserPointer(window));
if (self && self->input_) {
self->input_->handleMouseButtonEvent(button, action, mods);
}
}
void GLFWWindow::scrollCallback(GLFWwindow* window, double xoffset, double yoffset) {
GLFWWindow* self = static_cast<GLFWWindow*>(glfwGetWindowUserPointer(window));
if (self && self->input_) {
self->input_->handleScrollEvent(xoffset, yoffset);
}
}
void GLFWWindow::keyCallback(GLFWwindow* window, int key, int scancode, int action, int mods) {
GLFWWindow* self = static_cast<GLFWWindow*>(glfwGetWindowUserPointer(window));
if (self && self->input_) {
self->input_->handleKeyEvent(key, scancode, action, mods);
}
}
void GLFWWindow::joystickCallback(int jid, int event) {
// 通过全局回调找到对应的窗口实例
// 由于 GLFW 的 joystick 回调没有窗口参数,我们需要其他方式处理
// 这里简化处理,让输入系统在 update() 中轮询
}
} // namespace extra2d

View File

@ -0,0 +1,99 @@
#pragma once
#include <extra2d/platform/iwindow.h>
#include <GLFW/glfw3.h>
namespace extra2d {
class GLFWInput;
/**
* @brief GLFW
*/
class GLFWWindow : public IWindow {
public:
GLFWWindow();
~GLFWWindow() override;
bool create(const WindowConfigData& cfg) override;
void destroy() override;
void poll() override;
void swap() override;
bool shouldClose() const override;
void close() override;
void setTitle(const std::string& title) override;
void setSize(int w, int h) override;
void setPos(int x, int y) override;
void setFullscreen(bool fs) override;
void setVSync(bool vsync) override;
void setVisible(bool visible) override;
int width() const override;
int height() const override;
Size size() const override;
Vec2 pos() const override;
bool fullscreen() const override;
bool vsync() const override;
bool focused() const override;
bool minimized() const override;
float scaleX() const override;
float scaleY() const override;
void setCursor(Cursor cursor) override;
void showCursor(bool show) override;
void lockCursor(bool lock) override;
IInput* input() const override;
void onResize(ResizeCb cb) override;
void onClose(CloseCb cb) override;
void onFocus(FocusCb cb) override;
void* native() const override;
/**
* @brief GLFW
*/
GLFWwindow* glfwWindow() const { return glfwWindow_; }
private:
bool initGLFW();
void deinitGLFW();
void updateContentScale();
// GLFW 回调函数(静态)
static void framebufferSizeCallback(GLFWwindow* window, int width, int height);
static void windowCloseCallback(GLFWwindow* window);
static void windowFocusCallback(GLFWwindow* window, int focused);
static void windowIconifyCallback(GLFWwindow* window, int iconified);
static void cursorPosCallback(GLFWwindow* window, double xpos, double ypos);
static void mouseButtonCallback(GLFWwindow* window, int button, int action, int mods);
static void scrollCallback(GLFWwindow* window, double xoffset, double yoffset);
static void keyCallback(GLFWwindow* window, int key, int scancode, int action, int mods);
static void joystickCallback(int jid, int event);
GLFWwindow* glfwWindow_ = nullptr;
UniquePtr<GLFWInput> input_;
int width_ = 1280;
int height_ = 720;
bool fullscreen_ = false;
bool vsync_ = true;
bool focused_ = true;
bool minimized_ = false;
bool shouldClose_ = false;
float scaleX_ = 1.0f;
float scaleY_ = 1.0f;
bool cursorVisible_ = true;
bool cursorLocked_ = false;
ResizeCb resizeCb_;
CloseCb closeCb_;
FocusCb focusCb_;
};
} // namespace extra2d

View File

@ -6,7 +6,9 @@
namespace extra2d {
InputModule::InputModule(const Cfg& cfg) : cfg_(cfg) {}
InputModule::InputModule(std::function<void(InputCfg&)> configFn) {
configFn(cfg_);
}
InputModule::~InputModule() {
if (initialized_) {

View File

@ -2,7 +2,6 @@
#include <extra2d/platform/backend_factory.h>
#include <extra2d/core/service_locator.h>
#include <extra2d/utils/logger.h>
#include <SDL.h>
#ifdef __SWITCH__
#include <switch.h>
@ -10,10 +9,16 @@
namespace extra2d {
// 前向声明 SDL2 后端初始化函数
// 前向声明后端初始化函数
#if defined(E2D_BACKEND_SDL2)
void initSDL2Backend();
#elif defined(E2D_BACKEND_GLFW)
void initGLFWBackend();
#endif
WindowModule::WindowModule(const Cfg& cfg) : cfg_(cfg) {}
WindowModule::WindowModule(std::function<void(WindowCfg&)> configFn) {
configFn(cfg_);
}
WindowModule::~WindowModule() {
if (initialized_) {
@ -28,21 +33,16 @@ bool WindowModule::init() {
cfg_.mode = WindowMode::Fullscreen;
#endif
// 初始化SDL后端注册到工厂
// 初始化后端(注册到工厂)
#if defined(E2D_BACKEND_SDL2)
initSDL2Backend();
// 初始化SDL
Uint32 flags = SDL_INIT_VIDEO | SDL_INIT_EVENTS | SDL_INIT_TIMER;
#ifdef __SWITCH__
flags |= SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER;
#elif defined(E2D_BACKEND_GLFW)
initGLFWBackend();
#else
#error "No window backend defined"
#endif
if (SDL_Init(flags) != 0) {
E2D_LOG_ERROR("SDL_Init failed: {}", SDL_GetError());
return false;
}
sdlInited_ = true;
E2D_LOG_INFO("SDL initialized successfully");
E2D_LOG_INFO("Window backend initialized");
// 创建窗口配置
WindowConfigData winCfg;
@ -57,8 +57,7 @@ bool WindowModule::init() {
// 创建窗口(使用配置的后端)
win_ = BackendFactory::createWindow(cfg_.backend);
if (!win_) {
E2D_LOG_ERROR("Failed to create window backend");
shutdown();
E2D_LOG_ERROR("Failed to create window backend: {}", cfg_.backend);
return false;
}
@ -81,11 +80,6 @@ void WindowModule::shutdown() {
win_.reset();
}
if (sdlInited_) {
SDL_Quit();
sdlInited_ = false;
}
initialized_ = false;
}

View File

@ -3,126 +3,97 @@
namespace extra2d {
CameraService::CameraService() {
info_.name = "CameraService";
info_.priority = ServicePriority::Camera;
info_.enabled = true;
info_.name = "CameraService";
info_.priority = ServicePriority::Camera;
info_.enabled = true;
}
CameraService::CameraService(float left, float right, float bottom, float top)
: camera_(left, right, bottom, top) {
info_.name = "CameraService";
info_.priority = ServicePriority::Camera;
info_.enabled = true;
info_.name = "CameraService";
info_.priority = ServicePriority::Camera;
info_.enabled = true;
}
ServiceInfo CameraService::getServiceInfo() const {
return info_;
}
ServiceInfo CameraService::getServiceInfo() const { return info_; }
bool CameraService::initialize() {
camera_.setViewportAdapter(&viewportAdapter_);
setState(ServiceState::Running);
return true;
camera_.setViewportAdapter(&viewportAdapter_);
setState(ServiceState::Running);
return true;
}
void CameraService::shutdown() {
setState(ServiceState::Stopped);
void CameraService::shutdown() { setState(ServiceState::Stopped); }
void CameraService::setPosition(const Vec2 &position) {
camera_.setPos(position);
}
void CameraService::setPosition(const Vec2& position) {
camera_.setPos(position);
void CameraService::setPosition(float x, float y) { camera_.setPos(x, y); }
Vec2 CameraService::getPosition() const { return camera_.getPosition(); }
void CameraService::setRotation(float degrees) { camera_.setRotation(degrees); }
float CameraService::getRotation() const { return camera_.getRotation(); }
void CameraService::setZoom(float zoom) { camera_.setZoom(zoom); }
float CameraService::getZoom() const { return camera_.getZoom(); }
void CameraService::setViewport(float left, float right, float bottom,
float top) {
camera_.setViewport(left, right, bottom, top);
}
void CameraService::setPosition(float x, float y) {
camera_.setPos(x, y);
}
Vec2 CameraService::getPosition() const {
return camera_.getPosition();
}
void CameraService::setRotation(float degrees) {
camera_.setRotation(degrees);
}
float CameraService::getRotation() const {
return camera_.getRotation();
}
void CameraService::setZoom(float zoom) {
camera_.setZoom(zoom);
}
float CameraService::getZoom() const {
return camera_.getZoom();
}
void CameraService::setViewport(float left, float right, float bottom, float top) {
camera_.setViewport(left, right, bottom, top);
}
Rect CameraService::getViewport() const {
return camera_.getViewport();
}
Rect CameraService::getViewport() const { return camera_.getViewport(); }
glm::mat4 CameraService::getViewMatrix() const {
return camera_.getViewMatrix();
return camera_.getViewMatrix();
}
glm::mat4 CameraService::getProjectionMatrix() const {
return camera_.getProjectionMatrix();
return camera_.getProjectionMatrix();
}
glm::mat4 CameraService::getViewProjectionMatrix() const {
return camera_.getViewProjectionMatrix();
return camera_.getViewProjectionMatrix();
}
Vec2 CameraService::screenToWorld(const Vec2& screenPos) const {
return camera_.screenToWorld(screenPos);
Vec2 CameraService::screenToWorld(const Vec2 &screenPos) const {
return camera_.screenToWorld(screenPos);
}
Vec2 CameraService::worldToScreen(const Vec2& worldPos) const {
return camera_.worldToScreen(worldPos);
Vec2 CameraService::worldToScreen(const Vec2 &worldPos) const {
return camera_.worldToScreen(worldPos);
}
void CameraService::move(const Vec2& offset) {
camera_.move(offset);
void CameraService::move(const Vec2 &offset) { camera_.move(offset); }
void CameraService::move(float x, float y) { camera_.move(x, y); }
void CameraService::setBounds(const Rect &bounds) { camera_.setBounds(bounds); }
void CameraService::clearBounds() { camera_.clearBounds(); }
void CameraService::lookAt(const Vec2 &target) { camera_.lookAt(target); }
void CameraService::setViewportConfig(const ViewportConfig &config) {
viewportAdapter_.setConfig(config);
}
void CameraService::move(float x, float y) {
camera_.move(x, y);
}
void CameraService::setBounds(const Rect& bounds) {
camera_.setBounds(bounds);
}
void CameraService::clearBounds() {
camera_.clearBounds();
}
void CameraService::lookAt(const Vec2& target) {
camera_.lookAt(target);
}
void CameraService::setViewportConfig(const ViewportConfig& config) {
viewportAdapter_.setConfig(config);
}
const ViewportConfig& CameraService::getViewportConfig() const {
return viewportAdapter_.getConfig();
const ViewportConfig &CameraService::getViewportConfig() const {
return viewportAdapter_.getConfig();
}
void CameraService::updateViewport(int screenWidth, int screenHeight) {
viewportAdapter_.update(screenWidth, screenHeight);
viewportAdapter_.update(screenWidth, screenHeight);
}
const ViewportResult& CameraService::getViewportResult() const {
return viewportAdapter_.getResult();
const ViewportResult &CameraService::getViewportResult() const {
return viewportAdapter_.getResult();
}
void CameraService::applyViewportAdapter() {
camera_.applyViewportAdapter();
}
void CameraService::applyViewportAdapter() { camera_.applyViewportAdapter(); }
}
} // namespace extra2d

View File

@ -3,78 +3,63 @@
namespace extra2d {
EventService::EventService() {
info_.name = "EventService";
info_.priority = ServicePriority::Event;
info_.enabled = true;
info_.name = "EventService";
info_.priority = ServicePriority::Event;
info_.enabled = true;
}
ServiceInfo EventService::getServiceInfo() const {
return info_;
}
ServiceInfo EventService::getServiceInfo() const { return info_; }
bool EventService::initialize() {
setState(ServiceState::Running);
return true;
setState(ServiceState::Running);
return true;
}
void EventService::shutdown() {
queue_.clear();
dispatcher_.removeAllListeners();
setState(ServiceState::Stopped);
queue_.clear();
dispatcher_.removeAllListeners();
setState(ServiceState::Stopped);
}
void EventService::update(float deltaTime) {
if (getState() == ServiceState::Running) {
processQueue();
}
if (getState() == ServiceState::Running) {
processQueue();
}
}
void EventService::pushEvent(const Event& event) {
queue_.push(event);
}
void EventService::pushEvent(const Event &event) { queue_.push(event); }
void EventService::pushEvent(Event&& event) {
queue_.push(std::move(event));
}
void EventService::pushEvent(Event &&event) { queue_.push(std::move(event)); }
bool EventService::pollEvent(Event& event) {
return queue_.poll(event);
}
bool EventService::pollEvent(Event &event) { return queue_.poll(event); }
ListenerId EventService::addListener(EventType type, EventDispatcher::EventCallback callback) {
return dispatcher_.addListener(type, callback);
ListenerId EventService::addListener(EventType type,
EventDispatcher::EventCallback callback) {
return dispatcher_.addListener(type, callback);
}
void EventService::removeListener(ListenerId id) {
dispatcher_.removeListener(id);
dispatcher_.removeListener(id);
}
void EventService::removeAllListeners(EventType type) {
dispatcher_.removeAllListeners(type);
dispatcher_.removeAllListeners(type);
}
void EventService::removeAllListeners() {
dispatcher_.removeAllListeners();
}
void EventService::removeAllListeners() { dispatcher_.removeAllListeners(); }
void EventService::dispatch(Event& event) {
dispatcher_.dispatch(event);
}
void EventService::dispatch(Event &event) { dispatcher_.dispatch(event); }
void EventService::processQueue() {
dispatcher_.processQueue(queue_);
}
void EventService::processQueue() { dispatcher_.processQueue(queue_); }
size_t EventService::getListenerCount(EventType type) const {
return dispatcher_.getListenerCount(type);
return dispatcher_.getListenerCount(type);
}
size_t EventService::getTotalListenerCount() const {
return dispatcher_.getTotalListenerCount();
return dispatcher_.getTotalListenerCount();
}
size_t EventService::getQueueSize() const {
return queue_.size();
}
size_t EventService::getQueueSize() const { return queue_.size(); }
}
} // namespace extra2d

View File

@ -1,10 +1,7 @@
#include <extra2d/services/logger_service.h>
#include <extra2d/core/service_locator.h>
#include <cstdio>
#include <cstdarg>
#include <chrono>
#include <iomanip>
#include <sstream>
#include <cstdarg>
#include <cstdio>
#include <extra2d/services/logger_service.h>
#include <mutex>
namespace extra2d {
@ -12,160 +9,183 @@ namespace extra2d {
// ConsoleLogger 实现
class ConsoleLogger::Impl {
public:
std::mutex mutex_;
std::mutex mutex_;
};
ConsoleLogger::ConsoleLogger() : level_(LogLevel::Info), impl_(std::make_unique<Impl>()) {
info_.name = "ConsoleLogger";
info_.priority = ServicePriority::Core;
ConsoleLogger::ConsoleLogger()
: level_(LogLevel::Info), impl_(std::make_unique<Impl>()) {
info_.name = "ConsoleLogger";
info_.priority = ServicePriority::Core;
}
ConsoleLogger::~ConsoleLogger() = default;
bool ConsoleLogger::initialize() {
setState(ServiceState::Running);
return true;
setState(ServiceState::Running);
return true;
}
void ConsoleLogger::shutdown() {
setState(ServiceState::Stopped);
}
void ConsoleLogger::shutdown() { setState(ServiceState::Stopped); }
void ConsoleLogger::setLevel(LogLevel level) {
level_ = level;
}
void ConsoleLogger::setLevel(LogLevel level) { level_ = level; }
LogLevel ConsoleLogger::getLevel() const {
return level_;
}
LogLevel ConsoleLogger::getLevel() const { return level_; }
bool ConsoleLogger::isEnabled(LogLevel level) const {
return static_cast<int>(level) >= static_cast<int>(level_);
return static_cast<int>(level) >= static_cast<int>(level_);
}
void ConsoleLogger::log(LogLevel level, const char* fmt, ...) {
if (!isEnabled(level)) return;
char buffer[1024];
va_list args;
va_start(args, fmt);
vsnprintf(buffer, sizeof(buffer), fmt, args);
va_end(args);
output(level, buffer);
void ConsoleLogger::log(LogLevel level, const char *fmt, ...) {
if (!isEnabled(level))
return;
char buffer[1024];
va_list args;
va_start(args, fmt);
vsnprintf(buffer, sizeof(buffer), fmt, args);
va_end(args);
output(level, buffer);
}
void ConsoleLogger::log(LogLevel level, const std::string& msg) {
if (!isEnabled(level)) return;
output(level, msg.c_str());
void ConsoleLogger::log(LogLevel level, const std::string &msg) {
if (!isEnabled(level))
return;
output(level, msg.c_str());
}
void ConsoleLogger::trace(const char* fmt, ...) {
if (!isEnabled(LogLevel::Trace)) return;
char buffer[1024];
va_list args;
va_start(args, fmt);
vsnprintf(buffer, sizeof(buffer), fmt, args);
va_end(args);
output(LogLevel::Trace, buffer);
void ConsoleLogger::trace(const char *fmt, ...) {
if (!isEnabled(LogLevel::Trace))
return;
char buffer[1024];
va_list args;
va_start(args, fmt);
vsnprintf(buffer, sizeof(buffer), fmt, args);
va_end(args);
output(LogLevel::Trace, buffer);
}
void ConsoleLogger::debug(const char* fmt, ...) {
if (!isEnabled(LogLevel::Debug)) return;
char buffer[1024];
va_list args;
va_start(args, fmt);
vsnprintf(buffer, sizeof(buffer), fmt, args);
va_end(args);
output(LogLevel::Debug, buffer);
void ConsoleLogger::debug(const char *fmt, ...) {
if (!isEnabled(LogLevel::Debug))
return;
char buffer[1024];
va_list args;
va_start(args, fmt);
vsnprintf(buffer, sizeof(buffer), fmt, args);
va_end(args);
output(LogLevel::Debug, buffer);
}
void ConsoleLogger::info(const char* fmt, ...) {
if (!isEnabled(LogLevel::Info)) return;
char buffer[1024];
va_list args;
va_start(args, fmt);
vsnprintf(buffer, sizeof(buffer), fmt, args);
va_end(args);
output(LogLevel::Info, buffer);
void ConsoleLogger::info(const char *fmt, ...) {
if (!isEnabled(LogLevel::Info))
return;
char buffer[1024];
va_list args;
va_start(args, fmt);
vsnprintf(buffer, sizeof(buffer), fmt, args);
va_end(args);
output(LogLevel::Info, buffer);
}
void ConsoleLogger::warn(const char* fmt, ...) {
if (!isEnabled(LogLevel::Warn)) return;
char buffer[1024];
va_list args;
va_start(args, fmt);
vsnprintf(buffer, sizeof(buffer), fmt, args);
va_end(args);
output(LogLevel::Warn, buffer);
void ConsoleLogger::warn(const char *fmt, ...) {
if (!isEnabled(LogLevel::Warn))
return;
char buffer[1024];
va_list args;
va_start(args, fmt);
vsnprintf(buffer, sizeof(buffer), fmt, args);
va_end(args);
output(LogLevel::Warn, buffer);
}
void ConsoleLogger::error(const char* fmt, ...) {
if (!isEnabled(LogLevel::Error)) return;
char buffer[1024];
va_list args;
va_start(args, fmt);
vsnprintf(buffer, sizeof(buffer), fmt, args);
va_end(args);
output(LogLevel::Error, buffer);
void ConsoleLogger::error(const char *fmt, ...) {
if (!isEnabled(LogLevel::Error))
return;
char buffer[1024];
va_list args;
va_start(args, fmt);
vsnprintf(buffer, sizeof(buffer), fmt, args);
va_end(args);
output(LogLevel::Error, buffer);
}
void ConsoleLogger::fatal(const char* fmt, ...) {
if (!isEnabled(LogLevel::Fatal)) return;
char buffer[1024];
va_list args;
va_start(args, fmt);
vsnprintf(buffer, sizeof(buffer), fmt, args);
va_end(args);
output(LogLevel::Fatal, buffer);
void ConsoleLogger::fatal(const char *fmt, ...) {
if (!isEnabled(LogLevel::Fatal))
return;
char buffer[1024];
va_list args;
va_start(args, fmt);
vsnprintf(buffer, sizeof(buffer), fmt, args);
va_end(args);
output(LogLevel::Fatal, buffer);
}
void ConsoleLogger::output(LogLevel level, const char* msg) {
std::lock_guard<std::mutex> lock(impl_->mutex_);
auto now = std::chrono::system_clock::now();
auto time = std::chrono::system_clock::to_time_t(now);
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
now.time_since_epoch()) % 1000;
std::tm tm;
void ConsoleLogger::output(LogLevel level, const char *msg) {
std::lock_guard<std::mutex> lock(impl_->mutex_);
auto now = std::chrono::system_clock::now();
auto time = std::chrono::system_clock::to_time_t(now);
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
now.time_since_epoch()) %
1000;
std::tm tm;
#ifdef _WIN32
localtime_s(&tm, &time);
localtime_s(&tm, &time);
#else
localtime_r(&time, &tm);
localtime_r(&time, &tm);
#endif
const char* levelStr = getLevelString(level);
// 颜色代码
const char* color = "";
const char* reset = "\033[0m";
switch (level) {
case LogLevel::Trace: color = "\033[90m"; break;
case LogLevel::Debug: color = "\033[36m"; break;
case LogLevel::Info: color = "\033[32m"; break;
case LogLevel::Warn: color = "\033[33m"; break;
case LogLevel::Error: color = "\033[31m"; break;
case LogLevel::Fatal: color = "\033[35m"; break;
default: break;
}
printf("%s[%02d:%02d:%02d.%03d] [%s] %s%s\n",
color, tm.tm_hour, tm.tm_min, tm.tm_sec, (int)ms.count(),
levelStr, msg, reset);
const char *levelStr = getLevelString(level);
// 颜色代码
const char *color = "";
const char *reset = "\033[0m";
switch (level) {
case LogLevel::Trace:
color = "\033[90m";
break;
case LogLevel::Debug:
color = "\033[36m";
break;
case LogLevel::Info:
color = "\033[32m";
break;
case LogLevel::Warn:
color = "\033[33m";
break;
case LogLevel::Error:
color = "\033[31m";
break;
case LogLevel::Fatal:
color = "\033[35m";
break;
default:
break;
}
printf("%s[%02d:%02d:%02d.%03d] [%s] %s%s\n", color, tm.tm_hour, tm.tm_min,
tm.tm_sec, (int)ms.count(), levelStr, msg, reset);
}
const char* ConsoleLogger::getLevelString(LogLevel level) {
switch (level) {
case LogLevel::Trace: return "TRACE";
case LogLevel::Debug: return "DEBUG";
case LogLevel::Info: return "INFO";
case LogLevel::Warn: return "WARN";
case LogLevel::Error: return "ERROR";
case LogLevel::Fatal: return "FATAL";
default: return "UNKNOWN";
}
const char *ConsoleLogger::getLevelString(LogLevel level) {
switch (level) {
case LogLevel::Trace:
return "TRACE";
case LogLevel::Debug:
return "DEBUG";
case LogLevel::Info:
return "INFO";
case LogLevel::Warn:
return "WARN";
case LogLevel::Error:
return "ERROR";
case LogLevel::Fatal:
return "FATAL";
default:
return "UNKNOWN";
}
}
} // namespace extra2d

View File

@ -3,109 +3,92 @@
namespace extra2d {
SceneService::SceneService() {
info_.name = "SceneService";
info_.priority = ServicePriority::Scene;
info_.enabled = true;
info_.name = "SceneService";
info_.priority = ServicePriority::Scene;
info_.enabled = true;
}
ServiceInfo SceneService::getServiceInfo() const {
return info_;
}
ServiceInfo SceneService::getServiceInfo() const { return info_; }
bool SceneService::initialize() {
setState(ServiceState::Running);
return true;
setState(ServiceState::Running);
return true;
}
void SceneService::shutdown() {
manager_.end();
setState(ServiceState::Stopped);
manager_.end();
setState(ServiceState::Stopped);
}
void SceneService::update(float deltaTime) {
if (getState() == ServiceState::Running) {
manager_.update(deltaTime);
}
if (getState() == ServiceState::Running) {
manager_.update(deltaTime);
}
}
void SceneService::runWithScene(Ptr<Scene> scene) {
manager_.runWithScene(scene);
manager_.runWithScene(scene);
}
void SceneService::replaceScene(Ptr<Scene> scene) {
manager_.replaceScene(scene);
manager_.replaceScene(scene);
}
void SceneService::pushScene(Ptr<Scene> scene) {
manager_.pushScene(scene);
}
void SceneService::pushScene(Ptr<Scene> scene) { manager_.pushScene(scene); }
void SceneService::popScene() {
manager_.popScene();
}
void SceneService::popScene() { manager_.popScene(); }
void SceneService::popToRootScene() {
manager_.popToRootScene();
}
void SceneService::popToRootScene() { manager_.popToRootScene(); }
void SceneService::popToScene(const std::string& name) {
manager_.popToScene(name);
void SceneService::popToScene(const std::string &name) {
manager_.popToScene(name);
}
Ptr<Scene> SceneService::getCurrentScene() const {
return manager_.getCurrentScene();
return manager_.getCurrentScene();
}
Ptr<Scene> SceneService::getPreviousScene() const {
return manager_.getPreviousScene();
return manager_.getPreviousScene();
}
Ptr<Scene> SceneService::getRootScene() const {
return manager_.getRootScene();
return manager_.getRootScene();
}
Ptr<Scene> SceneService::getSceneByName(const std::string& name) const {
return manager_.getSceneByName(name);
Ptr<Scene> SceneService::getSceneByName(const std::string &name) const {
return manager_.getSceneByName(name);
}
size_t SceneService::getSceneCount() const {
return manager_.getSceneCount();
size_t SceneService::getSceneCount() const { return manager_.getSceneCount(); }
bool SceneService::isEmpty() const { return manager_.isEmpty(); }
bool SceneService::hasScene(const std::string &name) const {
return manager_.hasScene(name);
}
bool SceneService::isEmpty() const {
return manager_.isEmpty();
void SceneService::render(RenderBackend &renderer) {
manager_.render(renderer);
}
bool SceneService::hasScene(const std::string& name) const {
return manager_.hasScene(name);
}
void SceneService::render(RenderBackend& renderer) {
manager_.render(renderer);
}
void SceneService::collectRenderCommands(std::vector<RenderCommand>& commands) {
manager_.collectRenderCommands(commands);
void SceneService::collectRenderCommands(std::vector<RenderCommand> &commands) {
manager_.collectRenderCommands(commands);
}
bool SceneService::isTransitioning() const {
return manager_.isTransitioning();
return manager_.isTransitioning();
}
void SceneService::setTransitionCallback(SceneManager::TransitionCallback callback) {
manager_.setTransitionCallback(callback);
void SceneService::setTransitionCallback(
SceneManager::TransitionCallback callback) {
manager_.setTransitionCallback(callback);
}
void SceneService::end() {
manager_.end();
}
void SceneService::end() { manager_.end(); }
void SceneService::purgeCachedScenes() {
manager_.purgeCachedScenes();
}
void SceneService::purgeCachedScenes() { manager_.purgeCachedScenes(); }
void SceneService::enterScene(Ptr<Scene> scene) {
manager_.enterScene(scene);
}
void SceneService::enterScene(Ptr<Scene> scene) { manager_.enterScene(scene); }
}
} // namespace extra2d

View File

@ -3,57 +3,50 @@
namespace extra2d {
TimerService::TimerService() {
info_.name = "TimerService";
info_.priority = ServicePriority::Timer;
info_.enabled = true;
info_.name = "TimerService";
info_.priority = ServicePriority::Timer;
info_.enabled = true;
}
ServiceInfo TimerService::getServiceInfo() const {
return info_;
}
ServiceInfo TimerService::getServiceInfo() const { return info_; }
bool TimerService::initialize() {
setState(ServiceState::Running);
return true;
setState(ServiceState::Running);
return true;
}
void TimerService::shutdown() {
manager_.clear();
setState(ServiceState::Stopped);
manager_.clear();
setState(ServiceState::Stopped);
}
void TimerService::update(float deltaTime) {
if (getState() == ServiceState::Running) {
manager_.update(deltaTime);
}
if (getState() == ServiceState::Running) {
manager_.update(deltaTime);
}
}
uint32 TimerService::addTimer(float delay, Timer::Callback callback) {
return manager_.addTimer(delay, callback);
return manager_.addTimer(delay, callback);
}
uint32 TimerService::addRepeatingTimer(float interval, Timer::Callback callback) {
return manager_.addRepeatingTimer(interval, callback);
uint32 TimerService::addRepeatingTimer(float interval,
Timer::Callback callback) {
return manager_.addRepeatingTimer(interval, callback);
}
void TimerService::cancelTimer(uint32 timerId) {
manager_.cancelTimer(timerId);
manager_.cancelTimer(timerId);
}
void TimerService::pauseTimer(uint32 timerId) {
manager_.pauseTimer(timerId);
}
void TimerService::pauseTimer(uint32 timerId) { manager_.pauseTimer(timerId); }
void TimerService::resumeTimer(uint32 timerId) {
manager_.resumeTimer(timerId);
manager_.resumeTimer(timerId);
}
void TimerService::clear() {
manager_.clear();
}
void TimerService::clear() { manager_.clear(); }
size_t TimerService::getTimerCount() const {
return manager_.getTimerCount();
}
size_t TimerService::getTimerCount() const { return manager_.getTimerCount(); }
}
} // namespace extra2d

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