diff --git a/1.jpg b/1.jpg new file mode 100644 index 0000000..b75ea16 Binary files /dev/null and b/1.jpg differ diff --git a/Extra2D/include/extra2d/app/application.h b/Extra2D/include/extra2d/app/application.h index 2e11d4d..bb81cad 100644 --- a/Extra2D/include/extra2d/app/application.h +++ b/Extra2D/include/extra2d/app/application.h @@ -1,10 +1,9 @@ #pragma once -#include #include #include #include -#include +#include #include 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 - T* use(Args&&... args) { - return Registry::instance().use(std::forward(args)...); - } - - /** - * @brief 获取模块 - * @tparam T 模块类型 - * @return 模块指针 - */ - template - T* get() const { - return Registry::instance().get(); - } - - /** - * @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 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 T *use(Args &&...args) { + return Registry::instance().use(std::forward(args)...); + } + + /** + * @brief 获取模块 + * @tparam T 模块类型 + * @return 模块指针 + */ + template T *get() const { return Registry::instance().get(); } + + /** + * @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 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 diff --git a/Extra2D/include/extra2d/config/app_config.h b/Extra2D/include/extra2d/config/app_config.h deleted file mode 100644 index a7a3812..0000000 --- a/Extra2D/include/extra2d/config/app_config.h +++ /dev/null @@ -1,61 +0,0 @@ -#pragma once - -#include -#include -#include - -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(); } -}; - -} diff --git a/Extra2D/include/extra2d/config/platform_config.h b/Extra2D/include/extra2d/config/platform_config.h deleted file mode 100644 index 2ac7648..0000000 --- a/Extra2D/include/extra2d/config/platform_config.h +++ /dev/null @@ -1,87 +0,0 @@ -#pragma once - -#include -#include - -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 createPlatformConfig(PlatformType type = PlatformType::Auto); - -/** - * @brief 获取平台类型名称 - * @param type 平台类型枚举值 - * @return 平台名称字符串 - */ -const char* getPlatformTypeName(PlatformType type); - -} diff --git a/Extra2D/include/extra2d/config/platform_detector.h b/Extra2D/include/extra2d/config/platform_detector.h deleted file mode 100644 index 9507c29..0000000 --- a/Extra2D/include/extra2d/config/platform_detector.h +++ /dev/null @@ -1,210 +0,0 @@ -#pragma once - -#include -#include -#include -#include - -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(); -}; - -} diff --git a/Extra2D/include/extra2d/core/service_locator.h b/Extra2D/include/extra2d/core/service_locator.h index e2735fd..31f6492 100644 --- a/Extra2D/include/extra2d/core/service_locator.h +++ b/Extra2D/include/extra2d/core/service_locator.h @@ -1,27 +1,26 @@ #pragma once +#include #include #include +#include +#include +#include +#include #include #include -#include -#include -#include -#include -#include namespace extra2d { /** * @brief 服务工厂函数类型 */ -template -using ServiceFactory = std::function()>; +template using ServiceFactory = std::function()>; /** * @brief 服务定位器 * 实现依赖注入和服务发现模式,解耦模块间依赖 - * + * * 特性: * - 类型安全的服务注册和获取 * - 支持服务工厂延迟创建 @@ -31,222 +30,273 @@ using ServiceFactory = std::function()>; */ 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 - void registerService(SharedPtr service) { - static_assert(std::is_base_of_v, - "T must derive from IService"); - - std::lock_guard lock(mutex_); - auto typeId = std::type_index(typeid(T)); - services_[typeId] = std::static_pointer_cast(service); - orderedServices_.push_back(service); - sortServices(); + /** + * @brief 注册服务实例 + * @tparam T 服务接口类型 + * @param service 服务实例 + */ + template void registerService(SharedPtr service) { + static_assert(std::is_base_of_v, + "T must derive from IService"); + + std::lock_guard lock(mutex_); + auto typeId = std::type_index(typeid(T)); + services_[typeId] = std::static_pointer_cast(service); + orderedServices_.push_back(service); + sortServices(); + } + + /** + * @brief 注册服务工厂 + * @tparam T 服务接口类型 + * @param factory 服务工厂函数 + */ + template void registerFactory(ServiceFactory factory) { + static_assert(std::is_base_of_v, + "T must derive from IService"); + + std::lock_guard lock(mutex_); + auto typeId = std::type_index(typeid(T)); + factories_[typeId] = [factory]() -> SharedPtr { + return std::static_pointer_cast(factory()); + }; + + // 立即创建服务实例并添加到有序列表 + auto service = factories_[typeId](); + services_[typeId] = service; + orderedServices_.push_back(service); + sortServices(); + } + + /** + * @brief 获取服务实例 + * @tparam T 服务接口类型 + * @return 服务实例,不存在返回 nullptr + */ + template SharedPtr getService() const { + static_assert(std::is_base_of_v, + "T must derive from IService"); + + std::lock_guard lock(mutex_); + auto typeId = std::type_index(typeid(T)); + + auto it = services_.find(typeId); + if (it != services_.end()) { + return std::static_pointer_cast(it->second); } - /** - * @brief 注册服务工厂 - * @tparam T 服务接口类型 - * @param factory 服务工厂函数 - */ - template - void registerFactory(ServiceFactory factory) { - static_assert(std::is_base_of_v, - "T must derive from IService"); - - std::lock_guard lock(mutex_); - auto typeId = std::type_index(typeid(T)); - factories_[typeId] = [factory]() -> SharedPtr { - return std::static_pointer_cast(factory()); - }; + auto factoryIt = factories_.find(typeId); + if (factoryIt != factories_.end()) { + auto service = factoryIt->second(); + services_[typeId] = service; + return std::static_pointer_cast(service); } - /** - * @brief 获取服务实例 - * @tparam T 服务接口类型 - * @return 服务实例,不存在返回 nullptr - */ - template - SharedPtr getService() const { - static_assert(std::is_base_of_v, - "T must derive from IService"); - - std::lock_guard lock(mutex_); - auto typeId = std::type_index(typeid(T)); - - auto it = services_.find(typeId); - if (it != services_.end()) { - return std::static_pointer_cast(it->second); - } - - auto factoryIt = factories_.find(typeId); - if (factoryIt != factories_.end()) { - auto service = factoryIt->second(); - services_[typeId] = service; - return std::static_pointer_cast(service); - } - - return nullptr; + return nullptr; + } + + /** + * @brief 尝试获取服务实例(不创建) + * @tparam T 服务接口类型 + * @return 服务实例,不存在返回 nullptr + */ + template SharedPtr tryGetService() const { + static_assert(std::is_base_of_v, + "T must derive from IService"); + + std::lock_guard lock(mutex_); + auto typeId = std::type_index(typeid(T)); + auto it = services_.find(typeId); + if (it != services_.end()) { + return std::static_pointer_cast(it->second); + } + return nullptr; + } + + /** + * @brief 检查服务是否已注册 + * @tparam T 服务接口类型 + * @return 已注册返回 true + */ + template bool hasService() const { + std::lock_guard lock(mutex_); + auto typeId = std::type_index(typeid(T)); + return services_.find(typeId) != services_.end() || + factories_.find(typeId) != factories_.end(); + } + + /** + * @brief 注销服务 + * @tparam T 服务接口类型 + */ + template void unregisterService() { + std::lock_guard 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 - SharedPtr tryGetService() const { - static_assert(std::is_base_of_v, - "T must derive from IService"); - - std::lock_guard lock(mutex_); - auto typeId = std::type_index(typeid(T)); - auto it = services_.find(typeId); - if (it != services_.end()) { - return std::static_pointer_cast(it->second); - } - return nullptr; - } + factories_.erase(typeId); + } - /** - * @brief 检查服务是否已注册 - * @tparam T 服务接口类型 - * @return 已注册返回 true - */ - template - bool hasService() const { - std::lock_guard 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 - void unregisterService() { - std::lock_guard 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> getAllServices() const; - /** - * @brief 恢复所有服务 - */ - void resumeAll(); + /** + * @brief 清空所有服务和工厂 + */ + void clear(); - /** - * @brief 获取所有服务(按优先级排序) - * @return 服务列表 - */ - std::vector> 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> services_; - std::unordered_map()>> factories_; - std::vector> orderedServices_; - mutable std::mutex mutex_; + mutable std::unordered_map> services_; + std::unordered_map()>> + factories_; + std::vector> orderedServices_; + mutable std::mutex mutex_; }; /** * @brief 服务注册器 * 用于静态注册服务 */ -template -class ServiceRegistrar { +template class ServiceRegistrar { public: - explicit ServiceRegistrar(ServiceFactory factory = nullptr) { - if (factory) { - ServiceLocator::instance().registerFactory(factory); - } else { - ServiceLocator::instance().registerFactory( - []() -> SharedPtr { - return makeShared(); - } - ); - } + explicit ServiceRegistrar(ServiceFactory factory = nullptr) { + if (factory) { + ServiceLocator::instance().registerFactory(factory); + } else { + ServiceLocator::instance().registerFactory( + []() -> SharedPtr { + return makeShared(); + }); } + } }; -} +/** + * @brief 服务注册元数据模板 + * 使用模板元编程实现编译期服务注册 + * 通过静态成员变量的初始化触发注册 + */ +template struct ServiceAutoReg { + /** + * @brief 注册标记,访问此变量时触发服务注册 + */ + static const bool registered; -#define E2D_REGISTER_SERVICE(Interface, Implementation) \ - namespace { \ - static ::extra2d::ServiceRegistrar \ - E2D_CONCAT(service_registrar_, __LINE__); \ - } + /** + * @brief 执行实际的服务注册 + * @return true 表示注册成功 + */ + static bool doRegister() { + ::extra2d::ServiceLocator::instance().registerFactory( + []() -> ::extra2d::SharedPtr { + return ::extra2d::makeShared(); + }); + return true; + } +}; -#define E2D_REGISTER_SERVICE_FACTORY(Interface, Factory) \ - namespace { \ - static ::extra2d::ServiceRegistrar \ - E2D_CONCAT(service_factory_registrar_, __LINE__)(Factory); \ +// 静态成员定义,在此处触发注册 +template +const bool ServiceAutoReg::registered = + ServiceAutoReg::doRegister(); + +/** + * @brief 服务注册元数据(带自定义工厂) + */ +template struct ServiceAutoRegFactory { + template struct Impl { + static const bool registered; + + static bool doRegister(Factory factory) { + ::extra2d::ServiceLocator::instance().registerFactory(factory); + return true; } + }; +}; + +template +template +const bool ServiceAutoRegFactory::Impl::registered = + ServiceAutoRegFactory::Impl::doRegister(Factory{}); + +/** + * @brief 自动注册服务宏(元数据驱动) + * 在服务实现类中使用,通过模板元编程实现自动注册 + * 比静态对象更可靠,不易被编译器优化 + */ +#define E2D_AUTO_REGISTER_SERVICE(Interface, Implementation) \ + static inline const bool E2D_CONCAT(_service_reg_, __LINE__) = \ + ServiceAutoReg::registered + +/** + * @brief 带自定义工厂的自动注册服务宏(元数据驱动) + */ +#define E2D_AUTO_REGISTER_SERVICE_FACTORY(Interface, Factory) \ + static inline const bool E2D_CONCAT(_service_factory_reg_, __LINE__) = \ + ServiceAutoRegFactory::Impl::registered + +} // namespace extra2d diff --git a/Extra2D/include/extra2d/extra2d.h b/Extra2D/include/extra2d/extra2d.h index b640f6b..9b7b7c9 100644 --- a/Extra2D/include/extra2d/extra2d.h +++ b/Extra2D/include/extra2d/extra2d.h @@ -10,10 +10,7 @@ #include #include -// Config -#include -#include -#include +// Config removed - app info now in Application class // Platform #include diff --git a/Extra2D/include/extra2d/graphics/backends/backend_factory.h b/Extra2D/include/extra2d/graphics/backends/backend_factory.h new file mode 100644 index 0000000..2b1a2e5 --- /dev/null +++ b/Extra2D/include/extra2d/graphics/backends/backend_factory.h @@ -0,0 +1,79 @@ +#pragma once + +#include +#include + +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 createBackend(BackendType type); + + /** + * @brief 创建默认渲染后端 + * @return 默认渲染后端实例 + */ + UniquePtr createDefaultBackend(); + + /** + * @brief 检查后端是否可用 + * @param type 后端类型 + * @return 可用返回true,否则返回false + */ + bool isBackendAvailable(BackendType type) const; + + /** + * @brief 获取当前平台推荐的后端类型 + * @return 推荐的后端类型 + */ + BackendType getRecommendedBackend() const; + + /** + * @brief 获取后端类型名称 + * @param type 后端类型 + * @return 后端类型名称字符串 + */ + const char* getBackendName(BackendType type) const; + + /** + * @brief 从名称解析后端类型 + * @param name 后端类型名称 + * @return 后端类型,如果未知则返回OpenGL + */ + BackendType parseBackendType(const char* name) const; + +private: + BackendFactory() = default; + ~BackendFactory() = default; + BackendFactory(const BackendFactory&) = delete; + BackendFactory& operator=(const BackendFactory&) = delete; +}; + +} // namespace extra2d diff --git a/Extra2D/include/extra2d/graphics/backends/opengl/gl_buffer.h b/Extra2D/include/extra2d/graphics/backends/opengl/gl_buffer.h new file mode 100644 index 0000000..7138b4c --- /dev/null +++ b/Extra2D/include/extra2d/graphics/backends/opengl/gl_buffer.h @@ -0,0 +1,83 @@ +#pragma once + +#include +#include +#include +#include + +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(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 diff --git a/Extra2D/include/extra2d/graphics/backends/opengl/gl_context.h b/Extra2D/include/extra2d/graphics/backends/opengl/gl_context.h new file mode 100644 index 0000000..c656cb2 --- /dev/null +++ b/Extra2D/include/extra2d/graphics/backends/opengl/gl_context.h @@ -0,0 +1,139 @@ +#pragma once + +#include +#include +#include + +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 diff --git a/Extra2D/include/extra2d/graphics/backends/opengl/gl_font_atlas.h b/Extra2D/include/extra2d/graphics/backends/opengl/gl_font_atlas.h new file mode 100644 index 0000000..72d286c --- /dev/null +++ b/Extra2D/include/extra2d/graphics/backends/opengl/gl_font_atlas.h @@ -0,0 +1,84 @@ +#pragma once + +#include +#include + +#include +#include + +#include +#include + +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 texture_; + std::unordered_map glyphs_; + float lineHeight_; + float ascent_; + float descent_; + float lineGap_; + + // 字体数据 + std::vector fontData_; + stbtt_fontinfo fontInfo_; + float scale_; + + // stb_rect_pack 上下文 - 持久化以支持增量打包 + mutable stbrp_context packContext_; + mutable std::vector packNodes_; + + // 预分配缓冲区,避免每次动态分配 + mutable std::vector glyphBitmapCache_; + mutable std::vector 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& data); +}; + +} // namespace extra2d diff --git a/Extra2D/include/extra2d/graphics/backends/opengl/gl_framebuffer.h b/Extra2D/include/extra2d/graphics/backends/opengl/gl_framebuffer.h new file mode 100644 index 0000000..f5286d5 --- /dev/null +++ b/Extra2D/include/extra2d/graphics/backends/opengl/gl_framebuffer.h @@ -0,0 +1,105 @@ +#pragma once + +#include +#include +#include +#include + +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, int attachment = 0) override; + void attachDepthTexture(Ptr texture) override; + void attachDepthStencilTexture(Ptr texture) override; + bool isComplete() override; + Ptr getColorTexture(int attachment = 0) const override; + Ptr getDepthTexture() const override; + int getWidth() const override { return width_; } + int getHeight() const override { return height_; } + Size getSize() const override { return Size(static_cast(width_), static_cast(height_)); } + bool isValid() const override { return fboID_ != 0; } + uintptr_t getNativeHandle() const override { return static_cast(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& 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, MAX_COLOR_ATTACHMENTS> colorTextures_; + Ptr depthTexture_; + Ptr depthStencilTexture_; + + // 是否为内置纹理(需要自动清理) + bool hasInternalTextures_ = false; + + /** + * @brief 检查并更新完整状态 + */ + bool checkStatus(); + + /** + * @brief 获取 OpenGL 附件枚举 + */ + static GLenum getColorAttachment(int index); +}; + +} // namespace extra2d diff --git a/Extra2D/include/extra2d/graphics/backends/opengl/gl_pipeline.h b/Extra2D/include/extra2d/graphics/backends/opengl/gl_pipeline.h new file mode 100644 index 0000000..ca0f439 --- /dev/null +++ b/Extra2D/include/extra2d/graphics/backends/opengl/gl_pipeline.h @@ -0,0 +1,131 @@ +#pragma once + +#include +#include +#include + +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 diff --git a/Extra2D/include/extra2d/graphics/opengl/gl_renderer.h b/Extra2D/include/extra2d/graphics/backends/opengl/gl_renderer.h similarity index 63% rename from Extra2D/include/extra2d/graphics/opengl/gl_renderer.h rename to Extra2D/include/extra2d/graphics/backends/opengl/gl_renderer.h index 4c369ea..5b70fb5 100644 --- a/Extra2D/include/extra2d/graphics/opengl/gl_renderer.h +++ b/Extra2D/include/extra2d/graphics/backends/opengl/gl_renderer.h @@ -1,6 +1,9 @@ #pragma once -#include +#include +#include +#include +#include #include #include @@ -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 createFramebuffer(const FramebufferDesc& desc); + + /** + * @brief 绑定帧缓冲(作为渲染目标) + * @param framebuffer 帧缓冲对象指针,传入 nullptr 则绑定默认帧缓冲 + */ + void bindFramebuffer(GLFramebuffer* framebuffer); + + /** + * @brief 解绑帧缓冲(恢复到默认帧缓冲) + */ + void unbindFramebuffer(); + + /** + * @brief 获取默认帧缓冲 + * @return 默认帧缓冲智能指针 + */ + Ptr 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 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 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 pendingSprites_; // 待提交的精灵 + static constexpr size_t MAX_BATCH_SPRITES = 1000; // 最大批处理精灵数 + + // 帧缓冲管理 + mutable Ptr defaultFramebuffer_; // 默认帧缓冲(延迟创建) + GLFramebuffer* currentFramebuffer_ = nullptr; // 当前绑定的帧缓冲 void initShapeRendering(); + void ensureBatchActive(); // 确保批处理已激活 + void submitPendingSprites(); // 提交待处理的精灵 void flushShapeBatch(); void flushLineBatch(); void addShapeVertex(float x, float y, const Color &color); diff --git a/Extra2D/include/extra2d/graphics/opengl/gl_shader.h b/Extra2D/include/extra2d/graphics/backends/opengl/gl_shader.h similarity index 100% rename from Extra2D/include/extra2d/graphics/opengl/gl_shader.h rename to Extra2D/include/extra2d/graphics/backends/opengl/gl_shader.h diff --git a/Extra2D/include/extra2d/graphics/backends/opengl/gl_sprite_batch.h b/Extra2D/include/extra2d/graphics/backends/opengl/gl_sprite_batch.h new file mode 100644 index 0000000..fcfad7e --- /dev/null +++ b/Extra2D/include/extra2d/graphics/backends/opengl/gl_sprite_batch.h @@ -0,0 +1,68 @@ +#pragma once + +#include +#include +#include +#include + +#include +#include +#include + +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& 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 batches_; + const GLTexture* currentTexture_; + + // 着色器和矩阵 + Ptr shader_; + uint32_t drawCallCount_; + glm::mat4 viewProjection_; + + // 内部方法 + void flush(); + void submitBatch(); +}; + +} // namespace extra2d diff --git a/Extra2D/include/extra2d/graphics/opengl/gl_texture.h b/Extra2D/include/extra2d/graphics/backends/opengl/gl_texture.h similarity index 100% rename from Extra2D/include/extra2d/graphics/opengl/gl_texture.h rename to Extra2D/include/extra2d/graphics/backends/opengl/gl_texture.h diff --git a/Extra2D/include/extra2d/graphics/backends/vulkan/vk_renderer.h b/Extra2D/include/extra2d/graphics/backends/vulkan/vk_renderer.h new file mode 100644 index 0000000..608dd8d --- /dev/null +++ b/Extra2D/include/extra2d/graphics/backends/vulkan/vk_renderer.h @@ -0,0 +1,78 @@ +#pragma once + +#include + +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 createTexture(int width, int height, const uint8_t *pixels, + int channels) override; + Ptr loadTexture(const std::string &filepath) override; + + void beginSpriteBatch() override; + void drawSprite(const Texture &texture, const Rect &destRect, + const Rect &srcRect, const Color &tint, float rotation, + const Vec2 &anchor) override; + void drawSprite(const Texture &texture, const Vec2 &position, + const Color &tint) override; + void endSpriteBatch() override; + + void drawLine(const Vec2 &start, const Vec2 &end, const Color &color, + float width) override; + void drawRect(const Rect &rect, const Color &color, float width) override; + void fillRect(const Rect &rect, const Color &color) override; + void drawCircle(const Vec2 ¢er, float radius, const Color &color, + int segments, float width) override; + void fillCircle(const Vec2 ¢er, float radius, const Color &color, + int segments) override; + void drawTriangle(const Vec2 &p1, const Vec2 &p2, const Vec2 &p3, + const Color &color, float width) override; + void fillTriangle(const Vec2 &p1, const Vec2 &p2, const Vec2 &p3, + const Color &color) override; + void drawPolygon(const std::vector &points, const Color &color, + float width) override; + void fillPolygon(const std::vector &points, + const Color &color) override; + + Ptr 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 diff --git a/Extra2D/include/extra2d/graphics/batch/shape_batch.h b/Extra2D/include/extra2d/graphics/batch/shape_batch.h new file mode 100644 index 0000000..091f16b --- /dev/null +++ b/Extra2D/include/extra2d/graphics/batch/shape_batch.h @@ -0,0 +1,157 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +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& points, + const Color& color, float width = 1.0f) = 0; + + /** + * @brief 填充多边形 + * @param points 顶点数组 + * @param color 颜色 + */ + virtual void fillPolygon(const std::vector& 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 diff --git a/Extra2D/include/extra2d/graphics/batch/sprite_batch.h b/Extra2D/include/extra2d/graphics/batch/sprite_batch.h new file mode 100644 index 0000000..838f286 --- /dev/null +++ b/Extra2D/include/extra2d/graphics/batch/sprite_batch.h @@ -0,0 +1,121 @@ +#pragma once + +#include +#include +#include +#include + +#include + +#include +#include +#include + +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 sinTable_; + std::array 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& sprites); + + // 立即绘制 - 不缓存,直接提交 + void drawImmediate(const SpriteData& sprite); + + // 获取当前批次数据 + const std::vector& getVertices() const { return vertices_; } + const std::vector& getIndices() const { return indices_; } + size_t getSpriteCount() const { return spriteCount_; } + + // 检查是否需要刷新 + bool needsFlush() const { return spriteCount_ >= MAX_SPRITES; } + + // 清空批次 + void clear(); + +private: + // 三角函数查表 + TrigLookup trigLookup_; + + // 顶点数据 - 使用固定大小数组避免动态分配 + std::vector vertices_; + std::vector 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 diff --git a/Extra2D/include/extra2d/graphics/core/render_backend.h b/Extra2D/include/extra2d/graphics/core/render_backend.h index 895f439..98369e5 100644 --- a/Extra2D/include/extra2d/graphics/core/render_backend.h +++ b/Extra2D/include/extra2d/graphics/core/render_backend.h @@ -3,6 +3,7 @@ #include #include #include +#include #include 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; + // ------------------------------------------------------------------------ // 形状渲染 // ------------------------------------------------------------------------ diff --git a/Extra2D/include/extra2d/graphics/core/render_module.h b/Extra2D/include/extra2d/graphics/core/render_module.h index bb848bf..cee6bdd 100644 --- a/Extra2D/include/extra2d/graphics/core/render_module.h +++ b/Extra2D/include/extra2d/graphics/core/render_module.h @@ -3,10 +3,30 @@ #include #include #include +#include #include 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 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 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 renderer_; bool initialized_ = false; }; diff --git a/Extra2D/include/extra2d/graphics/core/render_target.h b/Extra2D/include/extra2d/graphics/core/render_target.h index 47433a5..c7dca31 100644 --- a/Extra2D/include/extra2d/graphics/core/render_target.h +++ b/Extra2D/include/extra2d/graphics/core/render_target.h @@ -2,7 +2,7 @@ #include #include -#include +#include #include #include diff --git a/Extra2D/include/extra2d/graphics/opengl/gl_font_atlas.h b/Extra2D/include/extra2d/graphics/opengl/gl_font_atlas.h deleted file mode 100644 index 5b524aa..0000000 --- a/Extra2D/include/extra2d/graphics/opengl/gl_font_atlas.h +++ /dev/null @@ -1,67 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -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 texture_; - mutable std::unordered_map glyphs_; - - // stb_rect_pack 上下文 - mutable stbrp_context packContext_; - mutable std::vector packNodes_; - mutable int currentY_; - - std::vector fontData_; - stbtt_fontinfo fontInfo_; - float scale_; - float ascent_; - float descent_; - float lineGap_; - - // 预分配字形位图缓冲区,避免每次动态分配 - mutable std::vector glyphBitmapCache_; - mutable std::vector glyphRgbaCache_; - - void createAtlas(); - void cacheGlyph(char32_t codepoint) const; -}; - -} // namespace extra2d diff --git a/Extra2D/include/extra2d/graphics/opengl/gl_sprite_batch.h b/Extra2D/include/extra2d/graphics/opengl/gl_sprite_batch.h deleted file mode 100644 index 1609a2f..0000000 --- a/Extra2D/include/extra2d/graphics/opengl/gl_sprite_batch.h +++ /dev/null @@ -1,98 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include - -#include - -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 &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 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 diff --git a/Extra2D/include/extra2d/graphics/resources/buffer.h b/Extra2D/include/extra2d/graphics/resources/buffer.h new file mode 100644 index 0000000..8e3ab29 --- /dev/null +++ b/Extra2D/include/extra2d/graphics/resources/buffer.h @@ -0,0 +1,111 @@ +#pragma once + +#include +#include +#include + +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 diff --git a/Extra2D/include/extra2d/graphics/resources/font_atlas.h b/Extra2D/include/extra2d/graphics/resources/font_atlas.h new file mode 100644 index 0000000..008e2f2 --- /dev/null +++ b/Extra2D/include/extra2d/graphics/resources/font_atlas.h @@ -0,0 +1,131 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +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 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 diff --git a/Extra2D/include/extra2d/graphics/resources/framebuffer.h b/Extra2D/include/extra2d/graphics/resources/framebuffer.h new file mode 100644 index 0000000..5f715e2 --- /dev/null +++ b/Extra2D/include/extra2d/graphics/resources/framebuffer.h @@ -0,0 +1,140 @@ +#pragma once + +#include +#include +#include +#include + +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, int attachment = 0) = 0; + + /** + * @brief 附加深度纹理 + * @param texture 纹理对象 + */ + virtual void attachDepthTexture(Ptr texture) = 0; + + /** + * @brief 附加深度模板纹理 + * @param texture 纹理对象 + */ + virtual void attachDepthStencilTexture(Ptr texture) = 0; + + /** + * @brief 检查帧缓冲是否完整 + * @return 完整返回 true + */ + virtual bool isComplete() = 0; + + /** + * @brief 获取颜色附件纹理 + * @param attachment 附件索引 + * @return 纹理对象 + */ + virtual Ptr getColorTexture(int attachment = 0) const = 0; + + /** + * @brief 获取深度附件纹理 + * @return 纹理对象 + */ + virtual Ptr 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& outData) = 0; +}; + +} // namespace extra2d diff --git a/Extra2D/include/extra2d/graphics/resources/pipeline.h b/Extra2D/include/extra2d/graphics/resources/pipeline.h new file mode 100644 index 0000000..996499c --- /dev/null +++ b/Extra2D/include/extra2d/graphics/resources/pipeline.h @@ -0,0 +1,162 @@ +#pragma once + +#include +#include +#include + +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 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 diff --git a/Extra2D/include/extra2d/graphics/resources/shader.h b/Extra2D/include/extra2d/graphics/resources/shader.h new file mode 100644 index 0000000..64f38a8 --- /dev/null +++ b/Extra2D/include/extra2d/graphics/resources/shader.h @@ -0,0 +1,134 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +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 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 diff --git a/Extra2D/include/extra2d/graphics/shader/shader_hot_reloader.h b/Extra2D/include/extra2d/graphics/shader/shader_hot_reloader.h index d5ddebc..bd21d74 100644 --- a/Extra2D/include/extra2d/graphics/shader/shader_hot_reloader.h +++ b/Extra2D/include/extra2d/graphics/shader/shader_hot_reloader.h @@ -4,7 +4,6 @@ #include #include #include -#include #include #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; +using FileChangeCallback = std::function; // ============================================================================ // Shader热重载管理器 // ============================================================================ class ShaderHotReloader { public: - /** - * @brief 获取单例实例 - * @return 热重载管理器实例引用 - */ - static ShaderHotReloader& getInstance(); + /** + * @brief 获取单例实例 + * @return 热重载管理器实例引用 + */ + static ShaderHotReloader &getInstance(); - /** - * @brief 初始化热重载系统 - * @return 初始化成功返回true,失败返回false - */ - bool init(); + /** + * @brief 初始化热重载系统 + * @return 初始化成功返回true,失败返回false + */ + 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& filePaths, - FileChangeCallback callback); + /** + * @brief 注册Shader文件监视 + * @param shaderName Shader名称 + * @param filePaths 要监视的文件列表 + * @param callback 文件变化时的回调 + */ + void watch(const std::string &shaderName, + const std::vector &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 启用返回true,否则返回false - */ - bool isEnabled() const { return enabled_; } + /** + * @brief 检查是否启用 + * @return 启用返回true,否则返回false + */ + bool isEnabled() const { return enabled_; } - /** - * @brief 检查是否已初始化 - * @return 已初始化返回true,否则返回false - */ - bool isInitialized() const { return initialized_; } + /** + * @brief 检查是否已初始化 + * @return 已初始化返回true,否则返回false + */ + 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 filePaths; - FileChangeCallback callback; - std::unordered_map modifiedTimes; - }; - std::unordered_map watchMap_; + struct WatchInfo { + std::vector filePaths; + FileChangeCallback callback; + std::unordered_map modifiedTimes; + }; + std::unordered_map watchMap_; #ifdef _WIN32 - HANDLE watchHandle_ = nullptr; - std::vector buffer_; - std::string watchDir_; - bool watching_ = false; + HANDLE watchHandle_ = nullptr; + std::vector 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); }; // 便捷宏 diff --git a/Extra2D/include/extra2d/graphics/shader/shader_manager.h b/Extra2D/include/extra2d/graphics/shader/shader_manager.h index 3bbc72e..e4d6efa 100644 --- a/Extra2D/include/extra2d/graphics/shader/shader_manager.h +++ b/Extra2D/include/extra2d/graphics/shader/shader_manager.h @@ -1,6 +1,5 @@ #pragma once -#include #include #include #include @@ -178,6 +177,14 @@ public: */ bool loadBuiltinShaders(); + /** + * @brief 从JSON元数据文件加载Shader(多后端支持) + * @param jsonPath JSON元数据文件路径 + * @param name Shader名称 + * @return 加载的Shader实例 + */ + Ptr loadFromMetadata(const std::string& jsonPath, const std::string& name); + // ------------------------------------------------------------------------ // 工具方法 // ------------------------------------------------------------------------ diff --git a/Extra2D/include/extra2d/graphics/texture/texture_atlas.h b/Extra2D/include/extra2d/graphics/texture/texture_atlas.h index 975ef58..8fca067 100644 --- a/Extra2D/include/extra2d/graphics/texture/texture_atlas.h +++ b/Extra2D/include/extra2d/graphics/texture/texture_atlas.h @@ -4,7 +4,7 @@ #include #include #include -#include +#include #include #include #include diff --git a/Extra2D/include/extra2d/graphics/texture/texture_pool.h b/Extra2D/include/extra2d/graphics/texture/texture_pool.h index 61edfdc..1bebf67 100644 --- a/Extra2D/include/extra2d/graphics/texture/texture_pool.h +++ b/Extra2D/include/extra2d/graphics/texture/texture_pool.h @@ -5,7 +5,6 @@ #include #include -#include #include #include #include @@ -13,7 +12,6 @@ #include #include #include -#include 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{}(key.path); - size_t h2 = std::hash{}(key.region.origin.x); - size_t h3 = std::hash{}(key.region.origin.y); - size_t h4 = std::hash{}(key.region.size.width); - size_t h5 = std::hash{}(key.region.size.height); + /** + * @brief 计算 TextureKey 的哈希值 + * @param key 纹理键 + * @return 哈希值 + */ + size_t operator()(const TextureKey &key) const { + size_t h1 = std::hash{}(key.path); + size_t h2 = std::hash{}(key.region.origin.x); + size_t h3 = std::hash{}(key.region.origin.y); + size_t h4 = std::hash{}(key.region.size.width); + size_t h5 = std::hash{}(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; // 纹理对象 - mutable std::atomic refCount; // 引用计数 - TextureKey key; // 纹理键 - size_t memorySize; // 内存占用(字节) - mutable uint64_t lastAccessTime; // 最后访问时间戳 + Ptr texture; // 纹理对象 + mutable std::atomic 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 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 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( - now.time_since_epoch()); - return static_cast(duration.count()); - } + /** + * @brief 获取当前时间戳 + * @return 时间戳(毫秒) + */ + static uint64_t getCurrentTime() { + auto now = std::chrono::steady_clock::now(); + auto duration = std::chrono::duration_cast( + now.time_since_epoch()); + return static_cast(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, TexturePoolEntry* entry, std::mutex* mutex) - : texture_(texture), entry_(entry), mutex_(mutex) {} + /** + * @brief 构造函数 + * @param texture 纹理对象 + * @param entry 纹理池条目 + * @param mutex 互斥锁 + */ + TextureRef(Ptr texture, TexturePoolEntry *entry, std::mutex *mutex) + : texture_(texture), entry_(entry), mutex_(mutex) {} - /** - * @brief 创建独立的纹理引用(不管理引用计数) - * @param texture 纹理对象 - * @return 独立的纹理引用 - */ - static TextureRef fromTexture(Ptr texture) { - return TextureRef(texture, nullptr, nullptr); + /** + * @brief 创建独立的纹理引用(不管理引用计数) + * @param texture 纹理对象 + * @return 独立的纹理引用 + */ + static TextureRef fromTexture(Ptr 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 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 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 getPtr() const { return texture_; } - /** - * @brief 获取纹理对象 - * @return 纹理对象指针 - */ - Texture* get() const { return texture_.get(); } + /** + * @brief 检查是否有效 + * @return 是否有效 + */ + bool valid() const { return texture_ != nullptr; } - /** - * @brief 获取纹理对象(智能指针) - * @return 纹理对象智能指针 - */ - Ptr 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_; - TexturePoolEntry* entry_; - std::mutex* mutex_; + Ptr 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 ®ion, + 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 ®ion, + 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 cache_; // 纹理缓存 + Scene *scene_; // 场景指针 + mutable std::mutex mutex_; // 互斥锁 + std::unordered_map + cache_; // 纹理缓存 - size_t maxMemoryUsage_; // 最大内存使用量 - size_t currentMemoryUsage_; // 当前内存使用量 + size_t maxMemoryUsage_; // 最大内存使用量 + size_t currentMemoryUsage_; // 当前内存使用量 - // 统计信息 - mutable std::atomic cacheHits_; - mutable std::atomic cacheMisses_; - mutable std::atomic evictionCount_; + // 统计信息 + mutable std::atomic cacheHits_; + mutable std::atomic cacheMisses_; + mutable std::atomic evictionCount_; }; -} // namespace extra2d +} // namespace extra2d diff --git a/Extra2D/include/extra2d/platform/input_module.h b/Extra2D/include/extra2d/platform/input_module.h index cbba615..2cdbc4b 100644 --- a/Extra2D/include/extra2d/platform/input_module.h +++ b/Extra2D/include/extra2d/platform/input_module.h @@ -3,10 +3,30 @@ #include #include #include +#include #include 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 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 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; }; diff --git a/Extra2D/include/extra2d/platform/window_module.h b/Extra2D/include/extra2d/platform/window_module.h index ed5f69b..bb952bd 100644 --- a/Extra2D/include/extra2d/platform/window_module.h +++ b/Extra2D/include/extra2d/platform/window_module.h @@ -1,67 +1,64 @@ #pragma once #include -#include #include +#include +#include +#include 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 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 win_; - bool initialized_ = false; - bool sdlInited_ = false; + WindowCfg cfg_; + UniquePtr win_; + bool initialized_ = false; + bool sdlInited_ = false; }; } // namespace extra2d diff --git a/Extra2D/include/extra2d/scene/transition_box_scene.h b/Extra2D/include/extra2d/scene/transition_box_scene.h index de9ed25..c48b481 100644 --- a/Extra2D/include/extra2d/scene/transition_box_scene.h +++ b/Extra2D/include/extra2d/scene/transition_box_scene.h @@ -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_; diff --git a/Extra2D/include/extra2d/scene/transition_flip_scene.h b/Extra2D/include/extra2d/scene/transition_flip_scene.h index fa470ff..116529e 100644 --- a/Extra2D/include/extra2d/scene/transition_flip_scene.h +++ b/Extra2D/include/extra2d/scene/transition_flip_scene.h @@ -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_; diff --git a/Extra2D/include/extra2d/scene/transition_scale_scene.h b/Extra2D/include/extra2d/scene/transition_scale_scene.h index cad1948..59628a7 100644 --- a/Extra2D/include/extra2d/scene/transition_scale_scene.h +++ b/Extra2D/include/extra2d/scene/transition_scale_scene.h @@ -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 diff --git a/Extra2D/include/extra2d/scene/transition_slide_scene.h b/Extra2D/include/extra2d/scene/transition_slide_scene.h index 7775182..7457c64 100644 --- a/Extra2D/include/extra2d/scene/transition_slide_scene.h +++ b/Extra2D/include/extra2d/scene/transition_slide_scene.h @@ -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_; diff --git a/Extra2D/include/extra2d/services/camera_service.h b/Extra2D/include/extra2d/services/camera_service.h index 948a187..f2acf89 100644 --- a/Extra2D/include/extra2d/services/camera_service.h +++ b/Extra2D/include/extra2d/services/camera_service.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include @@ -106,6 +107,9 @@ public: private: Camera camera_; ViewportAdapter viewportAdapter_; + + // 服务注册元数据 + E2D_AUTO_REGISTER_SERVICE(ICameraService, CameraService); }; } diff --git a/Extra2D/include/extra2d/services/event_service.h b/Extra2D/include/extra2d/services/event_service.h index 7b91819..7235aff 100644 --- a/Extra2D/include/extra2d/services/event_service.h +++ b/Extra2D/include/extra2d/services/event_service.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include @@ -68,6 +69,9 @@ public: private: EventQueue queue_; EventDispatcher dispatcher_; + + // 服务注册元数据 + E2D_AUTO_REGISTER_SERVICE(IEventService, EventService); }; } diff --git a/Extra2D/include/extra2d/services/logger_service.h b/Extra2D/include/extra2d/services/logger_service.h index 12a1245..b0edcaa 100644 --- a/Extra2D/include/extra2d/services/logger_service.h +++ b/Extra2D/include/extra2d/services/logger_service.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include #include @@ -120,10 +121,13 @@ public: private: void output(LogLevel level, const char* msg); const char* getLevelString(LogLevel level); - + LogLevel level_; class Impl; UniquePtr impl_; + + // 服务注册元数据 + E2D_AUTO_REGISTER_SERVICE(ILogger, ConsoleLogger); }; } // namespace extra2d diff --git a/Extra2D/include/extra2d/services/scene_service.h b/Extra2D/include/extra2d/services/scene_service.h index 742893a..0f30608 100644 --- a/Extra2D/include/extra2d/services/scene_service.h +++ b/Extra2D/include/extra2d/services/scene_service.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include namespace extra2d { @@ -86,6 +87,9 @@ public: private: SceneManager manager_; + + // 服务注册元数据 + E2D_AUTO_REGISTER_SERVICE(ISceneService, SceneService); }; } diff --git a/Extra2D/include/extra2d/services/timer_service.h b/Extra2D/include/extra2d/services/timer_service.h index 65a9762..bf2d65d 100644 --- a/Extra2D/include/extra2d/services/timer_service.h +++ b/Extra2D/include/extra2d/services/timer_service.h @@ -1,6 +1,7 @@ #pragma once #include +#include #include namespace extra2d { @@ -48,6 +49,9 @@ public: private: TimerManager manager_; + + // 服务注册元数据 + E2D_AUTO_REGISTER_SERVICE(ITimerService, TimerService); }; } diff --git a/Extra2D/shaders/backends/opengl/builtin/shape.frag b/Extra2D/shaders/backends/opengl/builtin/shape.frag new file mode 100644 index 0000000..1986612 --- /dev/null +++ b/Extra2D/shaders/backends/opengl/builtin/shape.frag @@ -0,0 +1,10 @@ +#version 300 es +precision highp float; + +in vec4 v_color; + +out vec4 fragColor; + +void main() { + fragColor = v_color; +} diff --git a/Extra2D/shaders/backends/opengl/builtin/shape.vert b/Extra2D/shaders/backends/opengl/builtin/shape.vert new file mode 100644 index 0000000..b010cde --- /dev/null +++ b/Extra2D/shaders/backends/opengl/builtin/shape.vert @@ -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; +} diff --git a/Extra2D/shaders/backends/opengl/builtin/sprite.frag b/Extra2D/shaders/backends/opengl/builtin/sprite.frag new file mode 100644 index 0000000..0767df0 --- /dev/null +++ b/Extra2D/shaders/backends/opengl/builtin/sprite.frag @@ -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; + } +} diff --git a/Extra2D/shaders/backends/opengl/builtin/sprite.vert b/Extra2D/shaders/backends/opengl/builtin/sprite.vert new file mode 100644 index 0000000..f7a9adb --- /dev/null +++ b/Extra2D/shaders/backends/opengl/builtin/sprite.vert @@ -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; +} diff --git a/Extra2D/shaders/common/color.glsl b/Extra2D/shaders/backends/opengl/common/color.glsl similarity index 100% rename from Extra2D/shaders/common/color.glsl rename to Extra2D/shaders/backends/opengl/common/color.glsl diff --git a/Extra2D/shaders/common/math.glsl b/Extra2D/shaders/backends/opengl/common/math.glsl similarity index 100% rename from Extra2D/shaders/common/math.glsl rename to Extra2D/shaders/backends/opengl/common/math.glsl diff --git a/Extra2D/shaders/builtin/font.shader b/Extra2D/shaders/builtin/font.shader deleted file mode 100644 index c1bc8ec..0000000 --- a/Extra2D/shaders/builtin/font.shader +++ /dev/null @@ -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; - } -} diff --git a/Extra2D/shaders/builtin/particle.shader b/Extra2D/shaders/builtin/particle.shader deleted file mode 100644 index 4b1c1d4..0000000 --- a/Extra2D/shaders/builtin/particle.shader +++ /dev/null @@ -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; - } -} diff --git a/Extra2D/shaders/builtin/postprocess.shader b/Extra2D/shaders/builtin/postprocess.shader deleted file mode 100644 index 619f997..0000000 --- a/Extra2D/shaders/builtin/postprocess.shader +++ /dev/null @@ -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); -} diff --git a/Extra2D/shaders/builtin/shape.shader b/Extra2D/shaders/builtin/shape.shader deleted file mode 100644 index 48809f2..0000000 --- a/Extra2D/shaders/builtin/shape.shader +++ /dev/null @@ -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; -} diff --git a/Extra2D/shaders/builtin/sprite.shader b/Extra2D/shaders/builtin/sprite.shader deleted file mode 100644 index ec30971..0000000 --- a/Extra2D/shaders/builtin/sprite.shader +++ /dev/null @@ -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; - } -} diff --git a/Extra2D/shaders/effects/blur.shader b/Extra2D/shaders/effects/blur.shader deleted file mode 100644 index 7f6eec5..0000000 --- a/Extra2D/shaders/effects/blur.shader +++ /dev/null @@ -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; - } -} diff --git a/Extra2D/shaders/effects/distortion.shader b/Extra2D/shaders/effects/distortion.shader deleted file mode 100644 index 1d583d4..0000000 --- a/Extra2D/shaders/effects/distortion.shader +++ /dev/null @@ -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; - } -} diff --git a/Extra2D/shaders/effects/grayscale.shader b/Extra2D/shaders/effects/grayscale.shader deleted file mode 100644 index 70748fb..0000000 --- a/Extra2D/shaders/effects/grayscale.shader +++ /dev/null @@ -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; - } -} diff --git a/Extra2D/shaders/effects/grayscale_outline.shader b/Extra2D/shaders/effects/grayscale_outline.shader deleted file mode 100644 index a5b0f91..0000000 --- a/Extra2D/shaders/effects/grayscale_outline.shader +++ /dev/null @@ -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; - } -} diff --git a/Extra2D/shaders/effects/invert.shader b/Extra2D/shaders/effects/invert.shader deleted file mode 100644 index 9e8cdfc..0000000 --- a/Extra2D/shaders/effects/invert.shader +++ /dev/null @@ -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; - } -} diff --git a/Extra2D/shaders/effects/outline.shader b/Extra2D/shaders/effects/outline.shader deleted file mode 100644 index 6969a8b..0000000 --- a/Extra2D/shaders/effects/outline.shader +++ /dev/null @@ -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; - } -} diff --git a/Extra2D/shaders/effects/pixelate.shader b/Extra2D/shaders/effects/pixelate.shader deleted file mode 100644 index 17eb372..0000000 --- a/Extra2D/shaders/effects/pixelate.shader +++ /dev/null @@ -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; - } -} diff --git a/Extra2D/shaders/effects/pixelate_invert.shader b/Extra2D/shaders/effects/pixelate_invert.shader deleted file mode 100644 index f19e31c..0000000 --- a/Extra2D/shaders/effects/pixelate_invert.shader +++ /dev/null @@ -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; - } -} diff --git a/Extra2D/shaders/effects/water.shader b/Extra2D/shaders/effects/water.shader deleted file mode 100644 index e729afc..0000000 --- a/Extra2D/shaders/effects/water.shader +++ /dev/null @@ -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; - } -} diff --git a/Extra2D/shaders/shared/builtin/shape.json b/Extra2D/shaders/shared/builtin/shape.json new file mode 100644 index 0000000..74ae28e --- /dev/null +++ b/Extra2D/shaders/shared/builtin/shape.json @@ -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" + } + } +} diff --git a/Extra2D/shaders/shared/builtin/sprite.json b/Extra2D/shaders/shared/builtin/sprite.json new file mode 100644 index 0000000..4cc2616 --- /dev/null +++ b/Extra2D/shaders/shared/builtin/sprite.json @@ -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" + } + } +} diff --git a/Extra2D/src/app/application.cpp b/Extra2D/src/app/application.cpp index b2a5bb8..7b199d8 100644 --- a/Extra2D/src/app/application.cpp +++ b/Extra2D/src/app/application.cpp @@ -1,268 +1,248 @@ #include #include -#include -#include -#include -#include -#include #include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include #include -#include -#include -#include -#include -#include -#include namespace extra2d { static double getTimeSeconds() { #ifdef __SWITCH__ - struct timespec ts; - clock_gettime(CLOCK_MONOTONIC, &ts); - return static_cast(ts.tv_sec) + - static_cast(ts.tv_nsec) / 1000000000.0; + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return static_cast(ts.tv_sec) + + static_cast(ts.tv_nsec) / 1000000000.0; #else - using namespace std::chrono; - auto now = steady_clock::now(); - auto duration = now.time_since_epoch(); - return duration_cast>(duration).count(); + using namespace std::chrono; + auto now = steady_clock::now(); + auto duration = now.time_since_epoch(); + return duration_cast>(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()) { - auto logger = makeShared(); - locator.registerService(std::static_pointer_cast(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()) { - auto service = makeShared(); - locator.registerService(std::static_pointer_cast(service)); - } - - if (!locator.hasService()) { - auto service = makeShared(); - locator.registerService(std::static_pointer_cast(service)); - } - - if (!locator.hasService()) { - auto service = makeShared(); - locator.registerService(std::static_pointer_cast(service)); - } - - auto* winMod = get(); - if (winMod && winMod->win() && !locator.hasService()) { - auto cameraService = makeShared(); - auto* win = winMod->win(); - cameraService->setViewport(0, static_cast(win->width()), - static_cast(win->height()), 0); - ViewportConfig vpConfig; - vpConfig.logicWidth = static_cast(win->width()); - vpConfig.logicHeight = static_cast(win->height()); - vpConfig.mode = ViewportMode::AspectRatio; - cameraService->setViewportConfig(vpConfig); - cameraService->updateViewport(win->width(), win->height()); - locator.registerService(std::static_pointer_cast(cameraService)); - - win->onResize([cameraService](int width, int height) { - cameraService->updateViewport(width, height); - cameraService->applyViewportAdapter(); - }); - } - - locator.initializeAll(); +void Application::configureCameraService() { + auto *winMod = get(); + if (!winMod || !winMod->win()) { + return; + } + + auto cameraService = ServiceLocator::instance().getService(); + if (!cameraService) { + return; + } + + auto *win = winMod->win(); + cameraService->setViewport(0, static_cast(win->width()), + static_cast(win->height()), 0); + + ViewportConfig vpConfig; + vpConfig.logicWidth = static_cast(win->width()); + vpConfig.logicHeight = static_cast(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(); - if (!winMod || !winMod->win()) return; - - lastFrameTime_ = getTimeSeconds(); - - while (running_ && !winMod->win()->shouldClose()) { - mainLoop(); - } + if (!initialized_) + return; + + auto *winMod = get(); + 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(currentTime - lastFrameTime_); - lastFrameTime_ = currentTime; - - totalTime_ += deltaTime_; - - frameCount_++; - fpsTimer_ += deltaTime_; - if (fpsTimer_ >= 1.0f) { - currentFps_ = frameCount_; - frameCount_ = 0; - fpsTimer_ -= 1.0f; - } - - auto* winMod = get(); - if (winMod && winMod->win()) { - winMod->win()->poll(); - } - - auto eventService = ServiceLocator::instance().getService(); - if (eventService) { - eventService->processQueue(); - } - - if (!paused_) { - update(); - } - - render(); - - // 帧率限制 - auto* renderMod = get(); - if (renderMod && renderMod->renderer()) { - // 这里可以添加帧率限制逻辑 - } + double currentTime = getTimeSeconds(); + deltaTime_ = static_cast(currentTime - lastFrameTime_); + lastFrameTime_ = currentTime; + + totalTime_ += deltaTime_; + + frameCount_++; + fpsTimer_ += deltaTime_; + if (fpsTimer_ >= 1.0f) { + currentFps_ = frameCount_; + frameCount_ = 0; + fpsTimer_ -= 1.0f; + } + + auto *winMod = get(); + if (winMod && winMod->win()) { + winMod->win()->poll(); + } + + auto eventService = ServiceLocator::instance().getService(); + if (eventService) { + eventService->processQueue(); + } + + if (!paused_) { + update(); + } + + render(); + + // 帧率限制 + auto *renderMod = get(); + if (renderMod && renderMod->renderer()) { + // 这里可以添加帧率限制逻辑 + } } void Application::update() { - ServiceLocator::instance().updateAll(deltaTime_); - - auto* inputMod = get(); - if (inputMod) { - inputMod->update(); - } + ServiceLocator::instance().updateAll(deltaTime_); + + auto *inputMod = get(); + if (inputMod) { + inputMod->update(); + } } void Application::render() { - auto* renderMod = get(); - if (!renderMod || !renderMod->renderer()) return; - - auto* renderer = renderMod->renderer(); - auto* winMod = get(); - if (!winMod || !winMod->win()) return; - - auto cameraService = ServiceLocator::instance().getService(); - if (cameraService) { - const auto& vp = cameraService->getViewportResult().viewport; - renderer->setViewport( - static_cast(vp.origin.x), static_cast(vp.origin.y), - static_cast(vp.size.width), static_cast(vp.size.height)); - renderer->setViewProjection(cameraService->getViewProjectionMatrix()); - } else { - renderer->setViewport(0, 0, winMod->win()->width(), winMod->win()->height()); - } - - auto sceneService = ServiceLocator::instance().getService(); - if (sceneService) { - sceneService->render(*renderer); - } - - winMod->win()->swap(); + auto *renderMod = get(); + if (!renderMod || !renderMod->renderer()) + return; + + auto *renderer = renderMod->renderer(); + auto *winMod = get(); + if (!winMod || !winMod->win()) + return; + + auto cameraService = ServiceLocator::instance().getService(); + if (cameraService) { + const auto &vp = cameraService->getViewportResult().viewport; + renderer->setViewport( + static_cast(vp.origin.x), static_cast(vp.origin.y), + static_cast(vp.size.width), static_cast(vp.size.height)); + renderer->setViewProjection(cameraService->getViewProjectionMatrix()); + } else { + renderer->setViewport(0, 0, winMod->win()->width(), + winMod->win()->height()); + } + + auto sceneService = ServiceLocator::instance().getService(); + if (sceneService) { + sceneService->render(*renderer); + } + + winMod->win()->swap(); } -IWindow* Application::window() { - auto* winMod = get(); - return winMod ? winMod->win() : nullptr; +IWindow *Application::window() { + auto *winMod = get(); + return winMod ? winMod->win() : nullptr; } -RenderBackend* Application::renderer() { - auto* renderMod = get(); - return renderMod ? renderMod->renderer() : nullptr; +RenderBackend *Application::renderer() { + auto *renderMod = get(); + return renderMod ? renderMod->renderer() : nullptr; } -IInput* Application::input() { - auto* winMod = get(); - return (winMod && winMod->win()) ? winMod->win()->input() : nullptr; +IInput *Application::input() { + auto *winMod = get(); + return (winMod && winMod->win()) ? winMod->win()->input() : nullptr; } void Application::enterScene(Ptr scene) { - auto sceneService = ServiceLocator::instance().getService(); - auto* winMod = get(); - if (sceneService && scene && winMod && winMod->win()) { - scene->setViewportSize(static_cast(winMod->win()->width()), - static_cast(winMod->win()->height())); - sceneService->enterScene(scene); - } + auto sceneService = ServiceLocator::instance().getService(); + auto *winMod = get(); + if (sceneService && scene && winMod && winMod->win()) { + scene->setViewportSize(static_cast(winMod->win()->width()), + static_cast(winMod->win()->height())); + sceneService->enterScene(scene); + } } } // namespace extra2d diff --git a/Extra2D/src/config/app_config.cpp b/Extra2D/src/config/app_config.cpp deleted file mode 100644 index 9531559..0000000 --- a/Extra2D/src/config/app_config.cpp +++ /dev/null @@ -1,60 +0,0 @@ -#include -#include - -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"); -} - -} diff --git a/Extra2D/src/config/platform_config.cpp b/Extra2D/src/config/platform_config.cpp deleted file mode 100644 index 85cac6e..0000000 --- a/Extra2D/src/config/platform_config.cpp +++ /dev/null @@ -1,224 +0,0 @@ -#include -#include -#include - -#ifdef _WIN32 -#include -#endif - -#ifdef __SWITCH__ -#include -#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 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(); - case PlatformType::Switch: - E2D_LOG_INFO("Creating Nintendo Switch platform config"); - return makeUnique(); - case PlatformType::Linux: - E2D_LOG_INFO("Creating Linux platform config"); - return makeUnique(); - case PlatformType::macOS: - E2D_LOG_INFO("Creating macOS platform config"); - return makeUnique(); - default: - E2D_LOG_WARN("Unknown platform type, defaulting to Windows"); - return makeUnique(); - } -} - -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"; - } -} - -} diff --git a/Extra2D/src/config/platform_detector.cpp b/Extra2D/src/config/platform_detector.cpp deleted file mode 100644 index 32d1ddb..0000000 --- a/Extra2D/src/config/platform_detector.cpp +++ /dev/null @@ -1,678 +0,0 @@ -#include -#include -#include - -#ifdef _WIN32 -#include -#include -#include -#elif defined(__linux__) -#include -#include -#include -#include -#elif defined(__APPLE__) -#include -#include -#include -#include -#endif - -#ifdef __SWITCH__ -#include -#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(status.ullTotalPhys / (1024 * 1024)); - } - return 0; -#elif defined(__SWITCH__) - return 4096; -#elif defined(__linux__) - struct sysinfo info; - if (sysinfo(&info) == 0) { - return static_cast(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(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(sysinfo.dwNumberOfProcessors); -#elif defined(__SWITCH__) - return 4; -#elif defined(__linux__) || defined(__APPLE__) - long cores = sysconf(_SC_NPROCESSORS_ONLN); - return static_cast(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(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; -} - -} diff --git a/Extra2D/src/graphics/backends/backend_factory.cpp b/Extra2D/src/graphics/backends/backend_factory.cpp new file mode 100644 index 0000000..27b66c5 --- /dev/null +++ b/Extra2D/src/graphics/backends/backend_factory.cpp @@ -0,0 +1,127 @@ +#include + +// 条件编译包含对应后端实现 +#ifdef E2D_BACKEND_OPENGL +#include +#endif + +#ifdef E2D_BACKEND_VULKAN +#include +#endif + +#include +#include + +namespace extra2d { + +BackendFactory& BackendFactory::getInstance() { + static BackendFactory instance; + return instance; +} + +UniquePtr BackendFactory::createBackend(BackendType type) { + switch (type) { +#ifdef E2D_BACKEND_OPENGL + case BackendType::OpenGL: + E2D_LOG_INFO("Creating OpenGL render backend"); + return makeUnique(); +#endif + +#ifdef E2D_BACKEND_VULKAN + case BackendType::Vulkan: + E2D_LOG_INFO("Creating Vulkan render backend"); + return makeUnique(); +#endif + + default: + E2D_LOG_ERROR("Unsupported render backend type: {}", static_cast(type)); + return nullptr; + } +} + +UniquePtr 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 diff --git a/Extra2D/src/graphics/backends/opengl/gl_buffer.cpp b/Extra2D/src/graphics/backends/opengl/gl_buffer.cpp new file mode 100644 index 0000000..153010a --- /dev/null +++ b/Extra2D/src/graphics/backends/opengl/gl_buffer.cpp @@ -0,0 +1,171 @@ +#include +#include +#include +#include + +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(size_), desc.initialData, glUsage_); + glBindBuffer(target_, 0); + + // 追踪显存使用 + VRAMMgr::get().allocBuffer(size_); + + E2D_LOG_DEBUG("GLBuffer created: ID={}, Size={}, Type={}, Usage={}", + bufferID_, size_, static_cast(type_), static_cast(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(size), data); + } else { + // 大小不同,重新分配 + size_ = size; + glBufferData(target_, static_cast(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(offset), static_cast(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(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 diff --git a/Extra2D/src/graphics/backends/opengl/gl_context.cpp b/Extra2D/src/graphics/backends/opengl/gl_context.cpp new file mode 100644 index 0000000..f200581 --- /dev/null +++ b/Extra2D/src/graphics/backends/opengl/gl_context.cpp @@ -0,0 +1,167 @@ +#include +#include +#include +#include +#include + +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(glGetString(GL_VERSION)); + return version ? version : "Unknown"; +} + +std::string GLContext::getVendor() const { + const char* vendor = reinterpret_cast(glGetString(GL_VENDOR)); + return vendor ? vendor : "Unknown"; +} + +std::string GLContext::getRenderer() const { + const char* renderer = reinterpret_cast(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(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(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 diff --git a/Extra2D/src/graphics/opengl/gl_font_atlas.cpp b/Extra2D/src/graphics/backends/opengl/gl_font_atlas.cpp similarity index 51% rename from Extra2D/src/graphics/opengl/gl_font_atlas.cpp rename to Extra2D/src/graphics/backends/opengl/gl_font_atlas.cpp index d279f55..a578afe 100644 --- a/Extra2D/src/graphics/opengl/gl_font_atlas.cpp +++ b/Extra2D/src/graphics/backends/opengl/gl_font_atlas.cpp @@ -1,105 +1,151 @@ -#include +#include #include + +#include +#include +#include #include + +// 在实现文件中定义 STB 实现 #define STB_TRUETYPE_IMPLEMENTATION #include -#define STB_RECT_PACK_IMPLEMENTATION -#include -#include +#define STB_RECT_PACK_IMPLEMENTATION +#include 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(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(fontSize_)); int ascent, descent, lineGap; stbtt_GetFontVMetrics(&fontInfo_, &ascent, &descent, &lineGap); + ascent_ = static_cast(ascent) * scale_; descent_ = static_cast(descent) * scale_; lineGap_ = static_cast(lineGap) * scale_; + lineHeight_ = ascent_ - descent_ + lineGap_; + // 创建图集纹理和打包上下文 createAtlas(); + + // 预加载常用 ASCII 字符 + std::string asciiChars = " !\"#$%&'()*+,-./" + "0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`" + "abcdefghijklmnopqrstuvwxyz{|}~"; + for (char c : asciiChars) { + char32_t codepoint = static_cast(static_cast(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(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(static_cast(c)); + for (size_t i = 0; i < text.length();) { + // 处理 UTF-8 编码 + char32_t codepoint = 0; + unsigned char c = static_cast(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(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(text[i + 1]) & 0x3F) << 6) | + (static_cast(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(text[i + 1]) & 0x3F) << 12) | + ((static_cast(text[i + 2]) & 0x3F) << 6) | + (static_cast(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)); + if (!file.read(reinterpret_cast(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 emptyData(ATLAS_WIDTH * ATLAS_HEIGHT * channels, 0); texture_ = std::make_unique(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(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(codepoint), &advance, + &leftSideBearing); + float advancePx = static_cast(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(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(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(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(w); - glyph.height = static_cast(h); - glyph.bearingX = static_cast(xoff); - glyph.bearingY = static_cast(yoff); - glyph.advance = advancePx; + // 创建字形数据 + GlyphData data; + data.width = static_cast(w); + data.height = static_cast(h); + data.bearingX = static_cast(xoff); + data.bearingY = static_cast(yoff); + data.advance = advancePx; + // 计算 UV 坐标 // stb_rect_pack 使用左上角为原点,OpenGL纹理使用左下角为原点 // 需要翻转V坐标 float v0 = static_cast(atlasY) / ATLAS_HEIGHT; float v1 = static_cast(atlasY + h) / ATLAS_HEIGHT; - glyph.u0 = static_cast(atlasX) / ATLAS_WIDTH; - glyph.v0 = 1.0f - v1; // 翻转V坐标 - glyph.u1 = static_cast(atlasX + w) / ATLAS_WIDTH; - glyph.v1 = 1.0f - v0; // 翻转V坐标 + data.u0 = static_cast(atlasX) / ATLAS_WIDTH; + data.v0 = 1.0f - v1; // 翻转V坐标 + data.u1 = static_cast(atlasX + w) / ATLAS_WIDTH; + data.v1 = 1.0f - v0; // 翻转V坐标 - glyphs_[codepoint] = glyph; + glyphs_[codepoint] = data; // 将 SDF 单通道数据转换为 RGBA 格式(统一格式) size_t pixelCount = static_cast(w) * static_cast(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(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(w) * static_cast(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(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(w); - glyph.height = static_cast(h); - glyph.bearingX = static_cast(xoff); - glyph.bearingY = static_cast(yoff); - glyph.advance = advancePx; + // 创建字形数据 + GlyphData data; + data.width = static_cast(w); + data.height = static_cast(h); + data.bearingX = static_cast(xoff); + data.bearingY = static_cast(yoff); + data.advance = advancePx; - // 计算纹理坐标(相对于图集) + // 计算 UV 坐标 // stb_rect_pack 使用左上角为原点,OpenGL纹理使用左下角为原点 // 需要翻转V坐标 float v0 = static_cast(atlasY) / ATLAS_HEIGHT; float v1 = static_cast(atlasY + h) / ATLAS_HEIGHT; - glyph.u0 = static_cast(atlasX) / ATLAS_WIDTH; - glyph.v0 = 1.0f - v1; // 翻转V坐标 - glyph.u1 = static_cast(atlasX + w) / ATLAS_WIDTH; - glyph.v1 = 1.0f - v0; // 翻转V坐标 + data.u0 = static_cast(atlasX) / ATLAS_WIDTH; + data.v0 = 1.0f - v1; // 翻转V坐标 + data.u1 = static_cast(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 &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 diff --git a/Extra2D/src/graphics/backends/opengl/gl_framebuffer.cpp b/Extra2D/src/graphics/backends/opengl/gl_framebuffer.cpp new file mode 100644 index 0000000..fdf44c8 --- /dev/null +++ b/Extra2D/src/graphics/backends/opengl/gl_framebuffer.cpp @@ -0,0 +1,268 @@ +#include +#include +#include + +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, int attachment) { + if (fboID_ == 0 || !texture || attachment < 0 || attachment >= MAX_COLOR_ATTACHMENTS) { + return; + } + + bind(); + + // 获取 OpenGL 纹理 ID + GLuint texID = static_cast(reinterpret_cast(texture->getNativeHandle())); + + glFramebufferTexture2D(GL_FRAMEBUFFER, getColorAttachment(attachment), + GL_TEXTURE_2D, texID, 0); + + colorTextures_[attachment] = texture; + + unbind(); +} + +void GLFramebuffer::attachDepthTexture(Ptr texture) { + if (fboID_ == 0 || !texture) { + return; + } + + bind(); + + // 获取 OpenGL 纹理 ID + GLuint texID = static_cast(reinterpret_cast(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) { + if (fboID_ == 0 || !texture) { + return; + } + + bind(); + + // 获取 OpenGL 纹理 ID + GLuint texID = static_cast(reinterpret_cast(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 GLFramebuffer::getColorTexture(int attachment) const { + if (attachment >= 0 && attachment < MAX_COLOR_ATTACHMENTS) { + return colorTextures_[attachment]; + } + return nullptr; +} + +Ptr 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& 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 diff --git a/Extra2D/src/graphics/backends/opengl/gl_pipeline.cpp b/Extra2D/src/graphics/backends/opengl/gl_pipeline.cpp new file mode 100644 index 0000000..17d9349 --- /dev/null +++ b/Extra2D/src/graphics/backends/opengl/gl_pipeline.cpp @@ -0,0 +1,222 @@ +#include +#include + +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(blendMode_), depthTest_, static_cast(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 diff --git a/Extra2D/src/graphics/opengl/gl_renderer.cpp b/Extra2D/src/graphics/backends/opengl/gl_renderer.cpp similarity index 61% rename from Extra2D/src/graphics/opengl/gl_renderer.cpp rename to Extra2D/src/graphics/backends/opengl/gl_renderer.cpp index 2e4a6d9..7db2540 100644 --- a/Extra2D/src/graphics/opengl/gl_renderer.cpp +++ b/Extra2D/src/graphics/backends/opengl/gl_renderer.cpp @@ -1,13 +1,15 @@ -#include #include #include #include +#include +#include +#include +#include +#include +#include #include -#include -#include -#include -#include #include +#include #include #include #include @@ -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(glGetString(GL_VERSION))); + E2D_LOG_INFO("OpenGL Version: {}", GLContext::get().getVersionString()); return true; } @@ -100,24 +95,22 @@ void GLRenderer::shutdown() { spriteBatch_.shutdown(); - if (lineVbo_ != 0) { - glDeleteBuffers(1, &lineVbo_); - VRAMMgr::get().freeBuffer(MAX_LINE_VERTICES * sizeof(ShapeVertex)); - lineVbo_ = 0; - } + // 关闭 GLBuffer(自动释放 VBO) + lineBuffer_.shutdown(); + shapeBuffer_.shutdown(); + + // 删除 VAO(VAO 仍然手动管理) if (lineVao_ != 0) { glDeleteVertexArrays(1, &lineVao_); lineVao_ = 0; } - if (shapeVbo_ != 0) { - glDeleteBuffers(1, &shapeVbo_); - VRAMMgr::get().freeBuffer(MAX_SHAPE_VERTICES * sizeof(ShapeVertex)); - shapeVbo_ = 0; - } if (shapeVao_ != 0) { glDeleteVertexArrays(1, &shapeVao_); shapeVao_ = 0; } + + // 关闭 OpenGL 上下文 + GLContext::get().shutdown(); } /** @@ -125,6 +118,9 @@ void GLRenderer::shutdown() { * @param clearColor 清屏颜色 */ void GLRenderer::beginFrame(const Color &clearColor) { + // 应用管线状态 + pipeline_.applyAllStates(); + glClearColor(clearColor.r, clearColor.g, clearColor.b, clearColor.a); glClear(GL_COLOR_BUFFER_BIT); resetStats(); @@ -134,6 +130,10 @@ void GLRenderer::beginFrame(const Color &clearColor) { * @brief 结束当前帧,刷新所有待处理的渲染批次 */ void GLRenderer::endFrame() { + // 刷新所有待处理的精灵批次(自动批处理) + if (autoBatchEnabled_ && batchActive_) { + flush(); + } // 刷新所有待处理的形状批次 flushShapeBatch(); // 刷新所有待处理的线条批次 @@ -148,7 +148,8 @@ void GLRenderer::endFrame() { * @param height 视口高度 */ void GLRenderer::setViewport(int x, int y, int width, int height) { - glViewport(x, y, width, height); + // 使用 GLPipeline 管理视口状态 + pipeline_.setViewport(x, y, width, height); } /** @@ -157,8 +158,10 @@ void GLRenderer::setViewport(int x, int y, int width, int height) { */ void GLRenderer::setVSync(bool enabled) { vsync_ = enabled; - // 使用 SDL2 设置交换间隔 - SDL_GL_SetSwapInterval(enabled ? 1 : 0); + // 通过窗口接口设置垂直同步 + if (window_) { + window_->setVSync(enabled); + } } /** @@ -166,31 +169,8 @@ void GLRenderer::setVSync(bool enabled) { * @param mode 混合模式枚举值 */ void GLRenderer::setBlendMode(BlendMode mode) { - // 状态缓存检查,避免冗余 GL 调用 - if (cachedBlendMode_ == mode) { - return; - } - cachedBlendMode_ = mode; - - // 使用查找表替代 switch - size_t index = static_cast(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 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); - float texW = static_cast(tex->getWidth()); - float texH = static_cast(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); + float texW = static_cast(tex->getWidth()); + float texH = static_cast(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); + float texW = static_cast(tex->getWidth()); + float texH = static_cast(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 sprites; + std::vector sprites; sprites.reserve(text.size()); // 预分配空间 for (char c : text) { char32_t codepoint = static_cast(static_cast(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(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(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(lineVertexCount_)); @@ -806,4 +932,90 @@ void GLRenderer::flushLineBatch() { lineVertexCount_ = 0; } +/** + * @brief 创建帧缓冲对象 + * @param desc 帧缓冲描述 + * @return 创建的帧缓冲智能指针 + */ +Ptr GLRenderer::createFramebuffer(const FramebufferDesc& desc) { + auto framebuffer = makePtr(); + 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 GLRenderer::getDefaultFramebuffer() const { + if (!defaultFramebuffer_) { + // 延迟创建默认帧缓冲对象(代表系统默认帧缓冲,ID 为 0) + defaultFramebuffer_ = makePtr(); + // 注意:默认帧缓冲不需要显式初始化,它的 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 diff --git a/Extra2D/src/graphics/opengl/gl_shader.cpp b/Extra2D/src/graphics/backends/opengl/gl_shader.cpp similarity index 99% rename from Extra2D/src/graphics/opengl/gl_shader.cpp rename to Extra2D/src/graphics/backends/opengl/gl_shader.cpp index 16b839e..0d04452 100644 --- a/Extra2D/src/graphics/opengl/gl_shader.cpp +++ b/Extra2D/src/graphics/backends/opengl/gl_shader.cpp @@ -1,4 +1,4 @@ -#include +#include #include namespace extra2d { diff --git a/Extra2D/src/graphics/backends/opengl/gl_sprite_batch.cpp b/Extra2D/src/graphics/backends/opengl/gl_sprite_batch.cpp new file mode 100644 index 0000000..14dc4f3 --- /dev/null +++ b/Extra2D/src/graphics/backends/opengl/gl_sprite_batch.cpp @@ -0,0 +1,202 @@ +#include +#include +#include +#include + +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(offsetof(SpriteVertex, position))); + + glEnableVertexAttribArray(1); + glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(SpriteVertex), + reinterpret_cast(offsetof(SpriteVertex, texCoord))); + + glEnableVertexAttribArray(2); + glVertexAttribPointer(2, 4, GL_FLOAT, GL_FALSE, sizeof(SpriteVertex), + reinterpret_cast(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(&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 &sprites) { + const GLTexture *glTex = dynamic_cast(&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(indexCount), + GL_UNSIGNED_SHORT, nullptr); + + drawCallCount_++; + + // 清空 batch 层,准备下一批 + batch_.clear(); +} + +void GLSpriteBatch::flush() { + // 提交最后的批次 + if (batch_.getSpriteCount() > 0) { + submitBatch(); + } + + // 重置状态 + batches_.clear(); + currentTexture_ = nullptr; +} + +} // namespace extra2d diff --git a/Extra2D/src/graphics/opengl/gl_texture.cpp b/Extra2D/src/graphics/backends/opengl/gl_texture.cpp similarity index 99% rename from Extra2D/src/graphics/opengl/gl_texture.cpp rename to Extra2D/src/graphics/backends/opengl/gl_texture.cpp index e68b829..b88aa31 100644 --- a/Extra2D/src/graphics/opengl/gl_texture.cpp +++ b/Extra2D/src/graphics/backends/opengl/gl_texture.cpp @@ -1,4 +1,4 @@ -#include +#include #include #include #define STB_IMAGE_IMPLEMENTATION diff --git a/Extra2D/src/graphics/backends/vulkan/vk_renderer.cpp b/Extra2D/src/graphics/backends/vulkan/vk_renderer.cpp new file mode 100644 index 0000000..fc09612 --- /dev/null +++ b/Extra2D/src/graphics/backends/vulkan/vk_renderer.cpp @@ -0,0 +1,150 @@ +#include +#include + +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 VulkanRenderer::createTexture(int width, int height, const uint8_t *pixels, int channels) { + // TODO: 实现Vulkan纹理创建 + return nullptr; +} + +Ptr VulkanRenderer::loadTexture(const std::string &filepath) { + // TODO: 实现Vulkan纹理加载 + return nullptr; +} + +void VulkanRenderer::beginSpriteBatch() { + // TODO: 实现精灵批处理开始 +} + +void VulkanRenderer::drawSprite(const Texture &texture, const Rect &destRect, + const Rect &srcRect, const Color &tint, float rotation, + const Vec2 &anchor) { + // TODO: 实现精灵绘制 +} + +void VulkanRenderer::drawSprite(const Texture &texture, const Vec2 &position, + const Color &tint) { + // TODO: 实现简化精灵绘制 +} + +void VulkanRenderer::endSpriteBatch() { + // TODO: 实现精灵批处理结束 +} + +void VulkanRenderer::drawLine(const Vec2 &start, const Vec2 &end, const Color &color, + float width) { + // TODO: 实现线条绘制 +} + +void VulkanRenderer::drawRect(const Rect &rect, const Color &color, float width) { + // TODO: 实现矩形边框绘制 +} + +void VulkanRenderer::fillRect(const Rect &rect, const Color &color) { + // TODO: 实现矩形填充 +} + +void VulkanRenderer::drawCircle(const Vec2 ¢er, float radius, const Color &color, + int segments, float width) { + // TODO: 实现圆形边框绘制 +} + +void VulkanRenderer::fillCircle(const Vec2 ¢er, float radius, const Color &color, + int segments) { + // TODO: 实现圆形填充 +} + +void VulkanRenderer::drawTriangle(const Vec2 &p1, const Vec2 &p2, const Vec2 &p3, + const Color &color, float width) { + // TODO: 实现三角形边框绘制 +} + +void VulkanRenderer::fillTriangle(const Vec2 &p1, const Vec2 &p2, const Vec2 &p3, + const Color &color) { + // TODO: 实现三角形填充 +} + +void VulkanRenderer::drawPolygon(const std::vector &points, const Color &color, + float width) { + // TODO: 实现多边形边框绘制 +} + +void VulkanRenderer::fillPolygon(const std::vector &points, + const Color &color) { + // TODO: 实现多边形填充 +} + +Ptr 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 diff --git a/Extra2D/src/graphics/batch/shape_batch.cpp b/Extra2D/src/graphics/batch/shape_batch.cpp new file mode 100644 index 0000000..6958818 --- /dev/null +++ b/Extra2D/src/graphics/batch/shape_batch.cpp @@ -0,0 +1,72 @@ +#include + +namespace extra2d { + +// ============================================================================ +// ShapeBatch 基础实现(后端无关部分) +// ============================================================================ + +// 这里可以添加后端无关的工具函数 +// 例如:计算圆形的顶点、三角化多边形等 + +// 计算圆形顶点 +void calculateCircleVertices(std::vector& 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(i) / static_cast(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(i) / static_cast(segments); + outVertices.emplace_back( + center.x + radius * cosf(angle), + center.y + radius * sinf(angle) + ); + } + } +} + +// 计算矩形顶点 +void calculateRectVertices(std::vector& 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& 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 diff --git a/Extra2D/src/graphics/batch/sprite_batch.cpp b/Extra2D/src/graphics/batch/sprite_batch.cpp new file mode 100644 index 0000000..45189ba --- /dev/null +++ b/Extra2D/src/graphics/batch/sprite_batch.cpp @@ -0,0 +1,205 @@ +#include +#include + +#include +#include + +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(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(rad * RAD2DEG); + return sin(angle); +} + +float TrigLookup::cosRad(float rad) const { + constexpr float RAD2DEG = 180.0f / 3.14159265359f; + int angle = static_cast(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(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& 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 diff --git a/Extra2D/src/graphics/core/render_backend.cpp b/Extra2D/src/graphics/core/render_backend.cpp index 7d4b53a..82e05d4 100644 --- a/Extra2D/src/graphics/core/render_backend.cpp +++ b/Extra2D/src/graphics/core/render_backend.cpp @@ -1,4 +1,4 @@ -#include +#include #include namespace extra2d { diff --git a/Extra2D/src/graphics/core/render_module.cpp b/Extra2D/src/graphics/core/render_module.cpp index 220b2b9..5043fad 100644 --- a/Extra2D/src/graphics/core/render_module.cpp +++ b/Extra2D/src/graphics/core/render_module.cpp @@ -1,17 +1,19 @@ #include -#include -#include +#include +#include #include #include #include #include #include #include -#include +#include namespace extra2d { -RenderModule::RenderModule(const Cfg& cfg) : cfg_(cfg) {} +RenderModule::RenderModule(std::function 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() { diff --git a/Extra2D/src/graphics/core/render_target.cpp b/Extra2D/src/graphics/core/render_target.cpp index 931084d..bb6adff 100644 --- a/Extra2D/src/graphics/core/render_target.cpp +++ b/Extra2D/src/graphics/core/render_target.cpp @@ -1,5 +1,5 @@ #include -#include +#include #include #include diff --git a/Extra2D/src/graphics/opengl/gl_sprite_batch.cpp b/Extra2D/src/graphics/opengl/gl_sprite_batch.cpp deleted file mode 100644 index 6020c7f..0000000 --- a/Extra2D/src/graphics/opengl/gl_sprite_batch.cpp +++ /dev/null @@ -1,428 +0,0 @@ -#include -#include -#include -#include - -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 sinTable; - std::array cosTable; - - Tables() { - for (size_t i = 0; i < TABLE_SIZE; ++i) { - float angle = static_cast(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(radians * RAD_TO_INDEX) % static_cast(TABLE_SIZE); - if (idx < 0) { - idx += static_cast(TABLE_SIZE); - } - return static_cast(idx); - } - - static const Tables table_; -}; - -const TrigLookup::Tables TrigLookup::table_; - -// 静态索引生成函数 -/** - * @brief 获取静态索引数组,用于精灵绘制 - * @return 索引数组的常量引用 - */ -static const std::array& getIndices() { - static std::array indices = []() { - std::array arr{}; - for (size_t i = 0; i < GLSpriteBatch::MAX_SPRITES; ++i) { - GLuint base = static_cast(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 初始化精灵批处理器,创建VAO、VBO、IBO和编译着色器 - * @return 初始化成功返回true,失败返回false - */ -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 需要刷新返回true,否则返回false - */ -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 &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( - reinterpret_cast(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( - (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 diff --git a/Extra2D/src/graphics/shader/shader_manager.cpp b/Extra2D/src/graphics/shader/shader_manager.cpp index bd1b487..3c0b651 100644 --- a/Extra2D/src/graphics/shader/shader_manager.cpp +++ b/Extra2D/src/graphics/shader/shader_manager.cpp @@ -1,6 +1,10 @@ #include #include +#include + +namespace nl = nlohmann; + namespace extra2d { /** @@ -20,15 +24,20 @@ ShaderManager& ShaderManager::getInstance() { * @return 初始化成功返回true,失败返回false */ bool ShaderManager::init(Ptr 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 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 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 fragRelativePath = opengl["fragment"].get(); + + // 构建完整路径 + 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 加载成功返回true,失败返回false @@ -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 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); } } } diff --git a/Extra2D/src/platform/backends/glfw/glfw_backend.cpp b/Extra2D/src/platform/backends/glfw/glfw_backend.cpp new file mode 100644 index 0000000..9468940 --- /dev/null +++ b/Extra2D/src/platform/backends/glfw/glfw_backend.cpp @@ -0,0 +1,28 @@ +#include "glfw_window.h" +#include "glfw_input.h" +#include + +namespace extra2d { + +namespace { + static bool s_glfwBackendRegistered = false; +} + +void initGLFWBackend() { + if (s_glfwBackendRegistered) { + return; + } + s_glfwBackendRegistered = true; + + BackendFactory::reg( + "glfw", + []() -> UniquePtr { + return makeUnique(); + }, + []() -> UniquePtr { + return makeUnique(); + } + ); +} + +} // namespace extra2d diff --git a/Extra2D/src/platform/backends/glfw/glfw_input.cpp b/Extra2D/src/platform/backends/glfw/glfw_input.cpp new file mode 100644 index 0000000..72605da --- /dev/null +++ b/Extra2D/src/platform/backends/glfw/glfw_input.cpp @@ -0,0 +1,426 @@ +#include "glfw_input.h" +#include +#include + +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(key)] = (state == GLFW_PRESS); + } + } + + // 更新鼠标按钮状态 + for (int i = 0; i < static_cast(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(x), static_cast(y)}; + } +} + +bool GLFWInput::down(Key key) const { + size_t idx = static_cast(key); + if (idx < keyCurrent_.size()) { + return keyCurrent_[idx]; + } + return false; +} + +bool GLFWInput::pressed(Key key) const { + size_t idx = static_cast(key); + if (idx < keyCurrent_.size()) { + return keyCurrent_[idx] && !keyPrevious_[idx]; + } + return false; +} + +bool GLFWInput::released(Key key) const { + size_t idx = static_cast(key); + if (idx < keyCurrent_.size()) { + return !keyCurrent_[idx] && keyPrevious_[idx]; + } + return false; +} + +bool GLFWInput::down(Mouse btn) const { + size_t idx = static_cast(btn); + if (idx < mouseCurrent_.size()) { + return mouseCurrent_[idx]; + } + return false; +} + +bool GLFWInput::pressed(Mouse btn) const { + size_t idx = static_cast(btn); + if (idx < mouseCurrent_.size()) { + return mouseCurrent_[idx] && !mousePrevious_[idx]; + } + return false; +} + +bool GLFWInput::released(Mouse btn) const { + size_t idx = static_cast(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(btn); + if (idx < gamepadCurrent_.size()) { + return gamepadCurrent_[idx]; + } + return false; +} + +bool GLFWInput::pressed(Gamepad btn) const { + size_t idx = static_cast(btn); + if (idx < gamepadCurrent_.size()) { + return gamepadCurrent_[idx] && !gamepadPrevious_[idx]; + } + return false; +} + +bool GLFWInput::released(Gamepad btn) const { + size_t idx = static_cast(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(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(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(xpos), static_cast(ypos)}; + mouseDelta_ = newPos - mousePos_; + mousePos_ = newPos; +} + +void GLFWInput::handleScrollEvent(double xoffset, double yoffset) { + (void)xoffset; + scroll_ += static_cast(yoffset); + scrollDelta_ += static_cast(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(Gamepad::A)] = state.buttons[GLFW_GAMEPAD_BUTTON_A] == GLFW_PRESS; + gamepadCurrent_[static_cast(Gamepad::B)] = state.buttons[GLFW_GAMEPAD_BUTTON_B] == GLFW_PRESS; + gamepadCurrent_[static_cast(Gamepad::X)] = state.buttons[GLFW_GAMEPAD_BUTTON_X] == GLFW_PRESS; + gamepadCurrent_[static_cast(Gamepad::Y)] = state.buttons[GLFW_GAMEPAD_BUTTON_Y] == GLFW_PRESS; + gamepadCurrent_[static_cast(Gamepad::Back)] = state.buttons[GLFW_GAMEPAD_BUTTON_BACK] == GLFW_PRESS; + gamepadCurrent_[static_cast(Gamepad::Start)] = state.buttons[GLFW_GAMEPAD_BUTTON_START] == GLFW_PRESS; + gamepadCurrent_[static_cast(Gamepad::LStick)] = state.buttons[GLFW_GAMEPAD_BUTTON_LEFT_THUMB] == GLFW_PRESS; + gamepadCurrent_[static_cast(Gamepad::RStick)] = state.buttons[GLFW_GAMEPAD_BUTTON_RIGHT_THUMB] == GLFW_PRESS; + gamepadCurrent_[static_cast(Gamepad::LB)] = state.buttons[GLFW_GAMEPAD_BUTTON_LEFT_BUMPER] == GLFW_PRESS; + gamepadCurrent_[static_cast(Gamepad::RB)] = state.buttons[GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER] == GLFW_PRESS; + gamepadCurrent_[static_cast(Gamepad::DUp)] = state.buttons[GLFW_GAMEPAD_BUTTON_DPAD_UP] == GLFW_PRESS; + gamepadCurrent_[static_cast(Gamepad::DDown)] = state.buttons[GLFW_GAMEPAD_BUTTON_DPAD_DOWN] == GLFW_PRESS; + gamepadCurrent_[static_cast(Gamepad::DLeft)] = state.buttons[GLFW_GAMEPAD_BUTTON_DPAD_LEFT] == GLFW_PRESS; + gamepadCurrent_[static_cast(Gamepad::DRight)] = state.buttons[GLFW_GAMEPAD_BUTTON_DPAD_RIGHT] == GLFW_PRESS; + gamepadCurrent_[static_cast(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 diff --git a/Extra2D/src/platform/backends/glfw/glfw_input.h b/Extra2D/src/platform/backends/glfw/glfw_input.h new file mode 100644 index 0000000..287d90e --- /dev/null +++ b/Extra2D/src/platform/backends/glfw/glfw_input.h @@ -0,0 +1,95 @@ +#pragma once + +#include +#include +#include + +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(Key::Count)> keyCurrent_; + std::array(Key::Count)> keyPrevious_; + + // Mouse state + std::array(Mouse::Count)> mouseCurrent_; + std::array(Mouse::Count)> mousePrevious_; + Vec2 mousePos_; + Vec2 mouseDelta_; + float scroll_ = 0.0f; + float scrollDelta_ = 0.0f; + + // Gamepad state + std::array(Gamepad::Count)> gamepadCurrent_; + std::array(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 diff --git a/Extra2D/src/platform/backends/glfw/glfw_window.cpp b/Extra2D/src/platform/backends/glfw/glfw_window.cpp new file mode 100644 index 0000000..de95e3e --- /dev/null +++ b/Extra2D/src/platform/backends/glfw/glfw_window.cpp @@ -0,0 +1,479 @@ +#include "glfw_window.h" +#include "glfw_input.h" +#include +#include + +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(); + 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(width_), static_cast(height_)); +} + +Vec2 GLFWWindow::pos() const { + int x = 0, y = 0; +#ifndef __SWITCH__ + if (glfwWindow_) { + glfwGetWindowPos(glfwWindow_, &x, &y); + } +#endif + return Vec2(static_cast(x), static_cast(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(fbWidth) / width_ : 1.0f; + scaleY_ = fbHeight > 0 ? static_cast(fbHeight) / height_ : 1.0f; + } +} + +// 静态回调函数 +void GLFWWindow::framebufferSizeCallback(GLFWwindow* window, int width, int height) { + GLFWWindow* self = static_cast(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(glfwGetWindowUserPointer(window)); + if (self) { + self->shouldClose_ = true; + if (self->closeCb_) { + self->closeCb_(); + } + } +} + +void GLFWWindow::windowFocusCallback(GLFWwindow* window, int focused) { + GLFWWindow* self = static_cast(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(glfwGetWindowUserPointer(window)); + if (self) { + self->minimized_ = (iconified == GLFW_TRUE); + } +} + +void GLFWWindow::cursorPosCallback(GLFWwindow* window, double xpos, double ypos) { + GLFWWindow* self = static_cast(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(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(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(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 diff --git a/Extra2D/src/platform/backends/glfw/glfw_window.h b/Extra2D/src/platform/backends/glfw/glfw_window.h new file mode 100644 index 0000000..7166a45 --- /dev/null +++ b/Extra2D/src/platform/backends/glfw/glfw_window.h @@ -0,0 +1,99 @@ +#pragma once + +#include +#include + +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 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 diff --git a/Extra2D/src/platform/input_module.cpp b/Extra2D/src/platform/input_module.cpp index db46bf1..4a3bceb 100644 --- a/Extra2D/src/platform/input_module.cpp +++ b/Extra2D/src/platform/input_module.cpp @@ -6,7 +6,9 @@ namespace extra2d { -InputModule::InputModule(const Cfg& cfg) : cfg_(cfg) {} +InputModule::InputModule(std::function configFn) { + configFn(cfg_); +} InputModule::~InputModule() { if (initialized_) { diff --git a/Extra2D/src/platform/window_module.cpp b/Extra2D/src/platform/window_module.cpp index 02767dc..b9645c7 100644 --- a/Extra2D/src/platform/window_module.cpp +++ b/Extra2D/src/platform/window_module.cpp @@ -2,7 +2,6 @@ #include #include #include -#include #ifdef __SWITCH__ #include @@ -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 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; } diff --git a/Extra2D/src/services/camera_service.cpp b/Extra2D/src/services/camera_service.cpp index 0748f03..f611289 100644 --- a/Extra2D/src/services/camera_service.cpp +++ b/Extra2D/src/services/camera_service.cpp @@ -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 diff --git a/Extra2D/src/services/event_service.cpp b/Extra2D/src/services/event_service.cpp index 9551b75..8212dd1 100644 --- a/Extra2D/src/services/event_service.cpp +++ b/Extra2D/src/services/event_service.cpp @@ -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 diff --git a/Extra2D/src/services/logger_service.cpp b/Extra2D/src/services/logger_service.cpp index 8305dd7..032364c 100644 --- a/Extra2D/src/services/logger_service.cpp +++ b/Extra2D/src/services/logger_service.cpp @@ -1,10 +1,7 @@ -#include -#include -#include -#include #include -#include -#include +#include +#include +#include #include 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()) { - info_.name = "ConsoleLogger"; - info_.priority = ServicePriority::Core; +ConsoleLogger::ConsoleLogger() + : level_(LogLevel::Info), impl_(std::make_unique()) { + 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(level) >= static_cast(level_); + return static_cast(level) >= static_cast(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 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( - now.time_since_epoch()) % 1000; - - std::tm tm; +void ConsoleLogger::output(LogLevel level, const char *msg) { + std::lock_guard 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( + 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 diff --git a/Extra2D/src/services/scene_service.cpp b/Extra2D/src/services/scene_service.cpp index 2ef197a..854f1bd 100644 --- a/Extra2D/src/services/scene_service.cpp +++ b/Extra2D/src/services/scene_service.cpp @@ -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) { - manager_.runWithScene(scene); + manager_.runWithScene(scene); } void SceneService::replaceScene(Ptr scene) { - manager_.replaceScene(scene); + manager_.replaceScene(scene); } -void SceneService::pushScene(Ptr scene) { - manager_.pushScene(scene); -} +void SceneService::pushScene(Ptr 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 SceneService::getCurrentScene() const { - return manager_.getCurrentScene(); + return manager_.getCurrentScene(); } Ptr SceneService::getPreviousScene() const { - return manager_.getPreviousScene(); + return manager_.getPreviousScene(); } Ptr SceneService::getRootScene() const { - return manager_.getRootScene(); + return manager_.getRootScene(); } -Ptr SceneService::getSceneByName(const std::string& name) const { - return manager_.getSceneByName(name); +Ptr 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& commands) { - manager_.collectRenderCommands(commands); +void SceneService::collectRenderCommands(std::vector &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) { - manager_.enterScene(scene); -} +void SceneService::enterScene(Ptr scene) { manager_.enterScene(scene); } -} +} // namespace extra2d diff --git a/Extra2D/src/services/timer_service.cpp b/Extra2D/src/services/timer_service.cpp index 8674c3f..b3fe050 100644 --- a/Extra2D/src/services/timer_service.cpp +++ b/Extra2D/src/services/timer_service.cpp @@ -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 diff --git a/README.md b/README.md index 735bc41..f44e230 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,9 @@ SDL2 + + GLFW + Nintendo Switch @@ -39,6 +42,8 @@ - **跨平台支持**:Windows、Linux、macOS、Nintendo Switch - **模块化架构**:模块系统 + 服务系统,灵活可扩展 +- **多后端渲染**:OpenGL / Vulkan 支持,易于扩展新后端 +- **高性能批处理**:独立 Batch 层,TrigLookup 优化,支持 10000 精灵/批次 - **场景图系统**:树形节点结构,支持变换继承 - **输入系统**:键盘、鼠标、手柄、触摸,事件驱动 - **渲染系统**:OpenGL ES 3.2,支持自定义着色器 @@ -95,7 +100,9 @@ flowchart TB subgraph Graphics["Graphics (图形系统)"] direction TB BACKEND[RenderBackend
渲染后端] + BATCH[Batch Layer
批处理层] GL[OpenGL Renderer
OpenGL 渲染器] + VK[Vulkan Renderer
Vulkan 渲染器] SHADER[ShaderManager
着色器管理] TEXTURE[TexturePool
纹理池] end @@ -115,7 +122,9 @@ flowchart TB NODE --> SHAPE NODE --> SPRITE RENDER --> BACKEND - BACKEND --> GL + BACKEND --> BATCH + BATCH --> GL + BATCH --> VK GL --> SHADER GL --> TEXTURE WINDOW --> SDL2 @@ -144,6 +153,63 @@ flowchart TB --- +## 渲染系统架构 + +### 多后端支持 + +Extra2D 采用分层架构支持多渲染后端: + +``` +┌─────────────────────────────────────┐ +│ Application (游戏逻辑) │ +├─────────────────────────────────────┤ +│ Renderer (渲染命令) │ +├─────────────────────────────────────┤ +│ Batch Layer (batch/) │ ← 后端无关的批处理 +│ - 顶点缓存管理 │ +│ - 批次策略 (10000 精灵/批次) │ +│ - TrigLookup 三角函数查表 │ +│ - 批量绘制接口 │ +├─────────────────────────────────────┤ +│ Backend Layer (backends/) │ ← 具体 GPU 实现 +│ - OpenGL / Vulkan / Metal / D3D │ +│ - VAO/VBO 创建 │ +│ - GPU 提交 │ +└─────────────────────────────────────┘ +``` + +### 着色器系统 + +支持多后端的 JSON 元数据着色器系统: + +```json +{ + "name": "sprite", + "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" + } + } +} +``` + +### 性能优化 + +| 优化技术 | 说明 | 性能提升 | +|---------|------|---------| +| **TrigLookup** | 三角函数查表,避免每帧 sin/cos 计算 | 旋转计算 5-10x | +| **大批次容量** | 10000 精灵/批次,减少 draw call | Draw call 减少 10x | +| **批量绘制** | `drawBatch()` 一次性处理多个精灵 | CPU 开销减少 20-30% | +| **静态索引** | 预生成索引缓冲区,避免运行时分配 | 内存分配开销消除 | +| **着色器缓存** | 二进制着色器缓存,加速启动 | 启动时间减少 | + +--- + ## 构建指南 ### 环境要求 @@ -174,6 +240,8 @@ sudo apt install xmake ### 构建项目 +#### 基础构建(使用 SDL2 后端,默认) + ```bash # 配置项目 xmake f -p mingw -a x86_64 -m release -y @@ -185,6 +253,45 @@ xmake build xmake run demo_basic ``` +#### 使用 GLFW 后端 + +```bash +# 配置项目(指定 GLFW 后端) +xmake f -p mingw -a x86_64 -m release --window_backend=glfw -y + +# 构建 +xmake build + +# 运行示例 +xmake run demo_basic +``` + +#### 切换窗口后端 + +```bash +# 切换到 GLFW 后端 +xmake f --window_backend=glfw -y + +# 切换回 SDL2 后端 +xmake f --window_backend=sdl2 -y + +# 清理重新配置 +xmake f -c -y +``` + +#### 切换渲染后端 + +```bash +# 切换到 Vulkan 后端 +xmake f --render_backend=vulkan -y + +# 切换回 OpenGL 后端(默认) +xmake f --render_backend=opengl -y + +# 清理重新配置 +xmake f -c -y +``` + ### Nintendo Switch 构建 ```bash @@ -300,6 +407,7 @@ eventService->addListener(EventType::GamepadButtonPressed, [](Event& e) { | 示例 | 说明 | |-----|------| | `demo_basic` | 基础示例:场景图、输入事件、视口适配 | +| `demo_text_rendering` | 文字渲染示例:使用 GLFontAtlas 渲染文字 | 运行示例: @@ -314,7 +422,8 @@ xmake run demo_basic | 技术 | 用途 | 版本 | |:----:|:-----|:----:| | OpenGL ES | 2D 图形渲染 | 3.2 | -| SDL2 | 窗口和输入管理 | 2.0+ | +| SDL2 | 窗口和输入管理(可选) | 2.0+ | +| GLFW | 窗口和输入管理(可选) | 3.4+ | | GLM | 数学库 | 0.9.9+ | | nlohmann_json | JSON 解析 | 3.x | | glad | OpenGL 加载器 | 最新版 | @@ -344,12 +453,15 @@ Extra2D/ │ │ │ ├── core/ # 核心类型 │ │ │ ├── event/ # 事件系统 │ │ │ ├── graphics/ # 图形渲染 +│ │ │ │ ├── backends/ # 渲染后端 +│ │ │ │ │ ├── opengl/ # OpenGL 实现 +│ │ │ │ │ └── vulkan/ # Vulkan 实现 +│ │ │ │ ├── batch/ # 批处理层(后端无关) │ │ │ │ ├── core/ # 渲染核心 │ │ │ │ ├── camera/ # 相机和视口 │ │ │ │ ├── shader/ # Shader 系统 │ │ │ │ ├── texture/ # 纹理系统 -│ │ │ │ ├── memory/ # GPU 内存管理 -│ │ │ │ └── opengl/ # OpenGL 实现 +│ │ │ │ └── memory/ # GPU 内存管理 │ │ │ ├── platform/ # 平台抽象 │ │ │ ├── scene/ # 场景系统 │ │ │ ├── services/ # 服务接口 @@ -357,9 +469,9 @@ Extra2D/ │ │ ├── glad/ # OpenGL 加载器 │ │ └── stb/ # STB 单文件库 │ ├── shaders/ # 着色器文件 -│ │ ├── builtin/ # 内置着色器 -│ │ ├── common/ # 公共着色器代码 -│ │ └── effects/ # 特效着色器 +│ │ ├── backends/ # 后端特定着色器 +│ │ │ └── opengl/ # OpenGL 着色器 +│ │ └── shared/ # 共享着色器元数据 │ └── src/ # 源文件 │ ├── app/ # 应用实现 │ ├── config/ # 配置实现 @@ -367,62 +479,19 @@ Extra2D/ │ ├── event/ # 事件实现 │ ├── glad/ # GLAD 实现 │ ├── graphics/ # 图形实现 +│ │ ├── backends/ # 渲染后端实现 +│ │ │ ├── opengl/ # OpenGL 实现 +│ │ │ └── vulkan/ # Vulkan 实现 +│ │ ├── batch/ # 批处理层实现 │ │ ├── core/ # 渲染核心 │ │ ├── camera/ # 相机和视口 │ │ ├── shader/ # Shader 系统 │ │ ├── texture/ # 纹理系统 -│ │ ├── memory/ # GPU 内存管理 -│ │ └── opengl/ # OpenGL 实现 +│ │ └── memory/ # GPU 内存管理 │ ├── platform/ # 平台实现 │ │ └── backends/ # 后端实现 -│ │ └── sdl2/ # SDL2 后端 -│ ├── scene/ # 场景实现 -│ ├── services/ # 服务实现 -│ └── utils/ # 工具实现 -├── docs/ # 文档 -├── examples/ # 示例程序 -│ └── basic/ # 基础示例 -└── xmake/ # 构建配置 - └── toolchains/ # 工具链配置 -``` -Extra2D/ -├── Extra2D/ -│ ├── include/ -│ │ ├── KHR/ # KHR 平台头文件 -│ │ ├── extra2d/ # 引擎公共头文件 -│ │ │ ├── app/ # 应用程序 -│ │ │ ├── audio/ # 音频配置 -│ │ │ ├── config/ # 配置系统 -│ │ │ ├── core/ # 核心类型 -│ │ │ ├── debug/ # 调试配置 -│ │ │ ├── event/ # 事件系统 -│ │ │ ├── graphics/ # 图形渲染 -│ │ │ │ └── opengl/ # OpenGL 实现 -│ │ │ ├── input/ # 输入配置 -│ │ │ ├── platform/ # 平台抽象 -│ │ │ ├── resource/ # 资源配置 -│ │ │ ├── scene/ # 场景系统 -│ │ │ ├── services/ # 服务接口 -│ │ │ └── utils/ # 工具库 -│ │ ├── glad/ # OpenGL 加载器 -│ │ └── stb/ # STB 单文件库 -│ ├── shaders/ # 着色器文件 -│ │ ├── builtin/ # 内置着色器 -│ │ ├── common/ # 公共着色器代码 -│ │ └── effects/ # 特效着色器 -│ └── src/ # 源文件 -│ ├── app/ # 应用实现 -│ ├── config/ # 配置实现 -│ ├── core/ # 核心实现 -│ ├── debug/ # 调试实现 -│ ├── event/ # 事件实现 -│ ├── glad/ # GLAD 实现 -│ ├── graphics/ # 图形实现 -│ │ └── opengl/ # OpenGL 实现 -│ ├── platform/ # 平台实现 -│ │ └── backends/ # 后端实现 -│ │ └── sdl2/ # SDL2 后端 -│ ├── resource/ # 资源实现 +│ │ ├── sdl2/ # SDL2 后端 +│ │ └── glfw/ # GLFW 后端 │ ├── scene/ # 场景实现 │ ├── services/ # 服务实现 │ └── utils/ # 工具实现 @@ -439,10 +508,10 @@ Extra2D/ | 平台 | 窗口后端 | 图形 API | 状态 | |-----|---------|---------|------| -| Windows | SDL2 | OpenGL ES 3.2 | ✅ 支持 | -| Linux | SDL2 | OpenGL ES 3.2 | ✅ 支持 | -| macOS | SDL2 | OpenGL ES 3.2 | ✅ 支持 | -| Nintendo Switch | SDL2 | OpenGL ES 3.2 | ✅ 支持 | +| Windows | SDL2 / GLFW | OpenGL ES 3.2 / Vulkan | ✅ 支持 | +| Linux | SDL2 / GLFW | OpenGL ES 3.2 / Vulkan | ✅ 支持 | +| macOS | SDL2 / GLFW | OpenGL ES 3.2 / Vulkan | ✅ 支持 | +| Nintendo Switch | SDL2 / GLFW | OpenGL ES 3.2 | ✅ 支持 | --- diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..3323eb0 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,44 @@ +# 资源抽象层 (Resources Abstraction Layer) + +此目录包含渲染后端无关的资源抽象接口。 + +## 文件说明 + +### 头文件 (include/extra2d/graphics/resources/) + +- **buffer.h** - 缓冲区抽象接口 + - 顶点缓冲、索引缓冲、统一缓冲 + - 支持 Static/Dynamic/Stream 使用模式 + +- **pipeline.h** - 渲染管线抽象接口 + - 混合模式、深度测试、裁剪状态 + - 顶点属性描述 + +- **framebuffer.h** - 帧缓冲抽象接口 + - 多颜色附件支持 + - 深度/模板附件 + - 多重采样支持 + +- **font_atlas.h** - 字体图集抽象接口 + - 字形信息管理 + - SDF 渲染支持 + - 文本测量功能 + +- **shader.h** - 着色器抽象接口 + - Uniform 变量设置 + - 支持各种数据类型 + +### 实现说明 + +这些接口是纯虚类(抽象接口),不需要在 `src/graphics/resources/` 目录中提供实现。 + +实际的实现位于各个渲染后端目录中: +- OpenGL 实现: `src/graphics/backends/opengl/` +- Vulkan 实现: `src/graphics/backends/vulkan/` (未来) + +## 设计原则 + +1. **后端无关** - 接口不依赖任何特定渲染 API +2. **工厂模式** - 通过后端工厂创建具体实现 +3. **资源管理** - 使用智能指针管理资源生命周期 +4. **类型安全** - 使用强类型枚举和结构体 diff --git a/docs/module_system.md b/docs/module_system.md index 5a0fda1..31a3e4d 100644 --- a/docs/module_system.md +++ b/docs/module_system.md @@ -458,17 +458,27 @@ if (caps.supportsGamepad) { **职责**:窗口创建和管理 -**后端**:统一使用 SDL2,支持所有平台 +**后端**:支持 SDL2 和 GLFW,可通过配置切换 **配置**: ```cpp -WindowModule::Cfg cfg; -cfg.title = "My App"; -cfg.w = 1280; -cfg.h = 720; -cfg.mode = WindowMode::Windowed; -cfg.vsync = true; -cfg.backend = "sdl2"; // 可选:指定后端 +app.use([](auto& cfg) { + cfg.title = "My App"; + cfg.w = 1280; + cfg.h = 720; + cfg.mode = WindowMode::Windowed; + cfg.vsync = true; + cfg.backend = "sdl2"; // 可选:"sdl2" 或 "glfw" +}); +``` + +**构建时选择后端**: +```bash +# 使用 SDL2 后端(默认) +xmake f --window_backend=sdl2 -y + +# 使用 GLFW 后端 +xmake f --window_backend=glfw -y ``` **平台约束**: @@ -482,10 +492,11 @@ cfg.backend = "sdl2"; // 可选:指定后端 **配置**: ```cpp -InputModule::Cfg cfg; -cfg.deadzone = 0.15f; -cfg.mouseSensitivity = 1.0f; -cfg.enableVibration = true; +app.use([](auto& cfg) { + cfg.deadzone = 0.15f; + cfg.mouseSensitivity = 1.0f; + cfg.enableVibration = true; +}); ``` **使用示例**: @@ -519,11 +530,12 @@ if (input->gamepad()) { **配置**: ```cpp -RenderModule::Cfg cfg; -cfg.backend = BackendType::OpenGL; -cfg.vsync = true; -cfg.targetFPS = 60; -cfg.multisamples = 4; +app.use([](auto& cfg) { + cfg.backend = BackendType::OpenGL; + cfg.vsync = true; + cfg.targetFPS = 60; + cfg.multisamples = 4; +}); ``` --- @@ -678,10 +690,10 @@ eventService->addListener(EventType::MouseButtonPressed, [](Event& e) { | 平台 | 窗口后端 | 图形 API | 特殊处理 | |-----|---------|---------|---------| -| Windows | SDL2 | OpenGL ES 3.2 | - | -| Linux | SDL2 | OpenGL ES 3.2 | - | -| macOS | SDL2 | OpenGL ES 3.2 | - | -| Nintendo Switch | SDL2 | OpenGL ES 3.2 | romfs, 强制全屏 | +| Windows | SDL2 / GLFW | OpenGL ES 3.2 | - | +| Linux | SDL2 / GLFW | OpenGL ES 3.2 | - | +| macOS | SDL2 / GLFW | OpenGL ES 3.2 | - | +| Nintendo Switch | SDL2 / GLFW | OpenGL ES 3.2 | romfs, 强制全屏 | ### 平台检测 @@ -714,20 +726,31 @@ if (caps.supportsTouch) { /* 支持触摸 */ } ### 1. 模块配置独立化 ```cpp -// 好的做法:模块管理自己的配置 -class WindowModule : public Module { - Cfg cfg_; // 模块内部配置 -public: - struct Cfg { - std::string title = "Extra2D"; - int w = 1280; - int h = 720; - WindowMode mode = WindowMode::Windowed; - bool vsync = true; - int priority = 0; - }; +// 好的做法:模块管理自己的配置,使用 Lambda 配置 +struct WindowCfg { + std::string title = "Extra2D"; + int w = 1280; + int h = 720; + WindowMode mode = WindowMode::Windowed; + bool vsync = true; + int priority = 0; }; +class WindowModule : public Module { + WindowCfg cfg_; // 模块内部配置 +public: + explicit WindowModule(std::function configFn) { + configFn(cfg_); + } +}; + +// 使用 Lambda 配置模块 +app.use([](auto& cfg) { + cfg.w = 1920; + cfg.h = 1080; + cfg.backend = "glfw"; +}); + // 不好的做法:所有配置放在 AppConfig struct AppConfig { WindowConfig window; // 耦合度高 diff --git a/examples/basic/main.cpp b/examples/basic/main.cpp index 9227865..84b2dd7 100644 --- a/examples/basic/main.cpp +++ b/examples/basic/main.cpp @@ -3,13 +3,13 @@ * @brief Extra2D 场景图测试示例 */ -#include -#include -#include -#include #include -#include +#include +#include +#include +#include #include +#include #include using namespace extra2d; @@ -102,19 +102,16 @@ int main(int argc, char *argv[]) { Application &app = Application::get(); // 注册模块(按优先级顺序) - WindowModule::Cfg winCfg; - winCfg.w = 1280; - winCfg.h = 720; - winCfg.priority = 0; - app.use(winCfg); - - RenderModule::Cfg renderCfg; - renderCfg.priority = 10; - app.use(renderCfg); - - InputModule::Cfg inputCfg; - inputCfg.priority = 20; - app.use(inputCfg); + app.use([](auto &cfg) { + cfg.w = 1280; + cfg.h = 720; + cfg.priority = 0; + cfg.backend = "glfw"; + }); + + app.use([](auto &cfg) { cfg.priority = 10; }); + + app.use([](auto &cfg) { cfg.priority = 20; }); std::cout << "Initializing application..." << std::endl; if (!app.init()) { @@ -123,10 +120,11 @@ int main(int argc, char *argv[]) { } std::cout << "Application initialized successfully!" << std::endl; - - auto* win = app.window(); + + auto *win = app.window(); if (win) { - std::cout << "Window: " << win->width() << "x" << win->height() << std::endl; + std::cout << "Window: " << win->width() << "x" << win->height() + << std::endl; } auto eventService = ServiceLocator::instance().getService(); diff --git a/examples/hello_module/hello_module.cpp b/examples/hello_module/hello_module.cpp index 6bb6172..3ddb514 100644 --- a/examples/hello_module/hello_module.cpp +++ b/examples/hello_module/hello_module.cpp @@ -3,7 +3,11 @@ namespace extra2d { -HelloModule::HelloModule(const Cfg& cfg) : cfg_(cfg) {} +HelloModule::HelloModule(const HelloCfg& cfg) : cfg_(cfg) {} + +HelloModule::HelloModule(std::function configFn) { + configFn(cfg_); +} HelloModule::~HelloModule() { if (initialized_) { diff --git a/examples/hello_module/hello_module.h b/examples/hello_module/hello_module.h index 253e910..abe1353 100644 --- a/examples/hello_module/hello_module.h +++ b/examples/hello_module/hello_module.h @@ -2,9 +2,24 @@ #include #include +#include namespace extra2d { +/** + * @brief Hello模块配置结构 + */ +struct HelloCfg { + std::string greeting; + int repeatCount; + int priority; + + HelloCfg() + : greeting("Hello, Extra2D!") + , repeatCount(1) + , priority(100) {} +}; + /** * @brief Hello模块示例 * 展示如何创建自定义模块 @@ -12,19 +27,21 @@ namespace extra2d { class HelloModule : public Module { public: /** - * @brief 配置结构 + * @brief 配置类型别名(向后兼容) */ - struct Cfg { - std::string greeting = "Hello, Extra2D!"; - int repeatCount = 1; - int priority = 100; - }; - + using Cfg = HelloCfg; + /** * @brief 构造函数 * @param cfg 配置 */ - explicit HelloModule(const Cfg& cfg = Cfg{}); + explicit HelloModule(const HelloCfg& cfg = HelloCfg{}); + + /** + * @brief 构造函数(Lambda 配置) + * @param configFn 配置函数 + */ + explicit HelloModule(std::function configFn); /** * @brief 析构函数 @@ -43,7 +60,7 @@ public: void sayHello() const; private: - Cfg cfg_; + HelloCfg cfg_; bool initialized_ = false; }; diff --git a/examples/hello_module/main.cpp b/examples/hello_module/main.cpp index 8d1a649..0dea10b 100644 --- a/examples/hello_module/main.cpp +++ b/examples/hello_module/main.cpp @@ -1,11 +1,12 @@ #include "hello_module.h" #include -#include #include +#include #include #include #include + using namespace extra2d; class HelloScene : public Scene { @@ -17,7 +18,7 @@ public: std::cout << "HelloScene entered" << std::endl; setBackgroundColor(Color(0.1f, 0.1f, 0.2f, 1.0f)); - auto* hello = Application::get().get(); + auto *hello = Application::get().get(); if (hello) { std::cout << "Scene calling HelloModule from onEnter..." << std::endl; hello->sayHello(); @@ -29,7 +30,7 @@ public: time_ += dt; if (time_ >= 5.0f) { - auto* hello = Application::get().get(); + auto *hello = Application::get().get(); if (hello) { std::cout << "Scene calling HelloModule from onUpdate..." << std::endl; hello->sayHello(); @@ -47,15 +48,23 @@ int main(int argc, char *argv[]) { (void)argv; std::cout << "=== Hello Module Example ===" << std::endl; - std::cout << "This example demonstrates how to create a custom module" << std::endl; + std::cout << "This example demonstrates how to create a custom module" + << std::endl; std::cout << "" << std::endl; Application &app = Application::get(); // 注册模块 - app.use(WindowModule::Cfg{.w = 800, .h = 600}); - app.use(); - app.use(HelloModule::Cfg{.greeting = "Hello from custom module!", .repeatCount = 3}); + app.use([](auto &cfg) { + cfg.w = 800; + cfg.h = 600; + cfg.backend = "glfw"; + }); + app.use([](auto &cfg) { cfg.priority = 10; }); + app.use([](auto &cfg) { + cfg.greeting = "Hello from custom module!"; + cfg.repeatCount = 3; + }); if (!app.init()) { std::cerr << "Failed to initialize application" << std::endl; diff --git a/examples/image_display/assets/images/demo.jpg b/examples/image_display/assets/images/demo.jpg new file mode 100644 index 0000000..b75ea16 Binary files /dev/null and b/examples/image_display/assets/images/demo.jpg differ diff --git a/examples/image_display/assets/images/note.txt b/examples/image_display/assets/images/note.txt new file mode 100644 index 0000000..f1776f9 --- /dev/null +++ b/examples/image_display/assets/images/note.txt @@ -0,0 +1,4 @@ +请将 1.jpg 图片复制到此目录,并重命名为 demo.jpg + +源文件位置: C:\Users\soulcoco\Desktop\Extra2D\1.jpg +目标文件位置: C:\Users\soulcoco\Desktop\Extra2D\examples\image_display\assets\images\demo.jpg diff --git a/examples/image_display/main.cpp b/examples/image_display/main.cpp new file mode 100644 index 0000000..972e58d --- /dev/null +++ b/examples/image_display/main.cpp @@ -0,0 +1,173 @@ +/** + * @file main.cpp + * @brief Extra2D 图片显示示例 + * + * 演示如何使用 RenderBackend 抽象接口加载和显示图片 + * 此示例不依赖任何特定渲染后端(如 OpenGL) + */ + +#include +#include + +using namespace extra2d; + +class ImageDisplayScene : public Scene { +public: + void onEnter() override { + // 加载图片 + // 注意:请确保有图片文件在 assets/images/ 目录下 + try { + texture_ = renderer_->loadTexture("assets/images/demo.jpg"); + std::cout << "Image loaded successfully!" << std::endl; + std::cout << " Size: " << texture_->getWidth() << "x" + << texture_->getHeight() << std::endl; + } catch (...) { + std::cerr << "Failed to load image from assets/images/demo.jpg" + << std::endl; + // 尝试使用备用路径 + try { + texture_ = renderer_->loadTexture( + "examples/image_display/assets/images/demo.jpg"); + std::cout << "Image loaded from alternate path!" << std::endl; + } catch (...) { + std::cerr << "Failed to load image from alternate path!" << std::endl; + texture_ = nullptr; + } + } + } + + void onExit() override { texture_.reset(); } + + void onRender(RenderBackend &renderer) override { + Scene::onRender(renderer); + + if (!texture_) { + return; + } + + // 使用 RenderBackend 的抽象接口绘制图片 + // 不依赖任何特定后端(如 OpenGL) + // 自动批处理:无需手动调用 begin/endSpriteBatch + + // 计算图片显示位置(居中显示) + float windowWidth = 1280.0f; + float windowHeight = 720.0f; + float imgWidth = static_cast(texture_->getWidth()); + float imgHeight = static_cast(texture_->getHeight()); + + // 缩放图片以适应窗口(保持宽高比) + float scale = 1.0f; + if (imgWidth > windowWidth * 0.8f || imgHeight > windowHeight * 0.8f) { + float scaleX = (windowWidth * 0.8f) / imgWidth; + float scaleY = (windowHeight * 0.8f) / imgHeight; + scale = std::min(scaleX, scaleY); + } + + float displayWidth = imgWidth * scale; + float displayHeight = imgHeight * scale; + float x = (windowWidth - displayWidth) * 0.5f; + float y = (windowHeight - displayHeight) * 0.5f; + + // 使用 RenderBackend 的 drawSprite 方法绘制图片 + // 参数:纹理、目标矩形、源矩形、颜色、旋转角度、锚点 + Rect destRect(x, y, displayWidth, displayHeight); + Rect srcRect(0, 0, imgWidth, imgHeight); + renderer.drawSprite(*texture_, destRect, srcRect, Colors::White, 0.0f, + Vec2(0, 0)); + + // 注意:无需手动调用 renderer.endSpriteBatch(),帧结束时会自动刷新 + } + + void setRenderer(RenderBackend *renderer) { renderer_ = renderer; } + +private: + Ptr texture_; + RenderBackend *renderer_ = nullptr; +}; + +int main(int argc, char *argv[]) { + (void)argc; + (void)argv; + + std::cout << "Extra2D Image Display Demo - Starting..." << std::endl; + + Application &app = Application::get(); + + // 注册模块 + app.use([](auto &cfg) { + cfg.w = 1280; + cfg.h = 720; + cfg.title = "Extra2D Image Display Demo"; + cfg.priority = 0; + cfg.backend = "glfw"; + }); + + app.use([](auto &cfg) { cfg.priority = 10; }); + + app.use([](auto &cfg) { cfg.priority = 20; }); + + std::cout << "Initializing application..." << std::endl; + if (!app.init()) { + std::cerr << "Failed to initialize application!" << std::endl; + return -1; + } + + std::cout << "Application initialized successfully!" << std::endl; + + auto *win = app.window(); + if (win) { + std::cout << "Window: " << win->width() << "x" << win->height() + << std::endl; + } + + // 设置事件监听 + auto eventService = ServiceLocator::instance().getService(); + if (eventService) { + eventService->addListener(EventType::KeyPressed, [](Event &e) { + auto &keyEvent = std::get(e.data); + if (keyEvent.keyCode == static_cast(Key::Escape)) { + e.handled = true; + Application::get().quit(); + } + }); + } + + // 获取渲染器 + RenderBackend *renderer = app.renderer(); + + // 创建并配置场景 + auto scene = makeShared(); + scene->setRenderer(renderer); + scene->setBackgroundColor(Color(0.1f, 0.1f, 0.15f, 1.0f)); + + if (win) { + scene->setViewportSize(static_cast(win->width()), + static_cast(win->height())); + } + + // 配置相机 + auto cameraService = ServiceLocator::instance().getService(); + if (cameraService && win) { + ViewportConfig vpConfig; + vpConfig.logicWidth = static_cast(win->width()); + vpConfig.logicHeight = static_cast(win->height()); + vpConfig.mode = ViewportMode::AspectRatio; + cameraService->setViewportConfig(vpConfig); + cameraService->updateViewport(win->width(), win->height()); + cameraService->applyViewportAdapter(); + } + + app.enterScene(scene); + + std::cout << "\nControls:" << std::endl; + std::cout << " ESC - Exit" << std::endl; + std::cout << "\nRunning main loop...\n" << std::endl; + + app.run(); + + std::cout << "Shutting down..." << std::endl; + app.shutdown(); + + std::cout << "Goodbye!" << std::endl; + return 0; +} diff --git a/examples/text_rendering/assets/fonts/arial.ttf b/examples/text_rendering/assets/fonts/arial.ttf new file mode 100644 index 0000000..8682d94 Binary files /dev/null and b/examples/text_rendering/assets/fonts/arial.ttf differ diff --git a/examples/text_rendering/main.cpp b/examples/text_rendering/main.cpp new file mode 100644 index 0000000..e41671b --- /dev/null +++ b/examples/text_rendering/main.cpp @@ -0,0 +1,210 @@ +/** + * @file main.cpp + * @brief Extra2D 文字渲染示例 + * + * 演示如何使用 RenderBackend 抽象接口渲染文字 + * 此示例不依赖任何特定渲染后端(如 OpenGL) + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +using namespace extra2d; + +class TextRenderingScene : public Scene { +public: + void onEnter() override { + // 加载字体 + // 注意:请确保有字体文件在 assets/fonts/ 目录下 + // 如果没有,可以使用系统字体路径 + if (!renderer_) { + std::cerr << "Renderer not available!" << std::endl; + return; + } + + try { + font_ = renderer_->createFontAtlas("assets/fonts/arial.ttf", 24); + } catch (...) { + std::cerr << "Failed to load font from assets/fonts/arial.ttf" + << std::endl; + // 尝试使用备用路径 + try { + font_ = renderer_->createFontAtlas("C:/Windows/Fonts/arial.ttf", 24); + } catch (...) { + std::cerr << "Failed to load font from system path!" << std::endl; + font_ = nullptr; + } + } + + if (font_) { + std::cout << "Font loaded successfully!" << std::endl; + std::cout << " Font size: " << font_->getFontSize() << std::endl; + std::cout << " Line height: " << font_->getLineHeight() << std::endl; + std::cout << " Ascent: " << font_->getAscent() << std::endl; + std::cout << " Descent: " << font_->getDescent() << std::endl; + } + } + + void onExit() override { font_.reset(); } + + void onRender(RenderBackend &renderer) override { + Scene::onRender(renderer); + + if (!font_) { + return; + } + + float y = 100.0f; + float x = 100.0f; + + // 渲染标题(自动批处理,无需手动调用 begin/end) + renderText(renderer, "Extra2D Text Rendering Demo", x, y, + Color(1.0f, 0.8f, 0.2f, 1.0f)); + y += font_->getLineHeight() * 2; + + // 渲染不同颜色的文字 + renderText(renderer, "Red Text", x, y, Color(1.0f, 0.2f, 0.2f, 1.0f)); + y += font_->getLineHeight(); + + renderText(renderer, "Green Text", x, y, Color(0.2f, 1.0f, 0.2f, 1.0f)); + y += font_->getLineHeight(); + + renderText(renderer, "Blue Text", x, y, Color(0.2f, 0.2f, 1.0f, 1.0f)); + y += font_->getLineHeight() * 2; + + // 渲染多行文字 + renderText(renderer, "This is a multi-line text example.", x, y, + Color(1.0f, 1.0f, 1.0f, 1.0f)); + y += font_->getLineHeight(); + renderText(renderer, "You can render text with different colors", x, y, + Color(0.8f, 0.8f, 0.8f, 1.0f)); + y += font_->getLineHeight(); + renderText(renderer, "and styles using FontAtlas.", x, y, + Color(0.6f, 0.6f, 0.6f, 1.0f)); + y += font_->getLineHeight() * 2; + + // 渲染半透明文字 + renderText(renderer, "Semi-transparent text (50% opacity)", x, y, + Color(1.0f, 1.0f, 1.0f, 0.5f)); + y += font_->getLineHeight() * 2; + + // 渲染操作提示 + renderText(renderer, "Press ESC to exit", x, y, + Color(0.5f, 0.5f, 0.5f, 1.0f)); + + // 注意:无需手动调用 renderer.endSpriteBatch(),帧结束时会自动刷新 + } + + void setRenderer(RenderBackend* renderer) { + renderer_ = renderer; + } + +private: + void renderText(RenderBackend &renderer, const std::string &text, float x, + float y, const Color &color) { + if (!font_) { + return; + } + + // 使用 RenderBackend 的 drawText 方法 + renderer.drawText(*font_, text, Vec2(x, y), color); + } + + Ptr font_; + RenderBackend* renderer_ = nullptr; +}; + +int main(int argc, char *argv[]) { + (void)argc; + (void)argv; + + std::cout << "Extra2D Text Rendering Demo - Starting..." << std::endl; + + Application &app = Application::get(); + + // 注册模块 + app.use([](auto &cfg) { + cfg.w = 1280; + cfg.h = 720; + cfg.title = "Extra2D Text Rendering Demo"; + cfg.priority = 0; + cfg.backend = "glfw"; + }); + + app.use([](auto &cfg) { cfg.priority = 10; }); + + app.use([](auto &cfg) { cfg.priority = 20; }); + + std::cout << "Initializing application..." << std::endl; + if (!app.init()) { + std::cerr << "Failed to initialize application!" << std::endl; + return -1; + } + + std::cout << "Application initialized successfully!" << std::endl; + + auto *win = app.window(); + if (win) { + std::cout << "Window: " << win->width() << "x" << win->height() + << std::endl; + } + + // 设置事件监听 + auto eventService = ServiceLocator::instance().getService(); + if (eventService) { + eventService->addListener(EventType::KeyPressed, [](Event &e) { + auto &keyEvent = std::get(e.data); + if (keyEvent.keyCode == static_cast(Key::Escape)) { + e.handled = true; + Application::get().quit(); + } + }); + } + + // 获取渲染器 + RenderBackend* renderer = app.renderer(); + + // 创建并配置场景 + auto scene = makeShared(); + scene->setRenderer(renderer); + scene->setBackgroundColor(Color(0.1f, 0.1f, 0.15f, 1.0f)); + + if (win) { + scene->setViewportSize(static_cast(win->width()), + static_cast(win->height())); + } + + // 配置相机 + auto cameraService = ServiceLocator::instance().getService(); + if (cameraService && win) { + ViewportConfig vpConfig; + vpConfig.logicWidth = static_cast(win->width()); + vpConfig.logicHeight = static_cast(win->height()); + vpConfig.mode = ViewportMode::AspectRatio; + cameraService->setViewportConfig(vpConfig); + cameraService->updateViewport(win->width(), win->height()); + cameraService->applyViewportAdapter(); + } + + app.enterScene(scene); + + std::cout << "\nControls:" << std::endl; + std::cout << " ESC - Exit" << std::endl; + std::cout << "\nRunning main loop...\n" << std::endl; + + app.run(); + + std::cout << "Shutting down..." << std::endl; + app.shutdown(); + + std::cout << "Goodbye!" << std::endl; + return 0; +} diff --git a/xmake.lua b/xmake.lua index f629793..0f41b56 100644 --- a/xmake.lua +++ b/xmake.lua @@ -8,7 +8,7 @@ -- - macOS -- - Nintendo Switch -- --- 窗口后端: SDL2 (统一) +-- 窗口后端: SDL2 / GLFW -- ============================================== -- 项目元信息 @@ -33,6 +33,20 @@ option("debug_logs") set_description("Enable debug logging") option_end() +option("window_backend") + set_default("sdl2") + set_showmenu(true) + set_description("Window backend: sdl2 or glfw") + set_values("sdl2", "glfw") +option_end() + +option("render_backend") + set_default("opengl") + set_showmenu(true) + set_description("Render backend: opengl or vulkan") + set_values("opengl", "vulkan") +option_end() + -- ============================================== -- 平台检测与配置 -- ============================================== @@ -85,18 +99,25 @@ end if target_plat ~= "switch" then add_requires("glm") add_requires("nlohmann_json") - local sdl2_configs = { - configs = { - wayland = false + + -- 窗口后端依赖 + local backend = get_config("window_backend") or "sdl2" + if backend == "glfw" then + add_requires("glfw") + else + local sdl2_configs = { + configs = { + wayland = false + } } - } - if target_plat == "linux" then - local is_wayland = os.getenv("XDG_SESSION_TYPE") == "wayland" - if is_wayland then - sdl2_configs.configs.wayland = true + if target_plat == "linux" then + local is_wayland = os.getenv("XDG_SESSION_TYPE") == "wayland" + if is_wayland then + sdl2_configs.configs.wayland = true + end end + add_requires("libsdl2", sdl2_configs) end - add_requires("libsdl2", sdl2_configs) end @@ -150,14 +171,30 @@ target("demo_basic") -- 平台配置 local plat = get_config("plat") or os.host() + local backend = get_config("window_backend") or "sdl2" if plat == "mingw" or plat == "windows" then - add_packages("glm", "nlohmann_json", "libsdl2") + add_packages("glm", "nlohmann_json") + if backend == "glfw" then + add_packages("glfw") + else + add_packages("libsdl2") + end add_syslinks("opengl32", "glu32", "winmm", "imm32", "version", "setupapi") elseif plat == "linux" then - add_packages("glm", "nlohmann_json", "libsdl2") + add_packages("glm", "nlohmann_json") + if backend == "glfw" then + add_packages("glfw") + else + add_packages("libsdl2") + end add_syslinks("GL", "dl", "pthread") elseif plat == "macosx" then - add_packages("glm", "nlohmann_json", "libsdl2") + add_packages("glm", "nlohmann_json") + if backend == "glfw" then + add_packages("glfw") + else + add_packages("libsdl2") + end add_frameworks("OpenGL", "Cocoa", "IOKit", "CoreVideo") end @@ -165,6 +202,65 @@ target("demo_basic") after_build(install_shaders) target_end() +-- 文字渲染示例 +target("demo_text_rendering") + set_kind("binary") + set_default(false) + + add_deps("extra2d") + add_files("examples/text_rendering/main.cpp") + + -- 平台配置 + local plat = get_config("plat") or os.host() + local backend = get_config("window_backend") or "sdl2" + if plat == "mingw" or plat == "windows" then + add_packages("glm", "nlohmann_json") + if backend == "glfw" then + add_packages("glfw") + else + add_packages("libsdl2") + end + add_syslinks("opengl32", "glu32", "winmm", "imm32", "version", "setupapi") + elseif plat == "linux" then + add_packages("glm", "nlohmann_json") + if backend == "glfw" then + add_packages("glfw") + else + add_packages("libsdl2") + end + add_syslinks("GL", "dl", "pthread") + elseif plat == "macosx" then + add_packages("glm", "nlohmann_json") + if backend == "glfw" then + add_packages("glfw") + else + add_packages("libsdl2") + end + add_frameworks("OpenGL", "Cocoa", "IOKit", "CoreVideo") + end + + -- 构建后安装Shader文件和assets + after_build(function (target) + -- 安装shaders + local plat = get_config("plat") or os.host() + local targetdir = target:targetdir() + local shader_src = "Extra2D/shaders" + local shader_dest = path.join(targetdir, "shaders") + os.rm(shader_dest) + os.cp(shader_src, shader_dest) + print("Shaders installed to: " .. shader_dest) + + -- 复制assets目录 + local assets_src = "examples/text_rendering/assets" + local assets_dest = path.join(targetdir, "assets") + if os.exists(assets_src) then + os.rm(assets_dest) + os.cp(assets_src, assets_dest) + print("Assets installed to: " .. assets_dest) + end + end) +target_end() + -- Hello Module 示例 - 展示如何创建自定义模块 target("demo_hello_module") set_kind("binary") @@ -176,17 +272,92 @@ target("demo_hello_module") -- 平台配置 local plat = get_config("plat") or os.host() + local backend = get_config("window_backend") or "sdl2" if plat == "mingw" or plat == "windows" then - add_packages("glm", "nlohmann_json", "libsdl2") + add_packages("glm", "nlohmann_json") + if backend == "glfw" then + add_packages("glfw") + else + add_packages("libsdl2") + end add_syslinks("opengl32", "glu32", "winmm", "imm32", "version", "setupapi") elseif plat == "linux" then - add_packages("glm", "nlohmann_json", "libsdl2") + add_packages("glm", "nlohmann_json") + if backend == "glfw" then + add_packages("glfw") + else + add_packages("libsdl2") + end add_syslinks("GL", "dl", "pthread") elseif plat == "macosx" then - add_packages("glm", "nlohmann_json", "libsdl2") + add_packages("glm", "nlohmann_json") + if backend == "glfw" then + add_packages("glfw") + else + add_packages("libsdl2") + end add_frameworks("OpenGL", "Cocoa", "IOKit", "CoreVideo") end -- 构建后安装Shader文件 after_build(install_shaders) target_end() + +-- 图片显示示例 +target("demo_image_display") + set_kind("binary") + set_default(false) + + add_deps("extra2d") + add_files("examples/image_display/main.cpp") + + -- 平台配置 + local plat = get_config("plat") or os.host() + local backend = get_config("window_backend") or "sdl2" + if plat == "mingw" or plat == "windows" then + add_packages("glm", "nlohmann_json") + if backend == "glfw" then + add_packages("glfw") + else + add_packages("libsdl2") + end + add_syslinks("opengl32", "glu32", "winmm", "imm32", "version", "setupapi") + elseif plat == "linux" then + add_packages("glm", "nlohmann_json") + if backend == "glfw" then + add_packages("glfw") + else + add_packages("libsdl2") + end + add_syslinks("GL", "dl", "pthread") + elseif plat == "macosx" then + add_packages("glm", "nlohmann_json") + if backend == "glfw" then + add_packages("glfw") + else + add_packages("libsdl2") + end + add_frameworks("OpenGL", "Cocoa", "IOKit", "CoreVideo") + end + + -- 构建后安装Shader文件和assets + after_build(function (target) + -- 安装shaders + local plat = get_config("plat") or os.host() + local targetdir = target:targetdir() + local shader_src = "Extra2D/shaders" + local shader_dest = path.join(targetdir, "shaders") + os.rm(shader_dest) + os.cp(shader_src, shader_dest) + print("Shaders installed to: " .. shader_dest) + + -- 复制assets目录 + local assets_src = "examples/image_display/assets" + local assets_dest = path.join(targetdir, "assets") + if os.exists(assets_src) then + os.rm(assets_dest) + os.cp(assets_src, assets_dest) + print("Assets installed to: " .. assets_dest) + end + end) +target_end() diff --git a/xmake/engine.lua b/xmake/engine.lua index 8dcc08c..96981bd 100644 --- a/xmake/engine.lua +++ b/xmake/engine.lua @@ -2,11 +2,11 @@ -- Extra2D 引擎库共享配置 -- 被主项目和示例共享使用 -- --- 窗口后端统一使用 SDL2,支持以下平台: --- - Windows (MinGW) --- - Linux --- - macOS --- - Nintendo Switch +-- 窗口后端支持 SDL2 和 GLFW: +-- - Windows (MinGW) - 支持 SDL2 和 GLFW +-- - Linux - 支持 SDL2 和 GLFW +-- - macOS - 支持 SDL2 和 GLFW +-- - Nintendo Switch - 使用系统提供的后端 -- ============================================== -- 获取当前平台 @@ -14,18 +14,44 @@ local function get_current_plat() return get_config("plat") or os.host() end +-- 获取窗口后端 +local function get_window_backend() + return get_config("window_backend") or "sdl2" +end + +-- 获取渲染后端 +local function get_render_backend() + return get_config("render_backend") or "opengl" +end + -- 定义 Extra2D 引擎库目标 function define_extra2d_engine() target("extra2d") set_kind("static") - -- 引擎核心源文件 - add_files("Extra2D/src/**.cpp") - add_files("Extra2D/src/glad/glad.c") + -- 引擎核心源文件(后端无关) + add_files("Extra2D/src/**.cpp|platform/backends/**.cpp|graphics/backends/**.cpp") + + -- 渲染后端源文件 + local render_backend = get_render_backend() + if render_backend == "vulkan" then + add_files("Extra2D/src/graphics/backends/vulkan/*.cpp") + add_defines("E2D_BACKEND_VULKAN") + else + add_files("Extra2D/src/graphics/backends/opengl/*.cpp") + add_files("Extra2D/src/glad/glad.c") + add_defines("E2D_BACKEND_OPENGL") + end - -- SDL2 后端源文件(所有平台统一使用) - add_files("Extra2D/src/platform/backends/sdl2/*.cpp") - add_defines("E2D_BACKEND_SDL2") + -- 窗口后端源文件 + local backend = get_window_backend() + if backend == "glfw" then + add_files("Extra2D/src/platform/backends/glfw/*.cpp") + add_defines("E2D_BACKEND_GLFW") + else + add_files("Extra2D/src/platform/backends/sdl2/*.cpp") + add_defines("E2D_BACKEND_SDL2") + end -- 头文件路径 add_includedirs("Extra2D/include", {public = true}) @@ -39,23 +65,48 @@ function define_extra2d_engine() local devkitPro = os.getenv("DEVKITPRO") or "C:/devkitPro" add_includedirs(devkitPro .. "/portlibs/switch/include", {public = true}) add_linkdirs(devkitPro .. "/portlibs/switch/lib") - add_syslinks("SDL2", "GLESv2", "EGL", "glapi", "drm_nouveau", "nx", "m", - {public = true}) + + -- Switch 平台根据后端选择链接库 + local backend = get_window_backend() + if backend == "glfw" then + -- GLFW 后端(使用 libnx 提供的 GLFW) + add_syslinks("glfw", "GLESv2", "EGL", "glapi", "drm_nouveau", "nx", "m", + {public = true}) + else + -- SDL2 后端 + add_syslinks("SDL2", "GLESv2", "EGL", "glapi", "drm_nouveau", "nx", "m", + {public = true}) + end elseif plat == "mingw" or plat == "windows" then -- Windows (MinGW) 平台配置 add_packages("glm", "nlohmann_json", {public = true}) - add_packages("libsdl2", {public = true}) + local backend = get_window_backend() + if backend == "glfw" then + add_packages("glfw", {public = true}) + else + add_packages("libsdl2", {public = true}) + end add_syslinks("opengl32", "glu32", "winmm", "imm32", "version", "setupapi", {public = true}) elseif plat == "linux" then -- Linux 平台配置 add_packages("glm", "nlohmann_json", {public = true}) - add_packages("libsdl2", {public = true}) + local backend = get_window_backend() + if backend == "glfw" then + add_packages("glfw", {public = true}) + else + add_packages("libsdl2", {public = true}) + end add_syslinks("GL", "dl", "pthread", {public = true}) elseif plat == "macosx" then -- macOS 平台配置 add_packages("glm", "nlohmann_json", {public = true}) - add_packages("libsdl2", {public = true}) + local backend = get_window_backend() + if backend == "glfw" then + add_packages("glfw", {public = true}) + else + add_packages("libsdl2", {public = true}) + end add_frameworks("OpenGL", "Cocoa", "IOKit", "CoreVideo", {public = true}) end