Compare commits
17 Commits
d3a8c6c979
...
583e866861
| Author | SHA1 | Date |
|---|---|---|
|
|
583e866861 | |
|
|
edd47a890a | |
|
|
69606230da | |
|
|
6babd376c9 | |
|
|
2748e80dea | |
|
|
c8a6ea19e3 | |
|
|
32e12b8c99 | |
|
|
6b4ce69657 | |
|
|
30b677f192 | |
|
|
89fb955eb0 | |
|
|
f1cf6a6d85 | |
|
|
a4276e4376 | |
|
|
d2660a86bb | |
|
|
61dea772f7 | |
|
|
4f02ad0e39 | |
|
|
4b1de5e36a | |
|
|
9f83b8fde5 |
|
|
@ -1,10 +1,9 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <extra2d/core/types.h>
|
|
||||||
#include <extra2d/core/module.h>
|
#include <extra2d/core/module.h>
|
||||||
#include <extra2d/core/registry.h>
|
#include <extra2d/core/registry.h>
|
||||||
#include <extra2d/core/service_locator.h>
|
#include <extra2d/core/service_locator.h>
|
||||||
#include <extra2d/config/app_config.h>
|
#include <extra2d/core/types.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
@ -26,14 +25,20 @@ public:
|
||||||
Application(const Application &) = delete;
|
Application(const Application &) = delete;
|
||||||
Application &operator=(const Application &) = delete;
|
Application &operator=(const Application &) = delete;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 应用信息
|
||||||
|
*/
|
||||||
|
std::string appName = "Extra2D App";
|
||||||
|
std::string appVersion = "1.0.0";
|
||||||
|
std::string organization = "";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 注册模块
|
* @brief 注册模块
|
||||||
* @tparam T 模块类型
|
* @tparam T 模块类型
|
||||||
* @tparam Args 构造函数参数
|
* @tparam Args 构造函数参数
|
||||||
* @return 模块指针
|
* @return 模块指针
|
||||||
*/
|
*/
|
||||||
template<typename T, typename... Args>
|
template <typename T, typename... Args> T *use(Args &&...args) {
|
||||||
T* use(Args&&... args) {
|
|
||||||
return Registry::instance().use<T>(std::forward<Args>(args)...);
|
return Registry::instance().use<T>(std::forward<Args>(args)...);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -42,10 +47,7 @@ public:
|
||||||
* @tparam T 模块类型
|
* @tparam T 模块类型
|
||||||
* @return 模块指针
|
* @return 模块指针
|
||||||
*/
|
*/
|
||||||
template<typename T>
|
template <typename T> T *get() const { return Registry::instance().get<T>(); }
|
||||||
T* get() const {
|
|
||||||
return Registry::instance().get<T>();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 初始化
|
* @brief 初始化
|
||||||
|
|
@ -53,13 +55,6 @@ public:
|
||||||
*/
|
*/
|
||||||
bool init();
|
bool init();
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 初始化(带配置)
|
|
||||||
* @param config 应用配置
|
|
||||||
* @return 初始化成功返回 true
|
|
||||||
*/
|
|
||||||
bool init(const AppConfig& config);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 关闭
|
* @brief 关闭
|
||||||
*/
|
*/
|
||||||
|
|
@ -123,7 +118,7 @@ private:
|
||||||
void mainLoop();
|
void mainLoop();
|
||||||
void update();
|
void update();
|
||||||
void render();
|
void render();
|
||||||
void registerCoreServices();
|
void configureCameraService();
|
||||||
|
|
||||||
bool initialized_ = false;
|
bool initialized_ = false;
|
||||||
bool running_ = false;
|
bool running_ = false;
|
||||||
|
|
@ -136,8 +131,6 @@ private:
|
||||||
int frameCount_ = 0;
|
int frameCount_ = 0;
|
||||||
float fpsTimer_ = 0.0f;
|
float fpsTimer_ = 0.0f;
|
||||||
int currentFps_ = 0;
|
int currentFps_ = 0;
|
||||||
|
|
||||||
AppConfig appConfig_;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace extra2d
|
} // namespace extra2d
|
||||||
|
|
|
||||||
|
|
@ -1,61 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <extra2d/config/platform_config.h>
|
|
||||||
#include <extra2d/core/types.h>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @file app_config.h
|
|
||||||
* @brief 应用级别配置
|
|
||||||
*
|
|
||||||
* 本文件仅包含应用级别的配置项,不包含任何模块特定配置。
|
|
||||||
* 各模块应该在自己的模块文件中定义配置结构,并实现 IModuleConfig 接口。
|
|
||||||
*
|
|
||||||
* 模块配置通过 ModuleRegistry 注册,由 ConfigManager 统一管理。
|
|
||||||
* 这种设计遵循开闭原则,新增模块无需修改引擎核心代码。
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 应用配置结构体
|
|
||||||
* 仅包含应用级别的配置项,模块配置由各模块自行管理
|
|
||||||
*/
|
|
||||||
struct AppConfig {
|
|
||||||
std::string appName = "Extra2D App";
|
|
||||||
std::string appVersion = "1.0.0";
|
|
||||||
std::string organization = "";
|
|
||||||
std::string configFile = "config.json";
|
|
||||||
PlatformType targetPlatform = PlatformType::Auto;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 创建默认配置
|
|
||||||
* @return 默认的应用配置实例
|
|
||||||
*/
|
|
||||||
static AppConfig createDefault();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 验证配置的有效性
|
|
||||||
* @return 如果配置有效返回 true,否则返回 false
|
|
||||||
*/
|
|
||||||
bool validate() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 重置为默认值
|
|
||||||
*/
|
|
||||||
void reset();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 合并另一个配置(非默认值覆盖当前值)
|
|
||||||
* @param other 要合并的配置
|
|
||||||
*/
|
|
||||||
void merge(const AppConfig& other);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查配置是否有效
|
|
||||||
* @return 如果所有必要字段都有效返回 true
|
|
||||||
*/
|
|
||||||
bool isValid() const { return validate(); }
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,87 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <extra2d/core/types.h>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @file platform_config.h
|
|
||||||
* @brief 平台配置接口
|
|
||||||
*
|
|
||||||
* 平台配置只提供平台能力信息,不再直接修改应用配置。
|
|
||||||
* 各模块通过 IModuleConfig::applyPlatformConstraints() 处理平台约束。
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 平台类型枚举
|
|
||||||
*/
|
|
||||||
enum class PlatformType {
|
|
||||||
Auto,
|
|
||||||
Windows,
|
|
||||||
Switch,
|
|
||||||
Linux,
|
|
||||||
macOS
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 平台能力结构
|
|
||||||
*/
|
|
||||||
struct PlatformCapabilities {
|
|
||||||
bool supportsWindowed = true;
|
|
||||||
bool supportsFullscreen = true;
|
|
||||||
bool supportsBorderless = true;
|
|
||||||
bool supportsCursor = true;
|
|
||||||
bool supportsCursorHide = true;
|
|
||||||
bool supportsDPIAwareness = true;
|
|
||||||
bool supportsVSync = true;
|
|
||||||
bool supportsMultiMonitor = true;
|
|
||||||
bool supportsClipboard = true;
|
|
||||||
bool supportsGamepad = true;
|
|
||||||
bool supportsTouch = false;
|
|
||||||
bool supportsKeyboard = true;
|
|
||||||
bool supportsMouse = true;
|
|
||||||
bool supportsResize = true;
|
|
||||||
bool supportsHighDPI = true;
|
|
||||||
int maxTextureSize = 16384;
|
|
||||||
int preferredScreenWidth = 1920;
|
|
||||||
int preferredScreenHeight = 1080;
|
|
||||||
float defaultDPI = 96.0f;
|
|
||||||
|
|
||||||
bool hasWindowSupport() const { return supportsWindowed || supportsFullscreen || supportsBorderless; }
|
|
||||||
bool hasInputSupport() const { return supportsKeyboard || supportsMouse || supportsGamepad || supportsTouch; }
|
|
||||||
bool isDesktop() const { return supportsKeyboard && supportsMouse && supportsWindowed; }
|
|
||||||
bool isConsole() const { return !supportsWindowed && supportsGamepad; }
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 平台配置抽象接口
|
|
||||||
*/
|
|
||||||
class PlatformConfig {
|
|
||||||
public:
|
|
||||||
virtual ~PlatformConfig() = default;
|
|
||||||
|
|
||||||
virtual PlatformType platformType() const = 0;
|
|
||||||
virtual const char* platformName() const = 0;
|
|
||||||
virtual const PlatformCapabilities& capabilities() const = 0;
|
|
||||||
|
|
||||||
virtual int getRecommendedWidth() const = 0;
|
|
||||||
virtual int getRecommendedHeight() const = 0;
|
|
||||||
virtual bool isResolutionSupported(int width, int height) const = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 创建平台配置实例
|
|
||||||
* @param type 平台类型,默认为 Auto(自动检测)
|
|
||||||
* @return 平台配置的智能指针
|
|
||||||
*/
|
|
||||||
UniquePtr<PlatformConfig> createPlatformConfig(PlatformType type = PlatformType::Auto);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取平台类型名称
|
|
||||||
* @param type 平台类型枚举值
|
|
||||||
* @return 平台名称字符串
|
|
||||||
*/
|
|
||||||
const char* getPlatformTypeName(PlatformType type);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,210 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <extra2d/config/app_config.h>
|
|
||||||
#include <extra2d/config/platform_config.h>
|
|
||||||
#include <extra2d/core/types.h>
|
|
||||||
#include <string>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 平台检测器工具类
|
|
||||||
// ============================================================================
|
|
||||||
class PlatformDetector {
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief 检测当前运行平台
|
|
||||||
* @return 当前平台的类型
|
|
||||||
*/
|
|
||||||
static PlatformType detect();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取平台名称字符串
|
|
||||||
* @return 平台名称(如 "Windows", "Linux", "macOS", "Switch")
|
|
||||||
*/
|
|
||||||
static const char* platformName();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取指定平台类型的名称
|
|
||||||
* @param type 平台类型
|
|
||||||
* @return 平台名称字符串
|
|
||||||
*/
|
|
||||||
static const char* platformName(PlatformType type);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查当前平台是否为桌面平台
|
|
||||||
* @return 如果是桌面平台返回 true
|
|
||||||
*/
|
|
||||||
static bool isDesktopPlatform();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查当前平台是否为游戏主机平台
|
|
||||||
* @return 如果是游戏主机平台返回 true
|
|
||||||
*/
|
|
||||||
static bool isConsolePlatform();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查当前平台是否为移动平台
|
|
||||||
* @return 如果是移动平台返回 true
|
|
||||||
*/
|
|
||||||
static bool isMobilePlatform();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取当前平台的能力
|
|
||||||
* @return 平台能力结构
|
|
||||||
*/
|
|
||||||
static PlatformCapabilities capabilities();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取指定平台的能力
|
|
||||||
* @param type 平台类型
|
|
||||||
* @return 平台能力结构
|
|
||||||
*/
|
|
||||||
static PlatformCapabilities capabilities(PlatformType type);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取当前平台的默认配置
|
|
||||||
* @return 平台默认的应用配置
|
|
||||||
*/
|
|
||||||
static AppConfig platformDefaults();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取指定平台的默认配置
|
|
||||||
* @param type 平台类型
|
|
||||||
* @return 平台默认的应用配置
|
|
||||||
*/
|
|
||||||
static AppConfig platformDefaults(PlatformType type);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取当前平台的推荐分辨率
|
|
||||||
* @param width 输出宽度
|
|
||||||
* @param height 输出高度
|
|
||||||
*/
|
|
||||||
static void getRecommendedResolution(int& width, int& height);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取当前平台的默认 DPI
|
|
||||||
* @return 默认 DPI 值
|
|
||||||
*/
|
|
||||||
static float getDefaultDPI();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查当前平台是否支持指定功能
|
|
||||||
* @param feature 功能名称
|
|
||||||
* @return 如果支持返回 true
|
|
||||||
*/
|
|
||||||
static bool supportsFeature(const std::string& feature);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取系统内存大小
|
|
||||||
* @return 系统内存大小(MB),如果无法获取返回 0
|
|
||||||
*/
|
|
||||||
static int getSystemMemoryMB();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取 CPU 核心数
|
|
||||||
* @return CPU 核心数
|
|
||||||
*/
|
|
||||||
static int getCPUCoreCount();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查是否支持多线程渲染
|
|
||||||
* @return 如果支持返回 true
|
|
||||||
*/
|
|
||||||
static bool supportsMultithreadedRendering();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取平台特定的配置路径
|
|
||||||
* @param appName 应用名称
|
|
||||||
* @return 配置文件目录路径
|
|
||||||
*/
|
|
||||||
static std::string getConfigPath(const std::string& appName);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取平台特定的存档路径
|
|
||||||
* @param appName 应用名称
|
|
||||||
* @return 存档文件目录路径
|
|
||||||
*/
|
|
||||||
static std::string getSavePath(const std::string& appName);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取平台特定的缓存路径
|
|
||||||
* @param appName 应用名称
|
|
||||||
* @return 缓存文件目录路径
|
|
||||||
*/
|
|
||||||
static std::string getCachePath(const std::string& appName);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取平台特定的日志路径
|
|
||||||
* @param appName 应用名称
|
|
||||||
* @return 日志文件目录路径
|
|
||||||
*/
|
|
||||||
static std::string getLogPath(const std::string& appName);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取平台特定的资源路径(Shader、纹理等)
|
|
||||||
* Switch平台使用romfs,其他平台使用相对路径
|
|
||||||
* @param appName 应用名称
|
|
||||||
* @return 资源目录路径
|
|
||||||
*/
|
|
||||||
static std::string getResourcePath(const std::string& appName = "");
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取平台特定的Shader路径
|
|
||||||
* @param appName 应用名称
|
|
||||||
* @return Shader目录路径
|
|
||||||
*/
|
|
||||||
static std::string getShaderPath(const std::string& appName = "");
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取平台特定的Shader缓存路径
|
|
||||||
* Switch平台使用sdmc,其他平台使用系统缓存目录
|
|
||||||
* @param appName 应用名称
|
|
||||||
* @return Shader缓存目录路径
|
|
||||||
*/
|
|
||||||
static std::string getShaderCachePath(const std::string& appName = "");
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查平台是否使用romfs(只读文件系统)
|
|
||||||
* @return 使用romfs返回true
|
|
||||||
*/
|
|
||||||
static bool usesRomfs();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查平台是否支持热重载
|
|
||||||
* Switch平台不支持热重载(romfs只读)
|
|
||||||
* @return 支持热重载返回true
|
|
||||||
*/
|
|
||||||
static bool supportsHotReload();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查平台是否为小端字节序
|
|
||||||
* @return 如果是小端字节序返回 true
|
|
||||||
*/
|
|
||||||
static bool isLittleEndian();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查平台是否为大端字节序
|
|
||||||
* @return 如果是大端字节序返回 true
|
|
||||||
*/
|
|
||||||
static bool isBigEndian();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取平台信息摘要
|
|
||||||
* @return 平台信息字符串
|
|
||||||
*/
|
|
||||||
static std::string getPlatformSummary();
|
|
||||||
|
|
||||||
private:
|
|
||||||
static PlatformCapabilities getWindowsCapabilities();
|
|
||||||
static PlatformCapabilities getLinuxCapabilities();
|
|
||||||
static PlatformCapabilities getMacOSCapabilities();
|
|
||||||
static PlatformCapabilities getSwitchCapabilities();
|
|
||||||
|
|
||||||
static AppConfig getWindowsDefaults();
|
|
||||||
static AppConfig getLinuxDefaults();
|
|
||||||
static AppConfig getMacOSDefaults();
|
|
||||||
static AppConfig getSwitchDefaults();
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,22 +1,21 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <extra2d/core/service_interface.h>
|
#include <extra2d/core/service_interface.h>
|
||||||
#include <extra2d/core/types.h>
|
#include <extra2d/core/types.h>
|
||||||
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
#include <typeindex>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <mutex>
|
|
||||||
#include <functional>
|
|
||||||
#include <typeindex>
|
|
||||||
#include <memory>
|
|
||||||
#include <algorithm>
|
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 服务工厂函数类型
|
* @brief 服务工厂函数类型
|
||||||
*/
|
*/
|
||||||
template<typename T>
|
template <typename T> using ServiceFactory = std::function<SharedPtr<T>()>;
|
||||||
using ServiceFactory = std::function<SharedPtr<T>()>;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 服务定位器
|
* @brief 服务定位器
|
||||||
|
|
@ -45,8 +44,7 @@ public:
|
||||||
* @tparam T 服务接口类型
|
* @tparam T 服务接口类型
|
||||||
* @param service 服务实例
|
* @param service 服务实例
|
||||||
*/
|
*/
|
||||||
template<typename T>
|
template <typename T> void registerService(SharedPtr<T> service) {
|
||||||
void registerService(SharedPtr<T> service) {
|
|
||||||
static_assert(std::is_base_of_v<IService, T>,
|
static_assert(std::is_base_of_v<IService, T>,
|
||||||
"T must derive from IService");
|
"T must derive from IService");
|
||||||
|
|
||||||
|
|
@ -62,8 +60,7 @@ public:
|
||||||
* @tparam T 服务接口类型
|
* @tparam T 服务接口类型
|
||||||
* @param factory 服务工厂函数
|
* @param factory 服务工厂函数
|
||||||
*/
|
*/
|
||||||
template<typename T>
|
template <typename T> void registerFactory(ServiceFactory<T> factory) {
|
||||||
void registerFactory(ServiceFactory<T> factory) {
|
|
||||||
static_assert(std::is_base_of_v<IService, T>,
|
static_assert(std::is_base_of_v<IService, T>,
|
||||||
"T must derive from IService");
|
"T must derive from IService");
|
||||||
|
|
||||||
|
|
@ -72,6 +69,12 @@ public:
|
||||||
factories_[typeId] = [factory]() -> SharedPtr<IService> {
|
factories_[typeId] = [factory]() -> SharedPtr<IService> {
|
||||||
return std::static_pointer_cast<IService>(factory());
|
return std::static_pointer_cast<IService>(factory());
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 立即创建服务实例并添加到有序列表
|
||||||
|
auto service = factories_[typeId]();
|
||||||
|
services_[typeId] = service;
|
||||||
|
orderedServices_.push_back(service);
|
||||||
|
sortServices();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -79,8 +82,7 @@ public:
|
||||||
* @tparam T 服务接口类型
|
* @tparam T 服务接口类型
|
||||||
* @return 服务实例,不存在返回 nullptr
|
* @return 服务实例,不存在返回 nullptr
|
||||||
*/
|
*/
|
||||||
template<typename T>
|
template <typename T> SharedPtr<T> getService() const {
|
||||||
SharedPtr<T> getService() const {
|
|
||||||
static_assert(std::is_base_of_v<IService, T>,
|
static_assert(std::is_base_of_v<IService, T>,
|
||||||
"T must derive from IService");
|
"T must derive from IService");
|
||||||
|
|
||||||
|
|
@ -107,8 +109,7 @@ public:
|
||||||
* @tparam T 服务接口类型
|
* @tparam T 服务接口类型
|
||||||
* @return 服务实例,不存在返回 nullptr
|
* @return 服务实例,不存在返回 nullptr
|
||||||
*/
|
*/
|
||||||
template<typename T>
|
template <typename T> SharedPtr<T> tryGetService() const {
|
||||||
SharedPtr<T> tryGetService() const {
|
|
||||||
static_assert(std::is_base_of_v<IService, T>,
|
static_assert(std::is_base_of_v<IService, T>,
|
||||||
"T must derive from IService");
|
"T must derive from IService");
|
||||||
|
|
||||||
|
|
@ -126,8 +127,7 @@ public:
|
||||||
* @tparam T 服务接口类型
|
* @tparam T 服务接口类型
|
||||||
* @return 已注册返回 true
|
* @return 已注册返回 true
|
||||||
*/
|
*/
|
||||||
template<typename T>
|
template <typename T> bool hasService() const {
|
||||||
bool hasService() const {
|
|
||||||
std::lock_guard<std::mutex> lock(mutex_);
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
auto typeId = std::type_index(typeid(T));
|
auto typeId = std::type_index(typeid(T));
|
||||||
return services_.find(typeId) != services_.end() ||
|
return services_.find(typeId) != services_.end() ||
|
||||||
|
|
@ -138,8 +138,7 @@ public:
|
||||||
* @brief 注销服务
|
* @brief 注销服务
|
||||||
* @tparam T 服务接口类型
|
* @tparam T 服务接口类型
|
||||||
*/
|
*/
|
||||||
template<typename T>
|
template <typename T> void unregisterService() {
|
||||||
void unregisterService() {
|
|
||||||
std::lock_guard<std::mutex> lock(mutex_);
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
auto typeId = std::type_index(typeid(T));
|
auto typeId = std::type_index(typeid(T));
|
||||||
|
|
||||||
|
|
@ -148,8 +147,8 @@ public:
|
||||||
auto service = it->second;
|
auto service = it->second;
|
||||||
services_.erase(it);
|
services_.erase(it);
|
||||||
|
|
||||||
auto orderIt = std::find(orderedServices_.begin(),
|
auto orderIt =
|
||||||
orderedServices_.end(), service);
|
std::find(orderedServices_.begin(), orderedServices_.end(), service);
|
||||||
if (orderIt != orderedServices_.end()) {
|
if (orderIt != orderedServices_.end()) {
|
||||||
orderedServices_.erase(orderIt);
|
orderedServices_.erase(orderIt);
|
||||||
}
|
}
|
||||||
|
|
@ -212,7 +211,8 @@ private:
|
||||||
void sortServices();
|
void sortServices();
|
||||||
|
|
||||||
mutable std::unordered_map<std::type_index, SharedPtr<IService>> services_;
|
mutable std::unordered_map<std::type_index, SharedPtr<IService>> services_;
|
||||||
std::unordered_map<std::type_index, std::function<SharedPtr<IService>()>> factories_;
|
std::unordered_map<std::type_index, std::function<SharedPtr<IService>()>>
|
||||||
|
factories_;
|
||||||
std::vector<SharedPtr<IService>> orderedServices_;
|
std::vector<SharedPtr<IService>> orderedServices_;
|
||||||
mutable std::mutex mutex_;
|
mutable std::mutex mutex_;
|
||||||
};
|
};
|
||||||
|
|
@ -221,8 +221,7 @@ private:
|
||||||
* @brief 服务注册器
|
* @brief 服务注册器
|
||||||
* 用于静态注册服务
|
* 用于静态注册服务
|
||||||
*/
|
*/
|
||||||
template<typename Interface, typename Implementation>
|
template <typename Interface, typename Implementation> class ServiceRegistrar {
|
||||||
class ServiceRegistrar {
|
|
||||||
public:
|
public:
|
||||||
explicit ServiceRegistrar(ServiceFactory<Interface> factory = nullptr) {
|
explicit ServiceRegistrar(ServiceFactory<Interface> factory = nullptr) {
|
||||||
if (factory) {
|
if (factory) {
|
||||||
|
|
@ -231,22 +230,73 @@ public:
|
||||||
ServiceLocator::instance().registerFactory<Interface>(
|
ServiceLocator::instance().registerFactory<Interface>(
|
||||||
[]() -> SharedPtr<Interface> {
|
[]() -> SharedPtr<Interface> {
|
||||||
return makeShared<Implementation>();
|
return makeShared<Implementation>();
|
||||||
}
|
});
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
/**
|
||||||
|
* @brief 服务注册元数据模板
|
||||||
|
* 使用模板元编程实现编译期服务注册
|
||||||
|
* 通过静态成员变量的初始化触发注册
|
||||||
|
*/
|
||||||
|
template <typename Interface, typename Implementation> struct ServiceAutoReg {
|
||||||
|
/**
|
||||||
|
* @brief 注册标记,访问此变量时触发服务注册
|
||||||
|
*/
|
||||||
|
static const bool registered;
|
||||||
|
|
||||||
#define E2D_REGISTER_SERVICE(Interface, Implementation) \
|
/**
|
||||||
namespace { \
|
* @brief 执行实际的服务注册
|
||||||
static ::extra2d::ServiceRegistrar<Interface, Implementation> \
|
* @return true 表示注册成功
|
||||||
E2D_CONCAT(service_registrar_, __LINE__); \
|
*/
|
||||||
|
static bool doRegister() {
|
||||||
|
::extra2d::ServiceLocator::instance().registerFactory<Interface>(
|
||||||
|
[]() -> ::extra2d::SharedPtr<Interface> {
|
||||||
|
return ::extra2d::makeShared<Implementation>();
|
||||||
|
});
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
#define E2D_REGISTER_SERVICE_FACTORY(Interface, Factory) \
|
// 静态成员定义,在此处触发注册
|
||||||
namespace { \
|
template <typename Interface, typename Implementation>
|
||||||
static ::extra2d::ServiceRegistrar<Interface, Interface> \
|
const bool ServiceAutoReg<Interface, Implementation>::registered =
|
||||||
E2D_CONCAT(service_factory_registrar_, __LINE__)(Factory); \
|
ServiceAutoReg<Interface, Implementation>::doRegister();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 服务注册元数据(带自定义工厂)
|
||||||
|
*/
|
||||||
|
template <typename Interface> struct ServiceAutoRegFactory {
|
||||||
|
template <typename Factory> struct Impl {
|
||||||
|
static const bool registered;
|
||||||
|
|
||||||
|
static bool doRegister(Factory factory) {
|
||||||
|
::extra2d::ServiceLocator::instance().registerFactory<Interface>(factory);
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
template <typename Interface>
|
||||||
|
template <typename Factory>
|
||||||
|
const bool ServiceAutoRegFactory<Interface>::Impl<Factory>::registered =
|
||||||
|
ServiceAutoRegFactory<Interface>::Impl<Factory>::doRegister(Factory{});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 自动注册服务宏(元数据驱动)
|
||||||
|
* 在服务实现类中使用,通过模板元编程实现自动注册
|
||||||
|
* 比静态对象更可靠,不易被编译器优化
|
||||||
|
*/
|
||||||
|
#define E2D_AUTO_REGISTER_SERVICE(Interface, Implementation) \
|
||||||
|
static inline const bool E2D_CONCAT(_service_reg_, __LINE__) = \
|
||||||
|
ServiceAutoReg<Interface, Implementation>::registered
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 带自定义工厂的自动注册服务宏(元数据驱动)
|
||||||
|
*/
|
||||||
|
#define E2D_AUTO_REGISTER_SERVICE_FACTORY(Interface, Factory) \
|
||||||
|
static inline const bool E2D_CONCAT(_service_factory_reg_, __LINE__) = \
|
||||||
|
ServiceAutoRegFactory<Interface>::Impl<Factory>::registered
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
|
||||||
|
|
@ -10,10 +10,7 @@
|
||||||
#include <extra2d/core/module.h>
|
#include <extra2d/core/module.h>
|
||||||
#include <extra2d/core/registry.h>
|
#include <extra2d/core/registry.h>
|
||||||
|
|
||||||
// Config
|
// Config removed - app info now in Application class
|
||||||
#include <extra2d/config/app_config.h>
|
|
||||||
#include <extra2d/config/platform_config.h>
|
|
||||||
#include <extra2d/config/platform_detector.h>
|
|
||||||
|
|
||||||
// Platform
|
// Platform
|
||||||
#include <extra2d/platform/iinput.h>
|
#include <extra2d/platform/iinput.h>
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/graphics/core/render_backend.h>
|
||||||
|
#include <extra2d/core/smart_ptr.h>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 渲染后端类型枚举
|
||||||
|
*/
|
||||||
|
enum class BackendType {
|
||||||
|
OpenGL, // OpenGL 4.x
|
||||||
|
Vulkan, // Vulkan 1.x
|
||||||
|
Metal, // Metal (macOS/iOS)
|
||||||
|
D3D11, // Direct3D 11
|
||||||
|
D3D12, // Direct3D 12
|
||||||
|
OpenGLES, // OpenGL ES (移动平台)
|
||||||
|
Count
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 后端工厂类,用于创建渲染后端实例
|
||||||
|
*/
|
||||||
|
class BackendFactory {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief 获取后端工厂单例
|
||||||
|
* @return 后端工厂实例引用
|
||||||
|
*/
|
||||||
|
static BackendFactory& getInstance();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 创建渲染后端
|
||||||
|
* @param type 后端类型
|
||||||
|
* @return 渲染后端实例
|
||||||
|
*/
|
||||||
|
UniquePtr<RenderBackend> createBackend(BackendType type);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 创建默认渲染后端
|
||||||
|
* @return 默认渲染后端实例
|
||||||
|
*/
|
||||||
|
UniquePtr<RenderBackend> createDefaultBackend();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查后端是否可用
|
||||||
|
* @param type 后端类型
|
||||||
|
* @return 可用返回true,否则返回false
|
||||||
|
*/
|
||||||
|
bool isBackendAvailable(BackendType type) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取当前平台推荐的后端类型
|
||||||
|
* @return 推荐的后端类型
|
||||||
|
*/
|
||||||
|
BackendType getRecommendedBackend() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取后端类型名称
|
||||||
|
* @param type 后端类型
|
||||||
|
* @return 后端类型名称字符串
|
||||||
|
*/
|
||||||
|
const char* getBackendName(BackendType type) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 从名称解析后端类型
|
||||||
|
* @param name 后端类型名称
|
||||||
|
* @return 后端类型,如果未知则返回OpenGL
|
||||||
|
*/
|
||||||
|
BackendType parseBackendType(const char* name) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
BackendFactory() = default;
|
||||||
|
~BackendFactory() = default;
|
||||||
|
BackendFactory(const BackendFactory&) = delete;
|
||||||
|
BackendFactory& operator=(const BackendFactory&) = delete;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,83 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/graphics/resources/buffer.h>
|
||||||
|
#include <glad/glad.h>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// OpenGL 缓冲区实现
|
||||||
|
// ============================================================================
|
||||||
|
class GLBuffer : public Buffer {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief 构造函数
|
||||||
|
*/
|
||||||
|
GLBuffer();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 析构函数
|
||||||
|
*/
|
||||||
|
~GLBuffer() override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 初始化缓冲区
|
||||||
|
* @param desc 缓冲区描述
|
||||||
|
* @return 成功返回 true
|
||||||
|
*/
|
||||||
|
bool init(const BufferDesc& desc);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 关闭缓冲区,释放资源
|
||||||
|
*/
|
||||||
|
void shutdown();
|
||||||
|
|
||||||
|
// Buffer 接口实现
|
||||||
|
void bind() override;
|
||||||
|
void unbind() override;
|
||||||
|
void setData(const void* data, size_t size) override;
|
||||||
|
void updateData(const void* data, size_t offset, size_t size) override;
|
||||||
|
void* map() override;
|
||||||
|
void unmap() override;
|
||||||
|
size_t getSize() const override { return size_; }
|
||||||
|
BufferType getType() const override { return type_; }
|
||||||
|
BufferUsage getUsage() const override { return usage_; }
|
||||||
|
bool isValid() const override { return bufferID_ != 0; }
|
||||||
|
uintptr_t getNativeHandle() const override { return static_cast<uintptr_t>(bufferID_); }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取 OpenGL 缓冲区 ID
|
||||||
|
* @return 缓冲区 ID
|
||||||
|
*/
|
||||||
|
GLuint getBufferID() const { return bufferID_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取 OpenGL 缓冲区目标类型
|
||||||
|
* @return 缓冲区目标类型
|
||||||
|
*/
|
||||||
|
GLenum getTarget() const { return target_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
GLuint bufferID_ = 0;
|
||||||
|
GLenum target_ = GL_ARRAY_BUFFER;
|
||||||
|
size_t size_ = 0;
|
||||||
|
BufferType type_ = BufferType::Vertex;
|
||||||
|
BufferUsage usage_ = BufferUsage::Static;
|
||||||
|
GLenum glUsage_ = GL_STATIC_DRAW;
|
||||||
|
bool mapped_ = false;
|
||||||
|
void* mappedPtr_ = nullptr;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 转换使用模式到 OpenGL 枚举
|
||||||
|
*/
|
||||||
|
static GLenum convertUsage(BufferUsage usage);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 转换缓冲区类型到 OpenGL 目标
|
||||||
|
*/
|
||||||
|
static GLenum convertType(BufferType type);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,139 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <glad/glad.h>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// OpenGL 版本信息
|
||||||
|
// ============================================================================
|
||||||
|
struct GLVersion {
|
||||||
|
int major = 0;
|
||||||
|
int minor = 0;
|
||||||
|
bool es = false; // 是否为 ES 版本
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// OpenGL 上下文管理类
|
||||||
|
// ============================================================================
|
||||||
|
class GLContext {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief 获取全局 GLContext 实例
|
||||||
|
*/
|
||||||
|
static GLContext& get();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 初始化 OpenGL 上下文
|
||||||
|
* @return 成功返回 true
|
||||||
|
*/
|
||||||
|
bool init();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 关闭 OpenGL 上下文
|
||||||
|
*/
|
||||||
|
void shutdown();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查上下文是否有效
|
||||||
|
* @return 有效返回 true
|
||||||
|
*/
|
||||||
|
bool isValid() const { return initialized_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取 OpenGL 版本信息
|
||||||
|
*/
|
||||||
|
const GLVersion& getVersion() const { return version_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取 OpenGL 版本字符串
|
||||||
|
*/
|
||||||
|
std::string getVersionString() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取 GPU 厂商信息
|
||||||
|
*/
|
||||||
|
std::string getVendor() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取 GPU 渲染器信息
|
||||||
|
*/
|
||||||
|
std::string getRenderer() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查是否支持指定扩展
|
||||||
|
* @param extension 扩展名称
|
||||||
|
* @return 支持返回 true
|
||||||
|
*/
|
||||||
|
bool hasExtension(const std::string& extension) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取最大纹理尺寸
|
||||||
|
*/
|
||||||
|
int getMaxTextureSize() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取最大纹理单元数
|
||||||
|
*/
|
||||||
|
int getMaxTextureUnits() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取最大顶点属性数
|
||||||
|
*/
|
||||||
|
int getMaxVertexAttribs() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取最大 uniform 缓冲区绑定点数
|
||||||
|
*/
|
||||||
|
int getMaxUniformBufferBindings() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查是否为 OpenGL ES
|
||||||
|
*/
|
||||||
|
bool isGLES() const { return version_.es; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查是否支持 VAO
|
||||||
|
*/
|
||||||
|
bool hasVAO() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查是否支持 FBO
|
||||||
|
*/
|
||||||
|
bool hasFBO() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查是否支持 Shader
|
||||||
|
*/
|
||||||
|
bool hasShader() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
GLContext() = default;
|
||||||
|
~GLContext() = default;
|
||||||
|
|
||||||
|
GLContext(const GLContext&) = delete;
|
||||||
|
GLContext& operator=(const GLContext&) = delete;
|
||||||
|
|
||||||
|
bool initialized_ = false;
|
||||||
|
GLVersion version_;
|
||||||
|
|
||||||
|
// 缓存的限制值
|
||||||
|
mutable int maxTextureSize_ = -1;
|
||||||
|
mutable int maxTextureUnits_ = -1;
|
||||||
|
mutable int maxVertexAttribs_ = -1;
|
||||||
|
mutable int maxUniformBufferBindings_ = -1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 解析 OpenGL 版本
|
||||||
|
*/
|
||||||
|
void parseVersion();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 加载 OpenGL 扩展
|
||||||
|
*/
|
||||||
|
bool loadExtensions();
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,84 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/graphics/backends/opengl/gl_texture.h>
|
||||||
|
#include <extra2d/graphics/texture/font.h>
|
||||||
|
|
||||||
|
#include <stb/stb_truetype.h>
|
||||||
|
#include <stb/stb_rect_pack.h>
|
||||||
|
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// OpenGL 字体图集实现 (使用 STB 库)
|
||||||
|
// 使用 stb_rect_pack 进行动态矩形打包,支持动态缓存字形
|
||||||
|
// ============================================================================
|
||||||
|
class GLFontAtlas : public FontAtlas {
|
||||||
|
public:
|
||||||
|
GLFontAtlas(const std::string& filepath, int fontSize, bool useSDF = false);
|
||||||
|
~GLFontAtlas() override;
|
||||||
|
|
||||||
|
// FontAtlas 接口实现
|
||||||
|
const Glyph* getGlyph(char32_t codepoint) const override;
|
||||||
|
Texture* getTexture() const override { return texture_.get(); }
|
||||||
|
int getFontSize() const override { return fontSize_; }
|
||||||
|
float getAscent() const override { return ascent_; }
|
||||||
|
float getDescent() const override { return descent_; }
|
||||||
|
float getLineGap() const override { return lineGap_; }
|
||||||
|
float getLineHeight() const override { return lineHeight_; }
|
||||||
|
bool isSDF() const override { return useSDF_; }
|
||||||
|
Vec2 measureText(const std::string& text) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
// 字形数据内部结构
|
||||||
|
struct GlyphData {
|
||||||
|
float width;
|
||||||
|
float height;
|
||||||
|
float bearingX;
|
||||||
|
float bearingY;
|
||||||
|
float advance;
|
||||||
|
float u0, v0, u1, v1;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 图集配置 - 增大尺寸以支持更多字符
|
||||||
|
static constexpr int ATLAS_WIDTH = 1024;
|
||||||
|
static constexpr int ATLAS_HEIGHT = 1024;
|
||||||
|
static constexpr int PADDING = 2; // 字形之间的间距
|
||||||
|
|
||||||
|
bool useSDF_;
|
||||||
|
int fontSize_;
|
||||||
|
|
||||||
|
Ptr<GLTexture> texture_;
|
||||||
|
std::unordered_map<char32_t, GlyphData> glyphs_;
|
||||||
|
float lineHeight_;
|
||||||
|
float ascent_;
|
||||||
|
float descent_;
|
||||||
|
float lineGap_;
|
||||||
|
|
||||||
|
// 字体数据
|
||||||
|
std::vector<unsigned char> fontData_;
|
||||||
|
stbtt_fontinfo fontInfo_;
|
||||||
|
float scale_;
|
||||||
|
|
||||||
|
// stb_rect_pack 上下文 - 持久化以支持增量打包
|
||||||
|
mutable stbrp_context packContext_;
|
||||||
|
mutable std::vector<stbrp_node> packNodes_;
|
||||||
|
|
||||||
|
// 预分配缓冲区,避免每次动态分配
|
||||||
|
mutable std::vector<uint8_t> glyphBitmapCache_;
|
||||||
|
mutable std::vector<uint8_t> glyphRgbaCache_;
|
||||||
|
|
||||||
|
// 初始化字体
|
||||||
|
bool initFont(const std::string& filepath);
|
||||||
|
// 创建空白图集纹理
|
||||||
|
void createAtlas();
|
||||||
|
// 缓存字形到图集
|
||||||
|
void cacheGlyph(char32_t codepoint);
|
||||||
|
// 更新图集纹理区域
|
||||||
|
void updateAtlas(int x, int y, int width, int height,
|
||||||
|
const std::vector<uint8_t>& data);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,105 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/graphics/resources/framebuffer.h>
|
||||||
|
#include <glad/glad.h>
|
||||||
|
#include <array>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// OpenGL 帧缓冲实现
|
||||||
|
// ============================================================================
|
||||||
|
class GLFramebuffer : public Framebuffer {
|
||||||
|
public:
|
||||||
|
// 最大颜色附件数
|
||||||
|
static constexpr int MAX_COLOR_ATTACHMENTS = 8;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 构造函数
|
||||||
|
*/
|
||||||
|
GLFramebuffer();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 析构函数
|
||||||
|
*/
|
||||||
|
~GLFramebuffer() override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 初始化帧缓冲
|
||||||
|
* @param desc 帧缓冲描述
|
||||||
|
* @return 成功返回 true
|
||||||
|
*/
|
||||||
|
bool init(const FramebufferDesc& desc);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 关闭帧缓冲,释放资源
|
||||||
|
*/
|
||||||
|
void shutdown();
|
||||||
|
|
||||||
|
// Framebuffer 接口实现
|
||||||
|
void bind() override;
|
||||||
|
void unbind() override;
|
||||||
|
void attachColorTexture(Ptr<Texture> texture, int attachment = 0) override;
|
||||||
|
void attachDepthTexture(Ptr<Texture> texture) override;
|
||||||
|
void attachDepthStencilTexture(Ptr<Texture> texture) override;
|
||||||
|
bool isComplete() override;
|
||||||
|
Ptr<Texture> getColorTexture(int attachment = 0) const override;
|
||||||
|
Ptr<Texture> getDepthTexture() const override;
|
||||||
|
int getWidth() const override { return width_; }
|
||||||
|
int getHeight() const override { return height_; }
|
||||||
|
Size getSize() const override { return Size(static_cast<float>(width_), static_cast<float>(height_)); }
|
||||||
|
bool isValid() const override { return fboID_ != 0; }
|
||||||
|
uintptr_t getNativeHandle() const override { return static_cast<uintptr_t>(fboID_); }
|
||||||
|
void clear(const Color& color, bool clearColor = true,
|
||||||
|
bool clearDepth = true, bool clearStencil = false) override;
|
||||||
|
void setViewport(int x, int y, int width, int height) override;
|
||||||
|
bool readPixels(int x, int y, int width, int height,
|
||||||
|
std::vector<uint8_t>& outData) override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取 OpenGL FBO ID
|
||||||
|
* @return FBO ID
|
||||||
|
*/
|
||||||
|
GLuint getFboID() const { return fboID_; }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 创建带内置纹理的帧缓冲(便捷方法)
|
||||||
|
* @param width 宽度
|
||||||
|
* @param height 高度
|
||||||
|
* @param colorFormat 颜色格式
|
||||||
|
* @param depthFormat 深度格式(可选)
|
||||||
|
* @return 成功返回 true
|
||||||
|
*/
|
||||||
|
bool createWithTextures(int width, int height,
|
||||||
|
PixelFormat colorFormat = PixelFormat::RGBA8,
|
||||||
|
PixelFormat depthFormat = PixelFormat::Depth24);
|
||||||
|
|
||||||
|
private:
|
||||||
|
GLuint fboID_ = 0;
|
||||||
|
int width_ = 0;
|
||||||
|
int height_ = 0;
|
||||||
|
int numColorAttachments_ = 1;
|
||||||
|
bool hasDepth_ = false;
|
||||||
|
bool hasStencil_ = false;
|
||||||
|
|
||||||
|
// 附件纹理
|
||||||
|
std::array<Ptr<Texture>, MAX_COLOR_ATTACHMENTS> colorTextures_;
|
||||||
|
Ptr<Texture> depthTexture_;
|
||||||
|
Ptr<Texture> depthStencilTexture_;
|
||||||
|
|
||||||
|
// 是否为内置纹理(需要自动清理)
|
||||||
|
bool hasInternalTextures_ = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查并更新完整状态
|
||||||
|
*/
|
||||||
|
bool checkStatus();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取 OpenGL 附件枚举
|
||||||
|
*/
|
||||||
|
static GLenum getColorAttachment(int index);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,131 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/graphics/resources/pipeline.h>
|
||||||
|
#include <glad/glad.h>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// OpenGL 管线状态实现
|
||||||
|
// ============================================================================
|
||||||
|
class GLPipeline : public Pipeline {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief 构造函数
|
||||||
|
*/
|
||||||
|
GLPipeline();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 析构函数
|
||||||
|
*/
|
||||||
|
~GLPipeline() override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 初始化管线
|
||||||
|
* @param desc 管线描述
|
||||||
|
* @return 成功返回 true
|
||||||
|
*/
|
||||||
|
bool init(const PipelineDesc& desc);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 关闭管线,释放资源
|
||||||
|
*/
|
||||||
|
void shutdown();
|
||||||
|
|
||||||
|
// Pipeline 接口实现
|
||||||
|
void bind() override;
|
||||||
|
void unbind() override;
|
||||||
|
void setBlendMode(BlendMode mode) override;
|
||||||
|
BlendMode getBlendMode() const override { return blendMode_; }
|
||||||
|
void setDepthTest(bool enabled) override;
|
||||||
|
void setDepthWrite(bool enabled) override;
|
||||||
|
void setDepthFunc(DepthFunc func) override;
|
||||||
|
void setCullMode(CullMode mode) override;
|
||||||
|
bool isValid() const override { return initialized_; }
|
||||||
|
uintptr_t getNativeHandle() const override { return 0; } // OpenGL 管线没有单一句柄
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置视口
|
||||||
|
* @param x 视口左下角X坐标
|
||||||
|
* @param y 视口左下角Y坐标
|
||||||
|
* @param width 视口宽度
|
||||||
|
* @param height 视口高度
|
||||||
|
*/
|
||||||
|
void setViewport(int x, int y, int width, int height);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取当前视口
|
||||||
|
* @param x 输出X坐标
|
||||||
|
* @param y 输出Y坐标
|
||||||
|
* @param width 输出宽度
|
||||||
|
* @param height 输出高度
|
||||||
|
*/
|
||||||
|
void getViewport(int& x, int& y, int& width, int& height) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 应用所有状态(用于初始化或重置)
|
||||||
|
*/
|
||||||
|
void applyAllStates();
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool initialized_ = false;
|
||||||
|
|
||||||
|
// 当前状态
|
||||||
|
BlendMode blendMode_ = BlendMode::Alpha;
|
||||||
|
bool blendEnabled_ = true;
|
||||||
|
bool depthTest_ = false;
|
||||||
|
bool depthWrite_ = false;
|
||||||
|
DepthFunc depthFunc_ = DepthFunc::Less;
|
||||||
|
CullMode cullMode_ = CullMode::None;
|
||||||
|
|
||||||
|
// 视口
|
||||||
|
int viewportX_ = 0;
|
||||||
|
int viewportY_ = 0;
|
||||||
|
int viewportWidth_ = 0;
|
||||||
|
int viewportHeight_ = 0;
|
||||||
|
|
||||||
|
// 状态缓存(避免冗余 GL 调用)
|
||||||
|
BlendMode cachedBlendMode_ = BlendMode::None;
|
||||||
|
bool cachedBlendEnabled_ = false;
|
||||||
|
bool cachedDepthTest_ = false;
|
||||||
|
bool cachedDepthWrite_ = false;
|
||||||
|
DepthFunc cachedDepthFunc_ = DepthFunc::Less;
|
||||||
|
CullMode cachedCullMode_ = CullMode::None;
|
||||||
|
int cachedViewportX_ = -1;
|
||||||
|
int cachedViewportY_ = -1;
|
||||||
|
int cachedViewportWidth_ = -1;
|
||||||
|
int cachedViewportHeight_ = -1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 应用混合状态
|
||||||
|
*/
|
||||||
|
void applyBlendState();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 应用深度状态
|
||||||
|
*/
|
||||||
|
void applyDepthState();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 应用裁剪状态
|
||||||
|
*/
|
||||||
|
void applyCullState();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 转换混合模式到 OpenGL 枚举
|
||||||
|
*/
|
||||||
|
static void getBlendFactors(BlendMode mode, GLenum& srcFactor, GLenum& dstFactor);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 转换深度函数到 OpenGL 枚举
|
||||||
|
*/
|
||||||
|
static GLenum convertDepthFunc(DepthFunc func);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 转换裁剪模式到 OpenGL 枚举
|
||||||
|
*/
|
||||||
|
static GLenum convertCullMode(CullMode mode);
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <extra2d/graphics/opengl/gl_sprite_batch.h>
|
#include <extra2d/graphics/backends/opengl/gl_buffer.h>
|
||||||
|
#include <extra2d/graphics/backends/opengl/gl_framebuffer.h>
|
||||||
|
#include <extra2d/graphics/backends/opengl/gl_pipeline.h>
|
||||||
|
#include <extra2d/graphics/backends/opengl/gl_sprite_batch.h>
|
||||||
#include <extra2d/graphics/core/render_backend.h>
|
#include <extra2d/graphics/core/render_backend.h>
|
||||||
#include <extra2d/graphics/shader/shader_interface.h>
|
#include <extra2d/graphics/shader/shader_interface.h>
|
||||||
|
|
||||||
|
|
@ -10,7 +13,10 @@
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
||||||
|
// 前向声明
|
||||||
class IWindow;
|
class IWindow;
|
||||||
|
class GLContext;
|
||||||
|
class GLFramebuffer;
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// OpenGL 渲染器实现
|
// OpenGL 渲染器实现
|
||||||
|
|
@ -48,6 +54,7 @@ public:
|
||||||
void drawSprite(const Texture &texture, const Vec2 &position,
|
void drawSprite(const Texture &texture, const Vec2 &position,
|
||||||
const Color &tint) override;
|
const Color &tint) override;
|
||||||
void endSpriteBatch() override;
|
void endSpriteBatch() override;
|
||||||
|
void flush() override;
|
||||||
|
|
||||||
void drawLine(const Vec2 &start, const Vec2 &end, const Color &color,
|
void drawLine(const Vec2 &start, const Vec2 &end, const Color &color,
|
||||||
float width) override;
|
float width) override;
|
||||||
|
|
@ -76,6 +83,42 @@ public:
|
||||||
Stats getStats() const override { return stats_; }
|
Stats getStats() const override { return stats_; }
|
||||||
void resetStats() override;
|
void resetStats() override;
|
||||||
|
|
||||||
|
// GLFramebuffer 相关方法
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 创建帧缓冲对象
|
||||||
|
* @param desc 帧缓冲描述
|
||||||
|
* @return 创建的帧缓冲智能指针
|
||||||
|
*/
|
||||||
|
Ptr<GLFramebuffer> createFramebuffer(const FramebufferDesc& desc);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 绑定帧缓冲(作为渲染目标)
|
||||||
|
* @param framebuffer 帧缓冲对象指针,传入 nullptr 则绑定默认帧缓冲
|
||||||
|
*/
|
||||||
|
void bindFramebuffer(GLFramebuffer* framebuffer);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 解绑帧缓冲(恢复到默认帧缓冲)
|
||||||
|
*/
|
||||||
|
void unbindFramebuffer();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取默认帧缓冲
|
||||||
|
* @return 默认帧缓冲智能指针
|
||||||
|
*/
|
||||||
|
Ptr<GLFramebuffer> getDefaultFramebuffer() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 清除当前绑定的帧缓冲
|
||||||
|
* @param color 清除颜色
|
||||||
|
* @param clearColor 是否清除颜色缓冲
|
||||||
|
* @param clearDepth 是否清除深度缓冲
|
||||||
|
* @param clearStencil 是否清除模板缓冲
|
||||||
|
*/
|
||||||
|
void clearFramebuffer(const Color& color, bool clearColor = true,
|
||||||
|
bool clearDepth = true, bool clearStencil = false);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// 形状批处理常量
|
// 形状批处理常量
|
||||||
static constexpr size_t MAX_CIRCLE_SEGMENTS = 128;
|
static constexpr size_t MAX_CIRCLE_SEGMENTS = 128;
|
||||||
|
|
@ -92,10 +135,10 @@ private:
|
||||||
GLSpriteBatch spriteBatch_;
|
GLSpriteBatch spriteBatch_;
|
||||||
Ptr<IShader> shapeShader_;
|
Ptr<IShader> shapeShader_;
|
||||||
|
|
||||||
GLuint shapeVao_;
|
GLuint shapeVao_; // 形状 VAO(手动管理,用于顶点属性配置)
|
||||||
GLuint shapeVbo_;
|
GLBuffer shapeBuffer_; // 形状 VBO(使用 GLBuffer 管理)
|
||||||
GLuint lineVao_; // 线条专用 VAO
|
GLuint lineVao_; // 线条 VAO(手动管理,用于顶点属性配置)
|
||||||
GLuint lineVbo_; // 线条专用 VBO
|
GLBuffer lineBuffer_; // 线条 VBO(使用 GLBuffer 管理)
|
||||||
|
|
||||||
glm::mat4 viewProjection_;
|
glm::mat4 viewProjection_;
|
||||||
std::vector<glm::mat4> transformStack_;
|
std::vector<glm::mat4> transformStack_;
|
||||||
|
|
@ -112,15 +155,23 @@ private:
|
||||||
size_t lineVertexCount_ = 0;
|
size_t lineVertexCount_ = 0;
|
||||||
float currentLineWidth_ = 1.0f;
|
float currentLineWidth_ = 1.0f;
|
||||||
|
|
||||||
// OpenGL 状态缓存
|
// OpenGL 管线状态管理
|
||||||
BlendMode cachedBlendMode_ = BlendMode::None;
|
GLPipeline pipeline_;
|
||||||
bool blendEnabled_ = false;
|
|
||||||
int cachedViewportX_ = 0;
|
// 自动批处理状态
|
||||||
int cachedViewportY_ = 0;
|
bool batchActive_ = false; // 批处理是否激活
|
||||||
int cachedViewportWidth_ = 0;
|
bool autoBatchEnabled_ = true; // 是否启用自动批处理
|
||||||
int cachedViewportHeight_ = 0;
|
const Texture* currentBatchTexture_ = nullptr; // 当前批处理的纹理
|
||||||
|
std::vector<SpriteData> pendingSprites_; // 待提交的精灵
|
||||||
|
static constexpr size_t MAX_BATCH_SPRITES = 1000; // 最大批处理精灵数
|
||||||
|
|
||||||
|
// 帧缓冲管理
|
||||||
|
mutable Ptr<GLFramebuffer> defaultFramebuffer_; // 默认帧缓冲(延迟创建)
|
||||||
|
GLFramebuffer* currentFramebuffer_ = nullptr; // 当前绑定的帧缓冲
|
||||||
|
|
||||||
void initShapeRendering();
|
void initShapeRendering();
|
||||||
|
void ensureBatchActive(); // 确保批处理已激活
|
||||||
|
void submitPendingSprites(); // 提交待处理的精灵
|
||||||
void flushShapeBatch();
|
void flushShapeBatch();
|
||||||
void flushLineBatch();
|
void flushLineBatch();
|
||||||
void addShapeVertex(float x, float y, const Color &color);
|
void addShapeVertex(float x, float y, const Color &color);
|
||||||
|
|
@ -0,0 +1,68 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/graphics/backends/opengl/gl_buffer.h>
|
||||||
|
#include <extra2d/graphics/backends/opengl/gl_texture.h>
|
||||||
|
#include <extra2d/graphics/batch/sprite_batch.h>
|
||||||
|
#include <extra2d/graphics/shader/shader_interface.h>
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <glad/glad.h>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// OpenGL 精灵批处理渲染器
|
||||||
|
// 使用 batch/sprite_batch 作为后端无关的批处理层
|
||||||
|
// ============================================================================
|
||||||
|
class GLSpriteBatch {
|
||||||
|
public:
|
||||||
|
GLSpriteBatch();
|
||||||
|
~GLSpriteBatch();
|
||||||
|
|
||||||
|
// 初始化/关闭
|
||||||
|
bool init();
|
||||||
|
void shutdown();
|
||||||
|
|
||||||
|
// 批处理生命周期
|
||||||
|
void begin(const glm::mat4& viewProjection);
|
||||||
|
void end();
|
||||||
|
|
||||||
|
// 绘制单个精灵
|
||||||
|
void draw(const Texture& texture, const SpriteData& data);
|
||||||
|
|
||||||
|
// 批量绘制(用于文本渲染优化)
|
||||||
|
void drawBatch(const Texture& texture, const std::vector<SpriteData>& sprites);
|
||||||
|
|
||||||
|
// 获取绘制调用次数
|
||||||
|
uint32_t getDrawCallCount() const { return drawCallCount_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
// OpenGL 对象
|
||||||
|
GLuint vao_;
|
||||||
|
GLBuffer vbo_; // 顶点缓冲区(动态)
|
||||||
|
GLBuffer ebo_; // 索引缓冲区(静态)
|
||||||
|
|
||||||
|
// 后端无关的批处理层
|
||||||
|
SpriteBatch batch_;
|
||||||
|
|
||||||
|
// 批次管理
|
||||||
|
struct Batch {
|
||||||
|
const GLTexture* texture;
|
||||||
|
size_t startVertex;
|
||||||
|
size_t vertexCount;
|
||||||
|
};
|
||||||
|
std::vector<Batch> batches_;
|
||||||
|
const GLTexture* currentTexture_;
|
||||||
|
|
||||||
|
// 着色器和矩阵
|
||||||
|
Ptr<IShader> shader_;
|
||||||
|
uint32_t drawCallCount_;
|
||||||
|
glm::mat4 viewProjection_;
|
||||||
|
|
||||||
|
// 内部方法
|
||||||
|
void flush();
|
||||||
|
void submitBatch();
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,78 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/graphics/core/render_backend.h>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Vulkan 渲染器实现(占位)
|
||||||
|
*
|
||||||
|
* 这是一个占位实现,用于展示Vulkan后端应该包含的内容。
|
||||||
|
* 完整的实现需要包含Vulkan上下文、设备、交换链、管线等。
|
||||||
|
*/
|
||||||
|
class VulkanRenderer : public RenderBackend {
|
||||||
|
public:
|
||||||
|
VulkanRenderer();
|
||||||
|
~VulkanRenderer() override;
|
||||||
|
|
||||||
|
// RenderBackend 接口实现
|
||||||
|
bool init(IWindow* window) override;
|
||||||
|
void shutdown() override;
|
||||||
|
|
||||||
|
void beginFrame(const Color &clearColor) override;
|
||||||
|
void endFrame() override;
|
||||||
|
void setViewport(int x, int y, int width, int height) override;
|
||||||
|
void setVSync(bool enabled) override;
|
||||||
|
|
||||||
|
void setBlendMode(BlendMode mode) override;
|
||||||
|
void setViewProjection(const glm::mat4 &matrix) override;
|
||||||
|
|
||||||
|
void pushTransform(const glm::mat4 &transform) override;
|
||||||
|
void popTransform() override;
|
||||||
|
glm::mat4 getCurrentTransform() const override;
|
||||||
|
|
||||||
|
Ptr<Texture> createTexture(int width, int height, const uint8_t *pixels,
|
||||||
|
int channels) override;
|
||||||
|
Ptr<Texture> loadTexture(const std::string &filepath) override;
|
||||||
|
|
||||||
|
void beginSpriteBatch() override;
|
||||||
|
void drawSprite(const Texture &texture, const Rect &destRect,
|
||||||
|
const Rect &srcRect, const Color &tint, float rotation,
|
||||||
|
const Vec2 &anchor) override;
|
||||||
|
void drawSprite(const Texture &texture, const Vec2 &position,
|
||||||
|
const Color &tint) override;
|
||||||
|
void endSpriteBatch() override;
|
||||||
|
|
||||||
|
void drawLine(const Vec2 &start, const Vec2 &end, const Color &color,
|
||||||
|
float width) override;
|
||||||
|
void drawRect(const Rect &rect, const Color &color, float width) override;
|
||||||
|
void fillRect(const Rect &rect, const Color &color) override;
|
||||||
|
void drawCircle(const Vec2 ¢er, float radius, const Color &color,
|
||||||
|
int segments, float width) override;
|
||||||
|
void fillCircle(const Vec2 ¢er, float radius, const Color &color,
|
||||||
|
int segments) override;
|
||||||
|
void drawTriangle(const Vec2 &p1, const Vec2 &p2, const Vec2 &p3,
|
||||||
|
const Color &color, float width) override;
|
||||||
|
void fillTriangle(const Vec2 &p1, const Vec2 &p2, const Vec2 &p3,
|
||||||
|
const Color &color) override;
|
||||||
|
void drawPolygon(const std::vector<Vec2> &points, const Color &color,
|
||||||
|
float width) override;
|
||||||
|
void fillPolygon(const std::vector<Vec2> &points,
|
||||||
|
const Color &color) override;
|
||||||
|
|
||||||
|
Ptr<FontAtlas> createFontAtlas(const std::string &filepath, int fontSize,
|
||||||
|
bool useSDF = false) override;
|
||||||
|
void drawText(const FontAtlas &font, const std::string &text,
|
||||||
|
const Vec2 &position, const Color &color) override;
|
||||||
|
void drawText(const FontAtlas &font, const std::string &text, float x,
|
||||||
|
float y, const Color &color) override;
|
||||||
|
|
||||||
|
Stats getStats() const override { return stats_; }
|
||||||
|
void resetStats() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
Stats stats_;
|
||||||
|
bool initialized_ = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,157 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/core/color.h>
|
||||||
|
#include <extra2d/core/math_types.h>
|
||||||
|
#include <extra2d/core/types.h>
|
||||||
|
#include <glm/mat4x4.hpp>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 形状顶点结构
|
||||||
|
// ============================================================================
|
||||||
|
struct ShapeVertex {
|
||||||
|
float x, y; // 位置
|
||||||
|
float r, g, b, a; // 颜色
|
||||||
|
|
||||||
|
ShapeVertex() = default;
|
||||||
|
ShapeVertex(float px, float py, const Color& c)
|
||||||
|
: x(px), y(py), r(c.r), g(c.g), b(c.b), a(c.a) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 形状批处理抽象接口 - 后端无关
|
||||||
|
// ============================================================================
|
||||||
|
class ShapeBatch {
|
||||||
|
public:
|
||||||
|
virtual ~ShapeBatch() = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 初始化形状批处理
|
||||||
|
* @return 成功返回 true
|
||||||
|
*/
|
||||||
|
virtual bool init() = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 关闭形状批处理,释放资源
|
||||||
|
*/
|
||||||
|
virtual void shutdown() = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 开始批处理
|
||||||
|
* @param viewProjection 视图投影矩阵
|
||||||
|
*/
|
||||||
|
virtual void begin(const glm::mat4& viewProjection) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 结束批处理并提交绘制
|
||||||
|
*/
|
||||||
|
virtual void end() = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 绘制线段
|
||||||
|
* @param start 起点
|
||||||
|
* @param end 终点
|
||||||
|
* @param color 颜色
|
||||||
|
* @param width 线宽
|
||||||
|
*/
|
||||||
|
virtual void drawLine(const Vec2& start, const Vec2& end,
|
||||||
|
const Color& color, float width = 1.0f) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 绘制矩形边框
|
||||||
|
* @param rect 矩形区域
|
||||||
|
* @param color 颜色
|
||||||
|
* @param width 边框宽度
|
||||||
|
*/
|
||||||
|
virtual void drawRect(const Rect& rect, const Color& color,
|
||||||
|
float width = 1.0f) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 填充矩形
|
||||||
|
* @param rect 矩形区域
|
||||||
|
* @param color 颜色
|
||||||
|
*/
|
||||||
|
virtual void fillRect(const Rect& rect, const Color& color) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 绘制圆形边框
|
||||||
|
* @param center 圆心
|
||||||
|
* @param radius 半径
|
||||||
|
* @param color 颜色
|
||||||
|
* @param segments 分段数
|
||||||
|
* @param width 边框宽度
|
||||||
|
*/
|
||||||
|
virtual void drawCircle(const Vec2& center, float radius,
|
||||||
|
const Color& color, int segments = 32,
|
||||||
|
float width = 1.0f) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 填充圆形
|
||||||
|
* @param center 圆心
|
||||||
|
* @param radius 半径
|
||||||
|
* @param color 颜色
|
||||||
|
* @param segments 分段数
|
||||||
|
*/
|
||||||
|
virtual void fillCircle(const Vec2& center, float radius,
|
||||||
|
const Color& color, int segments = 32) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 绘制三角形边框
|
||||||
|
* @param p1 顶点1
|
||||||
|
* @param p2 顶点2
|
||||||
|
* @param p3 顶点3
|
||||||
|
* @param color 颜色
|
||||||
|
* @param width 边框宽度
|
||||||
|
*/
|
||||||
|
virtual void drawTriangle(const Vec2& p1, const Vec2& p2, const Vec2& p3,
|
||||||
|
const Color& color, float width = 1.0f) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 填充三角形
|
||||||
|
* @param p1 顶点1
|
||||||
|
* @param p2 顶点2
|
||||||
|
* @param p3 顶点3
|
||||||
|
* @param color 颜色
|
||||||
|
*/
|
||||||
|
virtual void fillTriangle(const Vec2& p1, const Vec2& p2, const Vec2& p3,
|
||||||
|
const Color& color) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 绘制多边形边框
|
||||||
|
* @param points 顶点数组
|
||||||
|
* @param color 颜色
|
||||||
|
* @param width 边框宽度
|
||||||
|
*/
|
||||||
|
virtual void drawPolygon(const std::vector<Vec2>& points,
|
||||||
|
const Color& color, float width = 1.0f) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 填充多边形
|
||||||
|
* @param points 顶点数组
|
||||||
|
* @param color 颜色
|
||||||
|
*/
|
||||||
|
virtual void fillPolygon(const std::vector<Vec2>& points,
|
||||||
|
const Color& color) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取绘制调用次数
|
||||||
|
* @return 绘制调用次数
|
||||||
|
*/
|
||||||
|
virtual uint32_t getDrawCallCount() const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 重置绘制调用计数
|
||||||
|
*/
|
||||||
|
virtual void resetDrawCallCount() = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查是否有效
|
||||||
|
* @return 有效返回 true
|
||||||
|
*/
|
||||||
|
virtual bool isValid() const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,121 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/core/types.h>
|
||||||
|
#include <extra2d/core/math_types.h>
|
||||||
|
#include <extra2d/core/color.h>
|
||||||
|
#include <extra2d/graphics/texture/texture.h>
|
||||||
|
|
||||||
|
#include <glm/mat4x4.hpp>
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <vector>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 三角函数查表 - 避免每帧计算 sin/cos
|
||||||
|
// ============================================================================
|
||||||
|
class TrigLookup {
|
||||||
|
public:
|
||||||
|
TrigLookup();
|
||||||
|
|
||||||
|
// 通过角度(0-360)获取 sin/cos
|
||||||
|
float sin(int angle) const;
|
||||||
|
float cos(int angle) const;
|
||||||
|
|
||||||
|
// 通过弧度获取 sin/cos
|
||||||
|
float sinRad(float rad) const;
|
||||||
|
float cosRad(float rad) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
static constexpr int TABLE_SIZE = 360 * 4; // 0.25度精度
|
||||||
|
std::array<float, TABLE_SIZE> sinTable_;
|
||||||
|
std::array<float, TABLE_SIZE> cosTable_;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 精灵批次数据 - 后端无关
|
||||||
|
// ============================================================================
|
||||||
|
struct SpriteVertex {
|
||||||
|
Vec2 position;
|
||||||
|
Vec2 texCoord;
|
||||||
|
Color color;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct SpriteData {
|
||||||
|
Vec2 position;
|
||||||
|
Vec2 size;
|
||||||
|
float rotation;
|
||||||
|
Vec2 pivot;
|
||||||
|
Color color;
|
||||||
|
const Texture* texture;
|
||||||
|
Rect uvRect;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 通用精灵批处理 - 后端无关
|
||||||
|
// 负责:顶点生成、批次管理、三角函数查表
|
||||||
|
// ============================================================================
|
||||||
|
class SpriteBatch {
|
||||||
|
public:
|
||||||
|
static constexpr size_t MAX_SPRITES = 10000;
|
||||||
|
static constexpr size_t VERTICES_PER_SPRITE = 4;
|
||||||
|
static constexpr size_t INDICES_PER_SPRITE = 6;
|
||||||
|
static constexpr size_t MAX_VERTICES = MAX_SPRITES * VERTICES_PER_SPRITE;
|
||||||
|
static constexpr size_t MAX_INDICES = MAX_SPRITES * INDICES_PER_SPRITE;
|
||||||
|
|
||||||
|
SpriteBatch();
|
||||||
|
~SpriteBatch() = default;
|
||||||
|
|
||||||
|
// 开始批次
|
||||||
|
void begin(const glm::mat4& viewProjection);
|
||||||
|
|
||||||
|
// 结束批次 - 返回需要绘制的批次列表
|
||||||
|
void end();
|
||||||
|
|
||||||
|
// 绘制单个精灵
|
||||||
|
void draw(const SpriteData& sprite);
|
||||||
|
|
||||||
|
// 批量绘制 - 一次性处理多个精灵
|
||||||
|
void drawBatch(const std::vector<SpriteData>& sprites);
|
||||||
|
|
||||||
|
// 立即绘制 - 不缓存,直接提交
|
||||||
|
void drawImmediate(const SpriteData& sprite);
|
||||||
|
|
||||||
|
// 获取当前批次数据
|
||||||
|
const std::vector<SpriteVertex>& getVertices() const { return vertices_; }
|
||||||
|
const std::vector<uint16_t>& getIndices() const { return indices_; }
|
||||||
|
size_t getSpriteCount() const { return spriteCount_; }
|
||||||
|
|
||||||
|
// 检查是否需要刷新
|
||||||
|
bool needsFlush() const { return spriteCount_ >= MAX_SPRITES; }
|
||||||
|
|
||||||
|
// 清空批次
|
||||||
|
void clear();
|
||||||
|
|
||||||
|
private:
|
||||||
|
// 三角函数查表
|
||||||
|
TrigLookup trigLookup_;
|
||||||
|
|
||||||
|
// 顶点数据 - 使用固定大小数组避免动态分配
|
||||||
|
std::vector<SpriteVertex> vertices_;
|
||||||
|
std::vector<uint16_t> indices_;
|
||||||
|
size_t spriteCount_;
|
||||||
|
|
||||||
|
// 变换矩阵
|
||||||
|
glm::mat4 viewProjection_;
|
||||||
|
glm::mat4 cachedVP_;
|
||||||
|
bool vpDirty_;
|
||||||
|
|
||||||
|
// 生成索引
|
||||||
|
void generateIndices();
|
||||||
|
|
||||||
|
// 生成顶点
|
||||||
|
void generateVertices(const SpriteData& sprite, size_t vertexOffset);
|
||||||
|
|
||||||
|
// 刷新批次
|
||||||
|
void flush();
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
#include <extra2d/core/color.h>
|
#include <extra2d/core/color.h>
|
||||||
#include <extra2d/core/math_types.h>
|
#include <extra2d/core/math_types.h>
|
||||||
#include <extra2d/core/types.h>
|
#include <extra2d/core/types.h>
|
||||||
|
#include <extra2d/graphics/resources/pipeline.h>
|
||||||
#include <glm/mat4x4.hpp>
|
#include <glm/mat4x4.hpp>
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
@ -24,15 +25,7 @@ enum class BackendType {
|
||||||
// D3D12
|
// D3D12
|
||||||
};
|
};
|
||||||
|
|
||||||
// ============================================================================
|
// BlendMode 定义在 pipeline.h 中
|
||||||
// 混合模式
|
|
||||||
// ============================================================================
|
|
||||||
enum class BlendMode {
|
|
||||||
None, // 不混合
|
|
||||||
Alpha, // 标准 Alpha 混合
|
|
||||||
Additive, // 加法混合
|
|
||||||
Multiply // 乘法混合
|
|
||||||
};
|
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// 渲染后端抽象接口
|
// 渲染后端抽象接口
|
||||||
|
|
@ -78,6 +71,10 @@ public:
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
// 精灵批渲染
|
// 精灵批渲染
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
|
/**
|
||||||
|
* @brief 开始手动精灵批处理(高级用法)
|
||||||
|
* @note 一般情况下不需要调用,drawSprite/drawText 会自动管理批处理
|
||||||
|
*/
|
||||||
virtual void beginSpriteBatch() = 0;
|
virtual void beginSpriteBatch() = 0;
|
||||||
virtual void drawSprite(const Texture &texture, const Rect &destRect,
|
virtual void drawSprite(const Texture &texture, const Rect &destRect,
|
||||||
const Rect &srcRect, const Color &tint,
|
const Rect &srcRect, const Color &tint,
|
||||||
|
|
@ -86,6 +83,12 @@ public:
|
||||||
const Color &tint) = 0;
|
const Color &tint) = 0;
|
||||||
virtual void endSpriteBatch() = 0;
|
virtual void endSpriteBatch() = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 立即提交当前批处理
|
||||||
|
* @note 手动控制批处理提交时机,一般情况下不需要调用
|
||||||
|
*/
|
||||||
|
virtual void flush() = 0;
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
// 形状渲染
|
// 形状渲染
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -3,27 +3,22 @@
|
||||||
#include <extra2d/core/module.h>
|
#include <extra2d/core/module.h>
|
||||||
#include <extra2d/graphics/core/render_backend.h>
|
#include <extra2d/graphics/core/render_backend.h>
|
||||||
#include <extra2d/platform/window_module.h>
|
#include <extra2d/platform/window_module.h>
|
||||||
|
#include <functional>
|
||||||
#include <typeindex>
|
#include <typeindex>
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 渲染模块
|
* @brief 渲染模块配置结构
|
||||||
* 管理渲染后端
|
|
||||||
*/
|
*/
|
||||||
class RenderModule : public Module {
|
struct RenderCfg {
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief 配置结构
|
|
||||||
*/
|
|
||||||
struct Cfg {
|
|
||||||
BackendType backend;
|
BackendType backend;
|
||||||
int targetFPS;
|
int targetFPS;
|
||||||
bool vsync;
|
bool vsync;
|
||||||
int multisamples;
|
int multisamples;
|
||||||
int priority;
|
int priority;
|
||||||
|
|
||||||
Cfg()
|
RenderCfg()
|
||||||
: backend(BackendType::OpenGL)
|
: backend(BackendType::OpenGL)
|
||||||
, targetFPS(60)
|
, targetFPS(60)
|
||||||
, vsync(true)
|
, vsync(true)
|
||||||
|
|
@ -33,10 +28,16 @@ public:
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 构造函数
|
* @brief 渲染模块
|
||||||
* @param cfg 配置
|
* 管理渲染后端
|
||||||
*/
|
*/
|
||||||
explicit RenderModule(const Cfg& cfg = Cfg{});
|
class RenderModule : public Module {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief 构造函数(Lambda 配置)
|
||||||
|
* @param configFn 配置函数
|
||||||
|
*/
|
||||||
|
explicit RenderModule(std::function<void(RenderCfg&)> configFn);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 析构函数
|
* @brief 析构函数
|
||||||
|
|
@ -64,7 +65,7 @@ public:
|
||||||
RenderBackend* renderer() const { return renderer_.get(); }
|
RenderBackend* renderer() const { return renderer_.get(); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Cfg cfg_;
|
RenderCfg cfg_;
|
||||||
UniquePtr<RenderBackend> renderer_;
|
UniquePtr<RenderBackend> renderer_;
|
||||||
bool initialized_ = false;
|
bool initialized_ = false;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
#include <extra2d/core/color.h>
|
#include <extra2d/core/color.h>
|
||||||
#include <extra2d/core/types.h>
|
#include <extra2d/core/types.h>
|
||||||
#include <extra2d/graphics/opengl/gl_texture.h>
|
#include <extra2d/graphics/backends/opengl/gl_texture.h>
|
||||||
#include <extra2d/graphics/texture/texture.h>
|
#include <extra2d/graphics/texture/texture.h>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,67 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <extra2d/core/color.h>
|
|
||||||
#include <extra2d/core/math_types.h>
|
|
||||||
#include <extra2d/core/types.h>
|
|
||||||
#include <extra2d/graphics/texture/font.h>
|
|
||||||
#include <extra2d/graphics/opengl/gl_texture.h>
|
|
||||||
#include <extra2d/graphics/texture/texture.h>
|
|
||||||
#include <memory>
|
|
||||||
#include <stb/stb_rect_pack.h>
|
|
||||||
#include <stb/stb_truetype.h>
|
|
||||||
#include <unordered_map>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// OpenGL 字体图集实现 - 使用 stb_rect_pack 进行矩形打包
|
|
||||||
// ============================================================================
|
|
||||||
class GLFontAtlas : public FontAtlas {
|
|
||||||
public:
|
|
||||||
GLFontAtlas(const std::string &filepath, int fontSize, bool useSDF = false);
|
|
||||||
~GLFontAtlas();
|
|
||||||
|
|
||||||
// FontAtlas 接口实现
|
|
||||||
const Glyph *getGlyph(char32_t codepoint) const override;
|
|
||||||
Texture *getTexture() const override { return texture_.get(); }
|
|
||||||
int getFontSize() const override { return fontSize_; }
|
|
||||||
float getAscent() const override { return ascent_; }
|
|
||||||
float getDescent() const override { return descent_; }
|
|
||||||
float getLineGap() const override { return lineGap_; }
|
|
||||||
float getLineHeight() const override { return ascent_ - descent_ + lineGap_; }
|
|
||||||
Vec2 measureText(const std::string &text) override;
|
|
||||||
bool isSDF() const override { return useSDF_; }
|
|
||||||
|
|
||||||
private:
|
|
||||||
// 图集配置 - 增大尺寸以支持更多字符
|
|
||||||
static constexpr int ATLAS_WIDTH = 1024;
|
|
||||||
static constexpr int ATLAS_HEIGHT = 1024;
|
|
||||||
static constexpr int PADDING = 2; // 字形之间的间距
|
|
||||||
|
|
||||||
int fontSize_;
|
|
||||||
bool useSDF_;
|
|
||||||
mutable std::unique_ptr<GLTexture> texture_;
|
|
||||||
mutable std::unordered_map<char32_t, Glyph> glyphs_;
|
|
||||||
|
|
||||||
// stb_rect_pack 上下文
|
|
||||||
mutable stbrp_context packContext_;
|
|
||||||
mutable std::vector<stbrp_node> packNodes_;
|
|
||||||
mutable int currentY_;
|
|
||||||
|
|
||||||
std::vector<unsigned char> fontData_;
|
|
||||||
stbtt_fontinfo fontInfo_;
|
|
||||||
float scale_;
|
|
||||||
float ascent_;
|
|
||||||
float descent_;
|
|
||||||
float lineGap_;
|
|
||||||
|
|
||||||
// 预分配字形位图缓冲区,避免每次动态分配
|
|
||||||
mutable std::vector<uint8_t> glyphBitmapCache_;
|
|
||||||
mutable std::vector<uint8_t> glyphRgbaCache_;
|
|
||||||
|
|
||||||
void createAtlas();
|
|
||||||
void cacheGlyph(char32_t codepoint) const;
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,98 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <array>
|
|
||||||
#include <extra2d/core/color.h>
|
|
||||||
#include <extra2d/core/math_types.h>
|
|
||||||
#include <extra2d/core/types.h>
|
|
||||||
#include <extra2d/graphics/opengl/gl_shader.h>
|
|
||||||
#include <extra2d/graphics/texture/texture.h>
|
|
||||||
#include <glm/mat4x4.hpp>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
#include <glad/glad.h>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// OpenGL 精灵批渲染器 - 优化版本
|
|
||||||
// ============================================================================
|
|
||||||
class GLSpriteBatch {
|
|
||||||
public:
|
|
||||||
static constexpr size_t MAX_SPRITES = 10000;
|
|
||||||
static constexpr size_t VERTICES_PER_SPRITE = 4;
|
|
||||||
static constexpr size_t INDICES_PER_SPRITE = 6;
|
|
||||||
static constexpr size_t MAX_VERTICES = MAX_SPRITES * VERTICES_PER_SPRITE;
|
|
||||||
static constexpr size_t MAX_INDICES = MAX_SPRITES * INDICES_PER_SPRITE;
|
|
||||||
|
|
||||||
struct Vertex {
|
|
||||||
glm::vec2 position;
|
|
||||||
glm::vec2 texCoord;
|
|
||||||
glm::vec4 color;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct SpriteData {
|
|
||||||
glm::vec2 position;
|
|
||||||
glm::vec2 size;
|
|
||||||
glm::vec2 texCoordMin;
|
|
||||||
glm::vec2 texCoordMax;
|
|
||||||
glm::vec4 color;
|
|
||||||
float rotation;
|
|
||||||
glm::vec2 anchor;
|
|
||||||
bool isSDF = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
GLSpriteBatch();
|
|
||||||
~GLSpriteBatch();
|
|
||||||
|
|
||||||
bool init();
|
|
||||||
void shutdown();
|
|
||||||
|
|
||||||
void begin(const glm::mat4 &viewProjection);
|
|
||||||
void draw(const Texture &texture, const SpriteData &data);
|
|
||||||
void end();
|
|
||||||
|
|
||||||
// 批量绘制接口 - 用于自动批处理
|
|
||||||
void drawBatch(const Texture &texture,
|
|
||||||
const std::vector<SpriteData> &sprites);
|
|
||||||
|
|
||||||
// 立即绘制(不缓存)
|
|
||||||
void drawImmediate(const Texture &texture, const SpriteData &data);
|
|
||||||
|
|
||||||
// 统计
|
|
||||||
uint32_t getDrawCallCount() const { return drawCallCount_; }
|
|
||||||
uint32_t getSpriteCount() const { return spriteCount_; }
|
|
||||||
uint32_t getBatchCount() const { return batchCount_; }
|
|
||||||
|
|
||||||
// 检查是否需要刷新
|
|
||||||
bool needsFlush(const Texture &texture, bool isSDF) const;
|
|
||||||
|
|
||||||
private:
|
|
||||||
GLuint vao_;
|
|
||||||
GLuint vbo_;
|
|
||||||
GLuint ibo_;
|
|
||||||
GLShader shader_;
|
|
||||||
|
|
||||||
// 使用固定大小数组减少内存分配
|
|
||||||
std::array<Vertex, MAX_VERTICES> vertexBuffer_;
|
|
||||||
size_t vertexCount_;
|
|
||||||
|
|
||||||
const Texture *currentTexture_;
|
|
||||||
bool currentIsSDF_;
|
|
||||||
glm::mat4 viewProjection_;
|
|
||||||
|
|
||||||
// 缓存上一帧的 viewProjection,避免重复设置
|
|
||||||
glm::mat4 cachedViewProjection_;
|
|
||||||
bool viewProjectionDirty_ = true;
|
|
||||||
|
|
||||||
uint32_t drawCallCount_;
|
|
||||||
uint32_t spriteCount_;
|
|
||||||
uint32_t batchCount_;
|
|
||||||
|
|
||||||
void flush();
|
|
||||||
void setupShader();
|
|
||||||
|
|
||||||
// 添加顶点到缓冲区
|
|
||||||
void addVertices(const SpriteData &data);
|
|
||||||
};
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -0,0 +1,111 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/core/types.h>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 缓冲区类型枚举
|
||||||
|
// ============================================================================
|
||||||
|
enum class BufferType {
|
||||||
|
Vertex, // 顶点缓冲
|
||||||
|
Index, // 索引缓冲
|
||||||
|
Uniform // 统一缓冲
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 缓冲区使用模式枚举
|
||||||
|
// ============================================================================
|
||||||
|
enum class BufferUsage {
|
||||||
|
Static, // 静态数据,很少更新
|
||||||
|
Dynamic, // 动态数据,频繁更新
|
||||||
|
Stream // 流式数据,每帧更新
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 缓冲区描述结构
|
||||||
|
// ============================================================================
|
||||||
|
struct BufferDesc {
|
||||||
|
BufferType type = BufferType::Vertex;
|
||||||
|
BufferUsage usage = BufferUsage::Static;
|
||||||
|
size_t size = 0; // 缓冲区大小(字节)
|
||||||
|
const void* initialData = nullptr; // 初始数据
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 缓冲区抽象接口 - 渲染后端无关
|
||||||
|
// ============================================================================
|
||||||
|
class Buffer {
|
||||||
|
public:
|
||||||
|
virtual ~Buffer() = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 绑定缓冲区
|
||||||
|
*/
|
||||||
|
virtual void bind() = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 解绑缓冲区
|
||||||
|
*/
|
||||||
|
virtual void unbind() = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置缓冲区数据(完全替换)
|
||||||
|
* @param data 数据指针
|
||||||
|
* @param size 数据大小(字节)
|
||||||
|
*/
|
||||||
|
virtual void setData(const void* data, size_t size) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 更新缓冲区部分数据
|
||||||
|
* @param data 数据指针
|
||||||
|
* @param offset 偏移量(字节)
|
||||||
|
* @param size 数据大小(字节)
|
||||||
|
*/
|
||||||
|
virtual void updateData(const void* data, size_t offset, size_t size) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 映射缓冲区到内存(用于直接写入)
|
||||||
|
* @return 映射后的内存指针,失败返回 nullptr
|
||||||
|
*/
|
||||||
|
virtual void* map() = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 解除缓冲区映射
|
||||||
|
*/
|
||||||
|
virtual void unmap() = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取缓冲区大小
|
||||||
|
* @return 缓冲区大小(字节)
|
||||||
|
*/
|
||||||
|
virtual size_t getSize() const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取缓冲区类型
|
||||||
|
* @return 缓冲区类型
|
||||||
|
*/
|
||||||
|
virtual BufferType getType() const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取缓冲区使用模式
|
||||||
|
* @return 使用模式
|
||||||
|
*/
|
||||||
|
virtual BufferUsage getUsage() const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查缓冲区是否有效
|
||||||
|
* @return 有效返回 true
|
||||||
|
*/
|
||||||
|
virtual bool isValid() const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取原生句柄(后端特定)
|
||||||
|
* @return 原生句柄值
|
||||||
|
*/
|
||||||
|
virtual uintptr_t getNativeHandle() const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,131 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/core/color.h>
|
||||||
|
#include <extra2d/core/math_types.h>
|
||||||
|
#include <extra2d/core/types.h>
|
||||||
|
#include <extra2d/graphics/texture/texture.h>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 字形信息结构
|
||||||
|
// ============================================================================
|
||||||
|
struct Glyph {
|
||||||
|
float width = 0; // 字形宽度
|
||||||
|
float height = 0; // 字形高度
|
||||||
|
float bearingX = 0; // 水平偏移
|
||||||
|
float bearingY = 0; // 垂直偏移(从基线到字形顶部)
|
||||||
|
float advance = 0; // 水平步进
|
||||||
|
float u0 = 0, v0 = 0; // 纹理坐标左下角
|
||||||
|
float u1 = 0, v1 = 0; // 纹理坐标右上角
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 字体图集描述结构
|
||||||
|
// ============================================================================
|
||||||
|
struct FontAtlasDesc {
|
||||||
|
std::string filepath; // 字体文件路径
|
||||||
|
int fontSize = 16; // 字体大小
|
||||||
|
bool useSDF = false; // 是否使用SDF渲染
|
||||||
|
int atlasSize = 512; // 图集大小
|
||||||
|
int padding = 2; // 字形间距
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 字体图集抽象接口 - 渲染后端无关
|
||||||
|
// ============================================================================
|
||||||
|
class FontAtlas {
|
||||||
|
public:
|
||||||
|
virtual ~FontAtlas() = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 初始化字体图集
|
||||||
|
* @param desc 字体图集描述
|
||||||
|
* @return 成功返回 true
|
||||||
|
*/
|
||||||
|
virtual bool init(const FontAtlasDesc& desc) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 关闭字体图集,释放资源
|
||||||
|
*/
|
||||||
|
virtual void shutdown() = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取字形信息
|
||||||
|
* @param codepoint Unicode 码点
|
||||||
|
* @return 字形信息指针,未找到返回 nullptr
|
||||||
|
*/
|
||||||
|
virtual const Glyph* getGlyph(char32_t codepoint) const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取纹理
|
||||||
|
* @return 纹理对象
|
||||||
|
*/
|
||||||
|
virtual Ptr<Texture> getTexture() const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取字体大小
|
||||||
|
* @return 字体大小
|
||||||
|
*/
|
||||||
|
virtual int getFontSize() const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取行高
|
||||||
|
* @return 行高
|
||||||
|
*/
|
||||||
|
virtual float getLineHeight() const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取基线到顶部的距离
|
||||||
|
* @return 上升高度
|
||||||
|
*/
|
||||||
|
virtual float getAscent() const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取基线到底部的距离
|
||||||
|
* @return 下降高度
|
||||||
|
*/
|
||||||
|
virtual float getDescent() const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 计算文本宽度
|
||||||
|
* @param text 文本内容
|
||||||
|
* @return 文本宽度
|
||||||
|
*/
|
||||||
|
virtual float measureText(const std::string& text) const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 计算文本尺寸
|
||||||
|
* @param text 文本内容
|
||||||
|
* @return 文本尺寸
|
||||||
|
*/
|
||||||
|
virtual Size measureTextSize(const std::string& text) const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 是否使用SDF渲染
|
||||||
|
* @return 使用SDF返回 true
|
||||||
|
*/
|
||||||
|
virtual bool isSDF() const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查字体图集是否有效
|
||||||
|
* @return 有效返回 true
|
||||||
|
*/
|
||||||
|
virtual bool isValid() const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 预加载字符到图集
|
||||||
|
* @param text 需要预加载的文本
|
||||||
|
* @return 成功加载的字符数
|
||||||
|
*/
|
||||||
|
virtual int preloadGlyphs(const std::string& text) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 清空已加载的字形缓存
|
||||||
|
*/
|
||||||
|
virtual void clearCache() = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,140 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/core/color.h>
|
||||||
|
#include <extra2d/core/types.h>
|
||||||
|
#include <extra2d/graphics/texture/texture.h>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 帧缓冲描述结构
|
||||||
|
// ============================================================================
|
||||||
|
struct FramebufferDesc {
|
||||||
|
int width = 0; // 帧缓冲宽度
|
||||||
|
int height = 0; // 帧缓冲高度
|
||||||
|
int colorAttachments = 1; // 颜色附件数量
|
||||||
|
bool hasDepth = false; // 是否有深度附件
|
||||||
|
bool hasStencil = false; // 是否有模板附件
|
||||||
|
bool multisample = false; // 是否多重采样
|
||||||
|
int samples = 4; // 采样数(多重采样时有效)
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 帧缓冲抽象接口 - 渲染后端无关
|
||||||
|
// ============================================================================
|
||||||
|
class Framebuffer {
|
||||||
|
public:
|
||||||
|
virtual ~Framebuffer() = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 绑定帧缓冲(作为渲染目标)
|
||||||
|
*/
|
||||||
|
virtual void bind() = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 解绑帧缓冲(恢复到默认帧缓冲)
|
||||||
|
*/
|
||||||
|
virtual void unbind() = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 附加颜色纹理
|
||||||
|
* @param texture 纹理对象
|
||||||
|
* @param attachment 附件索引(0-7)
|
||||||
|
*/
|
||||||
|
virtual void attachColorTexture(Ptr<Texture> texture, int attachment = 0) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 附加深度纹理
|
||||||
|
* @param texture 纹理对象
|
||||||
|
*/
|
||||||
|
virtual void attachDepthTexture(Ptr<Texture> texture) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 附加深度模板纹理
|
||||||
|
* @param texture 纹理对象
|
||||||
|
*/
|
||||||
|
virtual void attachDepthStencilTexture(Ptr<Texture> texture) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查帧缓冲是否完整
|
||||||
|
* @return 完整返回 true
|
||||||
|
*/
|
||||||
|
virtual bool isComplete() = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取颜色附件纹理
|
||||||
|
* @param attachment 附件索引
|
||||||
|
* @return 纹理对象
|
||||||
|
*/
|
||||||
|
virtual Ptr<Texture> getColorTexture(int attachment = 0) const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取深度附件纹理
|
||||||
|
* @return 纹理对象
|
||||||
|
*/
|
||||||
|
virtual Ptr<Texture> getDepthTexture() const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取帧缓冲宽度
|
||||||
|
* @return 宽度
|
||||||
|
*/
|
||||||
|
virtual int getWidth() const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取帧缓冲高度
|
||||||
|
* @return 高度
|
||||||
|
*/
|
||||||
|
virtual int getHeight() const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取尺寸
|
||||||
|
* @return 尺寸
|
||||||
|
*/
|
||||||
|
virtual Size getSize() const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查帧缓冲是否有效
|
||||||
|
* @return 有效返回 true
|
||||||
|
*/
|
||||||
|
virtual bool isValid() const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取原生句柄(后端特定)
|
||||||
|
* @return 原生句柄值
|
||||||
|
*/
|
||||||
|
virtual uintptr_t getNativeHandle() const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 清除帧缓冲
|
||||||
|
* @param color 清除颜色
|
||||||
|
* @param clearColor 是否清除颜色缓冲
|
||||||
|
* @param clearDepth 是否清除深度缓冲
|
||||||
|
* @param clearStencil 是否清除模板缓冲
|
||||||
|
*/
|
||||||
|
virtual void clear(const Color& color, bool clearColor = true,
|
||||||
|
bool clearDepth = true, bool clearStencil = false) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置视口
|
||||||
|
* @param x 视口左下角X坐标
|
||||||
|
* @param y 视口左下角Y坐标
|
||||||
|
* @param width 视口宽度
|
||||||
|
* @param height 视口高度
|
||||||
|
*/
|
||||||
|
virtual void setViewport(int x, int y, int width, int height) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 读取像素数据
|
||||||
|
* @param x 起始X坐标
|
||||||
|
* @param y 起始Y坐标
|
||||||
|
* @param width 宽度
|
||||||
|
* @param height 高度
|
||||||
|
* @param outData 输出数据缓冲区
|
||||||
|
* @return 成功返回 true
|
||||||
|
*/
|
||||||
|
virtual bool readPixels(int x, int y, int width, int height,
|
||||||
|
std::vector<uint8_t>& outData) = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,162 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/core/color.h>
|
||||||
|
#include <extra2d/core/types.h>
|
||||||
|
#include <cstdint>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 混合模式枚举
|
||||||
|
// ============================================================================
|
||||||
|
enum class BlendMode {
|
||||||
|
None, // 不混合
|
||||||
|
Alpha, // 标准 Alpha 混合
|
||||||
|
Additive, // 加法混合
|
||||||
|
Multiply // 乘法混合
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 深度测试函数枚举
|
||||||
|
// ============================================================================
|
||||||
|
enum class DepthFunc {
|
||||||
|
Never, // 永不通过
|
||||||
|
Less, // 小于
|
||||||
|
Equal, // 等于
|
||||||
|
LessEqual, // 小于等于
|
||||||
|
Greater, // 大于
|
||||||
|
NotEqual, // 不等于
|
||||||
|
GreaterEqual,// 大于等于
|
||||||
|
Always // 总是通过
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 裁剪模式枚举
|
||||||
|
// ============================================================================
|
||||||
|
enum class CullMode {
|
||||||
|
None, // 不裁剪
|
||||||
|
Front, // 裁剪正面
|
||||||
|
Back, // 裁剪背面
|
||||||
|
Both // 裁剪双面
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 顶点属性格式枚举
|
||||||
|
// ============================================================================
|
||||||
|
enum class VertexFormat {
|
||||||
|
Float1, // 1个float
|
||||||
|
Float2, // 2个float
|
||||||
|
Float3, // 3个float
|
||||||
|
Float4, // 4个float
|
||||||
|
Byte4, // 4个byte
|
||||||
|
UByte4, // 4个ubyte
|
||||||
|
Short2, // 2个short
|
||||||
|
Short4 // 4个short
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 顶点属性描述
|
||||||
|
// ============================================================================
|
||||||
|
struct VertexAttribute {
|
||||||
|
uint32_t location = 0; // 属性位置
|
||||||
|
VertexFormat format = VertexFormat::Float3; // 数据格式
|
||||||
|
uint32_t offset = 0; // 在顶点结构中的偏移
|
||||||
|
uint32_t stride = 0; // 顶点结构大小
|
||||||
|
bool normalized = false; // 是否归一化
|
||||||
|
|
||||||
|
VertexAttribute() = default;
|
||||||
|
VertexAttribute(uint32_t loc, VertexFormat fmt, uint32_t off, uint32_t str, bool norm = false)
|
||||||
|
: location(loc), format(fmt), offset(off), stride(str), normalized(norm) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 管线描述结构
|
||||||
|
// ============================================================================
|
||||||
|
struct PipelineDesc {
|
||||||
|
// 混合状态
|
||||||
|
BlendMode blendMode = BlendMode::Alpha;
|
||||||
|
bool blendEnabled = true;
|
||||||
|
|
||||||
|
// 深度状态
|
||||||
|
bool depthTest = false;
|
||||||
|
bool depthWrite = false;
|
||||||
|
DepthFunc depthFunc = DepthFunc::Less;
|
||||||
|
|
||||||
|
// 裁剪状态
|
||||||
|
CullMode cullMode = CullMode::None;
|
||||||
|
|
||||||
|
// 顶点布局
|
||||||
|
std::vector<VertexAttribute> vertexAttributes;
|
||||||
|
|
||||||
|
// 着色器(由后端特定实现设置)
|
||||||
|
void* vertexShader = nullptr;
|
||||||
|
void* fragmentShader = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 渲染管线抽象接口 - 渲染后端无关
|
||||||
|
// ============================================================================
|
||||||
|
class Pipeline {
|
||||||
|
public:
|
||||||
|
virtual ~Pipeline() = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 绑定管线
|
||||||
|
*/
|
||||||
|
virtual void bind() = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 解绑管线
|
||||||
|
*/
|
||||||
|
virtual void unbind() = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置混合模式
|
||||||
|
* @param mode 混合模式
|
||||||
|
*/
|
||||||
|
virtual void setBlendMode(BlendMode mode) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取当前混合模式
|
||||||
|
* @return 混合模式
|
||||||
|
*/
|
||||||
|
virtual BlendMode getBlendMode() const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置深度测试
|
||||||
|
* @param enabled 是否启用
|
||||||
|
*/
|
||||||
|
virtual void setDepthTest(bool enabled) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置深度写入
|
||||||
|
* @param enabled 是否启用
|
||||||
|
*/
|
||||||
|
virtual void setDepthWrite(bool enabled) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置深度测试函数
|
||||||
|
* @param func 深度测试函数
|
||||||
|
*/
|
||||||
|
virtual void setDepthFunc(DepthFunc func) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置裁剪模式
|
||||||
|
* @param mode 裁剪模式
|
||||||
|
*/
|
||||||
|
virtual void setCullMode(CullMode mode) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查管线是否有效
|
||||||
|
* @return 有效返回 true
|
||||||
|
*/
|
||||||
|
virtual bool isValid() const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取原生句柄(后端特定)
|
||||||
|
* @return 原生句柄值
|
||||||
|
*/
|
||||||
|
virtual uintptr_t getNativeHandle() const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,134 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/core/color.h>
|
||||||
|
#include <extra2d/core/types.h>
|
||||||
|
#include <glm/mat4x4.hpp>
|
||||||
|
#include <glm/vec2.hpp>
|
||||||
|
#include <glm/vec3.hpp>
|
||||||
|
#include <glm/vec4.hpp>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 着色器类型枚举
|
||||||
|
// ============================================================================
|
||||||
|
enum class ShaderType {
|
||||||
|
Vertex, // 顶点着色器
|
||||||
|
Fragment, // 片段着色器
|
||||||
|
Geometry, // 几何着色器
|
||||||
|
Compute // 计算着色器
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 着色器描述结构
|
||||||
|
// ============================================================================
|
||||||
|
struct ShaderDesc {
|
||||||
|
std::string name; // 着色器名称
|
||||||
|
std::string vertexSource; // 顶点着色器源码
|
||||||
|
std::string fragmentSource; // 片段着色器源码
|
||||||
|
std::string geometrySource; // 几何着色器源码(可选)
|
||||||
|
std::vector<uint8_t> binaryData; // 预编译二进制数据(可选)
|
||||||
|
};
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 着色器抽象接口 - 渲染后端无关
|
||||||
|
// ============================================================================
|
||||||
|
class Shader {
|
||||||
|
public:
|
||||||
|
virtual ~Shader() = default;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 绑定着色器程序
|
||||||
|
*/
|
||||||
|
virtual void bind() = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 解绑着色器程序
|
||||||
|
*/
|
||||||
|
virtual void unbind() = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置布尔类型 uniform 变量
|
||||||
|
* @param name uniform 变量名
|
||||||
|
* @param value 布尔值
|
||||||
|
*/
|
||||||
|
virtual void setBool(const std::string& name, bool value) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置整数类型 uniform 变量
|
||||||
|
* @param name uniform 变量名
|
||||||
|
* @param value 整数值
|
||||||
|
*/
|
||||||
|
virtual void setInt(const std::string& name, int value) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置浮点类型 uniform 变量
|
||||||
|
* @param name uniform 变量名
|
||||||
|
* @param value 浮点值
|
||||||
|
*/
|
||||||
|
virtual void setFloat(const std::string& name, float value) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置二维向量类型 uniform 变量
|
||||||
|
* @param name uniform 变量名
|
||||||
|
* @param value 二维向量值
|
||||||
|
*/
|
||||||
|
virtual void setVec2(const std::string& name, const glm::vec2& value) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置三维向量类型 uniform 变量
|
||||||
|
* @param name uniform 变量名
|
||||||
|
* @param value 三维向量值
|
||||||
|
*/
|
||||||
|
virtual void setVec3(const std::string& name, const glm::vec3& value) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置四维向量类型 uniform 变量
|
||||||
|
* @param name uniform 变量名
|
||||||
|
* @param value 四维向量值
|
||||||
|
*/
|
||||||
|
virtual void setVec4(const std::string& name, const glm::vec4& value) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置 4x4 矩阵类型 uniform 变量
|
||||||
|
* @param name uniform 变量名
|
||||||
|
* @param value 4x4 矩阵值
|
||||||
|
*/
|
||||||
|
virtual void setMat4(const std::string& name, const glm::mat4& value) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置颜色类型 uniform 变量
|
||||||
|
* @param name uniform 变量名
|
||||||
|
* @param color 颜色值
|
||||||
|
*/
|
||||||
|
virtual void setColor(const std::string& name, const Color& color) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 设置纹理采样器
|
||||||
|
* @param name uniform 变量名
|
||||||
|
* @param slot 纹理槽位
|
||||||
|
*/
|
||||||
|
virtual void setTexture(const std::string& name, int slot) = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取着色器名称
|
||||||
|
* @return 着色器名称
|
||||||
|
*/
|
||||||
|
virtual const std::string& getName() const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 检查着色器是否有效
|
||||||
|
* @return 有效返回 true
|
||||||
|
*/
|
||||||
|
virtual bool isValid() const = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取原生句柄(后端特定)
|
||||||
|
* @return 原生句柄值
|
||||||
|
*/
|
||||||
|
virtual uintptr_t getNativeHandle() const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -4,7 +4,6 @@
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <unordered_set>
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
|
|
@ -19,12 +18,7 @@ namespace extra2d {
|
||||||
struct FileChangeEvent {
|
struct FileChangeEvent {
|
||||||
std::string filepath;
|
std::string filepath;
|
||||||
|
|
||||||
enum class Type {
|
enum class Type { Created, Modified, Deleted, Renamed } type;
|
||||||
Created,
|
|
||||||
Modified,
|
|
||||||
Deleted,
|
|
||||||
Renamed
|
|
||||||
} type;
|
|
||||||
|
|
||||||
uint64_t timestamp = 0;
|
uint64_t timestamp = 0;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <extra2d/config/platform_detector.h>
|
|
||||||
#include <extra2d/graphics/shader/shader_cache.h>
|
#include <extra2d/graphics/shader/shader_cache.h>
|
||||||
#include <extra2d/graphics/shader/shader_hot_reloader.h>
|
#include <extra2d/graphics/shader/shader_hot_reloader.h>
|
||||||
#include <extra2d/graphics/shader/shader_interface.h>
|
#include <extra2d/graphics/shader/shader_interface.h>
|
||||||
|
|
@ -178,6 +177,14 @@ public:
|
||||||
*/
|
*/
|
||||||
bool loadBuiltinShaders();
|
bool loadBuiltinShaders();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 从JSON元数据文件加载Shader(多后端支持)
|
||||||
|
* @param jsonPath JSON元数据文件路径
|
||||||
|
* @param name Shader名称
|
||||||
|
* @return 加载的Shader实例
|
||||||
|
*/
|
||||||
|
Ptr<IShader> loadFromMetadata(const std::string& jsonPath, const std::string& name);
|
||||||
|
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
// 工具方法
|
// 工具方法
|
||||||
// ------------------------------------------------------------------------
|
// ------------------------------------------------------------------------
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@
|
||||||
#include <extra2d/core/math_types.h>
|
#include <extra2d/core/math_types.h>
|
||||||
#include <extra2d/core/types.h>
|
#include <extra2d/core/types.h>
|
||||||
#include <extra2d/graphics/texture/texture.h>
|
#include <extra2d/graphics/texture/texture.h>
|
||||||
#include <extra2d/graphics/opengl/gl_texture.h>
|
#include <extra2d/graphics/backends/opengl/gl_texture.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@
|
||||||
#include <extra2d/graphics/texture/texture.h>
|
#include <extra2d/graphics/texture/texture.h>
|
||||||
#include <extra2d/utils/logger.h>
|
#include <extra2d/utils/logger.h>
|
||||||
|
|
||||||
#include <algorithm>
|
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
|
@ -13,7 +12,6 @@
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
||||||
|
|
@ -70,9 +68,7 @@ struct TextureKey {
|
||||||
* @param other 另一个 TextureKey
|
* @param other 另一个 TextureKey
|
||||||
* @return 是否不等
|
* @return 是否不等
|
||||||
*/
|
*/
|
||||||
bool operator!=(const TextureKey& other) const {
|
bool operator!=(const TextureKey &other) const { return !(*this == other); }
|
||||||
return !(*this == other);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
@ -115,11 +111,8 @@ struct TexturePoolEntry {
|
||||||
* @brief 默认构造函数
|
* @brief 默认构造函数
|
||||||
*/
|
*/
|
||||||
TexturePoolEntry()
|
TexturePoolEntry()
|
||||||
: texture(nullptr)
|
: texture(nullptr), refCount(0), key(), memorySize(0), lastAccessTime(0) {
|
||||||
, refCount(0)
|
}
|
||||||
, key()
|
|
||||||
, memorySize(0)
|
|
||||||
, lastAccessTime(0) {}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 构造函数
|
* @brief 构造函数
|
||||||
|
|
@ -128,22 +121,18 @@ struct TexturePoolEntry {
|
||||||
* @param memSize 内存占用
|
* @param memSize 内存占用
|
||||||
*/
|
*/
|
||||||
TexturePoolEntry(Ptr<Texture> tex, const TextureKey &k, size_t memSize)
|
TexturePoolEntry(Ptr<Texture> tex, const TextureKey &k, size_t memSize)
|
||||||
: texture(tex)
|
: texture(tex), refCount(1), key(k), memorySize(memSize),
|
||||||
, refCount(1)
|
lastAccessTime(getCurrentTime()) {}
|
||||||
, key(k)
|
|
||||||
, memorySize(memSize)
|
|
||||||
, lastAccessTime(getCurrentTime()) {}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 移动构造函数
|
* @brief 移动构造函数
|
||||||
* @param other 另一个条目
|
* @param other 另一个条目
|
||||||
*/
|
*/
|
||||||
TexturePoolEntry(TexturePoolEntry &&other) noexcept
|
TexturePoolEntry(TexturePoolEntry &&other) noexcept
|
||||||
: texture(std::move(other.texture))
|
: texture(std::move(other.texture)),
|
||||||
, refCount(other.refCount.load(std::memory_order_relaxed))
|
refCount(other.refCount.load(std::memory_order_relaxed)),
|
||||||
, key(std::move(other.key))
|
key(std::move(other.key)), memorySize(other.memorySize),
|
||||||
, memorySize(other.memorySize)
|
lastAccessTime(other.lastAccessTime) {}
|
||||||
, lastAccessTime(other.lastAccessTime) {}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 移动赋值运算符
|
* @brief 移动赋值运算符
|
||||||
|
|
@ -153,7 +142,8 @@ struct TexturePoolEntry {
|
||||||
TexturePoolEntry &operator=(TexturePoolEntry &&other) noexcept {
|
TexturePoolEntry &operator=(TexturePoolEntry &&other) noexcept {
|
||||||
if (this != &other) {
|
if (this != &other) {
|
||||||
texture = std::move(other.texture);
|
texture = std::move(other.texture);
|
||||||
refCount.store(other.refCount.load(std::memory_order_relaxed), std::memory_order_relaxed);
|
refCount.store(other.refCount.load(std::memory_order_relaxed),
|
||||||
|
std::memory_order_relaxed);
|
||||||
key = std::move(other.key);
|
key = std::move(other.key);
|
||||||
memorySize = other.memorySize;
|
memorySize = other.memorySize;
|
||||||
lastAccessTime = other.lastAccessTime;
|
lastAccessTime = other.lastAccessTime;
|
||||||
|
|
@ -226,9 +216,8 @@ public:
|
||||||
* @param other 另一个 TextureRef
|
* @param other 另一个 TextureRef
|
||||||
*/
|
*/
|
||||||
TextureRef(TextureRef &&other) noexcept
|
TextureRef(TextureRef &&other) noexcept
|
||||||
: texture_(std::move(other.texture_))
|
: texture_(std::move(other.texture_)), entry_(other.entry_),
|
||||||
, entry_(other.entry_)
|
mutex_(other.mutex_) {
|
||||||
, mutex_(other.mutex_) {
|
|
||||||
other.entry_ = nullptr;
|
other.entry_ = nullptr;
|
||||||
other.mutex_ = nullptr;
|
other.mutex_ = nullptr;
|
||||||
}
|
}
|
||||||
|
|
@ -423,7 +412,8 @@ public:
|
||||||
* @param options 加载选项
|
* @param options 加载选项
|
||||||
* @return 纹理引用
|
* @return 纹理引用
|
||||||
*/
|
*/
|
||||||
TextureRef getOrLoad(const std::string& path,
|
TextureRef
|
||||||
|
getOrLoad(const std::string &path,
|
||||||
const TextureLoadOptions &options = TextureLoadOptions());
|
const TextureLoadOptions &options = TextureLoadOptions());
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -433,7 +423,8 @@ public:
|
||||||
* @param options 加载选项
|
* @param options 加载选项
|
||||||
* @return 纹理引用
|
* @return 纹理引用
|
||||||
*/
|
*/
|
||||||
TextureRef getOrLoad(const std::string& path, const Rect& region,
|
TextureRef
|
||||||
|
getOrLoad(const std::string &path, const Rect ®ion,
|
||||||
const TextureLoadOptions &options = TextureLoadOptions());
|
const TextureLoadOptions &options = TextureLoadOptions());
|
||||||
|
|
||||||
// ========================================================================
|
// ========================================================================
|
||||||
|
|
@ -555,7 +546,8 @@ private:
|
||||||
|
|
||||||
Scene *scene_; // 场景指针
|
Scene *scene_; // 场景指针
|
||||||
mutable std::mutex mutex_; // 互斥锁
|
mutable std::mutex mutex_; // 互斥锁
|
||||||
std::unordered_map<TextureKey, TexturePoolEntry, TextureKeyHash> cache_; // 纹理缓存
|
std::unordered_map<TextureKey, TexturePoolEntry, TextureKeyHash>
|
||||||
|
cache_; // 纹理缓存
|
||||||
|
|
||||||
size_t maxMemoryUsage_; // 最大内存使用量
|
size_t maxMemoryUsage_; // 最大内存使用量
|
||||||
size_t currentMemoryUsage_; // 当前内存使用量
|
size_t currentMemoryUsage_; // 当前内存使用量
|
||||||
|
|
|
||||||
|
|
@ -3,27 +3,22 @@
|
||||||
#include <extra2d/core/module.h>
|
#include <extra2d/core/module.h>
|
||||||
#include <extra2d/platform/iinput.h>
|
#include <extra2d/platform/iinput.h>
|
||||||
#include <extra2d/platform/window_module.h>
|
#include <extra2d/platform/window_module.h>
|
||||||
|
#include <functional>
|
||||||
#include <typeindex>
|
#include <typeindex>
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 输入模块
|
* @brief 输入模块配置结构
|
||||||
* 管理输入设备
|
|
||||||
*/
|
*/
|
||||||
class InputModule : public Module {
|
struct InputCfg {
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief 配置结构
|
|
||||||
*/
|
|
||||||
struct Cfg {
|
|
||||||
float deadzone;
|
float deadzone;
|
||||||
float mouseSensitivity;
|
float mouseSensitivity;
|
||||||
bool enableVibration;
|
bool enableVibration;
|
||||||
int maxGamepads;
|
int maxGamepads;
|
||||||
int priority;
|
int priority;
|
||||||
|
|
||||||
Cfg()
|
InputCfg()
|
||||||
: deadzone(0.15f)
|
: deadzone(0.15f)
|
||||||
, mouseSensitivity(1.0f)
|
, mouseSensitivity(1.0f)
|
||||||
, enableVibration(true)
|
, enableVibration(true)
|
||||||
|
|
@ -33,10 +28,16 @@ public:
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 构造函数
|
* @brief 输入模块
|
||||||
* @param cfg 配置
|
* 管理输入设备
|
||||||
*/
|
*/
|
||||||
explicit InputModule(const Cfg& cfg = Cfg{});
|
class InputModule : public Module {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief 构造函数(Lambda 配置)
|
||||||
|
* @param configFn 配置函数
|
||||||
|
*/
|
||||||
|
explicit InputModule(std::function<void(InputCfg&)> configFn);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 析构函数
|
* @brief 析构函数
|
||||||
|
|
@ -69,7 +70,7 @@ public:
|
||||||
void update();
|
void update();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Cfg cfg_;
|
InputCfg cfg_;
|
||||||
IInput* input_ = nullptr;
|
IInput* input_ = nullptr;
|
||||||
bool initialized_ = false;
|
bool initialized_ = false;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,21 +1,17 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <extra2d/core/module.h>
|
#include <extra2d/core/module.h>
|
||||||
#include <extra2d/platform/window_config.h>
|
|
||||||
#include <extra2d/platform/iwindow.h>
|
#include <extra2d/platform/iwindow.h>
|
||||||
|
#include <extra2d/platform/window_config.h>
|
||||||
|
#include <functional>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 窗口模块
|
* @brief 窗口模块配置结构
|
||||||
* 管理窗口创建和生命周期
|
|
||||||
*/
|
*/
|
||||||
class WindowModule : public Module {
|
struct WindowCfg {
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief 配置结构
|
|
||||||
*/
|
|
||||||
struct Cfg {
|
|
||||||
std::string title;
|
std::string title;
|
||||||
int w;
|
int w;
|
||||||
int h;
|
int h;
|
||||||
|
|
@ -24,21 +20,22 @@ public:
|
||||||
int priority;
|
int priority;
|
||||||
std::string backend;
|
std::string backend;
|
||||||
|
|
||||||
Cfg()
|
WindowCfg()
|
||||||
: title("Extra2D")
|
: title("Extra2D"), w(1280), h(720), mode(WindowMode::Windowed),
|
||||||
, w(1280)
|
vsync(true), priority(0), backend("sdl2") {}
|
||||||
, h(720)
|
|
||||||
, mode(WindowMode::Windowed)
|
|
||||||
, vsync(true)
|
|
||||||
, priority(0)
|
|
||||||
, backend("sdl2") {}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 构造函数
|
* @brief 窗口模块
|
||||||
* @param cfg 配置
|
* 管理窗口创建和生命周期
|
||||||
*/
|
*/
|
||||||
explicit WindowModule(const Cfg& cfg = Cfg());
|
class WindowModule : public Module {
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief 构造函数(Lambda 配置)
|
||||||
|
* @param configFn 配置函数
|
||||||
|
*/
|
||||||
|
explicit WindowModule(std::function<void(WindowCfg &)> configFn);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 析构函数
|
* @brief 析构函数
|
||||||
|
|
@ -58,7 +55,7 @@ public:
|
||||||
IWindow *win() const { return win_.get(); }
|
IWindow *win() const { return win_.get(); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Cfg cfg_;
|
WindowCfg cfg_;
|
||||||
UniquePtr<IWindow> win_;
|
UniquePtr<IWindow> win_;
|
||||||
bool initialized_ = false;
|
bool initialized_ = false;
|
||||||
bool sdlInited_ = false;
|
bool sdlInited_ = false;
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,7 @@ public:
|
||||||
protected:
|
protected:
|
||||||
void onTransitionStart() override;
|
void onTransitionStart() override;
|
||||||
void renderContent(RenderBackend &renderer) override;
|
void renderContent(RenderBackend &renderer) override;
|
||||||
void updateTransition(float dt);
|
void updateTransition(float dt) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int divisions_;
|
int divisions_;
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ public:
|
||||||
protected:
|
protected:
|
||||||
void onTransitionStart() override;
|
void onTransitionStart() override;
|
||||||
void renderContent(RenderBackend &renderer) override;
|
void renderContent(RenderBackend &renderer) override;
|
||||||
void updateTransition(float dt);
|
void updateTransition(float dt) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Axis axis_;
|
Axis axis_;
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ public:
|
||||||
protected:
|
protected:
|
||||||
void onTransitionStart() override;
|
void onTransitionStart() override;
|
||||||
void renderContent(RenderBackend &renderer) override;
|
void renderContent(RenderBackend &renderer) override;
|
||||||
void updateTransition(float dt);
|
void updateTransition(float dt) override;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace extra2d
|
} // namespace extra2d
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ public:
|
||||||
protected:
|
protected:
|
||||||
void onTransitionStart() override;
|
void onTransitionStart() override;
|
||||||
void renderContent(RenderBackend &renderer) override;
|
void renderContent(RenderBackend &renderer) override;
|
||||||
void updateTransition(float dt);
|
void updateTransition(float dt) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
TransitionDirection direction_;
|
TransitionDirection direction_;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <extra2d/core/service_interface.h>
|
#include <extra2d/core/service_interface.h>
|
||||||
|
#include <extra2d/core/service_locator.h>
|
||||||
#include <extra2d/graphics/camera/camera.h>
|
#include <extra2d/graphics/camera/camera.h>
|
||||||
#include <extra2d/graphics/camera/viewport_adapter.h>
|
#include <extra2d/graphics/camera/viewport_adapter.h>
|
||||||
|
|
||||||
|
|
@ -106,6 +107,9 @@ public:
|
||||||
private:
|
private:
|
||||||
Camera camera_;
|
Camera camera_;
|
||||||
ViewportAdapter viewportAdapter_;
|
ViewportAdapter viewportAdapter_;
|
||||||
|
|
||||||
|
// 服务注册元数据
|
||||||
|
E2D_AUTO_REGISTER_SERVICE(ICameraService, CameraService);
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <extra2d/core/service_interface.h>
|
#include <extra2d/core/service_interface.h>
|
||||||
|
#include <extra2d/core/service_locator.h>
|
||||||
#include <extra2d/event/event_dispatcher.h>
|
#include <extra2d/event/event_dispatcher.h>
|
||||||
#include <extra2d/event/event_queue.h>
|
#include <extra2d/event/event_queue.h>
|
||||||
|
|
||||||
|
|
@ -68,6 +69,9 @@ public:
|
||||||
private:
|
private:
|
||||||
EventQueue queue_;
|
EventQueue queue_;
|
||||||
EventDispatcher dispatcher_;
|
EventDispatcher dispatcher_;
|
||||||
|
|
||||||
|
// 服务注册元数据
|
||||||
|
E2D_AUTO_REGISTER_SERVICE(IEventService, EventService);
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <extra2d/core/service_interface.h>
|
#include <extra2d/core/service_interface.h>
|
||||||
|
#include <extra2d/core/service_locator.h>
|
||||||
#include <extra2d/core/types.h>
|
#include <extra2d/core/types.h>
|
||||||
#include <cstdarg>
|
#include <cstdarg>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
@ -124,6 +125,9 @@ private:
|
||||||
LogLevel level_;
|
LogLevel level_;
|
||||||
class Impl;
|
class Impl;
|
||||||
UniquePtr<Impl> impl_;
|
UniquePtr<Impl> impl_;
|
||||||
|
|
||||||
|
// 服务注册元数据
|
||||||
|
E2D_AUTO_REGISTER_SERVICE(ILogger, ConsoleLogger);
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace extra2d
|
} // namespace extra2d
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <extra2d/core/service_interface.h>
|
#include <extra2d/core/service_interface.h>
|
||||||
|
#include <extra2d/core/service_locator.h>
|
||||||
#include <extra2d/scene/scene_manager.h>
|
#include <extra2d/scene/scene_manager.h>
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
@ -86,6 +87,9 @@ public:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
SceneManager manager_;
|
SceneManager manager_;
|
||||||
|
|
||||||
|
// 服务注册元数据
|
||||||
|
E2D_AUTO_REGISTER_SERVICE(ISceneService, SceneService);
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <extra2d/core/service_interface.h>
|
#include <extra2d/core/service_interface.h>
|
||||||
|
#include <extra2d/core/service_locator.h>
|
||||||
#include <extra2d/utils/timer.h>
|
#include <extra2d/utils/timer.h>
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
@ -48,6 +49,9 @@ public:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
TimerManager manager_;
|
TimerManager manager_;
|
||||||
|
|
||||||
|
// 服务注册元数据
|
||||||
|
E2D_AUTO_REGISTER_SERVICE(ITimerService, TimerService);
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
#version 300 es
|
||||||
|
precision highp float;
|
||||||
|
|
||||||
|
in vec4 v_color;
|
||||||
|
|
||||||
|
out vec4 fragColor;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
fragColor = v_color;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
#version 300 es
|
||||||
|
precision highp float;
|
||||||
|
|
||||||
|
layout(location = 0) in vec2 a_position;
|
||||||
|
layout(location = 1) in vec4 a_color;
|
||||||
|
|
||||||
|
uniform mat4 u_viewProjection;
|
||||||
|
|
||||||
|
out vec4 v_color;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
gl_Position = u_viewProjection * vec4(a_position, 0.0, 1.0);
|
||||||
|
v_color = a_color;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
#version 300 es
|
||||||
|
precision highp float;
|
||||||
|
|
||||||
|
in vec2 v_texCoord;
|
||||||
|
in vec4 v_color;
|
||||||
|
|
||||||
|
uniform sampler2D u_texture;
|
||||||
|
uniform float u_opacity;
|
||||||
|
|
||||||
|
out vec4 fragColor;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
vec4 texColor = texture(u_texture, v_texCoord);
|
||||||
|
fragColor = texColor * v_color;
|
||||||
|
fragColor.a *= u_opacity;
|
||||||
|
|
||||||
|
if (fragColor.a < 0.01) {
|
||||||
|
discard;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
#version 300 es
|
||||||
|
precision highp float;
|
||||||
|
|
||||||
|
layout(location = 0) in vec2 a_position;
|
||||||
|
layout(location = 1) in vec2 a_texCoord;
|
||||||
|
layout(location = 2) in vec4 a_color;
|
||||||
|
|
||||||
|
uniform mat4 u_viewProjection;
|
||||||
|
uniform mat4 u_model;
|
||||||
|
|
||||||
|
out vec2 v_texCoord;
|
||||||
|
out vec4 v_color;
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
gl_Position = u_viewProjection * u_model * vec4(a_position, 0.0, 1.0);
|
||||||
|
v_texCoord = a_texCoord;
|
||||||
|
v_color = a_color;
|
||||||
|
}
|
||||||
|
|
@ -1,56 +0,0 @@
|
||||||
// ============================================
|
|
||||||
// Extra2D Combined Shader File
|
|
||||||
// Name: font
|
|
||||||
// Category: builtin
|
|
||||||
// Version: 1.0
|
|
||||||
// ============================================
|
|
||||||
|
|
||||||
#meta
|
|
||||||
{
|
|
||||||
"name": "font",
|
|
||||||
"category": "builtin",
|
|
||||||
"author": "Extra2D Team",
|
|
||||||
"description": "字体渲染Shader,支持SDF"
|
|
||||||
}
|
|
||||||
|
|
||||||
#vertex
|
|
||||||
#version 300 es
|
|
||||||
precision highp float;
|
|
||||||
|
|
||||||
layout(location = 0) in vec2 a_position;
|
|
||||||
layout(location = 1) in vec2 a_texCoord;
|
|
||||||
layout(location = 2) in vec4 a_color;
|
|
||||||
|
|
||||||
uniform mat4 u_viewProjection;
|
|
||||||
uniform mat4 u_model;
|
|
||||||
|
|
||||||
out vec2 v_texCoord;
|
|
||||||
out vec4 v_color;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
gl_Position = u_viewProjection * u_model * vec4(a_position, 0.0, 1.0);
|
|
||||||
v_texCoord = a_texCoord;
|
|
||||||
v_color = a_color;
|
|
||||||
}
|
|
||||||
|
|
||||||
#fragment
|
|
||||||
#version 300 es
|
|
||||||
precision highp float;
|
|
||||||
|
|
||||||
in vec2 v_texCoord;
|
|
||||||
in vec4 v_color;
|
|
||||||
|
|
||||||
uniform sampler2D u_texture;
|
|
||||||
uniform float u_smoothing;
|
|
||||||
|
|
||||||
out vec4 fragColor;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
float dist = texture(u_texture, v_texCoord).r;
|
|
||||||
float alpha = smoothstep(0.5 - u_smoothing, 0.5 + u_smoothing, dist);
|
|
||||||
fragColor = vec4(v_color.rgb, v_color.a * alpha);
|
|
||||||
|
|
||||||
if (fragColor.a < 0.01) {
|
|
||||||
discard;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,53 +0,0 @@
|
||||||
// ============================================
|
|
||||||
// Extra2D Combined Shader File
|
|
||||||
// Name: particle
|
|
||||||
// Category: builtin
|
|
||||||
// Version: 1.0
|
|
||||||
// ============================================
|
|
||||||
|
|
||||||
#meta
|
|
||||||
{
|
|
||||||
"name": "particle",
|
|
||||||
"category": "builtin",
|
|
||||||
"author": "Extra2D Team",
|
|
||||||
"description": "粒子渲染Shader"
|
|
||||||
}
|
|
||||||
|
|
||||||
#vertex
|
|
||||||
#version 300 es
|
|
||||||
precision highp float;
|
|
||||||
|
|
||||||
layout(location = 0) in vec2 a_position;
|
|
||||||
layout(location = 1) in vec2 a_texCoord;
|
|
||||||
layout(location = 2) in vec4 a_color;
|
|
||||||
|
|
||||||
uniform mat4 u_viewProjection;
|
|
||||||
|
|
||||||
out vec2 v_texCoord;
|
|
||||||
out vec4 v_color;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
gl_Position = u_viewProjection * vec4(a_position, 0.0, 1.0);
|
|
||||||
v_texCoord = a_texCoord;
|
|
||||||
v_color = a_color;
|
|
||||||
}
|
|
||||||
|
|
||||||
#fragment
|
|
||||||
#version 300 es
|
|
||||||
precision highp float;
|
|
||||||
|
|
||||||
in vec2 v_texCoord;
|
|
||||||
in vec4 v_color;
|
|
||||||
|
|
||||||
uniform sampler2D u_texture;
|
|
||||||
|
|
||||||
out vec4 fragColor;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
vec4 texColor = texture(u_texture, v_texCoord);
|
|
||||||
fragColor = texColor * v_color;
|
|
||||||
|
|
||||||
if (fragColor.a < 0.01) {
|
|
||||||
discard;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,42 +0,0 @@
|
||||||
// ============================================
|
|
||||||
// Extra2D Combined Shader File
|
|
||||||
// Name: postprocess
|
|
||||||
// Category: builtin
|
|
||||||
// Version: 1.0
|
|
||||||
// ============================================
|
|
||||||
|
|
||||||
#meta
|
|
||||||
{
|
|
||||||
"name": "postprocess",
|
|
||||||
"category": "builtin",
|
|
||||||
"author": "Extra2D Team",
|
|
||||||
"description": "后处理基础Shader"
|
|
||||||
}
|
|
||||||
|
|
||||||
#vertex
|
|
||||||
#version 300 es
|
|
||||||
precision highp float;
|
|
||||||
|
|
||||||
layout(location = 0) in vec2 a_position;
|
|
||||||
layout(location = 1) in vec2 a_texCoord;
|
|
||||||
|
|
||||||
out vec2 v_texCoord;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
gl_Position = vec4(a_position, 0.0, 1.0);
|
|
||||||
v_texCoord = a_texCoord;
|
|
||||||
}
|
|
||||||
|
|
||||||
#fragment
|
|
||||||
#version 300 es
|
|
||||||
precision highp float;
|
|
||||||
|
|
||||||
in vec2 v_texCoord;
|
|
||||||
|
|
||||||
uniform sampler2D u_texture;
|
|
||||||
|
|
||||||
out vec4 fragColor;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
fragColor = texture(u_texture, v_texCoord);
|
|
||||||
}
|
|
||||||
|
|
@ -1,42 +0,0 @@
|
||||||
// ============================================
|
|
||||||
// Extra2D Combined Shader File
|
|
||||||
// Name: shape
|
|
||||||
// Category: builtin
|
|
||||||
// Version: 1.0
|
|
||||||
// ============================================
|
|
||||||
|
|
||||||
#meta
|
|
||||||
{
|
|
||||||
"name": "shape",
|
|
||||||
"category": "builtin",
|
|
||||||
"author": "Extra2D Team",
|
|
||||||
"description": "形状渲染Shader,支持顶点颜色批处理"
|
|
||||||
}
|
|
||||||
|
|
||||||
#vertex
|
|
||||||
#version 300 es
|
|
||||||
precision highp float;
|
|
||||||
|
|
||||||
layout(location = 0) in vec2 a_position;
|
|
||||||
layout(location = 1) in vec4 a_color;
|
|
||||||
|
|
||||||
uniform mat4 u_viewProjection;
|
|
||||||
|
|
||||||
out vec4 v_color;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
gl_Position = u_viewProjection * vec4(a_position, 0.0, 1.0);
|
|
||||||
v_color = a_color;
|
|
||||||
}
|
|
||||||
|
|
||||||
#fragment
|
|
||||||
#version 300 es
|
|
||||||
precision highp float;
|
|
||||||
|
|
||||||
in vec4 v_color;
|
|
||||||
|
|
||||||
out vec4 fragColor;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
fragColor = v_color;
|
|
||||||
}
|
|
||||||
|
|
@ -1,61 +0,0 @@
|
||||||
// ============================================
|
|
||||||
// Extra2D Combined Shader File
|
|
||||||
// Name: sprite
|
|
||||||
// Category: builtin
|
|
||||||
// Version: 1.0
|
|
||||||
// ============================================
|
|
||||||
|
|
||||||
#meta
|
|
||||||
{
|
|
||||||
"name": "sprite",
|
|
||||||
"category": "builtin",
|
|
||||||
"author": "Extra2D Team",
|
|
||||||
"description": "标准2D精灵渲染Shader",
|
|
||||||
"uniforms": {
|
|
||||||
"u_viewProjection": { "type": "mat4", "description": "视图投影矩阵" },
|
|
||||||
"u_model": { "type": "mat4", "description": "模型矩阵" },
|
|
||||||
"u_opacity": { "type": "float", "default": 1.0, "description": "透明度" }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#vertex
|
|
||||||
#version 300 es
|
|
||||||
precision highp float;
|
|
||||||
|
|
||||||
layout(location = 0) in vec2 a_position;
|
|
||||||
layout(location = 1) in vec2 a_texCoord;
|
|
||||||
layout(location = 2) in vec4 a_color;
|
|
||||||
|
|
||||||
uniform mat4 u_viewProjection;
|
|
||||||
uniform mat4 u_model;
|
|
||||||
|
|
||||||
out vec2 v_texCoord;
|
|
||||||
out vec4 v_color;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
gl_Position = u_viewProjection * u_model * vec4(a_position, 0.0, 1.0);
|
|
||||||
v_texCoord = a_texCoord;
|
|
||||||
v_color = a_color;
|
|
||||||
}
|
|
||||||
|
|
||||||
#fragment
|
|
||||||
#version 300 es
|
|
||||||
precision highp float;
|
|
||||||
|
|
||||||
in vec2 v_texCoord;
|
|
||||||
in vec4 v_color;
|
|
||||||
|
|
||||||
uniform sampler2D u_texture;
|
|
||||||
uniform float u_opacity;
|
|
||||||
|
|
||||||
out vec4 fragColor;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
vec4 texColor = texture(u_texture, v_texCoord);
|
|
||||||
fragColor = texColor * v_color;
|
|
||||||
fragColor.a *= u_opacity;
|
|
||||||
|
|
||||||
if (fragColor.a < 0.01) {
|
|
||||||
discard;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,71 +0,0 @@
|
||||||
// ============================================
|
|
||||||
// Extra2D Combined Shader File
|
|
||||||
// Name: blur
|
|
||||||
// Category: effects
|
|
||||||
// Version: 1.0
|
|
||||||
// ============================================
|
|
||||||
|
|
||||||
#meta
|
|
||||||
{
|
|
||||||
"name": "blur",
|
|
||||||
"category": "effects",
|
|
||||||
"author": "Extra2D Team",
|
|
||||||
"description": "模糊特效Shader"
|
|
||||||
}
|
|
||||||
|
|
||||||
#vertex
|
|
||||||
#version 300 es
|
|
||||||
precision highp float;
|
|
||||||
|
|
||||||
layout(location = 0) in vec2 a_position;
|
|
||||||
layout(location = 1) in vec2 a_texCoord;
|
|
||||||
layout(location = 2) in vec4 a_color;
|
|
||||||
|
|
||||||
uniform mat4 u_viewProjection;
|
|
||||||
uniform mat4 u_model;
|
|
||||||
|
|
||||||
out vec2 v_texCoord;
|
|
||||||
out vec4 v_color;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
gl_Position = u_viewProjection * u_model * vec4(a_position, 0.0, 1.0);
|
|
||||||
v_texCoord = a_texCoord;
|
|
||||||
v_color = a_color;
|
|
||||||
}
|
|
||||||
|
|
||||||
#fragment
|
|
||||||
#version 300 es
|
|
||||||
precision highp float;
|
|
||||||
|
|
||||||
in vec2 v_texCoord;
|
|
||||||
in vec4 v_color;
|
|
||||||
|
|
||||||
uniform sampler2D u_texture;
|
|
||||||
uniform float u_radius;
|
|
||||||
uniform vec2 u_textureSize;
|
|
||||||
uniform float u_opacity;
|
|
||||||
|
|
||||||
out vec4 fragColor;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
vec2 texel = u_radius / u_textureSize;
|
|
||||||
|
|
||||||
vec4 sum = vec4(0.0);
|
|
||||||
sum += texture(u_texture, v_texCoord + texel * vec2(-1.0, -1.0));
|
|
||||||
sum += texture(u_texture, v_texCoord + texel * vec2( 0.0, -1.0));
|
|
||||||
sum += texture(u_texture, v_texCoord + texel * vec2( 1.0, -1.0));
|
|
||||||
sum += texture(u_texture, v_texCoord + texel * vec2(-1.0, 0.0));
|
|
||||||
sum += texture(u_texture, v_texCoord + texel * vec2( 0.0, 0.0));
|
|
||||||
sum += texture(u_texture, v_texCoord + texel * vec2( 1.0, 0.0));
|
|
||||||
sum += texture(u_texture, v_texCoord + texel * vec2(-1.0, 1.0));
|
|
||||||
sum += texture(u_texture, v_texCoord + texel * vec2( 0.0, 1.0));
|
|
||||||
sum += texture(u_texture, v_texCoord + texel * vec2( 1.0, 1.0));
|
|
||||||
|
|
||||||
vec4 texColor = sum / 9.0;
|
|
||||||
fragColor = texColor * v_color;
|
|
||||||
fragColor.a *= u_opacity;
|
|
||||||
|
|
||||||
if (fragColor.a < 0.01) {
|
|
||||||
discard;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,64 +0,0 @@
|
||||||
// ============================================
|
|
||||||
// Extra2D Combined Shader File
|
|
||||||
// Name: distortion
|
|
||||||
// Category: effects
|
|
||||||
// Version: 1.0
|
|
||||||
// ============================================
|
|
||||||
|
|
||||||
#meta
|
|
||||||
{
|
|
||||||
"name": "distortion",
|
|
||||||
"category": "effects",
|
|
||||||
"author": "Extra2D Team",
|
|
||||||
"description": "扭曲特效Shader"
|
|
||||||
}
|
|
||||||
|
|
||||||
#vertex
|
|
||||||
#version 300 es
|
|
||||||
precision highp float;
|
|
||||||
|
|
||||||
layout(location = 0) in vec2 a_position;
|
|
||||||
layout(location = 1) in vec2 a_texCoord;
|
|
||||||
layout(location = 2) in vec4 a_color;
|
|
||||||
|
|
||||||
uniform mat4 u_viewProjection;
|
|
||||||
uniform mat4 u_model;
|
|
||||||
|
|
||||||
out vec2 v_texCoord;
|
|
||||||
out vec4 v_color;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
gl_Position = u_viewProjection * u_model * vec4(a_position, 0.0, 1.0);
|
|
||||||
v_texCoord = a_texCoord;
|
|
||||||
v_color = a_color;
|
|
||||||
}
|
|
||||||
|
|
||||||
#fragment
|
|
||||||
#version 300 es
|
|
||||||
precision highp float;
|
|
||||||
|
|
||||||
in vec2 v_texCoord;
|
|
||||||
in vec4 v_color;
|
|
||||||
|
|
||||||
uniform sampler2D u_texture;
|
|
||||||
uniform float u_distortionAmount;
|
|
||||||
uniform float u_time;
|
|
||||||
uniform float u_timeScale;
|
|
||||||
|
|
||||||
out vec4 fragColor;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
vec2 uv = v_texCoord;
|
|
||||||
|
|
||||||
float t = u_time * u_timeScale;
|
|
||||||
float dx = sin(uv.y * 10.0 + t) * u_distortionAmount;
|
|
||||||
float dy = cos(uv.x * 10.0 + t) * u_distortionAmount;
|
|
||||||
uv += vec2(dx, dy);
|
|
||||||
|
|
||||||
vec4 texColor = texture(u_texture, uv);
|
|
||||||
fragColor = texColor * v_color;
|
|
||||||
|
|
||||||
if (fragColor.a < 0.01) {
|
|
||||||
discard;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,61 +0,0 @@
|
||||||
// ============================================
|
|
||||||
// Extra2D Combined Shader File
|
|
||||||
// Name: grayscale
|
|
||||||
// Category: effects
|
|
||||||
// Version: 1.0
|
|
||||||
// ============================================
|
|
||||||
|
|
||||||
#meta
|
|
||||||
{
|
|
||||||
"name": "grayscale",
|
|
||||||
"category": "effects",
|
|
||||||
"author": "Extra2D Team",
|
|
||||||
"description": "灰度特效Shader"
|
|
||||||
}
|
|
||||||
|
|
||||||
#vertex
|
|
||||||
#version 300 es
|
|
||||||
precision highp float;
|
|
||||||
|
|
||||||
layout(location = 0) in vec2 a_position;
|
|
||||||
layout(location = 1) in vec2 a_texCoord;
|
|
||||||
layout(location = 2) in vec4 a_color;
|
|
||||||
|
|
||||||
uniform mat4 u_viewProjection;
|
|
||||||
uniform mat4 u_model;
|
|
||||||
|
|
||||||
out vec2 v_texCoord;
|
|
||||||
out vec4 v_color;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
gl_Position = u_viewProjection * u_model * vec4(a_position, 0.0, 1.0);
|
|
||||||
v_texCoord = a_texCoord;
|
|
||||||
v_color = a_color;
|
|
||||||
}
|
|
||||||
|
|
||||||
#fragment
|
|
||||||
#version 300 es
|
|
||||||
precision highp float;
|
|
||||||
|
|
||||||
in vec2 v_texCoord;
|
|
||||||
in vec4 v_color;
|
|
||||||
|
|
||||||
uniform sampler2D u_texture;
|
|
||||||
uniform float u_intensity;
|
|
||||||
uniform float u_opacity;
|
|
||||||
|
|
||||||
out vec4 fragColor;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
vec4 texColor = texture(u_texture, v_texCoord) * v_color;
|
|
||||||
|
|
||||||
float gray = dot(texColor.rgb, vec3(0.299, 0.587, 0.114));
|
|
||||||
texColor.rgb = mix(texColor.rgb, vec3(gray), u_intensity);
|
|
||||||
|
|
||||||
fragColor = texColor;
|
|
||||||
fragColor.a *= u_opacity;
|
|
||||||
|
|
||||||
if (fragColor.a < 0.01) {
|
|
||||||
discard;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,77 +0,0 @@
|
||||||
// ============================================
|
|
||||||
// Extra2D Combined Shader File
|
|
||||||
// Name: grayscale_outline
|
|
||||||
// Category: effects
|
|
||||||
// Version: 1.0
|
|
||||||
// ============================================
|
|
||||||
|
|
||||||
#meta
|
|
||||||
{
|
|
||||||
"name": "grayscale_outline",
|
|
||||||
"category": "effects",
|
|
||||||
"author": "Extra2D Team",
|
|
||||||
"description": "灰度+描边组合效果Shader"
|
|
||||||
}
|
|
||||||
|
|
||||||
#vertex
|
|
||||||
#version 300 es
|
|
||||||
precision highp float;
|
|
||||||
|
|
||||||
layout(location = 0) in vec2 a_position;
|
|
||||||
layout(location = 1) in vec2 a_texCoord;
|
|
||||||
layout(location = 2) in vec4 a_color;
|
|
||||||
|
|
||||||
uniform mat4 u_viewProjection;
|
|
||||||
uniform mat4 u_model;
|
|
||||||
|
|
||||||
out vec2 v_texCoord;
|
|
||||||
out vec4 v_color;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
gl_Position = u_viewProjection * u_model * vec4(a_position, 0.0, 1.0);
|
|
||||||
v_texCoord = a_texCoord;
|
|
||||||
v_color = a_color;
|
|
||||||
}
|
|
||||||
|
|
||||||
#fragment
|
|
||||||
#version 300 es
|
|
||||||
precision highp float;
|
|
||||||
|
|
||||||
in vec2 v_texCoord;
|
|
||||||
in vec4 v_color;
|
|
||||||
|
|
||||||
uniform sampler2D u_texture;
|
|
||||||
uniform float u_grayIntensity;
|
|
||||||
uniform vec4 u_outlineColor;
|
|
||||||
uniform float u_thickness;
|
|
||||||
uniform vec2 u_textureSize;
|
|
||||||
uniform float u_opacity;
|
|
||||||
|
|
||||||
out vec4 fragColor;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
vec4 color = texture(u_texture, v_texCoord) * v_color;
|
|
||||||
|
|
||||||
float gray = dot(color.rgb, vec3(0.299, 0.587, 0.114));
|
|
||||||
color.rgb = mix(color.rgb, vec3(gray), u_grayIntensity);
|
|
||||||
|
|
||||||
float alpha = 0.0;
|
|
||||||
vec2 offset = u_thickness / u_textureSize;
|
|
||||||
|
|
||||||
alpha += texture(u_texture, v_texCoord + vec2(-offset.x, 0.0)).a;
|
|
||||||
alpha += texture(u_texture, v_texCoord + vec2(offset.x, 0.0)).a;
|
|
||||||
alpha += texture(u_texture, v_texCoord + vec2(0.0, -offset.y)).a;
|
|
||||||
alpha += texture(u_texture, v_texCoord + vec2(0.0, offset.y)).a;
|
|
||||||
|
|
||||||
if (color.a < 0.1 && alpha > 0.0) {
|
|
||||||
fragColor = u_outlineColor;
|
|
||||||
} else {
|
|
||||||
fragColor = color;
|
|
||||||
}
|
|
||||||
|
|
||||||
fragColor.a *= u_opacity;
|
|
||||||
|
|
||||||
if (fragColor.a < 0.01) {
|
|
||||||
discard;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,60 +0,0 @@
|
||||||
// ============================================
|
|
||||||
// Extra2D Combined Shader File
|
|
||||||
// Name: invert
|
|
||||||
// Category: effects
|
|
||||||
// Version: 1.0
|
|
||||||
// ============================================
|
|
||||||
|
|
||||||
#meta
|
|
||||||
{
|
|
||||||
"name": "invert",
|
|
||||||
"category": "effects",
|
|
||||||
"author": "Extra2D Team",
|
|
||||||
"description": "反相特效Shader"
|
|
||||||
}
|
|
||||||
|
|
||||||
#vertex
|
|
||||||
#version 300 es
|
|
||||||
precision highp float;
|
|
||||||
|
|
||||||
layout(location = 0) in vec2 a_position;
|
|
||||||
layout(location = 1) in vec2 a_texCoord;
|
|
||||||
layout(location = 2) in vec4 a_color;
|
|
||||||
|
|
||||||
uniform mat4 u_viewProjection;
|
|
||||||
uniform mat4 u_model;
|
|
||||||
|
|
||||||
out vec2 v_texCoord;
|
|
||||||
out vec4 v_color;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
gl_Position = u_viewProjection * u_model * vec4(a_position, 0.0, 1.0);
|
|
||||||
v_texCoord = a_texCoord;
|
|
||||||
v_color = a_color;
|
|
||||||
}
|
|
||||||
|
|
||||||
#fragment
|
|
||||||
#version 300 es
|
|
||||||
precision highp float;
|
|
||||||
|
|
||||||
in vec2 v_texCoord;
|
|
||||||
in vec4 v_color;
|
|
||||||
|
|
||||||
uniform sampler2D u_texture;
|
|
||||||
uniform float u_strength;
|
|
||||||
uniform float u_opacity;
|
|
||||||
|
|
||||||
out vec4 fragColor;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
vec4 texColor = texture(u_texture, v_texCoord) * v_color;
|
|
||||||
vec3 inverted = vec3(1.0) - texColor.rgb;
|
|
||||||
texColor.rgb = mix(texColor.rgb, inverted, u_strength);
|
|
||||||
|
|
||||||
fragColor = texColor;
|
|
||||||
fragColor.a *= u_opacity;
|
|
||||||
|
|
||||||
if (fragColor.a < 0.01) {
|
|
||||||
discard;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,70 +0,0 @@
|
||||||
// ============================================
|
|
||||||
// Extra2D Combined Shader File
|
|
||||||
// Name: outline
|
|
||||||
// Category: effects
|
|
||||||
// Version: 1.0
|
|
||||||
// ============================================
|
|
||||||
|
|
||||||
#meta
|
|
||||||
{
|
|
||||||
"name": "outline",
|
|
||||||
"category": "effects",
|
|
||||||
"author": "Extra2D Team",
|
|
||||||
"description": "描边特效Shader"
|
|
||||||
}
|
|
||||||
|
|
||||||
#vertex
|
|
||||||
#version 300 es
|
|
||||||
precision highp float;
|
|
||||||
|
|
||||||
layout(location = 0) in vec2 a_position;
|
|
||||||
layout(location = 1) in vec2 a_texCoord;
|
|
||||||
layout(location = 2) in vec4 a_color;
|
|
||||||
|
|
||||||
uniform mat4 u_viewProjection;
|
|
||||||
uniform mat4 u_model;
|
|
||||||
|
|
||||||
out vec2 v_texCoord;
|
|
||||||
out vec4 v_color;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
gl_Position = u_viewProjection * u_model * vec4(a_position, 0.0, 1.0);
|
|
||||||
v_texCoord = a_texCoord;
|
|
||||||
v_color = a_color;
|
|
||||||
}
|
|
||||||
|
|
||||||
#fragment
|
|
||||||
#version 300 es
|
|
||||||
precision highp float;
|
|
||||||
|
|
||||||
in vec2 v_texCoord;
|
|
||||||
in vec4 v_color;
|
|
||||||
|
|
||||||
uniform sampler2D u_texture;
|
|
||||||
uniform vec4 u_outlineColor;
|
|
||||||
uniform float u_thickness;
|
|
||||||
uniform vec2 u_textureSize;
|
|
||||||
|
|
||||||
out vec4 fragColor;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
vec4 color = texture(u_texture, v_texCoord);
|
|
||||||
|
|
||||||
float alpha = 0.0;
|
|
||||||
vec2 offset = u_thickness / u_textureSize;
|
|
||||||
|
|
||||||
alpha += texture(u_texture, v_texCoord + vec2(-offset.x, 0.0)).a;
|
|
||||||
alpha += texture(u_texture, v_texCoord + vec2(offset.x, 0.0)).a;
|
|
||||||
alpha += texture(u_texture, v_texCoord + vec2(0.0, -offset.y)).a;
|
|
||||||
alpha += texture(u_texture, v_texCoord + vec2(0.0, offset.y)).a;
|
|
||||||
|
|
||||||
if (color.a < 0.1 && alpha > 0.0) {
|
|
||||||
fragColor = u_outlineColor;
|
|
||||||
} else {
|
|
||||||
fragColor = color;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (fragColor.a < 0.01) {
|
|
||||||
discard;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,61 +0,0 @@
|
||||||
// ============================================
|
|
||||||
// Extra2D Combined Shader File
|
|
||||||
// Name: pixelate
|
|
||||||
// Category: effects
|
|
||||||
// Version: 1.0
|
|
||||||
// ============================================
|
|
||||||
|
|
||||||
#meta
|
|
||||||
{
|
|
||||||
"name": "pixelate",
|
|
||||||
"category": "effects",
|
|
||||||
"author": "Extra2D Team",
|
|
||||||
"description": "像素化特效Shader"
|
|
||||||
}
|
|
||||||
|
|
||||||
#vertex
|
|
||||||
#version 300 es
|
|
||||||
precision highp float;
|
|
||||||
|
|
||||||
layout(location = 0) in vec2 a_position;
|
|
||||||
layout(location = 1) in vec2 a_texCoord;
|
|
||||||
layout(location = 2) in vec4 a_color;
|
|
||||||
|
|
||||||
uniform mat4 u_viewProjection;
|
|
||||||
uniform mat4 u_model;
|
|
||||||
|
|
||||||
out vec2 v_texCoord;
|
|
||||||
out vec4 v_color;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
gl_Position = u_viewProjection * u_model * vec4(a_position, 0.0, 1.0);
|
|
||||||
v_texCoord = a_texCoord;
|
|
||||||
v_color = a_color;
|
|
||||||
}
|
|
||||||
|
|
||||||
#fragment
|
|
||||||
#version 300 es
|
|
||||||
precision highp float;
|
|
||||||
|
|
||||||
in vec2 v_texCoord;
|
|
||||||
in vec4 v_color;
|
|
||||||
|
|
||||||
uniform sampler2D u_texture;
|
|
||||||
uniform float u_pixelSize;
|
|
||||||
uniform vec2 u_textureSize;
|
|
||||||
uniform float u_opacity;
|
|
||||||
|
|
||||||
out vec4 fragColor;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
vec2 pixel = u_pixelSize / u_textureSize;
|
|
||||||
vec2 uv = floor(v_texCoord / pixel) * pixel + pixel * 0.5;
|
|
||||||
|
|
||||||
vec4 texColor = texture(u_texture, uv);
|
|
||||||
fragColor = texColor * v_color;
|
|
||||||
fragColor.a *= u_opacity;
|
|
||||||
|
|
||||||
if (fragColor.a < 0.01) {
|
|
||||||
discard;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,66 +0,0 @@
|
||||||
// ============================================
|
|
||||||
// Extra2D Combined Shader File
|
|
||||||
// Name: pixelate_invert
|
|
||||||
// Category: effects
|
|
||||||
// Version: 1.0
|
|
||||||
// ============================================
|
|
||||||
|
|
||||||
#meta
|
|
||||||
{
|
|
||||||
"name": "pixelate_invert",
|
|
||||||
"category": "effects",
|
|
||||||
"author": "Extra2D Team",
|
|
||||||
"description": "像素化+反相组合效果Shader"
|
|
||||||
}
|
|
||||||
|
|
||||||
#vertex
|
|
||||||
#version 300 es
|
|
||||||
precision highp float;
|
|
||||||
|
|
||||||
layout(location = 0) in vec2 a_position;
|
|
||||||
layout(location = 1) in vec2 a_texCoord;
|
|
||||||
layout(location = 2) in vec4 a_color;
|
|
||||||
|
|
||||||
uniform mat4 u_viewProjection;
|
|
||||||
uniform mat4 u_model;
|
|
||||||
|
|
||||||
out vec2 v_texCoord;
|
|
||||||
out vec4 v_color;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
gl_Position = u_viewProjection * u_model * vec4(a_position, 0.0, 1.0);
|
|
||||||
v_texCoord = a_texCoord;
|
|
||||||
v_color = a_color;
|
|
||||||
}
|
|
||||||
|
|
||||||
#fragment
|
|
||||||
#version 300 es
|
|
||||||
precision highp float;
|
|
||||||
|
|
||||||
in vec2 v_texCoord;
|
|
||||||
in vec4 v_color;
|
|
||||||
|
|
||||||
uniform sampler2D u_texture;
|
|
||||||
uniform float u_pixelSize;
|
|
||||||
uniform vec2 u_textureSize;
|
|
||||||
uniform float u_invertStrength;
|
|
||||||
uniform float u_opacity;
|
|
||||||
|
|
||||||
out vec4 fragColor;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
vec2 pixel = u_pixelSize / u_textureSize;
|
|
||||||
vec2 uv = floor(v_texCoord / pixel) * pixel + pixel * 0.5;
|
|
||||||
|
|
||||||
vec4 color = texture(u_texture, uv) * v_color;
|
|
||||||
|
|
||||||
vec3 inverted = 1.0 - color.rgb;
|
|
||||||
color.rgb = mix(color.rgb, inverted, u_invertStrength);
|
|
||||||
|
|
||||||
fragColor = color;
|
|
||||||
fragColor.a *= u_opacity;
|
|
||||||
|
|
||||||
if (fragColor.a < 0.01) {
|
|
||||||
discard;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,63 +0,0 @@
|
||||||
// ============================================
|
|
||||||
// Extra2D Combined Shader File
|
|
||||||
// Name: water
|
|
||||||
// Category: effects
|
|
||||||
// Version: 1.0
|
|
||||||
// ============================================
|
|
||||||
|
|
||||||
#meta
|
|
||||||
{
|
|
||||||
"name": "water",
|
|
||||||
"category": "effects",
|
|
||||||
"author": "Extra2D Team",
|
|
||||||
"description": "水波纹特效Shader"
|
|
||||||
}
|
|
||||||
|
|
||||||
#vertex
|
|
||||||
#version 300 es
|
|
||||||
precision highp float;
|
|
||||||
|
|
||||||
layout(location = 0) in vec2 a_position;
|
|
||||||
layout(location = 1) in vec2 a_texCoord;
|
|
||||||
layout(location = 2) in vec4 a_color;
|
|
||||||
|
|
||||||
uniform mat4 u_viewProjection;
|
|
||||||
uniform mat4 u_model;
|
|
||||||
|
|
||||||
out vec2 v_texCoord;
|
|
||||||
out vec4 v_color;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
gl_Position = u_viewProjection * u_model * vec4(a_position, 0.0, 1.0);
|
|
||||||
v_texCoord = a_texCoord;
|
|
||||||
v_color = a_color;
|
|
||||||
}
|
|
||||||
|
|
||||||
#fragment
|
|
||||||
#version 300 es
|
|
||||||
precision highp float;
|
|
||||||
|
|
||||||
in vec2 v_texCoord;
|
|
||||||
in vec4 v_color;
|
|
||||||
|
|
||||||
uniform sampler2D u_texture;
|
|
||||||
uniform float u_waveSpeed;
|
|
||||||
uniform float u_waveAmplitude;
|
|
||||||
uniform float u_waveFrequency;
|
|
||||||
uniform float u_time;
|
|
||||||
|
|
||||||
out vec4 fragColor;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
vec2 uv = v_texCoord;
|
|
||||||
|
|
||||||
float wave = sin(uv.y * u_waveFrequency + u_time * u_waveSpeed) * u_waveAmplitude;
|
|
||||||
uv.x += wave;
|
|
||||||
|
|
||||||
vec4 texColor = texture(u_texture, uv);
|
|
||||||
fragColor = texColor * v_color;
|
|
||||||
|
|
||||||
if (fragColor.a < 0.01) {
|
|
||||||
discard;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
{
|
||||||
|
"name": "shape",
|
||||||
|
"category": "builtin",
|
||||||
|
"version": "1.0",
|
||||||
|
"description": "基本形状渲染Shader",
|
||||||
|
"uniforms": {
|
||||||
|
"u_viewProjection": { "type": "mat4", "description": "视图投影矩阵" }
|
||||||
|
},
|
||||||
|
"samplers": {},
|
||||||
|
"backends": {
|
||||||
|
"opengl": {
|
||||||
|
"vertex": "backends/opengl/builtin/shape.vert",
|
||||||
|
"fragment": "backends/opengl/builtin/shape.frag"
|
||||||
|
},
|
||||||
|
"vulkan": {
|
||||||
|
"vertex": "backends/vulkan/builtin/shape.vert.spv",
|
||||||
|
"fragment": "backends/vulkan/builtin/shape.frag.spv"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
{
|
||||||
|
"name": "sprite",
|
||||||
|
"category": "builtin",
|
||||||
|
"version": "1.0",
|
||||||
|
"description": "标准2D精灵渲染Shader",
|
||||||
|
"uniforms": {
|
||||||
|
"u_viewProjection": { "type": "mat4", "description": "视图投影矩阵" },
|
||||||
|
"u_model": { "type": "mat4", "description": "模型矩阵" },
|
||||||
|
"u_opacity": { "type": "float", "default": 1.0, "description": "透明度" }
|
||||||
|
},
|
||||||
|
"samplers": {
|
||||||
|
"u_texture": { "binding": 0, "description": "纹理采样器" }
|
||||||
|
},
|
||||||
|
"backends": {
|
||||||
|
"opengl": {
|
||||||
|
"vertex": "backends/opengl/builtin/sprite.vert",
|
||||||
|
"fragment": "backends/opengl/builtin/sprite.frag"
|
||||||
|
},
|
||||||
|
"vulkan": {
|
||||||
|
"vertex": "backends/vulkan/builtin/sprite.vert.spv",
|
||||||
|
"fragment": "backends/vulkan/builtin/sprite.frag.spv"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,20 +1,18 @@
|
||||||
#include <extra2d/app/application.h>
|
#include <extra2d/app/application.h>
|
||||||
#include <extra2d/core/registry.h>
|
#include <extra2d/core/registry.h>
|
||||||
#include <extra2d/platform/window_module.h>
|
|
||||||
#include <extra2d/platform/input_module.h>
|
|
||||||
#include <extra2d/graphics/core/render_module.h>
|
|
||||||
#include <extra2d/platform/iwindow.h>
|
|
||||||
#include <extra2d/platform/iinput.h>
|
|
||||||
#include <extra2d/graphics/core/render_backend.h>
|
#include <extra2d/graphics/core/render_backend.h>
|
||||||
|
#include <extra2d/graphics/core/render_module.h>
|
||||||
|
#include <extra2d/graphics/memory/vram_manager.h>
|
||||||
|
#include <extra2d/platform/iinput.h>
|
||||||
|
#include <extra2d/platform/input_module.h>
|
||||||
|
#include <extra2d/platform/iwindow.h>
|
||||||
|
#include <extra2d/platform/window_module.h>
|
||||||
|
#include <extra2d/services/camera_service.h>
|
||||||
|
#include <extra2d/services/event_service.h>
|
||||||
|
#include <extra2d/services/logger_service.h>
|
||||||
#include <extra2d/services/scene_service.h>
|
#include <extra2d/services/scene_service.h>
|
||||||
#include <extra2d/services/timer_service.h>
|
#include <extra2d/services/timer_service.h>
|
||||||
#include <extra2d/services/event_service.h>
|
|
||||||
#include <extra2d/services/camera_service.h>
|
|
||||||
#include <extra2d/services/logger_service.h>
|
|
||||||
#include <extra2d/graphics/memory/vram_manager.h>
|
|
||||||
|
|
||||||
#include <chrono>
|
|
||||||
#include <thread>
|
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
||||||
|
|
@ -37,9 +35,7 @@ Application& Application::get() {
|
||||||
return instance;
|
return instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
Application::Application() {
|
Application::Application() { Registry::instance().setApp(this); }
|
||||||
Registry::instance().setApp(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
Application::~Application() {
|
Application::~Application() {
|
||||||
if (initialized_) {
|
if (initialized_) {
|
||||||
|
|
@ -48,68 +44,48 @@ Application::~Application() {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Application::init() {
|
bool Application::init() {
|
||||||
AppConfig cfg;
|
|
||||||
return init(cfg);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Application::init(const AppConfig& config) {
|
|
||||||
if (initialized_) {
|
if (initialized_) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
appConfig_ = config;
|
|
||||||
|
|
||||||
// 首先注册日志服务(模块初始化可能需要它)
|
|
||||||
auto& locator = ServiceLocator::instance();
|
|
||||||
if (!locator.hasService<ILogger>()) {
|
|
||||||
auto logger = makeShared<ConsoleLogger>();
|
|
||||||
locator.registerService(std::static_pointer_cast<ILogger>(logger));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 初始化所有模块(拓扑排序)
|
// 初始化所有模块(拓扑排序)
|
||||||
|
// 服务通过 E2D_AUTO_REGISTER_SERVICE 宏自动注册
|
||||||
if (!Registry::instance().init()) {
|
if (!Registry::instance().init()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 模块初始化完成后,注册其他核心服务
|
// 配置相机服务(需要窗口信息)
|
||||||
registerCoreServices();
|
configureCameraService();
|
||||||
|
|
||||||
|
// 初始化所有服务
|
||||||
|
ServiceLocator::instance().initializeAll();
|
||||||
|
|
||||||
initialized_ = true;
|
initialized_ = true;
|
||||||
running_ = true;
|
running_ = true;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::registerCoreServices() {
|
void Application::configureCameraService() {
|
||||||
auto& locator = ServiceLocator::instance();
|
|
||||||
|
|
||||||
if (!locator.hasService<ISceneService>()) {
|
|
||||||
auto service = makeShared<SceneService>();
|
|
||||||
locator.registerService(std::static_pointer_cast<ISceneService>(service));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!locator.hasService<ITimerService>()) {
|
|
||||||
auto service = makeShared<TimerService>();
|
|
||||||
locator.registerService(std::static_pointer_cast<ITimerService>(service));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!locator.hasService<IEventService>()) {
|
|
||||||
auto service = makeShared<EventService>();
|
|
||||||
locator.registerService(std::static_pointer_cast<IEventService>(service));
|
|
||||||
}
|
|
||||||
|
|
||||||
auto *winMod = get<WindowModule>();
|
auto *winMod = get<WindowModule>();
|
||||||
if (winMod && winMod->win() && !locator.hasService<ICameraService>()) {
|
if (!winMod || !winMod->win()) {
|
||||||
auto cameraService = makeShared<CameraService>();
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto cameraService = ServiceLocator::instance().getService<ICameraService>();
|
||||||
|
if (!cameraService) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
auto *win = winMod->win();
|
auto *win = winMod->win();
|
||||||
cameraService->setViewport(0, static_cast<float>(win->width()),
|
cameraService->setViewport(0, static_cast<float>(win->width()),
|
||||||
static_cast<float>(win->height()), 0);
|
static_cast<float>(win->height()), 0);
|
||||||
|
|
||||||
ViewportConfig vpConfig;
|
ViewportConfig vpConfig;
|
||||||
vpConfig.logicWidth = static_cast<float>(win->width());
|
vpConfig.logicWidth = static_cast<float>(win->width());
|
||||||
vpConfig.logicHeight = static_cast<float>(win->height());
|
vpConfig.logicHeight = static_cast<float>(win->height());
|
||||||
vpConfig.mode = ViewportMode::AspectRatio;
|
vpConfig.mode = ViewportMode::AspectRatio;
|
||||||
cameraService->setViewportConfig(vpConfig);
|
cameraService->setViewportConfig(vpConfig);
|
||||||
cameraService->updateViewport(win->width(), win->height());
|
cameraService->updateViewport(win->width(), win->height());
|
||||||
locator.registerService(std::static_pointer_cast<ICameraService>(cameraService));
|
|
||||||
|
|
||||||
win->onResize([cameraService](int width, int height) {
|
win->onResize([cameraService](int width, int height) {
|
||||||
cameraService->updateViewport(width, height);
|
cameraService->updateViewport(width, height);
|
||||||
|
|
@ -117,14 +93,13 @@ void Application::registerCoreServices() {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
locator.initializeAll();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Application::shutdown() {
|
void Application::shutdown() {
|
||||||
if (!initialized_) return;
|
if (!initialized_)
|
||||||
|
return;
|
||||||
|
|
||||||
VRAMMgr::get().printStats();
|
VRAMMgr::get().printStats();
|
||||||
|
|
||||||
|
ServiceLocator::instance().shutdownAll();
|
||||||
ServiceLocator::instance().clear();
|
ServiceLocator::instance().clear();
|
||||||
Registry::instance().shutdown();
|
Registry::instance().shutdown();
|
||||||
Registry::instance().clear();
|
Registry::instance().clear();
|
||||||
|
|
@ -134,10 +109,12 @@ void Application::shutdown() {
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::run() {
|
void Application::run() {
|
||||||
if (!initialized_) return;
|
if (!initialized_)
|
||||||
|
return;
|
||||||
|
|
||||||
auto *winMod = get<WindowModule>();
|
auto *winMod = get<WindowModule>();
|
||||||
if (!winMod || !winMod->win()) return;
|
if (!winMod || !winMod->win())
|
||||||
|
return;
|
||||||
|
|
||||||
lastFrameTime_ = getTimeSeconds();
|
lastFrameTime_ = getTimeSeconds();
|
||||||
|
|
||||||
|
|
@ -215,11 +192,13 @@ void Application::update() {
|
||||||
|
|
||||||
void Application::render() {
|
void Application::render() {
|
||||||
auto *renderMod = get<RenderModule>();
|
auto *renderMod = get<RenderModule>();
|
||||||
if (!renderMod || !renderMod->renderer()) return;
|
if (!renderMod || !renderMod->renderer())
|
||||||
|
return;
|
||||||
|
|
||||||
auto *renderer = renderMod->renderer();
|
auto *renderer = renderMod->renderer();
|
||||||
auto *winMod = get<WindowModule>();
|
auto *winMod = get<WindowModule>();
|
||||||
if (!winMod || !winMod->win()) return;
|
if (!winMod || !winMod->win())
|
||||||
|
return;
|
||||||
|
|
||||||
auto cameraService = ServiceLocator::instance().getService<ICameraService>();
|
auto cameraService = ServiceLocator::instance().getService<ICameraService>();
|
||||||
if (cameraService) {
|
if (cameraService) {
|
||||||
|
|
@ -229,7 +208,8 @@ void Application::render() {
|
||||||
static_cast<int>(vp.size.width), static_cast<int>(vp.size.height));
|
static_cast<int>(vp.size.width), static_cast<int>(vp.size.height));
|
||||||
renderer->setViewProjection(cameraService->getViewProjectionMatrix());
|
renderer->setViewProjection(cameraService->getViewProjectionMatrix());
|
||||||
} else {
|
} else {
|
||||||
renderer->setViewport(0, 0, winMod->win()->width(), winMod->win()->height());
|
renderer->setViewport(0, 0, winMod->win()->width(),
|
||||||
|
winMod->win()->height());
|
||||||
}
|
}
|
||||||
|
|
||||||
auto sceneService = ServiceLocator::instance().getService<ISceneService>();
|
auto sceneService = ServiceLocator::instance().getService<ISceneService>();
|
||||||
|
|
|
||||||
|
|
@ -1,60 +0,0 @@
|
||||||
#include <extra2d/config/app_config.h>
|
|
||||||
#include <extra2d/utils/logger.h>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
AppConfig AppConfig::createDefault() {
|
|
||||||
AppConfig config;
|
|
||||||
|
|
||||||
config.appName = "Extra2D App";
|
|
||||||
config.appVersion = "1.0.0";
|
|
||||||
config.organization = "";
|
|
||||||
config.configFile = "config.json";
|
|
||||||
config.targetPlatform = PlatformType::Auto;
|
|
||||||
|
|
||||||
return config;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AppConfig::validate() const {
|
|
||||||
if (appName.empty()) {
|
|
||||||
E2D_LOG_ERROR("Config validation failed: app name cannot be empty");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (appVersion.empty()) {
|
|
||||||
E2D_LOG_ERROR("Config validation failed: app version cannot be empty");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (configFile.empty()) {
|
|
||||||
E2D_LOG_ERROR("Config validation failed: config file cannot be empty");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void AppConfig::reset() {
|
|
||||||
*this = createDefault();
|
|
||||||
E2D_LOG_INFO("App config reset to defaults");
|
|
||||||
}
|
|
||||||
|
|
||||||
void AppConfig::merge(const AppConfig& other) {
|
|
||||||
if (other.appName != "Extra2D App") {
|
|
||||||
appName = other.appName;
|
|
||||||
}
|
|
||||||
if (other.appVersion != "1.0.0") {
|
|
||||||
appVersion = other.appVersion;
|
|
||||||
}
|
|
||||||
if (!other.organization.empty()) {
|
|
||||||
organization = other.organization;
|
|
||||||
}
|
|
||||||
if (other.configFile != "config.json") {
|
|
||||||
configFile = other.configFile;
|
|
||||||
}
|
|
||||||
if (other.targetPlatform != PlatformType::Auto) {
|
|
||||||
targetPlatform = other.targetPlatform;
|
|
||||||
}
|
|
||||||
|
|
||||||
E2D_LOG_INFO("Merged app config");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,224 +0,0 @@
|
||||||
#include <extra2d/config/app_config.h>
|
|
||||||
#include <extra2d/config/platform_config.h>
|
|
||||||
#include <extra2d/utils/logger.h>
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
#include <windows.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef __SWITCH__
|
|
||||||
#include <switch.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
|
|
||||||
class WindowsPlatformConfig : public PlatformConfig {
|
|
||||||
public:
|
|
||||||
WindowsPlatformConfig() {
|
|
||||||
caps_.supportsWindowed = true;
|
|
||||||
caps_.supportsFullscreen = true;
|
|
||||||
caps_.supportsBorderless = true;
|
|
||||||
caps_.supportsCursor = true;
|
|
||||||
caps_.supportsCursorHide = true;
|
|
||||||
caps_.supportsDPIAwareness = true;
|
|
||||||
caps_.supportsVSync = true;
|
|
||||||
caps_.supportsMultiMonitor = true;
|
|
||||||
caps_.supportsClipboard = true;
|
|
||||||
caps_.supportsGamepad = true;
|
|
||||||
caps_.supportsTouch = false;
|
|
||||||
caps_.supportsKeyboard = true;
|
|
||||||
caps_.supportsMouse = true;
|
|
||||||
caps_.supportsResize = true;
|
|
||||||
caps_.supportsHighDPI = true;
|
|
||||||
caps_.maxTextureSize = 16384;
|
|
||||||
caps_.preferredScreenWidth = 1920;
|
|
||||||
caps_.preferredScreenHeight = 1080;
|
|
||||||
caps_.defaultDPI = 96.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
PlatformType platformType() const override { return PlatformType::Windows; }
|
|
||||||
const char *platformName() const override { return "Windows"; }
|
|
||||||
const PlatformCapabilities &capabilities() const override { return caps_; }
|
|
||||||
|
|
||||||
int getRecommendedWidth() const override { return 1920; }
|
|
||||||
int getRecommendedHeight() const override { return 1080; }
|
|
||||||
bool isResolutionSupported(int width, int height) const override {
|
|
||||||
return width >= 320 && height >= 240 && width <= caps_.maxTextureSize &&
|
|
||||||
height <= caps_.maxTextureSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
PlatformCapabilities caps_;
|
|
||||||
};
|
|
||||||
|
|
||||||
class LinuxPlatformConfig : public PlatformConfig {
|
|
||||||
public:
|
|
||||||
LinuxPlatformConfig() {
|
|
||||||
caps_.supportsWindowed = true;
|
|
||||||
caps_.supportsFullscreen = true;
|
|
||||||
caps_.supportsBorderless = true;
|
|
||||||
caps_.supportsCursor = true;
|
|
||||||
caps_.supportsCursorHide = true;
|
|
||||||
caps_.supportsDPIAwareness = true;
|
|
||||||
caps_.supportsVSync = true;
|
|
||||||
caps_.supportsMultiMonitor = true;
|
|
||||||
caps_.supportsClipboard = true;
|
|
||||||
caps_.supportsGamepad = true;
|
|
||||||
caps_.supportsTouch = false;
|
|
||||||
caps_.supportsKeyboard = true;
|
|
||||||
caps_.supportsMouse = true;
|
|
||||||
caps_.supportsResize = true;
|
|
||||||
caps_.supportsHighDPI = true;
|
|
||||||
caps_.maxTextureSize = 16384;
|
|
||||||
caps_.preferredScreenWidth = 1920;
|
|
||||||
caps_.preferredScreenHeight = 1080;
|
|
||||||
caps_.defaultDPI = 96.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
PlatformType platformType() const override { return PlatformType::Linux; }
|
|
||||||
const char *platformName() const override { return "Linux"; }
|
|
||||||
const PlatformCapabilities &capabilities() const override { return caps_; }
|
|
||||||
|
|
||||||
int getRecommendedWidth() const override { return 1920; }
|
|
||||||
int getRecommendedHeight() const override { return 1080; }
|
|
||||||
bool isResolutionSupported(int width, int height) const override {
|
|
||||||
return width >= 320 && height >= 240;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
PlatformCapabilities caps_;
|
|
||||||
};
|
|
||||||
|
|
||||||
class MacOSPlatformConfig : public PlatformConfig {
|
|
||||||
public:
|
|
||||||
MacOSPlatformConfig() {
|
|
||||||
caps_.supportsWindowed = true;
|
|
||||||
caps_.supportsFullscreen = true;
|
|
||||||
caps_.supportsBorderless = true;
|
|
||||||
caps_.supportsCursor = true;
|
|
||||||
caps_.supportsCursorHide = true;
|
|
||||||
caps_.supportsDPIAwareness = true;
|
|
||||||
caps_.supportsVSync = true;
|
|
||||||
caps_.supportsMultiMonitor = true;
|
|
||||||
caps_.supportsClipboard = true;
|
|
||||||
caps_.supportsGamepad = true;
|
|
||||||
caps_.supportsTouch = false;
|
|
||||||
caps_.supportsKeyboard = true;
|
|
||||||
caps_.supportsMouse = true;
|
|
||||||
caps_.supportsResize = true;
|
|
||||||
caps_.supportsHighDPI = true;
|
|
||||||
caps_.maxTextureSize = 16384;
|
|
||||||
caps_.preferredScreenWidth = 1920;
|
|
||||||
caps_.preferredScreenHeight = 1080;
|
|
||||||
caps_.defaultDPI = 144.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
PlatformType platformType() const override { return PlatformType::macOS; }
|
|
||||||
const char *platformName() const override { return "macOS"; }
|
|
||||||
const PlatformCapabilities &capabilities() const override { return caps_; }
|
|
||||||
|
|
||||||
int getRecommendedWidth() const override { return 1920; }
|
|
||||||
int getRecommendedHeight() const override { return 1080; }
|
|
||||||
bool isResolutionSupported(int width, int height) const override {
|
|
||||||
return width >= 320 && height >= 240;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
PlatformCapabilities caps_;
|
|
||||||
};
|
|
||||||
|
|
||||||
class SwitchPlatformConfig : public PlatformConfig {
|
|
||||||
public:
|
|
||||||
SwitchPlatformConfig() {
|
|
||||||
caps_.supportsWindowed = false;
|
|
||||||
caps_.supportsFullscreen = true;
|
|
||||||
caps_.supportsBorderless = false;
|
|
||||||
caps_.supportsCursor = false;
|
|
||||||
caps_.supportsCursorHide = false;
|
|
||||||
caps_.supportsDPIAwareness = false;
|
|
||||||
caps_.supportsVSync = true;
|
|
||||||
caps_.supportsMultiMonitor = false;
|
|
||||||
caps_.supportsClipboard = false;
|
|
||||||
caps_.supportsGamepad = true;
|
|
||||||
caps_.supportsTouch = true;
|
|
||||||
caps_.supportsKeyboard = false;
|
|
||||||
caps_.supportsMouse = false;
|
|
||||||
caps_.supportsResize = false;
|
|
||||||
caps_.supportsHighDPI = false;
|
|
||||||
caps_.maxTextureSize = 8192;
|
|
||||||
caps_.preferredScreenWidth = 1920;
|
|
||||||
caps_.preferredScreenHeight = 1080;
|
|
||||||
caps_.defaultDPI = 96.0f;
|
|
||||||
}
|
|
||||||
|
|
||||||
PlatformType platformType() const override { return PlatformType::Switch; }
|
|
||||||
const char *platformName() const override { return "Nintendo Switch"; }
|
|
||||||
const PlatformCapabilities &capabilities() const override { return caps_; }
|
|
||||||
|
|
||||||
int getRecommendedWidth() const override { return 1920; }
|
|
||||||
int getRecommendedHeight() const override { return 1080; }
|
|
||||||
bool isResolutionSupported(int width, int height) const override {
|
|
||||||
return (width == 1920 && height == 1080) ||
|
|
||||||
(width == 1280 && height == 720);
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
PlatformCapabilities caps_;
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
UniquePtr<PlatformConfig> createPlatformConfig(PlatformType type) {
|
|
||||||
if (type == PlatformType::Auto) {
|
|
||||||
#ifdef _WIN32
|
|
||||||
type = PlatformType::Windows;
|
|
||||||
#elif defined(__SWITCH__)
|
|
||||||
type = PlatformType::Switch;
|
|
||||||
#elif defined(__linux__)
|
|
||||||
type = PlatformType::Linux;
|
|
||||||
#elif defined(__APPLE__)
|
|
||||||
type = PlatformType::macOS;
|
|
||||||
#else
|
|
||||||
type = PlatformType::Windows;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
switch (type) {
|
|
||||||
case PlatformType::Windows:
|
|
||||||
E2D_LOG_INFO("Creating Windows platform config");
|
|
||||||
return makeUnique<WindowsPlatformConfig>();
|
|
||||||
case PlatformType::Switch:
|
|
||||||
E2D_LOG_INFO("Creating Nintendo Switch platform config");
|
|
||||||
return makeUnique<SwitchPlatformConfig>();
|
|
||||||
case PlatformType::Linux:
|
|
||||||
E2D_LOG_INFO("Creating Linux platform config");
|
|
||||||
return makeUnique<LinuxPlatformConfig>();
|
|
||||||
case PlatformType::macOS:
|
|
||||||
E2D_LOG_INFO("Creating macOS platform config");
|
|
||||||
return makeUnique<MacOSPlatformConfig>();
|
|
||||||
default:
|
|
||||||
E2D_LOG_WARN("Unknown platform type, defaulting to Windows");
|
|
||||||
return makeUnique<WindowsPlatformConfig>();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const char *getPlatformTypeName(PlatformType type) {
|
|
||||||
switch (type) {
|
|
||||||
case PlatformType::Auto:
|
|
||||||
return "Auto";
|
|
||||||
case PlatformType::Windows:
|
|
||||||
return "Windows";
|
|
||||||
case PlatformType::Switch:
|
|
||||||
return "Switch";
|
|
||||||
case PlatformType::Linux:
|
|
||||||
return "Linux";
|
|
||||||
case PlatformType::macOS:
|
|
||||||
return "macOS";
|
|
||||||
default:
|
|
||||||
return "Unknown";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,678 +0,0 @@
|
||||||
#include <extra2d/config/platform_detector.h>
|
|
||||||
#include <extra2d/config/platform_config.h>
|
|
||||||
#include <extra2d/utils/logger.h>
|
|
||||||
|
|
||||||
#ifdef _WIN32
|
|
||||||
#include <windows.h>
|
|
||||||
#include <shlobj.h>
|
|
||||||
#include <psapi.h>
|
|
||||||
#elif defined(__linux__)
|
|
||||||
#include <sys/sysinfo.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <pwd.h>
|
|
||||||
#include <cstdlib>
|
|
||||||
#elif defined(__APPLE__)
|
|
||||||
#include <sys/sysctl.h>
|
|
||||||
#include <unistd.h>
|
|
||||||
#include <pwd.h>
|
|
||||||
#include <cstdlib>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#ifdef __SWITCH__
|
|
||||||
#include <switch.h>
|
|
||||||
#endif
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检测当前运行平台
|
|
||||||
* 使用编译时宏判断当前平台类型
|
|
||||||
* @return 当前平台的类型枚举值
|
|
||||||
*/
|
|
||||||
PlatformType PlatformDetector::detect() {
|
|
||||||
#ifdef _WIN32
|
|
||||||
return PlatformType::Windows;
|
|
||||||
#elif defined(__SWITCH__)
|
|
||||||
return PlatformType::Switch;
|
|
||||||
#elif defined(__linux__)
|
|
||||||
return PlatformType::Linux;
|
|
||||||
#elif defined(__APPLE__)
|
|
||||||
return PlatformType::macOS;
|
|
||||||
#else
|
|
||||||
return PlatformType::Windows;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取当前平台名称字符串
|
|
||||||
* @return 平台名称(如 "Windows", "Linux", "macOS", "Switch")
|
|
||||||
*/
|
|
||||||
const char* PlatformDetector::platformName() {
|
|
||||||
return platformName(detect());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取指定平台类型的名称
|
|
||||||
* @param type 平台类型
|
|
||||||
* @return 平台名称字符串
|
|
||||||
*/
|
|
||||||
const char* PlatformDetector::platformName(PlatformType type) {
|
|
||||||
switch (type) {
|
|
||||||
case PlatformType::Windows: return "Windows";
|
|
||||||
case PlatformType::Switch: return "Nintendo Switch";
|
|
||||||
case PlatformType::Linux: return "Linux";
|
|
||||||
case PlatformType::macOS: return "macOS";
|
|
||||||
case PlatformType::Auto: return "Auto";
|
|
||||||
default: return "Unknown";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查当前平台是否为桌面平台
|
|
||||||
* @return 如果是桌面平台返回 true
|
|
||||||
*/
|
|
||||||
bool PlatformDetector::isDesktopPlatform() {
|
|
||||||
PlatformType type = detect();
|
|
||||||
return type == PlatformType::Windows ||
|
|
||||||
type == PlatformType::Linux ||
|
|
||||||
type == PlatformType::macOS;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查当前平台是否为游戏主机平台
|
|
||||||
* @return 如果是游戏主机平台返回 true
|
|
||||||
*/
|
|
||||||
bool PlatformDetector::isConsolePlatform() {
|
|
||||||
return detect() == PlatformType::Switch;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查当前平台是否为移动平台
|
|
||||||
* @return 如果是移动平台返回 true
|
|
||||||
*/
|
|
||||||
bool PlatformDetector::isMobilePlatform() {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取当前平台的能力
|
|
||||||
* @return 平台能力结构
|
|
||||||
*/
|
|
||||||
PlatformCapabilities PlatformDetector::capabilities() {
|
|
||||||
return capabilities(detect());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取指定平台的能力
|
|
||||||
* @param type 平台类型
|
|
||||||
* @return 平台能力结构
|
|
||||||
*/
|
|
||||||
PlatformCapabilities PlatformDetector::capabilities(PlatformType type) {
|
|
||||||
switch (type) {
|
|
||||||
case PlatformType::Windows:
|
|
||||||
return getWindowsCapabilities();
|
|
||||||
case PlatformType::Switch:
|
|
||||||
return getSwitchCapabilities();
|
|
||||||
case PlatformType::Linux:
|
|
||||||
return getLinuxCapabilities();
|
|
||||||
case PlatformType::macOS:
|
|
||||||
return getMacOSCapabilities();
|
|
||||||
default:
|
|
||||||
return getWindowsCapabilities();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取当前平台的默认配置
|
|
||||||
* @return 平台默认的应用配置
|
|
||||||
*/
|
|
||||||
AppConfig PlatformDetector::platformDefaults() {
|
|
||||||
return platformDefaults(detect());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取指定平台的默认配置
|
|
||||||
* @param type 平台类型
|
|
||||||
* @return 平台默认的应用配置
|
|
||||||
*/
|
|
||||||
AppConfig PlatformDetector::platformDefaults(PlatformType type) {
|
|
||||||
switch (type) {
|
|
||||||
case PlatformType::Windows:
|
|
||||||
return getWindowsDefaults();
|
|
||||||
case PlatformType::Switch:
|
|
||||||
return getSwitchDefaults();
|
|
||||||
case PlatformType::Linux:
|
|
||||||
return getLinuxDefaults();
|
|
||||||
case PlatformType::macOS:
|
|
||||||
return getMacOSDefaults();
|
|
||||||
default:
|
|
||||||
return AppConfig::createDefault();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取当前平台的推荐分辨率
|
|
||||||
* @param width 输出宽度
|
|
||||||
* @param height 输出高度
|
|
||||||
*/
|
|
||||||
void PlatformDetector::getRecommendedResolution(int& width, int& height) {
|
|
||||||
PlatformCapabilities caps = capabilities();
|
|
||||||
width = caps.preferredScreenWidth;
|
|
||||||
height = caps.preferredScreenHeight;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取当前平台的默认 DPI
|
|
||||||
* @return 默认 DPI 值
|
|
||||||
*/
|
|
||||||
float PlatformDetector::getDefaultDPI() {
|
|
||||||
return capabilities().defaultDPI;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查当前平台是否支持指定功能
|
|
||||||
* @param feature 功能名称
|
|
||||||
* @return 如果支持返回 true
|
|
||||||
*/
|
|
||||||
bool PlatformDetector::supportsFeature(const std::string& feature) {
|
|
||||||
PlatformCapabilities caps = capabilities();
|
|
||||||
|
|
||||||
if (feature == "windowed") return caps.supportsWindowed;
|
|
||||||
if (feature == "fullscreen") return caps.supportsFullscreen;
|
|
||||||
if (feature == "borderless") return caps.supportsBorderless;
|
|
||||||
if (feature == "cursor") return caps.supportsCursor;
|
|
||||||
if (feature == "cursor_hide") return caps.supportsCursorHide;
|
|
||||||
if (feature == "dpi_awareness") return caps.supportsDPIAwareness;
|
|
||||||
if (feature == "vsync") return caps.supportsVSync;
|
|
||||||
if (feature == "multi_monitor") return caps.supportsMultiMonitor;
|
|
||||||
if (feature == "clipboard") return caps.supportsClipboard;
|
|
||||||
if (feature == "gamepad") return caps.supportsGamepad;
|
|
||||||
if (feature == "touch") return caps.supportsTouch;
|
|
||||||
if (feature == "keyboard") return caps.supportsKeyboard;
|
|
||||||
if (feature == "mouse") return caps.supportsMouse;
|
|
||||||
if (feature == "resize") return caps.supportsResize;
|
|
||||||
if (feature == "high_dpi") return caps.supportsHighDPI;
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取系统内存大小
|
|
||||||
* @return 系统内存大小(MB),如果无法获取返回 0
|
|
||||||
*/
|
|
||||||
int PlatformDetector::getSystemMemoryMB() {
|
|
||||||
#ifdef _WIN32
|
|
||||||
MEMORYSTATUSEX status;
|
|
||||||
status.dwLength = sizeof(status);
|
|
||||||
if (GlobalMemoryStatusEx(&status)) {
|
|
||||||
return static_cast<int>(status.ullTotalPhys / (1024 * 1024));
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
#elif defined(__SWITCH__)
|
|
||||||
return 4096;
|
|
||||||
#elif defined(__linux__)
|
|
||||||
struct sysinfo info;
|
|
||||||
if (sysinfo(&info) == 0) {
|
|
||||||
return static_cast<int>(info.totalram * info.mem_unit / (1024 * 1024));
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
#elif defined(__APPLE__)
|
|
||||||
int mib[2] = {CTL_HW, HW_MEMSIZE};
|
|
||||||
int64_t memSize = 0;
|
|
||||||
size_t length = sizeof(memSize);
|
|
||||||
if (sysctl(mib, 2, &memSize, &length, nullptr, 0) == 0) {
|
|
||||||
return static_cast<int>(memSize / (1024 * 1024));
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
#else
|
|
||||||
return 0;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取 CPU 核心数
|
|
||||||
* @return CPU 核心数
|
|
||||||
*/
|
|
||||||
int PlatformDetector::getCPUCoreCount() {
|
|
||||||
#ifdef _WIN32
|
|
||||||
SYSTEM_INFO sysinfo;
|
|
||||||
GetSystemInfo(&sysinfo);
|
|
||||||
return static_cast<int>(sysinfo.dwNumberOfProcessors);
|
|
||||||
#elif defined(__SWITCH__)
|
|
||||||
return 4;
|
|
||||||
#elif defined(__linux__) || defined(__APPLE__)
|
|
||||||
long cores = sysconf(_SC_NPROCESSORS_ONLN);
|
|
||||||
return static_cast<int>(cores > 0 ? cores : 1);
|
|
||||||
#else
|
|
||||||
return 1;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查是否支持多线程渲染
|
|
||||||
* @return 如果支持返回 true
|
|
||||||
*/
|
|
||||||
bool PlatformDetector::supportsMultithreadedRendering() {
|
|
||||||
#ifdef __SWITCH__
|
|
||||||
return false;
|
|
||||||
#else
|
|
||||||
return getCPUCoreCount() >= 2;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取平台特定的配置路径
|
|
||||||
* @param appName 应用名称
|
|
||||||
* @return 配置文件目录路径
|
|
||||||
*/
|
|
||||||
std::string PlatformDetector::getConfigPath(const std::string& appName) {
|
|
||||||
#ifdef _WIN32
|
|
||||||
char path[MAX_PATH];
|
|
||||||
if (SUCCEEDED(SHGetFolderPathA(nullptr, CSIDL_APPDATA, nullptr, 0, path))) {
|
|
||||||
return std::string(path) + "\\" + appName + "\\config";
|
|
||||||
}
|
|
||||||
return ".\\config";
|
|
||||||
#elif defined(__SWITCH__)
|
|
||||||
return "sdmc:/config/" + appName;
|
|
||||||
#elif defined(__linux__)
|
|
||||||
const char* configHome = getenv("XDG_CONFIG_HOME");
|
|
||||||
if (configHome && configHome[0] != '\0') {
|
|
||||||
return std::string(configHome) + "/" + appName;
|
|
||||||
}
|
|
||||||
const char* home = getenv("HOME");
|
|
||||||
if (!home) {
|
|
||||||
struct passwd* pwd = getpwuid(getuid());
|
|
||||||
if (pwd) home = pwd->pw_dir;
|
|
||||||
}
|
|
||||||
if (home) {
|
|
||||||
return std::string(home) + "/.config/" + appName;
|
|
||||||
}
|
|
||||||
return "./config";
|
|
||||||
#elif defined(__APPLE__)
|
|
||||||
const char* home = getenv("HOME");
|
|
||||||
if (!home) {
|
|
||||||
struct passwd* pwd = getpwuid(getuid());
|
|
||||||
if (pwd) home = pwd->pw_dir;
|
|
||||||
}
|
|
||||||
if (home) {
|
|
||||||
return std::string(home) + "/Library/Application Support/" + appName + "/config";
|
|
||||||
}
|
|
||||||
return "./config";
|
|
||||||
#else
|
|
||||||
return "./config";
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取平台特定的存档路径
|
|
||||||
* @param appName 应用名称
|
|
||||||
* @return 存档文件目录路径
|
|
||||||
*/
|
|
||||||
std::string PlatformDetector::getSavePath(const std::string& appName) {
|
|
||||||
#ifdef _WIN32
|
|
||||||
char path[MAX_PATH];
|
|
||||||
if (SUCCEEDED(SHGetFolderPathA(nullptr, CSIDL_APPDATA, nullptr, 0, path))) {
|
|
||||||
return std::string(path) + "\\" + appName + "\\saves";
|
|
||||||
}
|
|
||||||
return ".\\saves";
|
|
||||||
#elif defined(__SWITCH__)
|
|
||||||
return "sdmc:/saves/" + appName;
|
|
||||||
#elif defined(__linux__)
|
|
||||||
const char* dataHome = getenv("XDG_DATA_HOME");
|
|
||||||
if (dataHome && dataHome[0] != '\0') {
|
|
||||||
return std::string(dataHome) + "/" + appName + "/saves";
|
|
||||||
}
|
|
||||||
const char* home = getenv("HOME");
|
|
||||||
if (!home) {
|
|
||||||
struct passwd* pwd = getpwuid(getuid());
|
|
||||||
if (pwd) home = pwd->pw_dir;
|
|
||||||
}
|
|
||||||
if (home) {
|
|
||||||
return std::string(home) + "/.local/share/" + appName + "/saves";
|
|
||||||
}
|
|
||||||
return "./saves";
|
|
||||||
#elif defined(__APPLE__)
|
|
||||||
const char* home = getenv("HOME");
|
|
||||||
if (!home) {
|
|
||||||
struct passwd* pwd = getpwuid(getuid());
|
|
||||||
if (pwd) home = pwd->pw_dir;
|
|
||||||
}
|
|
||||||
if (home) {
|
|
||||||
return std::string(home) + "/Library/Application Support/" + appName + "/saves";
|
|
||||||
}
|
|
||||||
return "./saves";
|
|
||||||
#else
|
|
||||||
return "./saves";
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取平台特定的缓存路径
|
|
||||||
* @param appName 应用名称
|
|
||||||
* @return 缓存文件目录路径
|
|
||||||
*/
|
|
||||||
std::string PlatformDetector::getCachePath(const std::string& appName) {
|
|
||||||
#ifdef _WIN32
|
|
||||||
char path[MAX_PATH];
|
|
||||||
if (SUCCEEDED(SHGetFolderPathA(nullptr, CSIDL_LOCAL_APPDATA, nullptr, 0, path))) {
|
|
||||||
return std::string(path) + "\\" + appName + "\\cache";
|
|
||||||
}
|
|
||||||
return ".\\cache";
|
|
||||||
#elif defined(__SWITCH__)
|
|
||||||
return "sdmc:/cache/" + appName;
|
|
||||||
#elif defined(__linux__)
|
|
||||||
const char* cacheHome = getenv("XDG_CACHE_HOME");
|
|
||||||
if (cacheHome && cacheHome[0] != '\0') {
|
|
||||||
return std::string(cacheHome) + "/" + appName;
|
|
||||||
}
|
|
||||||
const char* home = getenv("HOME");
|
|
||||||
if (!home) {
|
|
||||||
struct passwd* pwd = getpwuid(getuid());
|
|
||||||
if (pwd) home = pwd->pw_dir;
|
|
||||||
}
|
|
||||||
if (home) {
|
|
||||||
return std::string(home) + "/.cache/" + appName;
|
|
||||||
}
|
|
||||||
return "./cache";
|
|
||||||
#elif defined(__APPLE__)
|
|
||||||
const char* home = getenv("HOME");
|
|
||||||
if (!home) {
|
|
||||||
struct passwd* pwd = getpwuid(getuid());
|
|
||||||
if (pwd) home = pwd->pw_dir;
|
|
||||||
}
|
|
||||||
if (home) {
|
|
||||||
return std::string(home) + "/Library/Caches/" + appName;
|
|
||||||
}
|
|
||||||
return "./cache";
|
|
||||||
#else
|
|
||||||
return "./cache";
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取平台特定的日志路径
|
|
||||||
* @param appName 应用名称
|
|
||||||
* @return 日志文件目录路径
|
|
||||||
*/
|
|
||||||
std::string PlatformDetector::getLogPath(const std::string& appName) {
|
|
||||||
#ifdef _WIN32
|
|
||||||
char path[MAX_PATH];
|
|
||||||
if (SUCCEEDED(SHGetFolderPathA(nullptr, CSIDL_LOCAL_APPDATA, nullptr, 0, path))) {
|
|
||||||
return std::string(path) + "\\" + appName + "\\logs";
|
|
||||||
}
|
|
||||||
return ".\\logs";
|
|
||||||
#elif defined(__SWITCH__)
|
|
||||||
return "sdmc:/logs/" + appName;
|
|
||||||
#elif defined(__linux__)
|
|
||||||
const char* cacheHome = getenv("XDG_CACHE_HOME");
|
|
||||||
if (cacheHome && cacheHome[0] != '\0') {
|
|
||||||
return std::string(cacheHome) + "/" + appName + "/logs";
|
|
||||||
}
|
|
||||||
const char* home = getenv("HOME");
|
|
||||||
if (!home) {
|
|
||||||
struct passwd* pwd = getpwuid(getuid());
|
|
||||||
if (pwd) home = pwd->pw_dir;
|
|
||||||
}
|
|
||||||
if (home) {
|
|
||||||
return std::string(home) + "/.cache/" + appName + "/logs";
|
|
||||||
}
|
|
||||||
return "./logs";
|
|
||||||
#elif defined(__APPLE__)
|
|
||||||
const char* home = getenv("HOME");
|
|
||||||
if (!home) {
|
|
||||||
struct passwd* pwd = getpwuid(getuid());
|
|
||||||
if (pwd) home = pwd->pw_dir;
|
|
||||||
}
|
|
||||||
if (home) {
|
|
||||||
return std::string(home) + "/Library/Logs/" + appName;
|
|
||||||
}
|
|
||||||
return "./logs";
|
|
||||||
#else
|
|
||||||
return "./logs";
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取平台特定的资源路径(Shader、纹理等)
|
|
||||||
* Switch平台使用romfs,其他平台使用相对路径
|
|
||||||
* @param appName 应用名称
|
|
||||||
* @return 资源目录路径
|
|
||||||
*/
|
|
||||||
std::string PlatformDetector::getResourcePath(const std::string& appName) {
|
|
||||||
#ifdef __SWITCH__
|
|
||||||
(void)appName;
|
|
||||||
return "romfs:/";
|
|
||||||
#else
|
|
||||||
(void)appName;
|
|
||||||
return "./resources/";
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取平台特定的Shader路径
|
|
||||||
* @param appName 应用名称
|
|
||||||
* @return Shader目录路径
|
|
||||||
*/
|
|
||||||
std::string PlatformDetector::getShaderPath(const std::string& appName) {
|
|
||||||
#ifdef __SWITCH__
|
|
||||||
(void)appName;
|
|
||||||
return "romfs:/shaders/";
|
|
||||||
#else
|
|
||||||
(void)appName;
|
|
||||||
return "./shaders/";
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取平台特定的Shader缓存路径
|
|
||||||
* Switch平台使用sdmc,其他平台使用系统缓存目录
|
|
||||||
* @param appName 应用名称
|
|
||||||
* @return Shader缓存目录路径
|
|
||||||
*/
|
|
||||||
std::string PlatformDetector::getShaderCachePath(const std::string& appName) {
|
|
||||||
#ifdef __SWITCH__
|
|
||||||
std::string name = appName.empty() ? "extra2d" : appName;
|
|
||||||
return "sdmc:/cache/" + name + "/shaders/";
|
|
||||||
#else
|
|
||||||
return getCachePath(appName.empty() ? "extra2d" : appName) + "/shaders/";
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查平台是否使用romfs(只读文件系统)
|
|
||||||
* @return 使用romfs返回true
|
|
||||||
*/
|
|
||||||
bool PlatformDetector::usesRomfs() {
|
|
||||||
#ifdef __SWITCH__
|
|
||||||
return true;
|
|
||||||
#else
|
|
||||||
return false;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查平台是否支持热重载
|
|
||||||
* Switch平台不支持热重载(romfs只读)
|
|
||||||
* @return 支持热重载返回true
|
|
||||||
*/
|
|
||||||
bool PlatformDetector::supportsHotReload() {
|
|
||||||
#ifdef __SWITCH__
|
|
||||||
return false;
|
|
||||||
#else
|
|
||||||
return true;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查平台是否为小端字节序
|
|
||||||
* @return 如果是小端字节序返回 true
|
|
||||||
*/
|
|
||||||
bool PlatformDetector::isLittleEndian() {
|
|
||||||
union {
|
|
||||||
uint32_t i;
|
|
||||||
char c[4];
|
|
||||||
} test = {0x01020304};
|
|
||||||
return test.c[0] == 0x04;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 检查平台是否为大端字节序
|
|
||||||
* @return 如果是大端字节序返回 true
|
|
||||||
*/
|
|
||||||
bool PlatformDetector::isBigEndian() {
|
|
||||||
return !isLittleEndian();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 获取平台信息摘要
|
|
||||||
* @return 平台信息字符串
|
|
||||||
*/
|
|
||||||
std::string PlatformDetector::getPlatformSummary() {
|
|
||||||
std::string summary;
|
|
||||||
summary += "Platform: ";
|
|
||||||
summary += platformName();
|
|
||||||
summary += "\n";
|
|
||||||
summary += "Memory: ";
|
|
||||||
summary += std::to_string(getSystemMemoryMB());
|
|
||||||
summary += " MB\n";
|
|
||||||
summary += "CPU Cores: ";
|
|
||||||
summary += std::to_string(getCPUCoreCount());
|
|
||||||
summary += "\n";
|
|
||||||
summary += "Endianness: ";
|
|
||||||
summary += isLittleEndian() ? "Little Endian" : "Big Endian";
|
|
||||||
summary += "\n";
|
|
||||||
summary += "Desktop Platform: ";
|
|
||||||
summary += isDesktopPlatform() ? "Yes" : "No";
|
|
||||||
summary += "\n";
|
|
||||||
summary += "Console Platform: ";
|
|
||||||
summary += isConsolePlatform() ? "Yes" : "No";
|
|
||||||
summary += "\n";
|
|
||||||
summary += "Recommended Resolution: ";
|
|
||||||
int width, height;
|
|
||||||
getRecommendedResolution(width, height);
|
|
||||||
summary += std::to_string(width);
|
|
||||||
summary += "x";
|
|
||||||
summary += std::to_string(height);
|
|
||||||
summary += "\n";
|
|
||||||
summary += "Default DPI: ";
|
|
||||||
summary += std::to_string(static_cast<int>(getDefaultDPI()));
|
|
||||||
return summary;
|
|
||||||
}
|
|
||||||
|
|
||||||
PlatformCapabilities PlatformDetector::getWindowsCapabilities() {
|
|
||||||
PlatformCapabilities caps;
|
|
||||||
caps.supportsWindowed = true;
|
|
||||||
caps.supportsFullscreen = true;
|
|
||||||
caps.supportsBorderless = true;
|
|
||||||
caps.supportsCursor = true;
|
|
||||||
caps.supportsCursorHide = true;
|
|
||||||
caps.supportsDPIAwareness = true;
|
|
||||||
caps.supportsVSync = true;
|
|
||||||
caps.supportsMultiMonitor = true;
|
|
||||||
caps.supportsClipboard = true;
|
|
||||||
caps.supportsGamepad = true;
|
|
||||||
caps.supportsTouch = false;
|
|
||||||
caps.supportsKeyboard = true;
|
|
||||||
caps.supportsMouse = true;
|
|
||||||
caps.supportsResize = true;
|
|
||||||
caps.supportsHighDPI = true;
|
|
||||||
caps.maxTextureSize = 16384;
|
|
||||||
caps.preferredScreenWidth = 1920;
|
|
||||||
caps.preferredScreenHeight = 1080;
|
|
||||||
caps.defaultDPI = 96.0f;
|
|
||||||
return caps;
|
|
||||||
}
|
|
||||||
|
|
||||||
PlatformCapabilities PlatformDetector::getLinuxCapabilities() {
|
|
||||||
PlatformCapabilities caps;
|
|
||||||
caps.supportsWindowed = true;
|
|
||||||
caps.supportsFullscreen = true;
|
|
||||||
caps.supportsBorderless = true;
|
|
||||||
caps.supportsCursor = true;
|
|
||||||
caps.supportsCursorHide = true;
|
|
||||||
caps.supportsDPIAwareness = true;
|
|
||||||
caps.supportsVSync = true;
|
|
||||||
caps.supportsMultiMonitor = true;
|
|
||||||
caps.supportsClipboard = true;
|
|
||||||
caps.supportsGamepad = true;
|
|
||||||
caps.supportsTouch = false;
|
|
||||||
caps.supportsKeyboard = true;
|
|
||||||
caps.supportsMouse = true;
|
|
||||||
caps.supportsResize = true;
|
|
||||||
caps.supportsHighDPI = true;
|
|
||||||
caps.maxTextureSize = 16384;
|
|
||||||
caps.preferredScreenWidth = 1920;
|
|
||||||
caps.preferredScreenHeight = 1080;
|
|
||||||
caps.defaultDPI = 96.0f;
|
|
||||||
return caps;
|
|
||||||
}
|
|
||||||
|
|
||||||
PlatformCapabilities PlatformDetector::getMacOSCapabilities() {
|
|
||||||
PlatformCapabilities caps;
|
|
||||||
caps.supportsWindowed = true;
|
|
||||||
caps.supportsFullscreen = true;
|
|
||||||
caps.supportsBorderless = true;
|
|
||||||
caps.supportsCursor = true;
|
|
||||||
caps.supportsCursorHide = true;
|
|
||||||
caps.supportsDPIAwareness = true;
|
|
||||||
caps.supportsVSync = true;
|
|
||||||
caps.supportsMultiMonitor = true;
|
|
||||||
caps.supportsClipboard = true;
|
|
||||||
caps.supportsGamepad = true;
|
|
||||||
caps.supportsTouch = false;
|
|
||||||
caps.supportsKeyboard = true;
|
|
||||||
caps.supportsMouse = true;
|
|
||||||
caps.supportsResize = true;
|
|
||||||
caps.supportsHighDPI = true;
|
|
||||||
caps.maxTextureSize = 16384;
|
|
||||||
caps.preferredScreenWidth = 1920;
|
|
||||||
caps.preferredScreenHeight = 1080;
|
|
||||||
caps.defaultDPI = 144.0f;
|
|
||||||
return caps;
|
|
||||||
}
|
|
||||||
|
|
||||||
PlatformCapabilities PlatformDetector::getSwitchCapabilities() {
|
|
||||||
PlatformCapabilities caps;
|
|
||||||
caps.supportsWindowed = false;
|
|
||||||
caps.supportsFullscreen = true;
|
|
||||||
caps.supportsBorderless = false;
|
|
||||||
caps.supportsCursor = false;
|
|
||||||
caps.supportsCursorHide = false;
|
|
||||||
caps.supportsDPIAwareness = false;
|
|
||||||
caps.supportsVSync = true;
|
|
||||||
caps.supportsMultiMonitor = false;
|
|
||||||
caps.supportsClipboard = false;
|
|
||||||
caps.supportsGamepad = true;
|
|
||||||
caps.supportsTouch = true;
|
|
||||||
caps.supportsKeyboard = false;
|
|
||||||
caps.supportsMouse = false;
|
|
||||||
caps.supportsResize = false;
|
|
||||||
caps.supportsHighDPI = false;
|
|
||||||
caps.maxTextureSize = 8192;
|
|
||||||
caps.preferredScreenWidth = 1920;
|
|
||||||
caps.preferredScreenHeight = 1080;
|
|
||||||
caps.defaultDPI = 96.0f;
|
|
||||||
return caps;
|
|
||||||
}
|
|
||||||
|
|
||||||
AppConfig PlatformDetector::getWindowsDefaults() {
|
|
||||||
AppConfig config = AppConfig::createDefault();
|
|
||||||
return config;
|
|
||||||
}
|
|
||||||
|
|
||||||
AppConfig PlatformDetector::getLinuxDefaults() {
|
|
||||||
AppConfig config = AppConfig::createDefault();
|
|
||||||
return config;
|
|
||||||
}
|
|
||||||
|
|
||||||
AppConfig PlatformDetector::getMacOSDefaults() {
|
|
||||||
AppConfig config = AppConfig::createDefault();
|
|
||||||
return config;
|
|
||||||
}
|
|
||||||
|
|
||||||
AppConfig PlatformDetector::getSwitchDefaults() {
|
|
||||||
AppConfig config = AppConfig::createDefault();
|
|
||||||
return config;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,127 @@
|
||||||
|
#include <extra2d/graphics/backends/backend_factory.h>
|
||||||
|
|
||||||
|
// 条件编译包含对应后端实现
|
||||||
|
#ifdef E2D_BACKEND_OPENGL
|
||||||
|
#include <extra2d/graphics/backends/opengl/gl_renderer.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef E2D_BACKEND_VULKAN
|
||||||
|
#include <extra2d/graphics/backends/vulkan/vk_renderer.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include <extra2d/utils/logger.h>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
BackendFactory& BackendFactory::getInstance() {
|
||||||
|
static BackendFactory instance;
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
UniquePtr<RenderBackend> BackendFactory::createBackend(BackendType type) {
|
||||||
|
switch (type) {
|
||||||
|
#ifdef E2D_BACKEND_OPENGL
|
||||||
|
case BackendType::OpenGL:
|
||||||
|
E2D_LOG_INFO("Creating OpenGL render backend");
|
||||||
|
return makeUnique<GLRenderer>();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef E2D_BACKEND_VULKAN
|
||||||
|
case BackendType::Vulkan:
|
||||||
|
E2D_LOG_INFO("Creating Vulkan render backend");
|
||||||
|
return makeUnique<VulkanRenderer>();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
default:
|
||||||
|
E2D_LOG_ERROR("Unsupported render backend type: {}", static_cast<int>(type));
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
UniquePtr<RenderBackend> BackendFactory::createDefaultBackend() {
|
||||||
|
BackendType recommended = getRecommendedBackend();
|
||||||
|
return createBackend(recommended);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool BackendFactory::isBackendAvailable(BackendType type) const {
|
||||||
|
switch (type) {
|
||||||
|
#ifdef E2D_BACKEND_OPENGL
|
||||||
|
case BackendType::OpenGL:
|
||||||
|
return true;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef E2D_BACKEND_VULKAN
|
||||||
|
case BackendType::Vulkan:
|
||||||
|
return true;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
default:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BackendType BackendFactory::getRecommendedBackend() const {
|
||||||
|
// 平台特定的默认后端选择
|
||||||
|
// 优先级:Vulkan > OpenGL
|
||||||
|
|
||||||
|
#ifdef E2D_BACKEND_VULKAN
|
||||||
|
return BackendType::Vulkan;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef E2D_BACKEND_OPENGL
|
||||||
|
return BackendType::OpenGL;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
// 如果没有可用的后端,返回OpenGL作为默认值
|
||||||
|
return BackendType::OpenGL;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* BackendFactory::getBackendName(BackendType type) const {
|
||||||
|
switch (type) {
|
||||||
|
case BackendType::OpenGL:
|
||||||
|
return "OpenGL";
|
||||||
|
case BackendType::Vulkan:
|
||||||
|
return "Vulkan";
|
||||||
|
case BackendType::Metal:
|
||||||
|
return "Metal";
|
||||||
|
case BackendType::D3D11:
|
||||||
|
return "D3D11";
|
||||||
|
case BackendType::D3D12:
|
||||||
|
return "D3D12";
|
||||||
|
case BackendType::OpenGLES:
|
||||||
|
return "OpenGL ES";
|
||||||
|
default:
|
||||||
|
return "Unknown";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BackendType BackendFactory::parseBackendType(const char* name) const {
|
||||||
|
if (!name) {
|
||||||
|
return BackendType::OpenGL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (std::strcmp(name, "opengl") == 0 || std::strcmp(name, "OpenGL") == 0) {
|
||||||
|
return BackendType::OpenGL;
|
||||||
|
}
|
||||||
|
if (std::strcmp(name, "vulkan") == 0 || std::strcmp(name, "Vulkan") == 0) {
|
||||||
|
return BackendType::Vulkan;
|
||||||
|
}
|
||||||
|
if (std::strcmp(name, "metal") == 0 || std::strcmp(name, "Metal") == 0) {
|
||||||
|
return BackendType::Metal;
|
||||||
|
}
|
||||||
|
if (std::strcmp(name, "d3d11") == 0 || std::strcmp(name, "D3D11") == 0) {
|
||||||
|
return BackendType::D3D11;
|
||||||
|
}
|
||||||
|
if (std::strcmp(name, "d3d12") == 0 || std::strcmp(name, "D3D12") == 0) {
|
||||||
|
return BackendType::D3D12;
|
||||||
|
}
|
||||||
|
if (std::strcmp(name, "opengles") == 0 || std::strcmp(name, "OpenGLES") == 0) {
|
||||||
|
return BackendType::OpenGLES;
|
||||||
|
}
|
||||||
|
|
||||||
|
E2D_LOG_WARN("Unknown backend type '{}', defaulting to OpenGL", name);
|
||||||
|
return BackendType::OpenGL;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,171 @@
|
||||||
|
#include <extra2d/graphics/backends/opengl/gl_buffer.h>
|
||||||
|
#include <extra2d/graphics/memory/vram_manager.h>
|
||||||
|
#include <extra2d/utils/logger.h>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// GLBuffer 实现
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
GLBuffer::GLBuffer() = default;
|
||||||
|
|
||||||
|
GLBuffer::~GLBuffer() {
|
||||||
|
shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GLBuffer::init(const BufferDesc& desc) {
|
||||||
|
if (bufferID_ != 0) {
|
||||||
|
shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
type_ = desc.type;
|
||||||
|
usage_ = desc.usage;
|
||||||
|
size_ = desc.size;
|
||||||
|
target_ = convertType(type_);
|
||||||
|
glUsage_ = convertUsage(usage_);
|
||||||
|
|
||||||
|
// 生成缓冲区
|
||||||
|
glGenBuffers(1, &bufferID_);
|
||||||
|
if (bufferID_ == 0) {
|
||||||
|
E2D_LOG_ERROR("Failed to generate OpenGL buffer");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 绑定并分配缓冲区
|
||||||
|
glBindBuffer(target_, bufferID_);
|
||||||
|
glBufferData(target_, static_cast<GLsizeiptr>(size_), desc.initialData, glUsage_);
|
||||||
|
glBindBuffer(target_, 0);
|
||||||
|
|
||||||
|
// 追踪显存使用
|
||||||
|
VRAMMgr::get().allocBuffer(size_);
|
||||||
|
|
||||||
|
E2D_LOG_DEBUG("GLBuffer created: ID={}, Size={}, Type={}, Usage={}",
|
||||||
|
bufferID_, size_, static_cast<int>(type_), static_cast<int>(usage_));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLBuffer::shutdown() {
|
||||||
|
if (bufferID_ != 0) {
|
||||||
|
if (mapped_) {
|
||||||
|
unmap();
|
||||||
|
}
|
||||||
|
// 释放显存追踪
|
||||||
|
VRAMMgr::get().freeBuffer(size_);
|
||||||
|
glDeleteBuffers(1, &bufferID_);
|
||||||
|
E2D_LOG_DEBUG("GLBuffer destroyed: ID={}", bufferID_);
|
||||||
|
bufferID_ = 0;
|
||||||
|
}
|
||||||
|
size_ = 0;
|
||||||
|
mapped_ = false;
|
||||||
|
mappedPtr_ = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLBuffer::bind() {
|
||||||
|
if (bufferID_ != 0) {
|
||||||
|
glBindBuffer(target_, bufferID_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLBuffer::unbind() {
|
||||||
|
glBindBuffer(target_, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLBuffer::setData(const void* data, size_t size) {
|
||||||
|
if (bufferID_ == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bind();
|
||||||
|
|
||||||
|
// 如果大小相同,使用 glBufferSubData 更高效
|
||||||
|
if (size == size_) {
|
||||||
|
glBufferSubData(target_, 0, static_cast<GLsizeiptr>(size), data);
|
||||||
|
} else {
|
||||||
|
// 大小不同,重新分配
|
||||||
|
size_ = size;
|
||||||
|
glBufferData(target_, static_cast<GLsizeiptr>(size_), data, glUsage_);
|
||||||
|
}
|
||||||
|
|
||||||
|
unbind();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLBuffer::updateData(const void* data, size_t offset, size_t size) {
|
||||||
|
if (bufferID_ == 0 || data == nullptr || size == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (offset + size > size_) {
|
||||||
|
E2D_LOG_WARN("GLBuffer updateData out of bounds: offset={}, size={}, bufferSize={}",
|
||||||
|
offset, size, size_);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bind();
|
||||||
|
glBufferSubData(target_, static_cast<GLintptr>(offset), static_cast<GLsizeiptr>(size), data);
|
||||||
|
unbind();
|
||||||
|
}
|
||||||
|
|
||||||
|
void* GLBuffer::map() {
|
||||||
|
if (bufferID_ == 0 || mapped_) {
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
bind();
|
||||||
|
|
||||||
|
// 使用 glMapBufferRange 替代 glMapBuffer,更现代且安全
|
||||||
|
GLbitfield access = GL_MAP_WRITE_BIT;
|
||||||
|
if (usage_ == BufferUsage::Dynamic || usage_ == BufferUsage::Stream) {
|
||||||
|
access |= GL_MAP_INVALIDATE_BUFFER_BIT; // 暗示驱动可以丢弃旧数据
|
||||||
|
}
|
||||||
|
|
||||||
|
mappedPtr_ = glMapBufferRange(target_, 0, static_cast<GLsizeiptr>(size_), access);
|
||||||
|
if (mappedPtr_) {
|
||||||
|
mapped_ = true;
|
||||||
|
} else {
|
||||||
|
E2D_LOG_ERROR("Failed to map GLBuffer");
|
||||||
|
}
|
||||||
|
|
||||||
|
return mappedPtr_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLBuffer::unmap() {
|
||||||
|
if (!mapped_ || bufferID_ == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
glUnmapBuffer(target_);
|
||||||
|
mapped_ = false;
|
||||||
|
mappedPtr_ = nullptr;
|
||||||
|
unbind();
|
||||||
|
}
|
||||||
|
|
||||||
|
GLenum GLBuffer::convertUsage(BufferUsage usage) {
|
||||||
|
switch (usage) {
|
||||||
|
case BufferUsage::Static:
|
||||||
|
return GL_STATIC_DRAW;
|
||||||
|
case BufferUsage::Dynamic:
|
||||||
|
return GL_DYNAMIC_DRAW;
|
||||||
|
case BufferUsage::Stream:
|
||||||
|
return GL_STREAM_DRAW;
|
||||||
|
default:
|
||||||
|
return GL_STATIC_DRAW;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GLenum GLBuffer::convertType(BufferType type) {
|
||||||
|
switch (type) {
|
||||||
|
case BufferType::Vertex:
|
||||||
|
return GL_ARRAY_BUFFER;
|
||||||
|
case BufferType::Index:
|
||||||
|
return GL_ELEMENT_ARRAY_BUFFER;
|
||||||
|
case BufferType::Uniform:
|
||||||
|
return GL_UNIFORM_BUFFER;
|
||||||
|
default:
|
||||||
|
return GL_ARRAY_BUFFER;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,167 @@
|
||||||
|
#include <extra2d/graphics/backends/opengl/gl_context.h>
|
||||||
|
#include <extra2d/graphics/memory/gpu_context.h>
|
||||||
|
#include <extra2d/utils/logger.h>
|
||||||
|
#include <cstring>
|
||||||
|
#include <sstream>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// GLContext 实现
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
GLContext& GLContext::get() {
|
||||||
|
static GLContext instance;
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GLContext::init() {
|
||||||
|
if (initialized_) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析 OpenGL 版本
|
||||||
|
parseVersion();
|
||||||
|
|
||||||
|
// 加载扩展(GLAD 已在 glad.c 中完成)
|
||||||
|
if (!loadExtensions()) {
|
||||||
|
E2D_LOG_ERROR("Failed to load OpenGL extensions");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
initialized_ = true;
|
||||||
|
|
||||||
|
// 标记 GPU 上下文为有效
|
||||||
|
GPUContext::get().markValid();
|
||||||
|
|
||||||
|
E2D_LOG_INFO("OpenGL Context initialized");
|
||||||
|
E2D_LOG_INFO(" Version: {}", getVersionString());
|
||||||
|
E2D_LOG_INFO(" Vendor: {}", getVendor());
|
||||||
|
E2D_LOG_INFO(" Renderer: {}", getRenderer());
|
||||||
|
E2D_LOG_INFO(" Max Texture Size: {}", getMaxTextureSize());
|
||||||
|
E2D_LOG_INFO(" Max Texture Units: {}", getMaxTextureUnits());
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLContext::shutdown() {
|
||||||
|
// 标记 GPU 上下文为无效
|
||||||
|
GPUContext::get().markInvalid();
|
||||||
|
|
||||||
|
initialized_ = false;
|
||||||
|
version_ = GLVersion{};
|
||||||
|
maxTextureSize_ = -1;
|
||||||
|
maxTextureUnits_ = -1;
|
||||||
|
maxVertexAttribs_ = -1;
|
||||||
|
maxUniformBufferBindings_ = -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string GLContext::getVersionString() const {
|
||||||
|
const char* version = reinterpret_cast<const char*>(glGetString(GL_VERSION));
|
||||||
|
return version ? version : "Unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string GLContext::getVendor() const {
|
||||||
|
const char* vendor = reinterpret_cast<const char*>(glGetString(GL_VENDOR));
|
||||||
|
return vendor ? vendor : "Unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string GLContext::getRenderer() const {
|
||||||
|
const char* renderer = reinterpret_cast<const char*>(glGetString(GL_RENDERER));
|
||||||
|
return renderer ? renderer : "Unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GLContext::hasExtension(const std::string& extension) const {
|
||||||
|
GLint numExtensions = 0;
|
||||||
|
glGetIntegerv(GL_NUM_EXTENSIONS, &numExtensions);
|
||||||
|
|
||||||
|
for (GLint i = 0; i < numExtensions; ++i) {
|
||||||
|
const char* ext = reinterpret_cast<const char*>(glGetStringi(GL_EXTENSIONS, i));
|
||||||
|
if (ext && extension == ext) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int GLContext::getMaxTextureSize() const {
|
||||||
|
if (maxTextureSize_ < 0) {
|
||||||
|
glGetIntegerv(GL_MAX_TEXTURE_SIZE, &maxTextureSize_);
|
||||||
|
}
|
||||||
|
return maxTextureSize_;
|
||||||
|
}
|
||||||
|
|
||||||
|
int GLContext::getMaxTextureUnits() const {
|
||||||
|
if (maxTextureUnits_ < 0) {
|
||||||
|
glGetIntegerv(GL_MAX_TEXTURE_IMAGE_UNITS, &maxTextureUnits_);
|
||||||
|
}
|
||||||
|
return maxTextureUnits_;
|
||||||
|
}
|
||||||
|
|
||||||
|
int GLContext::getMaxVertexAttribs() const {
|
||||||
|
if (maxVertexAttribs_ < 0) {
|
||||||
|
glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &maxVertexAttribs_);
|
||||||
|
}
|
||||||
|
return maxVertexAttribs_;
|
||||||
|
}
|
||||||
|
|
||||||
|
int GLContext::getMaxUniformBufferBindings() const {
|
||||||
|
if (maxUniformBufferBindings_ < 0) {
|
||||||
|
glGetIntegerv(GL_MAX_UNIFORM_BUFFER_BINDINGS, &maxUniformBufferBindings_);
|
||||||
|
}
|
||||||
|
return maxUniformBufferBindings_;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GLContext::hasVAO() const {
|
||||||
|
// OpenGL 3.0+ 或 OpenGL ES 3.0+ 原生支持 VAO
|
||||||
|
if (version_.es) {
|
||||||
|
return version_.major >= 3;
|
||||||
|
}
|
||||||
|
return version_.major > 3 || (version_.major == 3 && version_.minor >= 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GLContext::hasFBO() const {
|
||||||
|
// OpenGL 3.0+ 或 OpenGL ES 2.0+ 原生支持 FBO
|
||||||
|
if (version_.es) {
|
||||||
|
return version_.major >= 2;
|
||||||
|
}
|
||||||
|
return version_.major >= 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GLContext::hasShader() const {
|
||||||
|
// OpenGL 2.0+ 或 OpenGL ES 2.0+ 原生支持 Shader
|
||||||
|
if (version_.es) {
|
||||||
|
return version_.major >= 2;
|
||||||
|
}
|
||||||
|
return version_.major >= 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLContext::parseVersion() {
|
||||||
|
const char* versionStr = reinterpret_cast<const char*>(glGetString(GL_VERSION));
|
||||||
|
if (!versionStr) {
|
||||||
|
version_ = GLVersion{0, 0, false};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string version(versionStr);
|
||||||
|
|
||||||
|
// 检查是否为 OpenGL ES
|
||||||
|
if (version.find("OpenGL ES") != std::string::npos) {
|
||||||
|
version_.es = true;
|
||||||
|
// 解析 ES 版本号,格式如 "OpenGL ES 3.0"
|
||||||
|
std::sscanf(version.c_str(), "OpenGL ES %d.%d", &version_.major, &version_.minor);
|
||||||
|
} else {
|
||||||
|
version_.es = false;
|
||||||
|
// 解析桌面版本号,格式如 "3.3.0 NVIDIA"
|
||||||
|
std::sscanf(version.c_str(), "%d.%d", &version_.major, &version_.minor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GLContext::loadExtensions() {
|
||||||
|
// GLAD 已经在 glad.c 中加载了所有扩展
|
||||||
|
// 这里可以添加额外的扩展检查
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -1,105 +1,151 @@
|
||||||
#include <extra2d/graphics/opengl/gl_font_atlas.h>
|
#include <extra2d/graphics/backends/opengl/gl_font_atlas.h>
|
||||||
#include <extra2d/utils/logger.h>
|
#include <extra2d/utils/logger.h>
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstring>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
|
||||||
|
// 在实现文件中定义 STB 实现
|
||||||
#define STB_TRUETYPE_IMPLEMENTATION
|
#define STB_TRUETYPE_IMPLEMENTATION
|
||||||
#include <stb/stb_truetype.h>
|
#include <stb/stb_truetype.h>
|
||||||
#define STB_RECT_PACK_IMPLEMENTATION
|
|
||||||
#include <algorithm>
|
|
||||||
#include <stb/stb_rect_pack.h>
|
|
||||||
|
|
||||||
|
#define STB_RECT_PACK_IMPLEMENTATION
|
||||||
|
#include <stb/stb_rect_pack.h>
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// 构造函数 - 初始化字体图集
|
// GLFontAtlas 构造函数
|
||||||
|
// 加载字体文件并初始化图集
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
/**
|
|
||||||
* @brief 构造函数,从字体文件初始化字体图集
|
|
||||||
* @param filepath 字体文件路径
|
|
||||||
* @param fontSize 字体大小(像素)
|
|
||||||
* @param useSDF 是否使用有符号距离场渲染
|
|
||||||
*/
|
|
||||||
GLFontAtlas::GLFontAtlas(const std::string &filepath, int fontSize, bool useSDF)
|
GLFontAtlas::GLFontAtlas(const std::string &filepath, int fontSize, bool useSDF)
|
||||||
: fontSize_(fontSize), useSDF_(useSDF), currentY_(0), scale_(0.0f),
|
: useSDF_(useSDF), fontSize_(fontSize), lineHeight_(0.0f), ascent_(0.0f),
|
||||||
ascent_(0.0f), descent_(0.0f), lineGap_(0.0f) {
|
descent_(0.0f), lineGap_(0.0f), scale_(0.0f) {
|
||||||
|
|
||||||
// 加载字体文件
|
// 加载字体文件
|
||||||
std::ifstream file(filepath, std::ios::binary | std::ios::ate);
|
if (!initFont(filepath)) {
|
||||||
if (!file.is_open()) {
|
E2D_LOG_ERROR("Failed to initialize font: {}", filepath);
|
||||||
E2D_LOG_ERROR("Failed to load font: {}", filepath);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::streamsize size = file.tellg();
|
|
||||||
file.seekg(0, std::ios::beg);
|
|
||||||
fontData_.resize(size);
|
|
||||||
if (!file.read(reinterpret_cast<char *>(fontData_.data()), size)) {
|
|
||||||
E2D_LOG_ERROR("Failed to read font file: {}", filepath);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 初始化 stb_truetype
|
|
||||||
if (!stbtt_InitFont(&fontInfo_, fontData_.data(),
|
|
||||||
stbtt_GetFontOffsetForIndex(fontData_.data(), 0))) {
|
|
||||||
E2D_LOG_ERROR("Failed to init font: {}", filepath);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 计算字体缩放比例和度量
|
||||||
scale_ = stbtt_ScaleForPixelHeight(&fontInfo_, static_cast<float>(fontSize_));
|
scale_ = stbtt_ScaleForPixelHeight(&fontInfo_, static_cast<float>(fontSize_));
|
||||||
|
|
||||||
int ascent, descent, lineGap;
|
int ascent, descent, lineGap;
|
||||||
stbtt_GetFontVMetrics(&fontInfo_, &ascent, &descent, &lineGap);
|
stbtt_GetFontVMetrics(&fontInfo_, &ascent, &descent, &lineGap);
|
||||||
|
|
||||||
ascent_ = static_cast<float>(ascent) * scale_;
|
ascent_ = static_cast<float>(ascent) * scale_;
|
||||||
descent_ = static_cast<float>(descent) * scale_;
|
descent_ = static_cast<float>(descent) * scale_;
|
||||||
lineGap_ = static_cast<float>(lineGap) * scale_;
|
lineGap_ = static_cast<float>(lineGap) * scale_;
|
||||||
|
lineHeight_ = ascent_ - descent_ + lineGap_;
|
||||||
|
|
||||||
|
// 创建图集纹理和打包上下文
|
||||||
createAtlas();
|
createAtlas();
|
||||||
|
|
||||||
|
// 预加载常用 ASCII 字符
|
||||||
|
std::string asciiChars = " !\"#$%&'()*+,-./"
|
||||||
|
"0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`"
|
||||||
|
"abcdefghijklmnopqrstuvwxyz{|}~";
|
||||||
|
for (char c : asciiChars) {
|
||||||
|
char32_t codepoint = static_cast<char32_t>(static_cast<unsigned char>(c));
|
||||||
|
cacheGlyph(codepoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
E2D_LOG_INFO("Font atlas created: {} ({}px, {}x{})", filepath, fontSize_,
|
||||||
|
ATLAS_WIDTH, ATLAS_HEIGHT);
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// 析构函数
|
// GLFontAtlas 析构函数
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
/**
|
GLFontAtlas::~GLFontAtlas() {
|
||||||
* @brief 析构函数
|
// 智能指针自动管理纹理资源
|
||||||
*/
|
}
|
||||||
GLFontAtlas::~GLFontAtlas() = default;
|
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// 获取字形 - 如果字形不存在则缓存它
|
// 获取字形信息 - 如果字形不存在则动态缓存
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
/**
|
|
||||||
* @brief 获取字形信息,如果字形不存在则动态缓存
|
|
||||||
* @param codepoint Unicode码点
|
|
||||||
* @return 字形信息指针,如果获取失败返回nullptr
|
|
||||||
*/
|
|
||||||
const Glyph *GLFontAtlas::getGlyph(char32_t codepoint) const {
|
const Glyph *GLFontAtlas::getGlyph(char32_t codepoint) const {
|
||||||
auto it = glyphs_.find(codepoint);
|
auto it = glyphs_.find(codepoint);
|
||||||
if (it == glyphs_.end()) {
|
if (it == glyphs_.end()) {
|
||||||
cacheGlyph(codepoint);
|
// 动态缓存新字形
|
||||||
|
const_cast<GLFontAtlas *>(this)->cacheGlyph(codepoint);
|
||||||
it = glyphs_.find(codepoint);
|
it = glyphs_.find(codepoint);
|
||||||
|
if (it == glyphs_.end()) {
|
||||||
|
return nullptr;
|
||||||
}
|
}
|
||||||
return (it != glyphs_.end()) ? &it->second : nullptr;
|
}
|
||||||
|
|
||||||
|
// 返回静态存储的 Glyph 数据
|
||||||
|
static Glyph glyph;
|
||||||
|
const auto &data = it->second;
|
||||||
|
glyph.width = data.width;
|
||||||
|
glyph.height = data.height;
|
||||||
|
glyph.bearingX = data.bearingX;
|
||||||
|
glyph.bearingY = data.bearingY;
|
||||||
|
glyph.advance = data.advance;
|
||||||
|
glyph.u0 = data.u0;
|
||||||
|
glyph.v0 = data.v0;
|
||||||
|
glyph.u1 = data.u1;
|
||||||
|
glyph.v1 = data.v1;
|
||||||
|
|
||||||
|
return &glyph;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// 测量文本尺寸
|
// 测量文本尺寸 - 支持多行文本
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
/**
|
|
||||||
* @brief 测量文本渲染后的尺寸
|
|
||||||
* @param text 要测量的文本
|
|
||||||
* @return 文本的宽度和高度
|
|
||||||
*/
|
|
||||||
Vec2 GLFontAtlas::measureText(const std::string &text) {
|
Vec2 GLFontAtlas::measureText(const std::string &text) {
|
||||||
float width = 0.0f;
|
float width = 0.0f;
|
||||||
float height = getAscent() - getDescent();
|
float maxWidth = 0.0f;
|
||||||
|
float height = lineHeight_;
|
||||||
float currentWidth = 0.0f;
|
float currentWidth = 0.0f;
|
||||||
|
|
||||||
for (char c : text) {
|
for (size_t i = 0; i < text.length();) {
|
||||||
char32_t codepoint = static_cast<char32_t>(static_cast<unsigned char>(c));
|
// 处理 UTF-8 编码
|
||||||
|
char32_t codepoint = 0;
|
||||||
|
unsigned char c = static_cast<unsigned char>(text[i]);
|
||||||
|
|
||||||
|
if ((c & 0x80) == 0) {
|
||||||
|
// 单字节 ASCII
|
||||||
|
codepoint = c;
|
||||||
|
i++;
|
||||||
|
} else if ((c & 0xE0) == 0xC0) {
|
||||||
|
// 2字节 UTF-8
|
||||||
|
if (i + 1 >= text.length())
|
||||||
|
break;
|
||||||
|
codepoint =
|
||||||
|
((c & 0x1F) << 6) | (static_cast<unsigned char>(text[i + 1]) & 0x3F);
|
||||||
|
i += 2;
|
||||||
|
} else if ((c & 0xF0) == 0xE0) {
|
||||||
|
// 3字节 UTF-8
|
||||||
|
if (i + 2 >= text.length())
|
||||||
|
break;
|
||||||
|
codepoint = ((c & 0x0F) << 12) |
|
||||||
|
((static_cast<unsigned char>(text[i + 1]) & 0x3F) << 6) |
|
||||||
|
(static_cast<unsigned char>(text[i + 2]) & 0x3F);
|
||||||
|
i += 3;
|
||||||
|
} else if ((c & 0xF8) == 0xF0) {
|
||||||
|
// 4字节 UTF-8
|
||||||
|
if (i + 3 >= text.length())
|
||||||
|
break;
|
||||||
|
codepoint = ((c & 0x07) << 18) |
|
||||||
|
((static_cast<unsigned char>(text[i + 1]) & 0x3F) << 12) |
|
||||||
|
((static_cast<unsigned char>(text[i + 2]) & 0x3F) << 6) |
|
||||||
|
(static_cast<unsigned char>(text[i + 3]) & 0x3F);
|
||||||
|
i += 4;
|
||||||
|
} else {
|
||||||
|
// 无效的 UTF-8,跳过
|
||||||
|
i++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理换行
|
||||||
if (codepoint == '\n') {
|
if (codepoint == '\n') {
|
||||||
width = std::max(width, currentWidth);
|
maxWidth = std::max(maxWidth, currentWidth);
|
||||||
currentWidth = 0.0f;
|
currentWidth = 0.0f;
|
||||||
height += getLineHeight();
|
height += lineHeight_;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -109,25 +155,51 @@ Vec2 GLFontAtlas::measureText(const std::string &text) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
width = std::max(width, currentWidth);
|
maxWidth = std::max(maxWidth, currentWidth);
|
||||||
return Vec2(width, height);
|
return Vec2(maxWidth, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 初始化字体 - 加载字体文件到内存
|
||||||
|
// ============================================================================
|
||||||
|
bool GLFontAtlas::initFont(const std::string &filepath) {
|
||||||
|
// 读取字体文件到内存
|
||||||
|
std::ifstream file(filepath, std::ios::binary | std::ios::ate);
|
||||||
|
if (!file.is_open()) {
|
||||||
|
E2D_LOG_ERROR("Failed to open font file: {}", filepath);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::streamsize size = file.tellg();
|
||||||
|
file.seekg(0, std::ios::beg);
|
||||||
|
|
||||||
|
fontData_.resize(static_cast<size_t>(size));
|
||||||
|
if (!file.read(reinterpret_cast<char *>(fontData_.data()), size)) {
|
||||||
|
E2D_LOG_ERROR("Failed to read font file: {}", filepath);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 初始化 STB 字体
|
||||||
|
if (!stbtt_InitFont(&fontInfo_, fontData_.data(), 0)) {
|
||||||
|
E2D_LOG_ERROR("Failed to initialize STB font: {}", filepath);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// 创建图集纹理 - 初始化空白纹理和矩形打包上下文
|
// 创建图集纹理 - 初始化空白纹理和矩形打包上下文
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
/**
|
|
||||||
* @brief 创建字体图集纹理,初始化空白纹理和矩形打包上下文
|
|
||||||
*/
|
|
||||||
void GLFontAtlas::createAtlas() {
|
void GLFontAtlas::createAtlas() {
|
||||||
// 统一使用 4 通道格式
|
// 统一使用 4 通道格式 (RGBA)
|
||||||
int channels = 4;
|
int channels = 4;
|
||||||
std::vector<uint8_t> emptyData(ATLAS_WIDTH * ATLAS_HEIGHT * channels, 0);
|
std::vector<uint8_t> emptyData(ATLAS_WIDTH * ATLAS_HEIGHT * channels, 0);
|
||||||
texture_ = std::make_unique<GLTexture>(ATLAS_WIDTH, ATLAS_HEIGHT,
|
texture_ = std::make_unique<GLTexture>(ATLAS_WIDTH, ATLAS_HEIGHT,
|
||||||
emptyData.data(), channels);
|
emptyData.data(), channels);
|
||||||
texture_->setFilter(true);
|
texture_->setFilter(true);
|
||||||
|
|
||||||
// 初始化矩形打包上下文
|
// 初始化矩形打包上下文 - 持久化以支持增量打包
|
||||||
packNodes_.resize(ATLAS_WIDTH);
|
packNodes_.resize(ATLAS_WIDTH);
|
||||||
stbrp_init_target(&packContext_, ATLAS_WIDTH, ATLAS_HEIGHT, packNodes_.data(),
|
stbrp_init_target(&packContext_, ATLAS_WIDTH, ATLAS_HEIGHT, packNodes_.data(),
|
||||||
ATLAS_WIDTH);
|
ATLAS_WIDTH);
|
||||||
|
|
@ -143,16 +215,19 @@ void GLFontAtlas::createAtlas() {
|
||||||
// 缓存字形 - 渲染字形到图集并存储信息
|
// 缓存字形 - 渲染字形到图集并存储信息
|
||||||
// 使用 stb_rect_pack 进行矩形打包
|
// 使用 stb_rect_pack 进行矩形打包
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
/**
|
void GLFontAtlas::cacheGlyph(char32_t codepoint) {
|
||||||
* @brief 缓存字形到图集,渲染字形位图并存储字形信息
|
// 检查是否已存在
|
||||||
* @param codepoint Unicode码点
|
if (glyphs_.find(codepoint) != glyphs_.end()) {
|
||||||
*/
|
return;
|
||||||
void GLFontAtlas::cacheGlyph(char32_t codepoint) const {
|
}
|
||||||
int advance = 0;
|
|
||||||
stbtt_GetCodepointHMetrics(&fontInfo_, static_cast<int>(codepoint), &advance,
|
|
||||||
nullptr);
|
|
||||||
float advancePx = advance * scale_;
|
|
||||||
|
|
||||||
|
// 获取字形水平度量
|
||||||
|
int advance, leftSideBearing;
|
||||||
|
stbtt_GetCodepointHMetrics(&fontInfo_, static_cast<int>(codepoint), &advance,
|
||||||
|
&leftSideBearing);
|
||||||
|
float advancePx = static_cast<float>(advance) * scale_;
|
||||||
|
|
||||||
|
// SDF 渲染模式
|
||||||
if (useSDF_) {
|
if (useSDF_) {
|
||||||
constexpr int SDF_PADDING = 8;
|
constexpr int SDF_PADDING = 8;
|
||||||
constexpr unsigned char ONEDGE_VALUE = 128;
|
constexpr unsigned char ONEDGE_VALUE = 128;
|
||||||
|
|
@ -162,15 +237,18 @@ void GLFontAtlas::cacheGlyph(char32_t codepoint) const {
|
||||||
unsigned char *sdf = stbtt_GetCodepointSDF(
|
unsigned char *sdf = stbtt_GetCodepointSDF(
|
||||||
&fontInfo_, scale_, static_cast<int>(codepoint), SDF_PADDING,
|
&fontInfo_, scale_, static_cast<int>(codepoint), SDF_PADDING,
|
||||||
ONEDGE_VALUE, PIXEL_DIST_SCALE, &w, &h, &xoff, &yoff);
|
ONEDGE_VALUE, PIXEL_DIST_SCALE, &w, &h, &xoff, &yoff);
|
||||||
|
|
||||||
if (!sdf || w <= 0 || h <= 0) {
|
if (!sdf || w <= 0 || h <= 0) {
|
||||||
if (sdf)
|
if (sdf)
|
||||||
stbtt_FreeSDF(sdf, nullptr);
|
stbtt_FreeSDF(sdf, nullptr);
|
||||||
Glyph glyph{};
|
// 创建空白字形(如空格)
|
||||||
glyph.advance = advancePx;
|
GlyphData data{};
|
||||||
glyphs_[codepoint] = glyph;
|
data.advance = advancePx;
|
||||||
|
glyphs_[codepoint] = data;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 使用 stb_rect_pack 打包矩形
|
||||||
stbrp_rect rect;
|
stbrp_rect rect;
|
||||||
rect.id = static_cast<int>(codepoint);
|
rect.id = static_cast<int>(codepoint);
|
||||||
rect.w = w + PADDING * 2;
|
rect.w = w + PADDING * 2;
|
||||||
|
|
@ -178,8 +256,7 @@ void GLFontAtlas::cacheGlyph(char32_t codepoint) const {
|
||||||
|
|
||||||
stbrp_pack_rects(&packContext_, &rect, 1);
|
stbrp_pack_rects(&packContext_, &rect, 1);
|
||||||
if (!rect.was_packed) {
|
if (!rect.was_packed) {
|
||||||
E2D_LOG_WARN("Font atlas is full, cannot cache codepoint: {}",
|
E2D_LOG_WARN("Font atlas is full, cannot cache codepoint: {}", codepoint);
|
||||||
static_cast<int>(codepoint));
|
|
||||||
stbtt_FreeSDF(sdf, nullptr);
|
stbtt_FreeSDF(sdf, nullptr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -187,23 +264,25 @@ void GLFontAtlas::cacheGlyph(char32_t codepoint) const {
|
||||||
int atlasX = rect.x + PADDING;
|
int atlasX = rect.x + PADDING;
|
||||||
int atlasY = rect.y + PADDING;
|
int atlasY = rect.y + PADDING;
|
||||||
|
|
||||||
Glyph glyph;
|
// 创建字形数据
|
||||||
glyph.width = static_cast<float>(w);
|
GlyphData data;
|
||||||
glyph.height = static_cast<float>(h);
|
data.width = static_cast<float>(w);
|
||||||
glyph.bearingX = static_cast<float>(xoff);
|
data.height = static_cast<float>(h);
|
||||||
glyph.bearingY = static_cast<float>(yoff);
|
data.bearingX = static_cast<float>(xoff);
|
||||||
glyph.advance = advancePx;
|
data.bearingY = static_cast<float>(yoff);
|
||||||
|
data.advance = advancePx;
|
||||||
|
|
||||||
|
// 计算 UV 坐标
|
||||||
// stb_rect_pack 使用左上角为原点,OpenGL纹理使用左下角为原点
|
// stb_rect_pack 使用左上角为原点,OpenGL纹理使用左下角为原点
|
||||||
// 需要翻转V坐标
|
// 需要翻转V坐标
|
||||||
float v0 = static_cast<float>(atlasY) / ATLAS_HEIGHT;
|
float v0 = static_cast<float>(atlasY) / ATLAS_HEIGHT;
|
||||||
float v1 = static_cast<float>(atlasY + h) / ATLAS_HEIGHT;
|
float v1 = static_cast<float>(atlasY + h) / ATLAS_HEIGHT;
|
||||||
glyph.u0 = static_cast<float>(atlasX) / ATLAS_WIDTH;
|
data.u0 = static_cast<float>(atlasX) / ATLAS_WIDTH;
|
||||||
glyph.v0 = 1.0f - v1; // 翻转V坐标
|
data.v0 = 1.0f - v1; // 翻转V坐标
|
||||||
glyph.u1 = static_cast<float>(atlasX + w) / ATLAS_WIDTH;
|
data.u1 = static_cast<float>(atlasX + w) / ATLAS_WIDTH;
|
||||||
glyph.v1 = 1.0f - v0; // 翻转V坐标
|
data.v1 = 1.0f - v0; // 翻转V坐标
|
||||||
|
|
||||||
glyphs_[codepoint] = glyph;
|
glyphs_[codepoint] = data;
|
||||||
|
|
||||||
// 将 SDF 单通道数据转换为 RGBA 格式(统一格式)
|
// 将 SDF 单通道数据转换为 RGBA 格式(统一格式)
|
||||||
size_t pixelCount = static_cast<size_t>(w) * static_cast<size_t>(h);
|
size_t pixelCount = static_cast<size_t>(w) * static_cast<size_t>(h);
|
||||||
|
|
@ -216,33 +295,33 @@ void GLFontAtlas::cacheGlyph(char32_t codepoint) const {
|
||||||
glyphRgbaCache_[i * 4 + 3] = alpha; // A - SDF 值存储在 Alpha 通道
|
glyphRgbaCache_[i * 4 + 3] = alpha; // A - SDF 值存储在 Alpha 通道
|
||||||
}
|
}
|
||||||
|
|
||||||
// 直接设置像素对齐为 4,无需查询当前状态
|
// 更新纹理 - OpenGL纹理坐标原点在左下角,需要将Y坐标翻转
|
||||||
glBindTexture(GL_TEXTURE_2D, texture_->getTextureID());
|
updateAtlas(atlasX, ATLAS_HEIGHT - atlasY - h, w, h, glyphRgbaCache_);
|
||||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
|
|
||||||
// OpenGL纹理坐标原点在左下角,需要将Y坐标翻转
|
|
||||||
glTexSubImage2D(GL_TEXTURE_2D, 0, atlasX, ATLAS_HEIGHT - atlasY - h, w, h,
|
|
||||||
GL_RGBA, GL_UNSIGNED_BYTE, glyphRgbaCache_.data());
|
|
||||||
|
|
||||||
stbtt_FreeSDF(sdf, nullptr);
|
stbtt_FreeSDF(sdf, nullptr);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 普通位图渲染模式
|
||||||
int x0 = 0, y0 = 0, x1 = 0, y1 = 0;
|
int x0 = 0, y0 = 0, x1 = 0, y1 = 0;
|
||||||
stbtt_GetCodepointBitmapBox(&fontInfo_, static_cast<int>(codepoint), scale_,
|
stbtt_GetCodepointBitmapBox(&fontInfo_, static_cast<int>(codepoint), scale_,
|
||||||
scale_, &x0, &y0, &x1, &y1);
|
scale_, &x0, &y0, &x1, &y1);
|
||||||
int w = x1 - x0;
|
int w = x1 - x0;
|
||||||
int h = y1 - y0;
|
int h = y1 - y0;
|
||||||
int xoff = x0;
|
int xoff = x0;
|
||||||
|
// y0 是相对于基线的偏移(通常为负值,表示在基线上方)
|
||||||
|
// bearingY 应该是字形顶部相对于基线的偏移
|
||||||
int yoff = y0;
|
int yoff = y0;
|
||||||
|
|
||||||
if (w <= 0 || h <= 0) {
|
if (w <= 0 || h <= 0) {
|
||||||
Glyph glyph{};
|
// 空白字符(如空格)
|
||||||
glyph.advance = advancePx;
|
GlyphData data{};
|
||||||
glyphs_[codepoint] = glyph;
|
data.advance = advancePx;
|
||||||
|
glyphs_[codepoint] = data;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 使用预分配缓冲区
|
// 使用预分配缓冲区渲染字形
|
||||||
size_t pixelCount = static_cast<size_t>(w) * static_cast<size_t>(h);
|
size_t pixelCount = static_cast<size_t>(w) * static_cast<size_t>(h);
|
||||||
glyphBitmapCache_.resize(pixelCount);
|
glyphBitmapCache_.resize(pixelCount);
|
||||||
stbtt_MakeCodepointBitmap(&fontInfo_, glyphBitmapCache_.data(), w, h, w,
|
stbtt_MakeCodepointBitmap(&fontInfo_, glyphBitmapCache_.data(), w, h, w,
|
||||||
|
|
@ -257,35 +336,32 @@ void GLFontAtlas::cacheGlyph(char32_t codepoint) const {
|
||||||
stbrp_pack_rects(&packContext_, &rect, 1);
|
stbrp_pack_rects(&packContext_, &rect, 1);
|
||||||
|
|
||||||
if (!rect.was_packed) {
|
if (!rect.was_packed) {
|
||||||
// 图集已满,无法缓存更多字形
|
E2D_LOG_WARN("Font atlas is full, cannot cache codepoint: {}", codepoint);
|
||||||
E2D_LOG_WARN("Font atlas is full, cannot cache codepoint: {}",
|
|
||||||
static_cast<int>(codepoint));
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int atlasX = rect.x + PADDING;
|
int atlasX = rect.x + PADDING;
|
||||||
int atlasY = rect.y + PADDING;
|
int atlasY = rect.y + PADDING;
|
||||||
|
|
||||||
// 创建字形信息
|
// 创建字形数据
|
||||||
Glyph glyph;
|
GlyphData data;
|
||||||
glyph.width = static_cast<float>(w);
|
data.width = static_cast<float>(w);
|
||||||
glyph.height = static_cast<float>(h);
|
data.height = static_cast<float>(h);
|
||||||
glyph.bearingX = static_cast<float>(xoff);
|
data.bearingX = static_cast<float>(xoff);
|
||||||
glyph.bearingY = static_cast<float>(yoff);
|
data.bearingY = static_cast<float>(yoff);
|
||||||
glyph.advance = advancePx;
|
data.advance = advancePx;
|
||||||
|
|
||||||
// 计算纹理坐标(相对于图集)
|
// 计算 UV 坐标
|
||||||
// stb_rect_pack 使用左上角为原点,OpenGL纹理使用左下角为原点
|
// stb_rect_pack 使用左上角为原点,OpenGL纹理使用左下角为原点
|
||||||
// 需要翻转V坐标
|
// 需要翻转V坐标
|
||||||
float v0 = static_cast<float>(atlasY) / ATLAS_HEIGHT;
|
float v0 = static_cast<float>(atlasY) / ATLAS_HEIGHT;
|
||||||
float v1 = static_cast<float>(atlasY + h) / ATLAS_HEIGHT;
|
float v1 = static_cast<float>(atlasY + h) / ATLAS_HEIGHT;
|
||||||
glyph.u0 = static_cast<float>(atlasX) / ATLAS_WIDTH;
|
data.u0 = static_cast<float>(atlasX) / ATLAS_WIDTH;
|
||||||
glyph.v0 = 1.0f - v1; // 翻转V坐标
|
data.v0 = 1.0f - v1; // 翻转V坐标
|
||||||
glyph.u1 = static_cast<float>(atlasX + w) / ATLAS_WIDTH;
|
data.u1 = static_cast<float>(atlasX + w) / ATLAS_WIDTH;
|
||||||
glyph.v1 = 1.0f - v0; // 翻转V坐标
|
data.v1 = 1.0f - v0; // 翻转V坐标
|
||||||
|
|
||||||
// 存储字形
|
glyphs_[codepoint] = data;
|
||||||
glyphs_[codepoint] = glyph;
|
|
||||||
|
|
||||||
// 将单通道字形数据转换为 RGBA 格式(白色字形,Alpha 通道存储灰度)
|
// 将单通道字形数据转换为 RGBA 格式(白色字形,Alpha 通道存储灰度)
|
||||||
glyphRgbaCache_.resize(pixelCount * 4);
|
glyphRgbaCache_.resize(pixelCount * 4);
|
||||||
|
|
@ -297,13 +373,21 @@ void GLFontAtlas::cacheGlyph(char32_t codepoint) const {
|
||||||
glyphRgbaCache_[i * 4 + 3] = alpha; // A
|
glyphRgbaCache_[i * 4 + 3] = alpha; // A
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新纹理 - 将字形数据上传到图集的指定位置
|
// 更新纹理 - OpenGL纹理坐标原点在左下角,需要将Y坐标翻转
|
||||||
// 直接设置像素对齐为 4,无需查询当前状态
|
updateAtlas(atlasX, ATLAS_HEIGHT - atlasY - h, w, h, glyphRgbaCache_);
|
||||||
glBindTexture(GL_TEXTURE_2D, texture_->getTextureID());
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// 更新图集纹理区域
|
||||||
|
// ============================================================================
|
||||||
|
void GLFontAtlas::updateAtlas(int x, int y, int width, int height,
|
||||||
|
const std::vector<uint8_t> &data) {
|
||||||
|
if (texture_) {
|
||||||
|
texture_->bind();
|
||||||
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
|
glPixelStorei(GL_UNPACK_ALIGNMENT, 4);
|
||||||
// OpenGL纹理坐标原点在左下角,需要将Y坐标翻转
|
glTexSubImage2D(GL_TEXTURE_2D, 0, x, y, width, height, GL_RGBA,
|
||||||
glTexSubImage2D(GL_TEXTURE_2D, 0, atlasX, ATLAS_HEIGHT - atlasY - h, w, h,
|
GL_UNSIGNED_BYTE, data.data());
|
||||||
GL_RGBA, GL_UNSIGNED_BYTE, glyphRgbaCache_.data());
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace extra2d
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,268 @@
|
||||||
|
#include <extra2d/graphics/backends/opengl/gl_framebuffer.h>
|
||||||
|
#include <extra2d/graphics/backends/opengl/gl_texture.h>
|
||||||
|
#include <extra2d/utils/logger.h>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// GLFramebuffer 实现
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
GLFramebuffer::GLFramebuffer() = default;
|
||||||
|
|
||||||
|
GLFramebuffer::~GLFramebuffer() {
|
||||||
|
shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GLFramebuffer::init(const FramebufferDesc& desc) {
|
||||||
|
if (fboID_ != 0) {
|
||||||
|
shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
width_ = desc.width;
|
||||||
|
height_ = desc.height;
|
||||||
|
numColorAttachments_ = desc.colorAttachments;
|
||||||
|
hasDepth_ = desc.hasDepth;
|
||||||
|
hasStencil_ = desc.hasStencil;
|
||||||
|
|
||||||
|
// 限制颜色附件数
|
||||||
|
if (numColorAttachments_ > MAX_COLOR_ATTACHMENTS) {
|
||||||
|
numColorAttachments_ = MAX_COLOR_ATTACHMENTS;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 生成 FBO
|
||||||
|
glGenFramebuffers(1, &fboID_);
|
||||||
|
if (fboID_ == 0) {
|
||||||
|
E2D_LOG_ERROR("Failed to generate OpenGL framebuffer");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
E2D_LOG_DEBUG("GLFramebuffer created: ID={}, Size={}x{}, ColorAttachments={}",
|
||||||
|
fboID_, width_, height_, numColorAttachments_);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLFramebuffer::shutdown() {
|
||||||
|
if (fboID_ != 0) {
|
||||||
|
glDeleteFramebuffers(1, &fboID_);
|
||||||
|
E2D_LOG_DEBUG("GLFramebuffer destroyed: ID={}", fboID_);
|
||||||
|
fboID_ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 清理纹理引用
|
||||||
|
for (auto& tex : colorTextures_) {
|
||||||
|
tex.reset();
|
||||||
|
}
|
||||||
|
depthTexture_.reset();
|
||||||
|
depthStencilTexture_.reset();
|
||||||
|
|
||||||
|
hasInternalTextures_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLFramebuffer::bind() {
|
||||||
|
if (fboID_ != 0) {
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, fboID_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLFramebuffer::unbind() {
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLFramebuffer::attachColorTexture(Ptr<Texture> texture, int attachment) {
|
||||||
|
if (fboID_ == 0 || !texture || attachment < 0 || attachment >= MAX_COLOR_ATTACHMENTS) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bind();
|
||||||
|
|
||||||
|
// 获取 OpenGL 纹理 ID
|
||||||
|
GLuint texID = static_cast<GLuint>(reinterpret_cast<uintptr_t>(texture->getNativeHandle()));
|
||||||
|
|
||||||
|
glFramebufferTexture2D(GL_FRAMEBUFFER, getColorAttachment(attachment),
|
||||||
|
GL_TEXTURE_2D, texID, 0);
|
||||||
|
|
||||||
|
colorTextures_[attachment] = texture;
|
||||||
|
|
||||||
|
unbind();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLFramebuffer::attachDepthTexture(Ptr<Texture> texture) {
|
||||||
|
if (fboID_ == 0 || !texture) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bind();
|
||||||
|
|
||||||
|
// 获取 OpenGL 纹理 ID
|
||||||
|
GLuint texID = static_cast<GLuint>(reinterpret_cast<uintptr_t>(texture->getNativeHandle()));
|
||||||
|
|
||||||
|
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
|
||||||
|
GL_TEXTURE_2D, texID, 0);
|
||||||
|
|
||||||
|
depthTexture_ = texture;
|
||||||
|
hasDepth_ = true;
|
||||||
|
hasStencil_ = false;
|
||||||
|
|
||||||
|
unbind();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLFramebuffer::attachDepthStencilTexture(Ptr<Texture> texture) {
|
||||||
|
if (fboID_ == 0 || !texture) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bind();
|
||||||
|
|
||||||
|
// 获取 OpenGL 纹理 ID
|
||||||
|
GLuint texID = static_cast<GLuint>(reinterpret_cast<uintptr_t>(texture->getNativeHandle()));
|
||||||
|
|
||||||
|
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT,
|
||||||
|
GL_TEXTURE_2D, texID, 0);
|
||||||
|
|
||||||
|
depthStencilTexture_ = texture;
|
||||||
|
hasDepth_ = true;
|
||||||
|
hasStencil_ = true;
|
||||||
|
|
||||||
|
unbind();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GLFramebuffer::isComplete() {
|
||||||
|
if (fboID_ == 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bind();
|
||||||
|
bool complete = checkStatus();
|
||||||
|
unbind();
|
||||||
|
|
||||||
|
return complete;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ptr<Texture> GLFramebuffer::getColorTexture(int attachment) const {
|
||||||
|
if (attachment >= 0 && attachment < MAX_COLOR_ATTACHMENTS) {
|
||||||
|
return colorTextures_[attachment];
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ptr<Texture> GLFramebuffer::getDepthTexture() const {
|
||||||
|
return depthTexture_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLFramebuffer::clear(const Color& color, bool clearColor,
|
||||||
|
bool clearDepth, bool clearStencil) {
|
||||||
|
if (fboID_ == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bind();
|
||||||
|
|
||||||
|
GLbitfield mask = 0;
|
||||||
|
|
||||||
|
if (clearColor) {
|
||||||
|
mask |= GL_COLOR_BUFFER_BIT;
|
||||||
|
glClearColor(color.r, color.g, color.b, color.a);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clearDepth) {
|
||||||
|
mask |= GL_DEPTH_BUFFER_BIT;
|
||||||
|
glClearDepthf(1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clearStencil) {
|
||||||
|
mask |= GL_STENCIL_BUFFER_BIT;
|
||||||
|
glClearStencil(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mask != 0) {
|
||||||
|
glClear(mask);
|
||||||
|
}
|
||||||
|
|
||||||
|
unbind();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLFramebuffer::setViewport(int x, int y, int width, int height) {
|
||||||
|
glViewport(x, y, width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GLFramebuffer::readPixels(int x, int y, int width, int height,
|
||||||
|
std::vector<uint8_t>& outData) {
|
||||||
|
if (fboID_ == 0 || width <= 0 || height <= 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算需要的缓冲区大小 (RGBA8)
|
||||||
|
size_t dataSize = width * height * 4;
|
||||||
|
outData.resize(dataSize);
|
||||||
|
|
||||||
|
bind();
|
||||||
|
glReadPixels(x, y, width, height, GL_RGBA, GL_UNSIGNED_BYTE, outData.data());
|
||||||
|
unbind();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GLFramebuffer::createWithTextures(int width, int height,
|
||||||
|
PixelFormat colorFormat,
|
||||||
|
PixelFormat depthFormat) {
|
||||||
|
FramebufferDesc desc;
|
||||||
|
desc.width = width;
|
||||||
|
desc.height = height;
|
||||||
|
desc.colorAttachments = 1;
|
||||||
|
desc.hasDepth = (depthFormat != PixelFormat::RGBA8);
|
||||||
|
desc.hasStencil = (depthFormat == PixelFormat::Depth24Stencil8);
|
||||||
|
|
||||||
|
if (!init(desc)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
hasInternalTextures_ = true;
|
||||||
|
|
||||||
|
// 创建颜色纹理
|
||||||
|
// 注意:这里简化处理,实际应该通过纹理工厂创建
|
||||||
|
// 暂时返回 true,实际纹理创建由调用者处理
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GLFramebuffer::checkStatus() {
|
||||||
|
GLenum status = glCheckFramebufferStatus(GL_FRAMEBUFFER);
|
||||||
|
|
||||||
|
switch (status) {
|
||||||
|
case GL_FRAMEBUFFER_COMPLETE:
|
||||||
|
return true;
|
||||||
|
case GL_FRAMEBUFFER_UNDEFINED:
|
||||||
|
E2D_LOG_ERROR("Framebuffer incomplete: GL_FRAMEBUFFER_UNDEFINED");
|
||||||
|
break;
|
||||||
|
case GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
|
||||||
|
E2D_LOG_ERROR("Framebuffer incomplete: GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT");
|
||||||
|
break;
|
||||||
|
case GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
|
||||||
|
E2D_LOG_ERROR("Framebuffer incomplete: GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT");
|
||||||
|
break;
|
||||||
|
#ifndef GL_ES_VERSION_2_0
|
||||||
|
case GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER:
|
||||||
|
E2D_LOG_ERROR("Framebuffer incomplete: GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER");
|
||||||
|
break;
|
||||||
|
case GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER:
|
||||||
|
E2D_LOG_ERROR("Framebuffer incomplete: GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER");
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
|
case GL_FRAMEBUFFER_UNSUPPORTED:
|
||||||
|
E2D_LOG_ERROR("Framebuffer incomplete: GL_FRAMEBUFFER_UNSUPPORTED");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
E2D_LOG_ERROR("Framebuffer incomplete: Unknown error {}", status);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
GLenum GLFramebuffer::getColorAttachment(int index) {
|
||||||
|
return GL_COLOR_ATTACHMENT0 + index;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,222 @@
|
||||||
|
#include <extra2d/graphics/backends/opengl/gl_pipeline.h>
|
||||||
|
#include <extra2d/utils/logger.h>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// GLPipeline 实现
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
GLPipeline::GLPipeline() = default;
|
||||||
|
|
||||||
|
GLPipeline::~GLPipeline() {
|
||||||
|
shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GLPipeline::init(const PipelineDesc& desc) {
|
||||||
|
if (initialized_) {
|
||||||
|
shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
blendMode_ = desc.blendMode;
|
||||||
|
blendEnabled_ = desc.blendEnabled;
|
||||||
|
depthTest_ = desc.depthTest;
|
||||||
|
depthWrite_ = desc.depthWrite;
|
||||||
|
depthFunc_ = desc.depthFunc;
|
||||||
|
cullMode_ = desc.cullMode;
|
||||||
|
|
||||||
|
initialized_ = true;
|
||||||
|
|
||||||
|
E2D_LOG_DEBUG("GLPipeline initialized: blendMode={}, depthTest={}, cullMode={}",
|
||||||
|
static_cast<int>(blendMode_), depthTest_, static_cast<int>(cullMode_));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLPipeline::shutdown() {
|
||||||
|
initialized_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLPipeline::bind() {
|
||||||
|
if (!initialized_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
applyAllStates();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLPipeline::unbind() {
|
||||||
|
// OpenGL 不需要显式解绑管线
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLPipeline::setBlendMode(BlendMode mode) {
|
||||||
|
blendMode_ = mode;
|
||||||
|
applyBlendState();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLPipeline::setDepthTest(bool enabled) {
|
||||||
|
depthTest_ = enabled;
|
||||||
|
applyDepthState();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLPipeline::setDepthWrite(bool enabled) {
|
||||||
|
depthWrite_ = enabled;
|
||||||
|
applyDepthState();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLPipeline::setDepthFunc(DepthFunc func) {
|
||||||
|
depthFunc_ = func;
|
||||||
|
applyDepthState();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLPipeline::setCullMode(CullMode mode) {
|
||||||
|
cullMode_ = mode;
|
||||||
|
applyCullState();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLPipeline::setViewport(int x, int y, int width, int height) {
|
||||||
|
viewportX_ = x;
|
||||||
|
viewportY_ = y;
|
||||||
|
viewportWidth_ = width;
|
||||||
|
viewportHeight_ = height;
|
||||||
|
|
||||||
|
// 检查缓存,避免冗余调用
|
||||||
|
if (x != cachedViewportX_ || y != cachedViewportY_ ||
|
||||||
|
width != cachedViewportWidth_ || height != cachedViewportHeight_) {
|
||||||
|
glViewport(x, y, width, height);
|
||||||
|
cachedViewportX_ = x;
|
||||||
|
cachedViewportY_ = y;
|
||||||
|
cachedViewportWidth_ = width;
|
||||||
|
cachedViewportHeight_ = height;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLPipeline::getViewport(int& x, int& y, int& width, int& height) const {
|
||||||
|
x = viewportX_;
|
||||||
|
y = viewportY_;
|
||||||
|
width = viewportWidth_;
|
||||||
|
height = viewportHeight_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLPipeline::applyAllStates() {
|
||||||
|
applyBlendState();
|
||||||
|
applyDepthState();
|
||||||
|
applyCullState();
|
||||||
|
|
||||||
|
// 应用视口
|
||||||
|
if (viewportWidth_ > 0 && viewportHeight_ > 0) {
|
||||||
|
setViewport(viewportX_, viewportY_, viewportWidth_, viewportHeight_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLPipeline::applyBlendState() {
|
||||||
|
// 检查是否需要启用/禁用混合
|
||||||
|
if (blendEnabled_ != cachedBlendEnabled_) {
|
||||||
|
if (blendEnabled_) {
|
||||||
|
glEnable(GL_BLEND);
|
||||||
|
} else {
|
||||||
|
glDisable(GL_BLEND);
|
||||||
|
}
|
||||||
|
cachedBlendEnabled_ = blendEnabled_;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果禁用了混合,不需要设置混合函数
|
||||||
|
if (!blendEnabled_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查混合模式是否改变
|
||||||
|
if (blendMode_ != cachedBlendMode_) {
|
||||||
|
GLenum srcFactor, dstFactor;
|
||||||
|
getBlendFactors(blendMode_, srcFactor, dstFactor);
|
||||||
|
glBlendFunc(srcFactor, dstFactor);
|
||||||
|
cachedBlendMode_ = blendMode_;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLPipeline::applyDepthState() {
|
||||||
|
// 深度测试
|
||||||
|
if (depthTest_ != cachedDepthTest_) {
|
||||||
|
if (depthTest_) {
|
||||||
|
glEnable(GL_DEPTH_TEST);
|
||||||
|
} else {
|
||||||
|
glDisable(GL_DEPTH_TEST);
|
||||||
|
}
|
||||||
|
cachedDepthTest_ = depthTest_;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 深度写入
|
||||||
|
if (depthWrite_ != cachedDepthWrite_) {
|
||||||
|
glDepthMask(depthWrite_ ? GL_TRUE : GL_FALSE);
|
||||||
|
cachedDepthWrite_ = depthWrite_;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 深度函数
|
||||||
|
if (depthFunc_ != cachedDepthFunc_) {
|
||||||
|
glDepthFunc(convertDepthFunc(depthFunc_));
|
||||||
|
cachedDepthFunc_ = depthFunc_;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLPipeline::applyCullState() {
|
||||||
|
// 检查裁剪模式是否改变
|
||||||
|
if (cullMode_ != cachedCullMode_) {
|
||||||
|
if (cullMode_ == CullMode::None) {
|
||||||
|
glDisable(GL_CULL_FACE);
|
||||||
|
} else {
|
||||||
|
glEnable(GL_CULL_FACE);
|
||||||
|
glCullFace(convertCullMode(cullMode_));
|
||||||
|
}
|
||||||
|
cachedCullMode_ = cullMode_;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLPipeline::getBlendFactors(BlendMode mode, GLenum& srcFactor, GLenum& dstFactor) {
|
||||||
|
switch (mode) {
|
||||||
|
case BlendMode::None:
|
||||||
|
srcFactor = GL_ONE;
|
||||||
|
dstFactor = GL_ZERO;
|
||||||
|
break;
|
||||||
|
case BlendMode::Alpha:
|
||||||
|
srcFactor = GL_SRC_ALPHA;
|
||||||
|
dstFactor = GL_ONE_MINUS_SRC_ALPHA;
|
||||||
|
break;
|
||||||
|
case BlendMode::Additive:
|
||||||
|
srcFactor = GL_SRC_ALPHA;
|
||||||
|
dstFactor = GL_ONE;
|
||||||
|
break;
|
||||||
|
case BlendMode::Multiply:
|
||||||
|
srcFactor = GL_DST_COLOR;
|
||||||
|
dstFactor = GL_ONE_MINUS_SRC_ALPHA;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
srcFactor = GL_SRC_ALPHA;
|
||||||
|
dstFactor = GL_ONE_MINUS_SRC_ALPHA;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GLenum GLPipeline::convertDepthFunc(DepthFunc func) {
|
||||||
|
switch (func) {
|
||||||
|
case DepthFunc::Never: return GL_NEVER;
|
||||||
|
case DepthFunc::Less: return GL_LESS;
|
||||||
|
case DepthFunc::Equal: return GL_EQUAL;
|
||||||
|
case DepthFunc::LessEqual: return GL_LEQUAL;
|
||||||
|
case DepthFunc::Greater: return GL_GREATER;
|
||||||
|
case DepthFunc::NotEqual: return GL_NOTEQUAL;
|
||||||
|
case DepthFunc::GreaterEqual: return GL_GEQUAL;
|
||||||
|
case DepthFunc::Always: return GL_ALWAYS;
|
||||||
|
default: return GL_LESS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GLenum GLPipeline::convertCullMode(CullMode mode) {
|
||||||
|
switch (mode) {
|
||||||
|
case CullMode::Front: return GL_FRONT;
|
||||||
|
case CullMode::Back: return GL_BACK;
|
||||||
|
case CullMode::Both: return GL_FRONT_AND_BACK;
|
||||||
|
default: return GL_BACK;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -1,13 +1,15 @@
|
||||||
#include <SDL.h>
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
#include <extra2d/graphics/backends/opengl/gl_context.h>
|
||||||
|
#include <extra2d/graphics/backends/opengl/gl_font_atlas.h>
|
||||||
|
#include <extra2d/graphics/backends/opengl/gl_framebuffer.h>
|
||||||
|
#include <extra2d/graphics/backends/opengl/gl_renderer.h>
|
||||||
|
#include <extra2d/graphics/backends/opengl/gl_texture.h>
|
||||||
|
#include <extra2d/graphics/batch/sprite_batch.h>
|
||||||
#include <extra2d/graphics/memory/gpu_context.h>
|
#include <extra2d/graphics/memory/gpu_context.h>
|
||||||
#include <extra2d/graphics/opengl/gl_font_atlas.h>
|
|
||||||
#include <extra2d/graphics/opengl/gl_renderer.h>
|
|
||||||
#include <extra2d/graphics/opengl/gl_texture.h>
|
|
||||||
#include <extra2d/graphics/shader/shader_manager.h>
|
|
||||||
#include <extra2d/graphics/memory/vram_manager.h>
|
#include <extra2d/graphics/memory/vram_manager.h>
|
||||||
|
#include <extra2d/graphics/shader/shader_manager.h>
|
||||||
#include <extra2d/platform/iwindow.h>
|
#include <extra2d/platform/iwindow.h>
|
||||||
#include <extra2d/utils/logger.h>
|
#include <extra2d/utils/logger.h>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
@ -17,30 +19,11 @@ namespace extra2d {
|
||||||
// VBO 初始大小(用于 VRAM 跟踪)
|
// VBO 初始大小(用于 VRAM 跟踪)
|
||||||
static constexpr size_t SHAPE_VBO_SIZE = 1024 * sizeof(float);
|
static constexpr size_t SHAPE_VBO_SIZE = 1024 * sizeof(float);
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// BlendMode 查找表 - 编译期构建,运行时 O(1) 查找
|
|
||||||
// ============================================================================
|
|
||||||
struct BlendState {
|
|
||||||
bool enable;
|
|
||||||
GLenum srcFactor;
|
|
||||||
GLenum dstFactor;
|
|
||||||
};
|
|
||||||
|
|
||||||
static constexpr BlendState BLEND_STATES[] = {
|
|
||||||
{false, 0, 0}, // BlendMode::None
|
|
||||||
{true, GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA}, // BlendMode::Alpha
|
|
||||||
{true, GL_SRC_ALPHA, GL_ONE}, // BlendMode::Additive
|
|
||||||
{true, GL_DST_COLOR, GL_ONE_MINUS_SRC_ALPHA} // BlendMode::Multiply
|
|
||||||
};
|
|
||||||
|
|
||||||
static constexpr size_t BLEND_STATE_COUNT =
|
|
||||||
sizeof(BLEND_STATES) / sizeof(BLEND_STATES[0]);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 构造函数,初始化OpenGL渲染器成员变量
|
* @brief 构造函数,初始化OpenGL渲染器成员变量
|
||||||
*/
|
*/
|
||||||
GLRenderer::GLRenderer()
|
GLRenderer::GLRenderer()
|
||||||
: window_(nullptr), shapeVao_(0), shapeVbo_(0), lineVao_(0), lineVbo_(0),
|
: window_(nullptr), shapeVao_(0), lineVao_(0),
|
||||||
vsync_(true), shapeVertexCount_(0), currentShapeMode_(GL_TRIANGLES),
|
vsync_(true), shapeVertexCount_(0), currentShapeMode_(GL_TRIANGLES),
|
||||||
lineVertexCount_(0), currentLineWidth_(1.0f) {
|
lineVertexCount_(0), currentLineWidth_(1.0f) {
|
||||||
resetStats();
|
resetStats();
|
||||||
|
|
@ -65,7 +48,11 @@ GLRenderer::~GLRenderer() { shutdown(); }
|
||||||
bool GLRenderer::init(IWindow *window) {
|
bool GLRenderer::init(IWindow *window) {
|
||||||
window_ = window;
|
window_ = window;
|
||||||
|
|
||||||
// Switch: GL 上下文已通过 SDL2 + EGL 初始化,无需 glewInit()
|
// 初始化 OpenGL 上下文(Switch 平台已通过 SDL2 + EGL 初始化,GLContext 会处理兼容性)
|
||||||
|
if (!GLContext::get().init()) {
|
||||||
|
E2D_LOG_ERROR("Failed to initialize OpenGL context");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
// 初始化精灵批渲染器
|
// 初始化精灵批渲染器
|
||||||
if (!spriteBatch_.init()) {
|
if (!spriteBatch_.init()) {
|
||||||
|
|
@ -76,16 +63,24 @@ bool GLRenderer::init(IWindow *window) {
|
||||||
// 初始化形状渲染
|
// 初始化形状渲染
|
||||||
initShapeRendering();
|
initShapeRendering();
|
||||||
|
|
||||||
// 设置 OpenGL 状态
|
// 初始化管线状态管理
|
||||||
glEnable(GL_BLEND);
|
PipelineDesc pipelineDesc;
|
||||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
pipelineDesc.blendMode = BlendMode::Alpha;
|
||||||
|
pipelineDesc.depthTest = false;
|
||||||
|
pipelineDesc.depthWrite = false;
|
||||||
|
if (!pipeline_.init(pipelineDesc)) {
|
||||||
|
E2D_LOG_ERROR("Failed to initialize GLPipeline");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 应用初始管线状态
|
||||||
|
pipeline_.applyAllStates();
|
||||||
|
|
||||||
// 标记 GPU 上下文为有效
|
// 标记 GPU 上下文为有效
|
||||||
GPUContext::get().markValid();
|
GPUContext::get().markValid();
|
||||||
|
|
||||||
E2D_LOG_INFO("OpenGL Renderer initialized");
|
E2D_LOG_INFO("OpenGL Renderer initialized");
|
||||||
E2D_LOG_INFO("OpenGL Version: {}",
|
E2D_LOG_INFO("OpenGL Version: {}", GLContext::get().getVersionString());
|
||||||
reinterpret_cast<const char *>(glGetString(GL_VERSION)));
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
@ -100,24 +95,22 @@ void GLRenderer::shutdown() {
|
||||||
|
|
||||||
spriteBatch_.shutdown();
|
spriteBatch_.shutdown();
|
||||||
|
|
||||||
if (lineVbo_ != 0) {
|
// 关闭 GLBuffer(自动释放 VBO)
|
||||||
glDeleteBuffers(1, &lineVbo_);
|
lineBuffer_.shutdown();
|
||||||
VRAMMgr::get().freeBuffer(MAX_LINE_VERTICES * sizeof(ShapeVertex));
|
shapeBuffer_.shutdown();
|
||||||
lineVbo_ = 0;
|
|
||||||
}
|
// 删除 VAO(VAO 仍然手动管理)
|
||||||
if (lineVao_ != 0) {
|
if (lineVao_ != 0) {
|
||||||
glDeleteVertexArrays(1, &lineVao_);
|
glDeleteVertexArrays(1, &lineVao_);
|
||||||
lineVao_ = 0;
|
lineVao_ = 0;
|
||||||
}
|
}
|
||||||
if (shapeVbo_ != 0) {
|
|
||||||
glDeleteBuffers(1, &shapeVbo_);
|
|
||||||
VRAMMgr::get().freeBuffer(MAX_SHAPE_VERTICES * sizeof(ShapeVertex));
|
|
||||||
shapeVbo_ = 0;
|
|
||||||
}
|
|
||||||
if (shapeVao_ != 0) {
|
if (shapeVao_ != 0) {
|
||||||
glDeleteVertexArrays(1, &shapeVao_);
|
glDeleteVertexArrays(1, &shapeVao_);
|
||||||
shapeVao_ = 0;
|
shapeVao_ = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 关闭 OpenGL 上下文
|
||||||
|
GLContext::get().shutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -125,6 +118,9 @@ void GLRenderer::shutdown() {
|
||||||
* @param clearColor 清屏颜色
|
* @param clearColor 清屏颜色
|
||||||
*/
|
*/
|
||||||
void GLRenderer::beginFrame(const Color &clearColor) {
|
void GLRenderer::beginFrame(const Color &clearColor) {
|
||||||
|
// 应用管线状态
|
||||||
|
pipeline_.applyAllStates();
|
||||||
|
|
||||||
glClearColor(clearColor.r, clearColor.g, clearColor.b, clearColor.a);
|
glClearColor(clearColor.r, clearColor.g, clearColor.b, clearColor.a);
|
||||||
glClear(GL_COLOR_BUFFER_BIT);
|
glClear(GL_COLOR_BUFFER_BIT);
|
||||||
resetStats();
|
resetStats();
|
||||||
|
|
@ -134,6 +130,10 @@ void GLRenderer::beginFrame(const Color &clearColor) {
|
||||||
* @brief 结束当前帧,刷新所有待处理的渲染批次
|
* @brief 结束当前帧,刷新所有待处理的渲染批次
|
||||||
*/
|
*/
|
||||||
void GLRenderer::endFrame() {
|
void GLRenderer::endFrame() {
|
||||||
|
// 刷新所有待处理的精灵批次(自动批处理)
|
||||||
|
if (autoBatchEnabled_ && batchActive_) {
|
||||||
|
flush();
|
||||||
|
}
|
||||||
// 刷新所有待处理的形状批次
|
// 刷新所有待处理的形状批次
|
||||||
flushShapeBatch();
|
flushShapeBatch();
|
||||||
// 刷新所有待处理的线条批次
|
// 刷新所有待处理的线条批次
|
||||||
|
|
@ -148,7 +148,8 @@ void GLRenderer::endFrame() {
|
||||||
* @param height 视口高度
|
* @param height 视口高度
|
||||||
*/
|
*/
|
||||||
void GLRenderer::setViewport(int x, int y, int width, int height) {
|
void GLRenderer::setViewport(int x, int y, int width, int height) {
|
||||||
glViewport(x, y, width, height);
|
// 使用 GLPipeline 管理视口状态
|
||||||
|
pipeline_.setViewport(x, y, width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -157,8 +158,10 @@ void GLRenderer::setViewport(int x, int y, int width, int height) {
|
||||||
*/
|
*/
|
||||||
void GLRenderer::setVSync(bool enabled) {
|
void GLRenderer::setVSync(bool enabled) {
|
||||||
vsync_ = enabled;
|
vsync_ = enabled;
|
||||||
// 使用 SDL2 设置交换间隔
|
// 通过窗口接口设置垂直同步
|
||||||
SDL_GL_SetSwapInterval(enabled ? 1 : 0);
|
if (window_) {
|
||||||
|
window_->setVSync(enabled);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -166,31 +169,8 @@ void GLRenderer::setVSync(bool enabled) {
|
||||||
* @param mode 混合模式枚举值
|
* @param mode 混合模式枚举值
|
||||||
*/
|
*/
|
||||||
void GLRenderer::setBlendMode(BlendMode mode) {
|
void GLRenderer::setBlendMode(BlendMode mode) {
|
||||||
// 状态缓存检查,避免冗余 GL 调用
|
// 使用 GLPipeline 管理混合状态
|
||||||
if (cachedBlendMode_ == mode) {
|
pipeline_.setBlendMode(mode);
|
||||||
return;
|
|
||||||
}
|
|
||||||
cachedBlendMode_ = mode;
|
|
||||||
|
|
||||||
// 使用查找表替代 switch
|
|
||||||
size_t index = static_cast<size_t>(mode);
|
|
||||||
if (index >= BLEND_STATE_COUNT) {
|
|
||||||
index = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
const BlendState &state = BLEND_STATES[index];
|
|
||||||
if (state.enable) {
|
|
||||||
if (!blendEnabled_) {
|
|
||||||
glEnable(GL_BLEND);
|
|
||||||
blendEnabled_ = true;
|
|
||||||
}
|
|
||||||
glBlendFunc(state.srcFactor, state.dstFactor);
|
|
||||||
} else {
|
|
||||||
if (blendEnabled_) {
|
|
||||||
glDisable(GL_BLEND);
|
|
||||||
blendEnabled_ = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -256,9 +236,45 @@ Ptr<Texture> GLRenderer::loadTexture(const std::string &filepath) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 开始精灵批处理
|
* @brief 确保批处理已激活(自动批处理内部使用)
|
||||||
*/
|
*/
|
||||||
void GLRenderer::beginSpriteBatch() { spriteBatch_.begin(viewProjection_); }
|
void GLRenderer::ensureBatchActive() {
|
||||||
|
if (!batchActive_) {
|
||||||
|
spriteBatch_.begin(viewProjection_);
|
||||||
|
batchActive_ = true;
|
||||||
|
currentBatchTexture_ = nullptr;
|
||||||
|
pendingSprites_.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 提交待处理的精灵(自动批处理内部使用)
|
||||||
|
*/
|
||||||
|
void GLRenderer::submitPendingSprites() {
|
||||||
|
if (pendingSprites_.empty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交所有待处理的精灵
|
||||||
|
spriteBatch_.drawBatch(*currentBatchTexture_, pendingSprites_);
|
||||||
|
pendingSprites_.clear();
|
||||||
|
currentBatchTexture_ = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 开始手动精灵批处理(高级用法)
|
||||||
|
* @note 一般情况下不需要调用,drawSprite/drawText 会自动管理批处理
|
||||||
|
*/
|
||||||
|
void GLRenderer::beginSpriteBatch() {
|
||||||
|
// 如果自动批处理已激活,先提交
|
||||||
|
if (autoBatchEnabled_ && batchActive_) {
|
||||||
|
flush();
|
||||||
|
}
|
||||||
|
// 禁用自动批处理,进入手动模式
|
||||||
|
autoBatchEnabled_ = false;
|
||||||
|
spriteBatch_.begin(viewProjection_);
|
||||||
|
batchActive_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 绘制精灵(带完整参数)
|
* @brief 绘制精灵(带完整参数)
|
||||||
|
|
@ -272,9 +288,21 @@ void GLRenderer::beginSpriteBatch() { spriteBatch_.begin(viewProjection_); }
|
||||||
void GLRenderer::drawSprite(const Texture &texture, const Rect &destRect,
|
void GLRenderer::drawSprite(const Texture &texture, const Rect &destRect,
|
||||||
const Rect &srcRect, const Color &tint,
|
const Rect &srcRect, const Color &tint,
|
||||||
float rotation, const Vec2 &anchor) {
|
float rotation, const Vec2 &anchor) {
|
||||||
GLSpriteBatch::SpriteData data;
|
// 自动批处理模式
|
||||||
data.position = glm::vec2(destRect.origin.x, destRect.origin.y);
|
if (autoBatchEnabled_) {
|
||||||
data.size = glm::vec2(destRect.size.width, destRect.size.height);
|
ensureBatchActive();
|
||||||
|
|
||||||
|
// 如果纹理变化或缓冲区满,先提交当前批次
|
||||||
|
if (currentBatchTexture_ != &texture ||
|
||||||
|
pendingSprites_.size() >= MAX_BATCH_SPRITES) {
|
||||||
|
submitPendingSprites();
|
||||||
|
currentBatchTexture_ = &texture;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建精灵数据
|
||||||
|
SpriteData data;
|
||||||
|
data.position = Vec2(destRect.origin.x, destRect.origin.y);
|
||||||
|
data.size = Vec2(destRect.size.width, destRect.size.height);
|
||||||
|
|
||||||
Texture *tex = const_cast<Texture *>(&texture);
|
Texture *tex = const_cast<Texture *>(&texture);
|
||||||
float texW = static_cast<float>(tex->getWidth());
|
float texW = static_cast<float>(tex->getWidth());
|
||||||
|
|
@ -286,16 +314,40 @@ void GLRenderer::drawSprite(const Texture &texture, const Rect &destRect,
|
||||||
float v1 = srcRect.origin.y / texH;
|
float v1 = srcRect.origin.y / texH;
|
||||||
float v2 = (srcRect.origin.y + srcRect.size.height) / texH;
|
float v2 = (srcRect.origin.y + srcRect.size.height) / texH;
|
||||||
|
|
||||||
data.texCoordMin = glm::vec2(glm::min(u1, u2), glm::min(v1, v2));
|
data.uvRect = Rect(Vec2(glm::min(u1, u2), glm::min(v1, v2)),
|
||||||
data.texCoordMax = glm::vec2(glm::max(u1, u2), glm::max(v1, v2));
|
Size(glm::abs(u2 - u1), glm::abs(v2 - v1)));
|
||||||
|
|
||||||
data.color = glm::vec4(tint.r, tint.g, tint.b, tint.a);
|
data.color = tint;
|
||||||
data.rotation = rotation * 3.14159f / 180.0f;
|
data.rotation = rotation * 3.14159f / 180.0f;
|
||||||
data.anchor = glm::vec2(anchor.x, anchor.y);
|
data.pivot = Vec2(anchor.x, anchor.y);
|
||||||
data.isSDF = false;
|
|
||||||
|
// 添加到待处理列表
|
||||||
|
pendingSprites_.push_back(data);
|
||||||
|
} else {
|
||||||
|
// 手动批处理模式
|
||||||
|
SpriteData data;
|
||||||
|
data.position = Vec2(destRect.origin.x, destRect.origin.y);
|
||||||
|
data.size = Vec2(destRect.size.width, destRect.size.height);
|
||||||
|
|
||||||
|
Texture *tex = const_cast<Texture *>(&texture);
|
||||||
|
float texW = static_cast<float>(tex->getWidth());
|
||||||
|
float texH = static_cast<float>(tex->getHeight());
|
||||||
|
|
||||||
|
float u1 = srcRect.origin.x / texW;
|
||||||
|
float u2 = (srcRect.origin.x + srcRect.size.width) / texW;
|
||||||
|
float v1 = srcRect.origin.y / texH;
|
||||||
|
float v2 = (srcRect.origin.y + srcRect.size.height) / texH;
|
||||||
|
|
||||||
|
data.uvRect = Rect(Vec2(glm::min(u1, u2), glm::min(v1, v2)),
|
||||||
|
Size(glm::abs(u2 - u1), glm::abs(v2 - v1)));
|
||||||
|
|
||||||
|
data.color = tint;
|
||||||
|
data.rotation = rotation * 3.14159f / 180.0f;
|
||||||
|
data.pivot = Vec2(anchor.x, anchor.y);
|
||||||
|
|
||||||
spriteBatch_.draw(texture, data);
|
spriteBatch_.draw(texture, data);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 绘制精灵(简化版本)
|
* @brief 绘制精灵(简化版本)
|
||||||
|
|
@ -313,11 +365,34 @@ void GLRenderer::drawSprite(const Texture &texture, const Vec2 &position,
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 结束精灵批处理并提交绘制
|
* @brief 结束手动精灵批处理并提交绘制
|
||||||
|
* @note 一般情况下不需要调用
|
||||||
*/
|
*/
|
||||||
void GLRenderer::endSpriteBatch() {
|
void GLRenderer::endSpriteBatch() {
|
||||||
|
if (autoBatchEnabled_) {
|
||||||
|
// 自动模式下,只是标记批处理结束
|
||||||
|
flush();
|
||||||
|
} else {
|
||||||
|
// 手动模式下,提交批处理并恢复自动模式
|
||||||
spriteBatch_.end();
|
spriteBatch_.end();
|
||||||
stats_.drawCalls += spriteBatch_.getDrawCallCount();
|
stats_.drawCalls += spriteBatch_.getDrawCallCount();
|
||||||
|
batchActive_ = false;
|
||||||
|
autoBatchEnabled_ = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 立即提交当前批处理
|
||||||
|
* @note 手动控制批处理提交时机,一般情况下不需要调用
|
||||||
|
*/
|
||||||
|
void GLRenderer::flush() {
|
||||||
|
if (autoBatchEnabled_ && batchActive_) {
|
||||||
|
submitPendingSprites();
|
||||||
|
spriteBatch_.end();
|
||||||
|
stats_.drawCalls += spriteBatch_.getDrawCallCount();
|
||||||
|
batchActive_ = false;
|
||||||
|
currentBatchTexture_ = nullptr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -578,13 +653,38 @@ void GLRenderer::drawText(const FontAtlas &font, const std::string &text,
|
||||||
float cursorY = y;
|
float cursorY = y;
|
||||||
float baselineY = cursorY + font.getAscent();
|
float baselineY = cursorY + font.getAscent();
|
||||||
|
|
||||||
|
// 确保批处理已激活(自动批处理)
|
||||||
|
if (autoBatchEnabled_) {
|
||||||
|
ensureBatchActive();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查纹理变化,如果纹理不同则先提交当前批次
|
||||||
|
if (autoBatchEnabled_ && currentBatchTexture_ != nullptr &&
|
||||||
|
currentBatchTexture_ != font.getTexture()) {
|
||||||
|
submitPendingSprites();
|
||||||
|
}
|
||||||
|
if (autoBatchEnabled_) {
|
||||||
|
currentBatchTexture_ = font.getTexture();
|
||||||
|
}
|
||||||
|
|
||||||
// 收集所有字符数据用于批处理
|
// 收集所有字符数据用于批处理
|
||||||
std::vector<GLSpriteBatch::SpriteData> sprites;
|
std::vector<SpriteData> sprites;
|
||||||
sprites.reserve(text.size()); // 预分配空间
|
sprites.reserve(text.size()); // 预分配空间
|
||||||
|
|
||||||
for (char c : text) {
|
for (char c : text) {
|
||||||
char32_t codepoint = static_cast<char32_t>(static_cast<unsigned char>(c));
|
char32_t codepoint = static_cast<char32_t>(static_cast<unsigned char>(c));
|
||||||
if (codepoint == '\n') {
|
if (codepoint == '\n') {
|
||||||
|
// 换行时,将当前行添加到待处理列表
|
||||||
|
if (!sprites.empty()) {
|
||||||
|
if (autoBatchEnabled_) {
|
||||||
|
pendingSprites_.insert(pendingSprites_.end(), sprites.begin(),
|
||||||
|
sprites.end());
|
||||||
|
} else {
|
||||||
|
// 手动模式直接提交
|
||||||
|
spriteBatch_.drawBatch(*font.getTexture(), sprites);
|
||||||
|
}
|
||||||
|
sprites.clear();
|
||||||
|
}
|
||||||
cursorX = x;
|
cursorX = x;
|
||||||
cursorY += font.getLineHeight();
|
cursorY += font.getLineHeight();
|
||||||
baselineY = cursorY + font.getAscent();
|
baselineY = cursorY + font.getAscent();
|
||||||
|
|
@ -596,32 +696,52 @@ void GLRenderer::drawText(const FontAtlas &font, const std::string &text,
|
||||||
float penX = cursorX;
|
float penX = cursorX;
|
||||||
cursorX += glyph->advance;
|
cursorX += glyph->advance;
|
||||||
|
|
||||||
if (glyph->width <= 0.0f || glyph->height <= 0.0f) {
|
// 使用 epsilon 比较浮点数,避免精度问题
|
||||||
|
constexpr float EPSILON = 0.001f;
|
||||||
|
if (glyph->width < EPSILON || glyph->height < EPSILON) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 计算字形位置
|
||||||
|
// bearingX: 水平偏移(从左边缘到字形左边缘)
|
||||||
|
// bearingY: 垂直偏移(从基线到字形顶部,通常为负值)
|
||||||
float xPos = penX + glyph->bearingX;
|
float xPos = penX + glyph->bearingX;
|
||||||
float yPos = baselineY + glyph->bearingY;
|
float yPos = baselineY + glyph->bearingY;
|
||||||
|
|
||||||
GLSpriteBatch::SpriteData data;
|
SpriteData data;
|
||||||
data.position = glm::vec2(xPos, yPos);
|
// 设置精灵中心位置(精灵批处理使用中心点)
|
||||||
data.size = glm::vec2(glyph->width, glyph->height);
|
data.position =
|
||||||
data.texCoordMin = glm::vec2(glyph->u0, glyph->v0);
|
Vec2(xPos + glyph->width * 0.5f, yPos + glyph->height * 0.5f);
|
||||||
data.texCoordMax = glm::vec2(glyph->u1, glyph->v1);
|
data.size = Vec2(glyph->width, glyph->height);
|
||||||
data.color = glm::vec4(color.r, color.g, color.b, color.a);
|
data.uvRect = Rect(Vec2(glyph->u0, glyph->v0),
|
||||||
|
Size(glyph->u1 - glyph->u0, glyph->v1 - glyph->v0));
|
||||||
|
data.color = color;
|
||||||
data.rotation = 0.0f;
|
data.rotation = 0.0f;
|
||||||
data.anchor = glm::vec2(0.0f, 0.0f);
|
// pivot (0.5, 0.5) 表示中心点,这样 position 就是精灵中心
|
||||||
data.isSDF = font.isSDF();
|
data.pivot = Vec2(0.5f, 0.5f);
|
||||||
|
|
||||||
sprites.push_back(data);
|
sprites.push_back(data);
|
||||||
|
|
||||||
|
// 自动批处理:如果缓冲区满,先提交当前批次
|
||||||
|
if (autoBatchEnabled_ && sprites.size() >= MAX_BATCH_SPRITES) {
|
||||||
|
pendingSprites_.insert(pendingSprites_.end(), sprites.begin(),
|
||||||
|
sprites.end());
|
||||||
|
sprites.clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 使用批处理绘制所有字符
|
// 提交剩余的字符
|
||||||
if (!sprites.empty()) {
|
if (!sprites.empty()) {
|
||||||
|
if (autoBatchEnabled_) {
|
||||||
|
pendingSprites_.insert(pendingSprites_.end(), sprites.begin(),
|
||||||
|
sprites.end());
|
||||||
|
} else {
|
||||||
|
// 手动模式下直接提交
|
||||||
spriteBatch_.drawBatch(*font.getTexture(), sprites);
|
spriteBatch_.drawBatch(*font.getTexture(), sprites);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 重置渲染统计信息
|
* @brief 重置渲染统计信息
|
||||||
|
|
@ -641,14 +761,19 @@ void GLRenderer::initShapeRendering() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建形状 VAO 和 VBO
|
// 初始化形状 GLBuffer(使用 Dynamic 使用模式,因为每帧都会更新)
|
||||||
|
BufferDesc shapeBufferDesc;
|
||||||
|
shapeBufferDesc.type = BufferType::Vertex;
|
||||||
|
shapeBufferDesc.usage = BufferUsage::Dynamic;
|
||||||
|
shapeBufferDesc.size = MAX_SHAPE_VERTICES * sizeof(ShapeVertex);
|
||||||
|
if (!shapeBuffer_.init(shapeBufferDesc)) {
|
||||||
|
E2D_LOG_ERROR("Failed to initialize shape buffer");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建形状 VAO(手动管理,用于顶点属性配置)
|
||||||
glGenVertexArrays(1, &shapeVao_);
|
glGenVertexArrays(1, &shapeVao_);
|
||||||
glGenBuffers(1, &shapeVbo_);
|
|
||||||
|
|
||||||
glBindVertexArray(shapeVao_);
|
glBindVertexArray(shapeVao_);
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, shapeVbo_);
|
shapeBuffer_.bind();
|
||||||
glBufferData(GL_ARRAY_BUFFER, MAX_SHAPE_VERTICES * sizeof(ShapeVertex),
|
|
||||||
nullptr, GL_DYNAMIC_DRAW);
|
|
||||||
|
|
||||||
// 位置属性 (location = 0)
|
// 位置属性 (location = 0)
|
||||||
glEnableVertexAttribArray(0);
|
glEnableVertexAttribArray(0);
|
||||||
|
|
@ -662,14 +787,19 @@ void GLRenderer::initShapeRendering() {
|
||||||
|
|
||||||
glBindVertexArray(0);
|
glBindVertexArray(0);
|
||||||
|
|
||||||
// 创建线条专用 VAO 和 VBO
|
// 初始化线条 GLBuffer(使用 Dynamic 使用模式,因为每帧都会更新)
|
||||||
|
BufferDesc lineBufferDesc;
|
||||||
|
lineBufferDesc.type = BufferType::Vertex;
|
||||||
|
lineBufferDesc.usage = BufferUsage::Dynamic;
|
||||||
|
lineBufferDesc.size = MAX_LINE_VERTICES * sizeof(ShapeVertex);
|
||||||
|
if (!lineBuffer_.init(lineBufferDesc)) {
|
||||||
|
E2D_LOG_ERROR("Failed to initialize line buffer");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建线条 VAO(手动管理,用于顶点属性配置)
|
||||||
glGenVertexArrays(1, &lineVao_);
|
glGenVertexArrays(1, &lineVao_);
|
||||||
glGenBuffers(1, &lineVbo_);
|
|
||||||
|
|
||||||
glBindVertexArray(lineVao_);
|
glBindVertexArray(lineVao_);
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, lineVbo_);
|
lineBuffer_.bind();
|
||||||
glBufferData(GL_ARRAY_BUFFER, MAX_LINE_VERTICES * sizeof(ShapeVertex),
|
|
||||||
nullptr, GL_DYNAMIC_DRAW);
|
|
||||||
|
|
||||||
// 位置属性 (location = 0)
|
// 位置属性 (location = 0)
|
||||||
glEnableVertexAttribArray(0);
|
glEnableVertexAttribArray(0);
|
||||||
|
|
@ -682,10 +812,6 @@ void GLRenderer::initShapeRendering() {
|
||||||
reinterpret_cast<void *>(offsetof(ShapeVertex, r)));
|
reinterpret_cast<void *>(offsetof(ShapeVertex, r)));
|
||||||
|
|
||||||
glBindVertexArray(0);
|
glBindVertexArray(0);
|
||||||
|
|
||||||
// VRAM 跟踪
|
|
||||||
VRAMMgr::get().allocBuffer(MAX_SHAPE_VERTICES * sizeof(ShapeVertex));
|
|
||||||
VRAMMgr::get().allocBuffer(MAX_LINE_VERTICES * sizeof(ShapeVertex));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -765,9 +891,9 @@ void GLRenderer::flushShapeBatch() {
|
||||||
shapeShader_->setMat4("u_viewProjection", viewProjection_);
|
shapeShader_->setMat4("u_viewProjection", viewProjection_);
|
||||||
}
|
}
|
||||||
|
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, shapeVbo_);
|
// 使用 GLBuffer::updateData() 更新缓冲区数据
|
||||||
glBufferSubData(GL_ARRAY_BUFFER, 0, shapeVertexCount_ * sizeof(ShapeVertex),
|
shapeBuffer_.updateData(shapeVertexCache_.data(), 0,
|
||||||
shapeVertexCache_.data());
|
shapeVertexCount_ * sizeof(ShapeVertex));
|
||||||
|
|
||||||
glBindVertexArray(shapeVao_);
|
glBindVertexArray(shapeVao_);
|
||||||
glDrawArrays(currentShapeMode_, 0, static_cast<GLsizei>(shapeVertexCount_));
|
glDrawArrays(currentShapeMode_, 0, static_cast<GLsizei>(shapeVertexCount_));
|
||||||
|
|
@ -794,9 +920,9 @@ void GLRenderer::flushLineBatch() {
|
||||||
shapeShader_->setMat4("u_viewProjection", viewProjection_);
|
shapeShader_->setMat4("u_viewProjection", viewProjection_);
|
||||||
}
|
}
|
||||||
|
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, lineVbo_);
|
// 使用 GLBuffer::updateData() 更新缓冲区数据
|
||||||
glBufferSubData(GL_ARRAY_BUFFER, 0, lineVertexCount_ * sizeof(ShapeVertex),
|
lineBuffer_.updateData(lineVertexCache_.data(), 0,
|
||||||
lineVertexCache_.data());
|
lineVertexCount_ * sizeof(ShapeVertex));
|
||||||
|
|
||||||
glBindVertexArray(lineVao_);
|
glBindVertexArray(lineVao_);
|
||||||
glDrawArrays(GL_LINES, 0, static_cast<GLsizei>(lineVertexCount_));
|
glDrawArrays(GL_LINES, 0, static_cast<GLsizei>(lineVertexCount_));
|
||||||
|
|
@ -806,4 +932,90 @@ void GLRenderer::flushLineBatch() {
|
||||||
lineVertexCount_ = 0;
|
lineVertexCount_ = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 创建帧缓冲对象
|
||||||
|
* @param desc 帧缓冲描述
|
||||||
|
* @return 创建的帧缓冲智能指针
|
||||||
|
*/
|
||||||
|
Ptr<GLFramebuffer> GLRenderer::createFramebuffer(const FramebufferDesc& desc) {
|
||||||
|
auto framebuffer = makePtr<GLFramebuffer>();
|
||||||
|
if (!framebuffer->init(desc)) {
|
||||||
|
E2D_LOG_ERROR("Failed to create framebuffer");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
return framebuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 绑定帧缓冲(作为渲染目标)
|
||||||
|
* @param framebuffer 帧缓冲对象指针,传入 nullptr 则绑定默认帧缓冲
|
||||||
|
*/
|
||||||
|
void GLRenderer::bindFramebuffer(GLFramebuffer* framebuffer) {
|
||||||
|
// 先刷新所有待处理的渲染批次
|
||||||
|
flush();
|
||||||
|
flushShapeBatch();
|
||||||
|
flushLineBatch();
|
||||||
|
|
||||||
|
if (framebuffer == nullptr) {
|
||||||
|
// 绑定默认帧缓冲(ID 为 0)
|
||||||
|
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||||
|
currentFramebuffer_ = nullptr;
|
||||||
|
E2D_LOG_TRACE("Bound default framebuffer (0)");
|
||||||
|
} else {
|
||||||
|
// 绑定自定义帧缓冲
|
||||||
|
framebuffer->bind();
|
||||||
|
currentFramebuffer_ = framebuffer;
|
||||||
|
E2D_LOG_TRACE("Bound custom framebuffer (ID: {})", framebuffer->getFboID());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 解绑帧缓冲(恢复到默认帧缓冲)
|
||||||
|
*/
|
||||||
|
void GLRenderer::unbindFramebuffer() {
|
||||||
|
bindFramebuffer(nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取默认帧缓冲
|
||||||
|
* @return 默认帧缓冲智能指针
|
||||||
|
*/
|
||||||
|
Ptr<GLFramebuffer> GLRenderer::getDefaultFramebuffer() const {
|
||||||
|
if (!defaultFramebuffer_) {
|
||||||
|
// 延迟创建默认帧缓冲对象(代表系统默认帧缓冲,ID 为 0)
|
||||||
|
defaultFramebuffer_ = makePtr<GLFramebuffer>();
|
||||||
|
// 注意:默认帧缓冲不需要显式初始化,它的 FBO ID 为 0
|
||||||
|
}
|
||||||
|
return defaultFramebuffer_;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 清除当前绑定的帧缓冲
|
||||||
|
* @param color 清除颜色
|
||||||
|
* @param clearColor 是否清除颜色缓冲
|
||||||
|
* @param clearDepth 是否清除深度缓冲
|
||||||
|
* @param clearStencil 是否清除模板缓冲
|
||||||
|
*/
|
||||||
|
void GLRenderer::clearFramebuffer(const Color& color, bool clearColor,
|
||||||
|
bool clearDepth, bool clearStencil) {
|
||||||
|
GLbitfield mask = 0;
|
||||||
|
|
||||||
|
if (clearColor) {
|
||||||
|
glClearColor(color.r, color.g, color.b, color.a);
|
||||||
|
mask |= GL_COLOR_BUFFER_BIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clearDepth) {
|
||||||
|
mask |= GL_DEPTH_BUFFER_BIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clearStencil) {
|
||||||
|
mask |= GL_STENCIL_BUFFER_BIT;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mask != 0) {
|
||||||
|
glClear(mask);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace extra2d
|
} // namespace extra2d
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
#include <extra2d/graphics/opengl/gl_shader.h>
|
#include <extra2d/graphics/backends/opengl/gl_shader.h>
|
||||||
#include <extra2d/utils/logger.h>
|
#include <extra2d/utils/logger.h>
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
@ -0,0 +1,202 @@
|
||||||
|
#include <extra2d/graphics/backends/opengl/gl_sprite_batch.h>
|
||||||
|
#include <extra2d/graphics/backends/opengl/gl_texture.h>
|
||||||
|
#include <extra2d/graphics/shader/shader_manager.h>
|
||||||
|
#include <extra2d/utils/logger.h>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
GLSpriteBatch::GLSpriteBatch()
|
||||||
|
: vao_(0), currentTexture_(nullptr), drawCallCount_(0) {}
|
||||||
|
|
||||||
|
GLSpriteBatch::~GLSpriteBatch() { shutdown(); }
|
||||||
|
|
||||||
|
bool GLSpriteBatch::init() {
|
||||||
|
// 从ShaderManager获取精灵着色器
|
||||||
|
shader_ = ShaderManager::getInstance().getBuiltin("sprite");
|
||||||
|
if (!shader_) {
|
||||||
|
E2D_LOG_ERROR("Failed to get builtin sprite shader");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建 VAO
|
||||||
|
glGenVertexArrays(1, &vao_);
|
||||||
|
glBindVertexArray(vao_);
|
||||||
|
|
||||||
|
// 初始化 VBO(顶点缓冲区)- 动态使用模式
|
||||||
|
BufferDesc vboDesc;
|
||||||
|
vboDesc.type = BufferType::Vertex;
|
||||||
|
vboDesc.usage = BufferUsage::Dynamic;
|
||||||
|
vboDesc.size = SpriteBatch::MAX_VERTICES * sizeof(SpriteVertex);
|
||||||
|
vboDesc.initialData = nullptr;
|
||||||
|
if (!vbo_.init(vboDesc)) {
|
||||||
|
E2D_LOG_ERROR("Failed to initialize sprite batch VBO");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
vbo_.bind();
|
||||||
|
|
||||||
|
// 设置顶点属性
|
||||||
|
glEnableVertexAttribArray(0);
|
||||||
|
glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, sizeof(SpriteVertex),
|
||||||
|
reinterpret_cast<void *>(offsetof(SpriteVertex, position)));
|
||||||
|
|
||||||
|
glEnableVertexAttribArray(1);
|
||||||
|
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, sizeof(SpriteVertex),
|
||||||
|
reinterpret_cast<void *>(offsetof(SpriteVertex, texCoord)));
|
||||||
|
|
||||||
|
glEnableVertexAttribArray(2);
|
||||||
|
glVertexAttribPointer(2, 4, GL_FLOAT, GL_FALSE, sizeof(SpriteVertex),
|
||||||
|
reinterpret_cast<void *>(offsetof(SpriteVertex, color)));
|
||||||
|
|
||||||
|
// 初始化 EBO(索引缓冲区)- 静态使用模式
|
||||||
|
BufferDesc eboDesc;
|
||||||
|
eboDesc.type = BufferType::Index;
|
||||||
|
eboDesc.usage = BufferUsage::Static;
|
||||||
|
eboDesc.size = batch_.getIndices().size() * sizeof(uint16_t);
|
||||||
|
eboDesc.initialData = batch_.getIndices().data();
|
||||||
|
if (!ebo_.init(eboDesc)) {
|
||||||
|
E2D_LOG_ERROR("Failed to initialize sprite batch EBO");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
ebo_.bind();
|
||||||
|
|
||||||
|
glBindVertexArray(0);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLSpriteBatch::shutdown() {
|
||||||
|
// 使用 GLBuffer::shutdown() 释放缓冲区资源
|
||||||
|
vbo_.shutdown();
|
||||||
|
ebo_.shutdown();
|
||||||
|
|
||||||
|
if (vao_ != 0) {
|
||||||
|
glDeleteVertexArrays(1, &vao_);
|
||||||
|
vao_ = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLSpriteBatch::begin(const glm::mat4 &viewProjection) {
|
||||||
|
batch_.begin(viewProjection);
|
||||||
|
batches_.clear();
|
||||||
|
currentTexture_ = nullptr;
|
||||||
|
drawCallCount_ = 0;
|
||||||
|
// 保存 viewProjection 矩阵供后续使用
|
||||||
|
viewProjection_ = viewProjection;
|
||||||
|
|
||||||
|
// 绑定 VAO 和缓冲区
|
||||||
|
glBindVertexArray(vao_);
|
||||||
|
vbo_.bind();
|
||||||
|
ebo_.bind();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLSpriteBatch::end() {
|
||||||
|
if (batch_.getSpriteCount() > 0) {
|
||||||
|
flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解绑缓冲区
|
||||||
|
vbo_.unbind();
|
||||||
|
ebo_.unbind();
|
||||||
|
glBindVertexArray(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLSpriteBatch::draw(const Texture &texture, const SpriteData &data) {
|
||||||
|
const GLTexture *glTex = dynamic_cast<const GLTexture *>(&texture);
|
||||||
|
if (!glTex) {
|
||||||
|
E2D_LOG_WARN("Invalid texture type for sprite batch");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果纹理改变或批次已满,先提交当前批次
|
||||||
|
if (currentTexture_ != glTex || batch_.needsFlush()) {
|
||||||
|
if (batch_.getSpriteCount() > 0) {
|
||||||
|
submitBatch();
|
||||||
|
}
|
||||||
|
currentTexture_ = glTex;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用 batch 层生成顶点
|
||||||
|
batch_.draw(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLSpriteBatch::drawBatch(const Texture &texture,
|
||||||
|
const std::vector<SpriteData> &sprites) {
|
||||||
|
const GLTexture *glTex = dynamic_cast<const GLTexture *>(&texture);
|
||||||
|
if (!glTex) {
|
||||||
|
E2D_LOG_WARN("Invalid texture type for sprite batch");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 批量处理精灵
|
||||||
|
for (const auto &data : sprites) {
|
||||||
|
// 如果纹理改变或批次已满,先提交当前批次
|
||||||
|
if (currentTexture_ != glTex || batch_.needsFlush()) {
|
||||||
|
if (batch_.getSpriteCount() > 0) {
|
||||||
|
submitBatch();
|
||||||
|
}
|
||||||
|
currentTexture_ = glTex;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用 batch 层生成顶点
|
||||||
|
batch_.draw(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLSpriteBatch::submitBatch() {
|
||||||
|
if (batch_.getSpriteCount() == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 记录批次信息
|
||||||
|
Batch batchInfo;
|
||||||
|
batchInfo.texture = currentTexture_;
|
||||||
|
batchInfo.startVertex = 0; // 每次提交都是新的缓冲区
|
||||||
|
batchInfo.vertexCount = batch_.getSpriteCount() * 4;
|
||||||
|
batches_.push_back(batchInfo);
|
||||||
|
|
||||||
|
// 绑定着色器并设置uniform
|
||||||
|
if (shader_) {
|
||||||
|
shader_->bind();
|
||||||
|
// 设置视图投影矩阵
|
||||||
|
shader_->setMat4("u_viewProjection", viewProjection_);
|
||||||
|
// 设置模型矩阵为单位矩阵(精灵位置已经在顶点生成时计算)
|
||||||
|
glm::mat4 model(1.0f);
|
||||||
|
shader_->setMat4("u_model", model);
|
||||||
|
// 设置纹理采样器
|
||||||
|
shader_->setInt("u_texture", 0);
|
||||||
|
// 设置透明度
|
||||||
|
shader_->setFloat("u_opacity", 1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上传顶点数据 - 使用 orphaning 策略优化动态缓冲区
|
||||||
|
// 通过传入 nullptr 进行 orphaning,告诉驱动器可以丢弃旧缓冲区并分配新内存
|
||||||
|
// 这样可以避免 GPU 等待,提高性能
|
||||||
|
size_t vertexDataSize = batch_.getVertices().size() * sizeof(SpriteVertex);
|
||||||
|
vbo_.setData(nullptr, vertexDataSize); // orphaning
|
||||||
|
vbo_.updateData(batch_.getVertices().data(), 0, vertexDataSize);
|
||||||
|
|
||||||
|
// 绘制
|
||||||
|
currentTexture_->bind(0);
|
||||||
|
|
||||||
|
size_t indexCount = batch_.getSpriteCount() * 6;
|
||||||
|
glDrawElements(GL_TRIANGLES, static_cast<GLsizei>(indexCount),
|
||||||
|
GL_UNSIGNED_SHORT, nullptr);
|
||||||
|
|
||||||
|
drawCallCount_++;
|
||||||
|
|
||||||
|
// 清空 batch 层,准备下一批
|
||||||
|
batch_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLSpriteBatch::flush() {
|
||||||
|
// 提交最后的批次
|
||||||
|
if (batch_.getSpriteCount() > 0) {
|
||||||
|
submitBatch();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置状态
|
||||||
|
batches_.clear();
|
||||||
|
currentTexture_ = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
#include <extra2d/graphics/opengl/gl_texture.h>
|
#include <extra2d/graphics/backends/opengl/gl_texture.h>
|
||||||
#include <extra2d/graphics/memory/gpu_context.h>
|
#include <extra2d/graphics/memory/gpu_context.h>
|
||||||
#include <extra2d/graphics/memory/vram_manager.h>
|
#include <extra2d/graphics/memory/vram_manager.h>
|
||||||
#define STB_IMAGE_IMPLEMENTATION
|
#define STB_IMAGE_IMPLEMENTATION
|
||||||
|
|
@ -0,0 +1,150 @@
|
||||||
|
#include <extra2d/graphics/backends/vulkan/vk_renderer.h>
|
||||||
|
#include <extra2d/utils/logger.h>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
VulkanRenderer::VulkanRenderer() = default;
|
||||||
|
|
||||||
|
VulkanRenderer::~VulkanRenderer() {
|
||||||
|
shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool VulkanRenderer::init(IWindow* window) {
|
||||||
|
E2D_LOG_WARN("Vulkan renderer is not fully implemented yet");
|
||||||
|
initialized_ = true;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanRenderer::shutdown() {
|
||||||
|
initialized_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanRenderer::beginFrame(const Color &clearColor) {
|
||||||
|
// TODO: 实现Vulkan帧开始
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanRenderer::endFrame() {
|
||||||
|
// TODO: 实现Vulkan帧结束
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanRenderer::setViewport(int x, int y, int width, int height) {
|
||||||
|
// TODO: 实现视口设置
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanRenderer::setVSync(bool enabled) {
|
||||||
|
// TODO: 实现垂直同步设置
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanRenderer::setBlendMode(BlendMode mode) {
|
||||||
|
// TODO: 实现混合模式设置
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanRenderer::setViewProjection(const glm::mat4 &matrix) {
|
||||||
|
// TODO: 实现视图投影矩阵设置
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanRenderer::pushTransform(const glm::mat4 &transform) {
|
||||||
|
// TODO: 实现变换矩阵入栈
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanRenderer::popTransform() {
|
||||||
|
// TODO: 实现变换矩阵出栈
|
||||||
|
}
|
||||||
|
|
||||||
|
glm::mat4 VulkanRenderer::getCurrentTransform() const {
|
||||||
|
return glm::mat4(1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ptr<Texture> VulkanRenderer::createTexture(int width, int height, const uint8_t *pixels, int channels) {
|
||||||
|
// TODO: 实现Vulkan纹理创建
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ptr<Texture> VulkanRenderer::loadTexture(const std::string &filepath) {
|
||||||
|
// TODO: 实现Vulkan纹理加载
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanRenderer::beginSpriteBatch() {
|
||||||
|
// TODO: 实现精灵批处理开始
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanRenderer::drawSprite(const Texture &texture, const Rect &destRect,
|
||||||
|
const Rect &srcRect, const Color &tint, float rotation,
|
||||||
|
const Vec2 &anchor) {
|
||||||
|
// TODO: 实现精灵绘制
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanRenderer::drawSprite(const Texture &texture, const Vec2 &position,
|
||||||
|
const Color &tint) {
|
||||||
|
// TODO: 实现简化精灵绘制
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanRenderer::endSpriteBatch() {
|
||||||
|
// TODO: 实现精灵批处理结束
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanRenderer::drawLine(const Vec2 &start, const Vec2 &end, const Color &color,
|
||||||
|
float width) {
|
||||||
|
// TODO: 实现线条绘制
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanRenderer::drawRect(const Rect &rect, const Color &color, float width) {
|
||||||
|
// TODO: 实现矩形边框绘制
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanRenderer::fillRect(const Rect &rect, const Color &color) {
|
||||||
|
// TODO: 实现矩形填充
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanRenderer::drawCircle(const Vec2 ¢er, float radius, const Color &color,
|
||||||
|
int segments, float width) {
|
||||||
|
// TODO: 实现圆形边框绘制
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanRenderer::fillCircle(const Vec2 ¢er, float radius, const Color &color,
|
||||||
|
int segments) {
|
||||||
|
// TODO: 实现圆形填充
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanRenderer::drawTriangle(const Vec2 &p1, const Vec2 &p2, const Vec2 &p3,
|
||||||
|
const Color &color, float width) {
|
||||||
|
// TODO: 实现三角形边框绘制
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanRenderer::fillTriangle(const Vec2 &p1, const Vec2 &p2, const Vec2 &p3,
|
||||||
|
const Color &color) {
|
||||||
|
// TODO: 实现三角形填充
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanRenderer::drawPolygon(const std::vector<Vec2> &points, const Color &color,
|
||||||
|
float width) {
|
||||||
|
// TODO: 实现多边形边框绘制
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanRenderer::fillPolygon(const std::vector<Vec2> &points,
|
||||||
|
const Color &color) {
|
||||||
|
// TODO: 实现多边形填充
|
||||||
|
}
|
||||||
|
|
||||||
|
Ptr<FontAtlas> VulkanRenderer::createFontAtlas(const std::string &filepath, int fontSize,
|
||||||
|
bool useSDF) {
|
||||||
|
// TODO: 实现字体图集创建
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanRenderer::drawText(const FontAtlas &font, const std::string &text,
|
||||||
|
const Vec2 &position, const Color &color) {
|
||||||
|
// TODO: 实现文本绘制
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanRenderer::drawText(const FontAtlas &font, const std::string &text, float x,
|
||||||
|
float y, const Color &color) {
|
||||||
|
// TODO: 实现文本绘制
|
||||||
|
}
|
||||||
|
|
||||||
|
void VulkanRenderer::resetStats() {
|
||||||
|
stats_ = Stats{};
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,72 @@
|
||||||
|
#include <extra2d/graphics/batch/shape_batch.h>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// ShapeBatch 基础实现(后端无关部分)
|
||||||
|
// ============================================================================
|
||||||
|
|
||||||
|
// 这里可以添加后端无关的工具函数
|
||||||
|
// 例如:计算圆形的顶点、三角化多边形等
|
||||||
|
|
||||||
|
// 计算圆形顶点
|
||||||
|
void calculateCircleVertices(std::vector<Vec2>& outVertices,
|
||||||
|
const Vec2& center, float radius,
|
||||||
|
int segments, bool fill) {
|
||||||
|
outVertices.clear();
|
||||||
|
outVertices.reserve(fill ? segments + 1 : segments);
|
||||||
|
|
||||||
|
if (fill) {
|
||||||
|
// 填充圆形:中心点 + 边缘点
|
||||||
|
outVertices.push_back(center);
|
||||||
|
for (int i = 0; i <= segments; ++i) {
|
||||||
|
float angle = 2.0f * 3.14159265359f * static_cast<float>(i) / static_cast<float>(segments);
|
||||||
|
outVertices.emplace_back(
|
||||||
|
center.x + radius * cosf(angle),
|
||||||
|
center.y + radius * sinf(angle)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 圆形边框:只保留边缘点
|
||||||
|
for (int i = 0; i < segments; ++i) {
|
||||||
|
float angle = 2.0f * 3.14159265359f * static_cast<float>(i) / static_cast<float>(segments);
|
||||||
|
outVertices.emplace_back(
|
||||||
|
center.x + radius * cosf(angle),
|
||||||
|
center.y + radius * sinf(angle)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算矩形顶点
|
||||||
|
void calculateRectVertices(std::vector<Vec2>& outVertices, const Rect& rect) {
|
||||||
|
outVertices.clear();
|
||||||
|
outVertices.reserve(4);
|
||||||
|
|
||||||
|
float x1 = rect.origin.x;
|
||||||
|
float y1 = rect.origin.y;
|
||||||
|
float x2 = rect.origin.x + rect.size.width;
|
||||||
|
float y2 = rect.origin.y + rect.size.height;
|
||||||
|
|
||||||
|
outVertices.emplace_back(x1, y1); // 左上
|
||||||
|
outVertices.emplace_back(x2, y1); // 右上
|
||||||
|
outVertices.emplace_back(x2, y2); // 右下
|
||||||
|
outVertices.emplace_back(x1, y2); // 左下
|
||||||
|
}
|
||||||
|
|
||||||
|
// 简单的多边形三角化(扇形三角化,适用于凸多边形)
|
||||||
|
void triangulatePolygon(std::vector<uint16_t>& outIndices, int vertexCount) {
|
||||||
|
outIndices.clear();
|
||||||
|
if (vertexCount < 3) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 扇形三角化:以第一个顶点为扇形中心
|
||||||
|
for (int i = 1; i < vertexCount - 1; ++i) {
|
||||||
|
outIndices.push_back(0);
|
||||||
|
outIndices.push_back(i);
|
||||||
|
outIndices.push_back(i + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,205 @@
|
||||||
|
#include <extra2d/graphics/batch/sprite_batch.h>
|
||||||
|
#include <extra2d/utils/logger.h>
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// TrigLookup 实现 - 三角函数查表
|
||||||
|
// ============================================================================
|
||||||
|
TrigLookup::TrigLookup() {
|
||||||
|
constexpr float PI = 3.14159265359f;
|
||||||
|
constexpr float DEG2RAD = PI / 180.0f;
|
||||||
|
|
||||||
|
for (int i = 0; i < TABLE_SIZE; ++i) {
|
||||||
|
float angle = static_cast<float>(i) * (360.0f / TABLE_SIZE) * DEG2RAD;
|
||||||
|
sinTable_[i] = std::sin(angle);
|
||||||
|
cosTable_[i] = std::cos(angle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float TrigLookup::sin(int angle) const {
|
||||||
|
// 规范化角度到 0-360
|
||||||
|
angle = ((angle % 360) + 360) % 360;
|
||||||
|
return sinTable_[angle * 4];
|
||||||
|
}
|
||||||
|
|
||||||
|
float TrigLookup::cos(int angle) const {
|
||||||
|
// 规范化角度到 0-360
|
||||||
|
angle = ((angle % 360) + 360) % 360;
|
||||||
|
return cosTable_[angle * 4];
|
||||||
|
}
|
||||||
|
|
||||||
|
float TrigLookup::sinRad(float rad) const {
|
||||||
|
constexpr float RAD2DEG = 180.0f / 3.14159265359f;
|
||||||
|
int angle = static_cast<int>(rad * RAD2DEG);
|
||||||
|
return sin(angle);
|
||||||
|
}
|
||||||
|
|
||||||
|
float TrigLookup::cosRad(float rad) const {
|
||||||
|
constexpr float RAD2DEG = 180.0f / 3.14159265359f;
|
||||||
|
int angle = static_cast<int>(rad * RAD2DEG);
|
||||||
|
return cos(angle);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// SpriteBatch 实现
|
||||||
|
// ============================================================================
|
||||||
|
SpriteBatch::SpriteBatch()
|
||||||
|
: spriteCount_(0)
|
||||||
|
, vpDirty_(true) {
|
||||||
|
// 预分配顶点缓冲区
|
||||||
|
vertices_.reserve(MAX_VERTICES);
|
||||||
|
indices_.reserve(MAX_INDICES);
|
||||||
|
|
||||||
|
// 生成静态索引缓冲区
|
||||||
|
generateIndices();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpriteBatch::generateIndices() {
|
||||||
|
indices_.clear();
|
||||||
|
for (size_t i = 0; i < MAX_SPRITES; ++i) {
|
||||||
|
uint16_t base = static_cast<uint16_t>(i * 4);
|
||||||
|
// 两个三角形: (0,1,2) 和 (0,2,3)
|
||||||
|
indices_.push_back(base + 0);
|
||||||
|
indices_.push_back(base + 1);
|
||||||
|
indices_.push_back(base + 2);
|
||||||
|
indices_.push_back(base + 0);
|
||||||
|
indices_.push_back(base + 2);
|
||||||
|
indices_.push_back(base + 3);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpriteBatch::begin(const glm::mat4& viewProjection) {
|
||||||
|
viewProjection_ = viewProjection;
|
||||||
|
vpDirty_ = true;
|
||||||
|
spriteCount_ = 0;
|
||||||
|
vertices_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpriteBatch::end() {
|
||||||
|
// 批次结束,数据已准备好供后端使用
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpriteBatch::draw(const SpriteData& sprite) {
|
||||||
|
if (spriteCount_ >= MAX_SPRITES) {
|
||||||
|
// 缓冲区已满,需要刷新
|
||||||
|
flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
generateVertices(sprite, spriteCount_ * VERTICES_PER_SPRITE);
|
||||||
|
spriteCount_++;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpriteBatch::drawBatch(const std::vector<SpriteData>& sprites) {
|
||||||
|
size_t index = 0;
|
||||||
|
while (index < sprites.size()) {
|
||||||
|
// 计算剩余空间
|
||||||
|
size_t remainingSpace = MAX_SPRITES - spriteCount_;
|
||||||
|
size_t batchSize = std::min(remainingSpace, sprites.size() - index);
|
||||||
|
|
||||||
|
// 批量生成顶点
|
||||||
|
for (size_t i = 0; i < batchSize; ++i) {
|
||||||
|
generateVertices(sprites[index + i], spriteCount_ * VERTICES_PER_SPRITE);
|
||||||
|
spriteCount_++;
|
||||||
|
}
|
||||||
|
|
||||||
|
index += batchSize;
|
||||||
|
|
||||||
|
// 如果缓冲区已满,需要刷新(由后端处理)
|
||||||
|
if (spriteCount_ >= MAX_SPRITES && index < sprites.size()) {
|
||||||
|
// 通知后端刷新,然后继续
|
||||||
|
// 注意:这里只是准备数据,实际 GPU 提交由后端决定
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpriteBatch::drawImmediate(const SpriteData& sprite) {
|
||||||
|
// 立即绘制模式:清空当前批次,只绘制这一个精灵
|
||||||
|
clear();
|
||||||
|
draw(sprite);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpriteBatch::generateVertices(const SpriteData& sprite, size_t vertexOffset) {
|
||||||
|
// 确保顶点缓冲区足够
|
||||||
|
if (vertices_.size() < vertexOffset + VERTICES_PER_SPRITE) {
|
||||||
|
vertices_.resize(vertexOffset + VERTICES_PER_SPRITE);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算旋转(使用查表)
|
||||||
|
float c = 1.0f;
|
||||||
|
float s = 0.0f;
|
||||||
|
if (sprite.rotation != 0.0f) {
|
||||||
|
c = trigLookup_.cosRad(sprite.rotation);
|
||||||
|
s = trigLookup_.sinRad(sprite.rotation);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算精灵的四个角(相对于中心点)
|
||||||
|
float halfWidth = sprite.size.x * 0.5f;
|
||||||
|
float halfHeight = sprite.size.y * 0.5f;
|
||||||
|
|
||||||
|
// 考虑 pivot 偏移
|
||||||
|
float pivotOffsetX = (sprite.pivot.x - 0.5f) * sprite.size.x;
|
||||||
|
float pivotOffsetY = (sprite.pivot.y - 0.5f) * sprite.size.y;
|
||||||
|
|
||||||
|
// 四个角的本地坐标
|
||||||
|
Vec2 localCorners[4] = {
|
||||||
|
Vec2(-halfWidth - pivotOffsetX, -halfHeight - pivotOffsetY), // 左下
|
||||||
|
Vec2( halfWidth - pivotOffsetX, -halfHeight - pivotOffsetY), // 右下
|
||||||
|
Vec2( halfWidth - pivotOffsetX, halfHeight - pivotOffsetY), // 右上
|
||||||
|
Vec2(-halfWidth - pivotOffsetX, halfHeight - pivotOffsetY) // 左上
|
||||||
|
};
|
||||||
|
|
||||||
|
// 应用旋转和平移
|
||||||
|
Vec2 worldPos = sprite.position;
|
||||||
|
|
||||||
|
for (int i = 0; i < 4; ++i) {
|
||||||
|
Vec2 rotated;
|
||||||
|
if (sprite.rotation != 0.0f) {
|
||||||
|
rotated.x = localCorners[i].x * c - localCorners[i].y * s;
|
||||||
|
rotated.y = localCorners[i].x * s + localCorners[i].y * c;
|
||||||
|
} else {
|
||||||
|
rotated = localCorners[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
vertices_[vertexOffset + i].position = worldPos + rotated;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置纹理坐标
|
||||||
|
// uvRect.origin = (u0, v0) - 左下
|
||||||
|
// uvRect.size = (width, height) - 从左上到右下的尺寸
|
||||||
|
float u0 = sprite.uvRect.origin.x;
|
||||||
|
float v0 = sprite.uvRect.origin.y;
|
||||||
|
float u1 = u0 + sprite.uvRect.size.width;
|
||||||
|
float v1 = v0 + sprite.uvRect.size.height;
|
||||||
|
|
||||||
|
// 顶点顺序: 左下, 右下, 右上, 左上
|
||||||
|
// 注意: 在 gl_font_atlas 中 v0 > v1 (因为翻转了V坐标)
|
||||||
|
// 所以 v0 对应底部,v1 对应顶部
|
||||||
|
vertices_[vertexOffset + 0].texCoord = Vec2(u0, v0); // 左下
|
||||||
|
vertices_[vertexOffset + 1].texCoord = Vec2(u1, v0); // 右下
|
||||||
|
vertices_[vertexOffset + 2].texCoord = Vec2(u1, v1); // 右上
|
||||||
|
vertices_[vertexOffset + 3].texCoord = Vec2(u0, v1); // 左上
|
||||||
|
|
||||||
|
// 设置颜色
|
||||||
|
for (int i = 0; i < 4; ++i) {
|
||||||
|
vertices_[vertexOffset + i].color = sprite.color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpriteBatch::flush() {
|
||||||
|
// 标记需要刷新 - 实际刷新由后端处理
|
||||||
|
// 这里只是重置计数器,让后端知道需要提交当前批次
|
||||||
|
spriteCount_ = 0;
|
||||||
|
vertices_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SpriteBatch::clear() {
|
||||||
|
spriteCount_ = 0;
|
||||||
|
vertices_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
#include <extra2d/graphics/opengl/gl_renderer.h>
|
#include <extra2d/graphics/backends/opengl/gl_renderer.h>
|
||||||
#include <extra2d/graphics/core/render_backend.h>
|
#include <extra2d/graphics/core/render_backend.h>
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,19 @@
|
||||||
#include <extra2d/graphics/core/render_module.h>
|
#include <extra2d/graphics/core/render_module.h>
|
||||||
#include <extra2d/graphics/opengl/gl_renderer.h>
|
#include <extra2d/graphics/backends/opengl/gl_renderer.h>
|
||||||
#include <extra2d/graphics/opengl/gl_shader.h>
|
#include <extra2d/graphics/backends/opengl/gl_shader.h>
|
||||||
#include <extra2d/graphics/shader/shader_manager.h>
|
#include <extra2d/graphics/shader/shader_manager.h>
|
||||||
#include <extra2d/core/registry.h>
|
#include <extra2d/core/registry.h>
|
||||||
#include <extra2d/core/service_locator.h>
|
#include <extra2d/core/service_locator.h>
|
||||||
#include <extra2d/app/application.h>
|
#include <extra2d/app/application.h>
|
||||||
#include <extra2d/platform/window_module.h>
|
#include <extra2d/platform/window_module.h>
|
||||||
#include <extra2d/utils/logger.h>
|
#include <extra2d/utils/logger.h>
|
||||||
#include <SDL.h>
|
#include <filesystem>
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
||||||
RenderModule::RenderModule(const Cfg& cfg) : cfg_(cfg) {}
|
RenderModule::RenderModule(std::function<void(RenderCfg&)> configFn) {
|
||||||
|
configFn(cfg_);
|
||||||
|
}
|
||||||
|
|
||||||
RenderModule::~RenderModule() {
|
RenderModule::~RenderModule() {
|
||||||
if (initialized_) {
|
if (initialized_) {
|
||||||
|
|
@ -20,14 +22,13 @@ RenderModule::~RenderModule() {
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::string getExecutableDir() {
|
static std::string getExecutableDir() {
|
||||||
char* basePath = SDL_GetBasePath();
|
try {
|
||||||
if (basePath) {
|
auto currentPath = std::filesystem::current_path();
|
||||||
std::string path(basePath);
|
return currentPath.string() + "/";
|
||||||
SDL_free(basePath);
|
} catch (...) {
|
||||||
return path;
|
|
||||||
}
|
|
||||||
return "./";
|
return "./";
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool RenderModule::init() {
|
bool RenderModule::init() {
|
||||||
if (initialized_) return true;
|
if (initialized_) return true;
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
#include <glad/glad.h>
|
#include <glad/glad.h>
|
||||||
#include <extra2d/graphics/opengl/gl_texture.h>
|
#include <extra2d/graphics/backends/opengl/gl_texture.h>
|
||||||
#include <extra2d/graphics/core/render_target.h>
|
#include <extra2d/graphics/core/render_target.h>
|
||||||
#include <extra2d/utils/logger.h>
|
#include <extra2d/utils/logger.h>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,428 +0,0 @@
|
||||||
#include <cstring>
|
|
||||||
#include <extra2d/graphics/opengl/gl_sprite_batch.h>
|
|
||||||
#include <extra2d/utils/logger.h>
|
|
||||||
#include <glm/gtc/matrix_transform.hpp>
|
|
||||||
|
|
||||||
namespace extra2d {
|
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// 三角函数查表 - 避免每帧重复计算 sin/cos
|
|
||||||
// ============================================================================
|
|
||||||
/**
|
|
||||||
* @brief 三角函数查表类,避免每帧重复计算sin/cos
|
|
||||||
*/
|
|
||||||
class TrigLookup {
|
|
||||||
public:
|
|
||||||
static constexpr size_t TABLE_SIZE = 360 * 4; // 0.25度精度
|
|
||||||
static constexpr float INDEX_SCALE = 4.0f; // 每度4个采样点
|
|
||||||
static constexpr float RAD_TO_INDEX = INDEX_SCALE * 180.0f / 3.14159265359f;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 查表获取sin值
|
|
||||||
* @param radians 弧度值
|
|
||||||
* @return sin值
|
|
||||||
*/
|
|
||||||
static float sinRad(float radians) {
|
|
||||||
return table_.sinTable[normalizeIndexRad(radians)];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 查表获取cos值
|
|
||||||
* @param radians 弧度值
|
|
||||||
* @return cos值
|
|
||||||
*/
|
|
||||||
static float cosRad(float radians) {
|
|
||||||
return table_.cosTable[normalizeIndexRad(radians)];
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
struct Tables {
|
|
||||||
std::array<float, TABLE_SIZE> sinTable;
|
|
||||||
std::array<float, TABLE_SIZE> cosTable;
|
|
||||||
|
|
||||||
Tables() {
|
|
||||||
for (size_t i = 0; i < TABLE_SIZE; ++i) {
|
|
||||||
float angle = static_cast<float>(i) / INDEX_SCALE * 3.14159265359f / 180.0f;
|
|
||||||
sinTable[i] = std::sin(angle);
|
|
||||||
cosTable[i] = std::cos(angle);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
static size_t normalizeIndexRad(float radians) {
|
|
||||||
int idx = static_cast<int>(radians * RAD_TO_INDEX) % static_cast<int>(TABLE_SIZE);
|
|
||||||
if (idx < 0) {
|
|
||||||
idx += static_cast<int>(TABLE_SIZE);
|
|
||||||
}
|
|
||||||
return static_cast<size_t>(idx);
|
|
||||||
}
|
|
||||||
|
|
||||||
static const Tables table_;
|
|
||||||
};
|
|
||||||
|
|
||||||
const TrigLookup::Tables TrigLookup::table_;
|
|
||||||
|
|
||||||
// 静态索引生成函数
|
|
||||||
/**
|
|
||||||
* @brief 获取静态索引数组,用于精灵绘制
|
|
||||||
* @return 索引数组的常量引用
|
|
||||||
*/
|
|
||||||
static const std::array<GLuint, GLSpriteBatch::MAX_INDICES>& getIndices() {
|
|
||||||
static std::array<GLuint, GLSpriteBatch::MAX_INDICES> indices = []() {
|
|
||||||
std::array<GLuint, GLSpriteBatch::MAX_INDICES> arr{};
|
|
||||||
for (size_t i = 0; i < GLSpriteBatch::MAX_SPRITES; ++i) {
|
|
||||||
GLuint base = static_cast<GLuint>(i * GLSpriteBatch::VERTICES_PER_SPRITE);
|
|
||||||
arr[i * GLSpriteBatch::INDICES_PER_SPRITE + 0] = base + 0;
|
|
||||||
arr[i * GLSpriteBatch::INDICES_PER_SPRITE + 1] = base + 1;
|
|
||||||
arr[i * GLSpriteBatch::INDICES_PER_SPRITE + 2] = base + 2;
|
|
||||||
arr[i * GLSpriteBatch::INDICES_PER_SPRITE + 3] = base + 0;
|
|
||||||
arr[i * GLSpriteBatch::INDICES_PER_SPRITE + 4] = base + 2;
|
|
||||||
arr[i * GLSpriteBatch::INDICES_PER_SPRITE + 5] = base + 3;
|
|
||||||
}
|
|
||||||
return arr;
|
|
||||||
}();
|
|
||||||
return indices;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 顶点着色器 (GLES 3.2)
|
|
||||||
static const char *SPRITE_VERTEX_SHADER = R"(
|
|
||||||
#version 300 es
|
|
||||||
precision highp float;
|
|
||||||
layout(location = 0) in vec2 aPosition;
|
|
||||||
layout(location = 1) in vec2 aTexCoord;
|
|
||||||
layout(location = 2) in vec4 aColor;
|
|
||||||
|
|
||||||
uniform mat4 uViewProjection;
|
|
||||||
|
|
||||||
out vec2 vTexCoord;
|
|
||||||
out vec4 vColor;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
gl_Position = uViewProjection * vec4(aPosition, 0.0, 1.0);
|
|
||||||
vTexCoord = aTexCoord;
|
|
||||||
vColor = aColor;
|
|
||||||
}
|
|
||||||
)";
|
|
||||||
|
|
||||||
// 片段着色器 (GLES 3.2)
|
|
||||||
// SDF 常量硬编码:ONEDGE_VALUE=128/255=0.502, PIXEL_DIST_SCALE=255/64=3.98
|
|
||||||
// SDF 值存储在 Alpha 通道
|
|
||||||
static const char *SPRITE_FRAGMENT_SHADER = R"(
|
|
||||||
#version 300 es
|
|
||||||
precision highp float;
|
|
||||||
in vec2 vTexCoord;
|
|
||||||
in vec4 vColor;
|
|
||||||
|
|
||||||
uniform sampler2D uTexture;
|
|
||||||
uniform int uUseSDF;
|
|
||||||
|
|
||||||
out vec4 fragColor;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
if (uUseSDF == 1) {
|
|
||||||
float dist = texture(uTexture, vTexCoord).a;
|
|
||||||
float sd = (dist - 0.502) * 3.98;
|
|
||||||
float w = fwidth(sd);
|
|
||||||
float alpha = smoothstep(-w, w, sd);
|
|
||||||
fragColor = vec4(vColor.rgb, vColor.a * alpha);
|
|
||||||
} else {
|
|
||||||
fragColor = texture(uTexture, vTexCoord) * vColor;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 构造函数,初始化精灵批处理器成员变量
|
|
||||||
*/
|
|
||||||
GLSpriteBatch::GLSpriteBatch()
|
|
||||||
: vao_(0), vbo_(0), ibo_(0), vertexCount_(0), currentTexture_(nullptr),
|
|
||||||
currentIsSDF_(false), drawCallCount_(0), spriteCount_(0), batchCount_(0) {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 析构函数,调用shutdown释放资源
|
|
||||||
*/
|
|
||||||
GLSpriteBatch::~GLSpriteBatch() { shutdown(); }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 初始化精灵批处理器,创建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<SpriteData> &sprites) {
|
|
||||||
if (sprites.empty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果当前有未提交的批次且纹理不同,先刷新
|
|
||||||
if (currentTexture_ != nullptr && currentTexture_ != &texture) {
|
|
||||||
flush();
|
|
||||||
}
|
|
||||||
|
|
||||||
currentTexture_ = &texture;
|
|
||||||
currentIsSDF_ = sprites[0].isSDF; // 假设批量中的精灵 SDF 状态一致
|
|
||||||
|
|
||||||
// 分批处理,避免超过缓冲区大小
|
|
||||||
size_t index = 0;
|
|
||||||
while (index < sprites.size()) {
|
|
||||||
size_t remainingSpace = (MAX_VERTICES - vertexCount_) / VERTICES_PER_SPRITE;
|
|
||||||
size_t batchSize = std::min(sprites.size() - index, remainingSpace);
|
|
||||||
|
|
||||||
for (size_t i = 0; i < batchSize; ++i) {
|
|
||||||
addVertices(sprites[index + i]);
|
|
||||||
spriteCount_++;
|
|
||||||
}
|
|
||||||
|
|
||||||
index += batchSize;
|
|
||||||
|
|
||||||
// 如果还有更多精灵,刷新当前批次
|
|
||||||
if (index < sprites.size()) {
|
|
||||||
flush();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
batchCount_++;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 立即绘制精灵,不缓存
|
|
||||||
* @param texture 纹理引用
|
|
||||||
* @param data 精灵数据
|
|
||||||
*/
|
|
||||||
void GLSpriteBatch::drawImmediate(const Texture &texture,
|
|
||||||
const SpriteData &data) {
|
|
||||||
// 立即绘制,不缓存 - 用于需要立即显示的情况
|
|
||||||
flush(); // 先提交当前批次
|
|
||||||
|
|
||||||
currentTexture_ = &texture;
|
|
||||||
currentIsSDF_ = data.isSDF;
|
|
||||||
addVertices(data);
|
|
||||||
spriteCount_++;
|
|
||||||
|
|
||||||
flush(); // 立即提交
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 结束批处理,提交所有待绘制的精灵
|
|
||||||
*/
|
|
||||||
void GLSpriteBatch::end() {
|
|
||||||
if (vertexCount_ > 0) {
|
|
||||||
flush();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief 刷新批次,执行实际的OpenGL绘制调用
|
|
||||||
*/
|
|
||||||
void GLSpriteBatch::flush() {
|
|
||||||
if (vertexCount_ == 0 || currentTexture_ == nullptr) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 绑定纹理
|
|
||||||
GLuint texID = static_cast<GLuint>(
|
|
||||||
reinterpret_cast<uintptr_t>(currentTexture_->getNativeHandle()));
|
|
||||||
glActiveTexture(GL_TEXTURE0);
|
|
||||||
glBindTexture(GL_TEXTURE_2D, texID);
|
|
||||||
|
|
||||||
// 使用着色器
|
|
||||||
shader_.bind();
|
|
||||||
shader_.setMat4("uViewProjection", viewProjection_);
|
|
||||||
shader_.setInt("uTexture", 0);
|
|
||||||
shader_.setInt("uUseSDF", currentIsSDF_ ? 1 : 0);
|
|
||||||
// SDF 常量已硬编码到着色器中
|
|
||||||
|
|
||||||
// 更新 VBO 数据 - 只更新实际使用的部分
|
|
||||||
glBindBuffer(GL_ARRAY_BUFFER, vbo_);
|
|
||||||
glBufferSubData(GL_ARRAY_BUFFER, 0, vertexCount_ * sizeof(Vertex),
|
|
||||||
vertexBuffer_.data());
|
|
||||||
|
|
||||||
// 绘制
|
|
||||||
glBindVertexArray(vao_);
|
|
||||||
GLsizei indexCount = static_cast<GLsizei>(
|
|
||||||
(vertexCount_ / VERTICES_PER_SPRITE) * INDICES_PER_SPRITE);
|
|
||||||
glDrawElements(GL_TRIANGLES, indexCount, GL_UNSIGNED_INT, nullptr);
|
|
||||||
|
|
||||||
drawCallCount_++;
|
|
||||||
batchCount_++;
|
|
||||||
|
|
||||||
// 重置状态
|
|
||||||
vertexCount_ = 0;
|
|
||||||
currentTexture_ = nullptr;
|
|
||||||
currentIsSDF_ = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace extra2d
|
|
||||||
|
|
@ -1,6 +1,10 @@
|
||||||
#include <extra2d/graphics/shader/shader_manager.h>
|
#include <extra2d/graphics/shader/shader_manager.h>
|
||||||
#include <extra2d/utils/logger.h>
|
#include <extra2d/utils/logger.h>
|
||||||
|
|
||||||
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
|
namespace nl = nlohmann;
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -20,13 +24,18 @@ ShaderManager& ShaderManager::getInstance() {
|
||||||
* @return 初始化成功返回true,失败返回false
|
* @return 初始化成功返回true,失败返回false
|
||||||
*/
|
*/
|
||||||
bool ShaderManager::init(Ptr<IShaderFactory> factory, const std::string& appName) {
|
bool ShaderManager::init(Ptr<IShaderFactory> factory, const std::string& appName) {
|
||||||
std::string shaderDir = PlatformDetector::getShaderPath(appName);
|
// 使用相对路径作为Shader目录
|
||||||
std::string cacheDir = PlatformDetector::getShaderCachePath(appName);
|
std::string shaderDir = "shaders/";
|
||||||
|
std::string cacheDir = "cache/shaders/";
|
||||||
|
|
||||||
hotReloadSupported_ = PlatformDetector::supportsHotReload();
|
// 非Switch平台支持热重载
|
||||||
|
#ifndef __SWITCH__
|
||||||
|
hotReloadSupported_ = true;
|
||||||
|
#else
|
||||||
|
hotReloadSupported_ = false;
|
||||||
|
#endif
|
||||||
|
|
||||||
E2D_LOG_INFO("Platform: {} (HotReload: {})",
|
E2D_LOG_INFO("ShaderManager init (HotReload: {})",
|
||||||
PlatformDetector::platformName(),
|
|
||||||
hotReloadSupported_ ? "supported" : "not supported");
|
hotReloadSupported_ ? "supported" : "not supported");
|
||||||
|
|
||||||
return init(shaderDir, cacheDir, factory);
|
return init(shaderDir, cacheDir, factory);
|
||||||
|
|
@ -56,7 +65,12 @@ bool ShaderManager::init(const std::string& shaderDir,
|
||||||
cacheDir_ = cacheDir;
|
cacheDir_ = cacheDir;
|
||||||
factory_ = factory;
|
factory_ = factory;
|
||||||
|
|
||||||
hotReloadSupported_ = PlatformDetector::supportsHotReload();
|
// 非Switch平台支持热重载
|
||||||
|
#ifndef __SWITCH__
|
||||||
|
hotReloadSupported_ = true;
|
||||||
|
#else
|
||||||
|
hotReloadSupported_ = false;
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifdef __SWITCH__
|
#ifdef __SWITCH__
|
||||||
if (!ShaderCache::getInstance().init(cacheDir_)) {
|
if (!ShaderCache::getInstance().init(cacheDir_)) {
|
||||||
|
|
@ -440,10 +454,76 @@ Ptr<IShader> ShaderManager::getBuiltin(const std::string& name) {
|
||||||
return shader;
|
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";
|
std::string path = shaderDir_ + "builtin/" + name + ".shader";
|
||||||
return loadFromCombinedFile(path);
|
return loadFromCombinedFile(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 从JSON元数据文件加载Shader(多后端支持)
|
||||||
|
* @param jsonPath JSON元数据文件路径
|
||||||
|
* @param name Shader名称
|
||||||
|
* @return 加载的Shader实例
|
||||||
|
*/
|
||||||
|
Ptr<IShader> ShaderManager::loadFromMetadata(const std::string& jsonPath, const std::string& name) {
|
||||||
|
if (!initialized_) {
|
||||||
|
E2D_LOG_ERROR("ShaderManager not initialized");
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查是否已加载
|
||||||
|
auto it = shaders_.find(name);
|
||||||
|
if (it != shaders_.end()) {
|
||||||
|
return it->second.shader;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 读取JSON文件
|
||||||
|
std::string jsonContent = loader_.readFile(jsonPath);
|
||||||
|
if (jsonContent.empty()) {
|
||||||
|
E2D_LOG_ERROR("Failed to read shader metadata: {}", jsonPath);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 使用nlohmann/json解析
|
||||||
|
nl::json j = nl::json::parse(jsonContent);
|
||||||
|
|
||||||
|
// 获取OpenGL后端路径
|
||||||
|
if (!j.contains("backends") || !j["backends"].contains("opengl")) {
|
||||||
|
E2D_LOG_ERROR("No OpenGL backend found in shader metadata: {}", jsonPath);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto& opengl = j["backends"]["opengl"];
|
||||||
|
if (!opengl.contains("vertex") || !opengl.contains("fragment")) {
|
||||||
|
E2D_LOG_ERROR("Missing vertex or fragment path in shader metadata: {}", jsonPath);
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string vertRelativePath = opengl["vertex"].get<std::string>();
|
||||||
|
std::string fragRelativePath = opengl["fragment"].get<std::string>();
|
||||||
|
|
||||||
|
// 构建完整路径
|
||||||
|
std::string vertPath = shaderDir_ + vertRelativePath;
|
||||||
|
std::string fragPath = shaderDir_ + fragRelativePath;
|
||||||
|
|
||||||
|
E2D_LOG_DEBUG("Loading shader from metadata: {} -> vert: {}, frag: {}", name, vertPath, fragPath);
|
||||||
|
|
||||||
|
// 使用分离文件加载
|
||||||
|
return loadFromFiles(name, vertPath, fragPath);
|
||||||
|
|
||||||
|
} catch (const nl::json::exception& e) {
|
||||||
|
E2D_LOG_ERROR("Failed to parse shader metadata {}: {}", jsonPath, e.what());
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief 加载所有内置Shader
|
* @brief 加载所有内置Shader
|
||||||
* @return 加载成功返回true,失败返回false
|
* @return 加载成功返回true,失败返回false
|
||||||
|
|
@ -465,17 +545,27 @@ bool ShaderManager::loadBuiltinShaders() {
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const char* name : builtinNames) {
|
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;
|
std::string shaderName = std::string("builtin_") + name;
|
||||||
|
|
||||||
if (!loadFromCombinedFile(path)) {
|
Ptr<IShader> shader = nullptr;
|
||||||
E2D_LOG_ERROR("Failed to load builtin {} shader from: {}", name, path);
|
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;
|
allSuccess = false;
|
||||||
} else {
|
} else {
|
||||||
|
// 同时注册带 builtin_ 前缀的名称
|
||||||
auto it = shaders_.find(name);
|
auto it = shaders_.find(name);
|
||||||
if (it != shaders_.end()) {
|
if (it != shaders_.end()) {
|
||||||
shaders_[shaderName] = it->second;
|
shaders_[shaderName] = it->second;
|
||||||
shaders_.erase(it);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
#include "glfw_window.h"
|
||||||
|
#include "glfw_input.h"
|
||||||
|
#include <extra2d/platform/backend_factory.h>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
static bool s_glfwBackendRegistered = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void initGLFWBackend() {
|
||||||
|
if (s_glfwBackendRegistered) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
s_glfwBackendRegistered = true;
|
||||||
|
|
||||||
|
BackendFactory::reg(
|
||||||
|
"glfw",
|
||||||
|
[]() -> UniquePtr<IWindow> {
|
||||||
|
return makeUnique<GLFWWindow>();
|
||||||
|
},
|
||||||
|
[]() -> UniquePtr<IInput> {
|
||||||
|
return makeUnique<GLFWInput>();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,426 @@
|
||||||
|
#include "glfw_input.h"
|
||||||
|
#include <extra2d/utils/logger.h>
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
// GLFW 按键到引擎按键的映射
|
||||||
|
static Key glfwToKey(int glfwKey) {
|
||||||
|
switch (glfwKey) {
|
||||||
|
// 字母键
|
||||||
|
case GLFW_KEY_A: return Key::A;
|
||||||
|
case GLFW_KEY_B: return Key::B;
|
||||||
|
case GLFW_KEY_C: return Key::C;
|
||||||
|
case GLFW_KEY_D: return Key::D;
|
||||||
|
case GLFW_KEY_E: return Key::E;
|
||||||
|
case GLFW_KEY_F: return Key::F;
|
||||||
|
case GLFW_KEY_G: return Key::G;
|
||||||
|
case GLFW_KEY_H: return Key::H;
|
||||||
|
case GLFW_KEY_I: return Key::I;
|
||||||
|
case GLFW_KEY_J: return Key::J;
|
||||||
|
case GLFW_KEY_K: return Key::K;
|
||||||
|
case GLFW_KEY_L: return Key::L;
|
||||||
|
case GLFW_KEY_M: return Key::M;
|
||||||
|
case GLFW_KEY_N: return Key::N;
|
||||||
|
case GLFW_KEY_O: return Key::O;
|
||||||
|
case GLFW_KEY_P: return Key::P;
|
||||||
|
case GLFW_KEY_Q: return Key::Q;
|
||||||
|
case GLFW_KEY_R: return Key::R;
|
||||||
|
case GLFW_KEY_S: return Key::S;
|
||||||
|
case GLFW_KEY_T: return Key::T;
|
||||||
|
case GLFW_KEY_U: return Key::U;
|
||||||
|
case GLFW_KEY_V: return Key::V;
|
||||||
|
case GLFW_KEY_W: return Key::W;
|
||||||
|
case GLFW_KEY_X: return Key::X;
|
||||||
|
case GLFW_KEY_Y: return Key::Y;
|
||||||
|
case GLFW_KEY_Z: return Key::Z;
|
||||||
|
|
||||||
|
// 数字键
|
||||||
|
case GLFW_KEY_0: return Key::Num0;
|
||||||
|
case GLFW_KEY_1: return Key::Num1;
|
||||||
|
case GLFW_KEY_2: return Key::Num2;
|
||||||
|
case GLFW_KEY_3: return Key::Num3;
|
||||||
|
case GLFW_KEY_4: return Key::Num4;
|
||||||
|
case GLFW_KEY_5: return Key::Num5;
|
||||||
|
case GLFW_KEY_6: return Key::Num6;
|
||||||
|
case GLFW_KEY_7: return Key::Num7;
|
||||||
|
case GLFW_KEY_8: return Key::Num8;
|
||||||
|
case GLFW_KEY_9: return Key::Num9;
|
||||||
|
|
||||||
|
// 功能键
|
||||||
|
case GLFW_KEY_F1: return Key::F1;
|
||||||
|
case GLFW_KEY_F2: return Key::F2;
|
||||||
|
case GLFW_KEY_F3: return Key::F3;
|
||||||
|
case GLFW_KEY_F4: return Key::F4;
|
||||||
|
case GLFW_KEY_F5: return Key::F5;
|
||||||
|
case GLFW_KEY_F6: return Key::F6;
|
||||||
|
case GLFW_KEY_F7: return Key::F7;
|
||||||
|
case GLFW_KEY_F8: return Key::F8;
|
||||||
|
case GLFW_KEY_F9: return Key::F9;
|
||||||
|
case GLFW_KEY_F10: return Key::F10;
|
||||||
|
case GLFW_KEY_F11: return Key::F11;
|
||||||
|
case GLFW_KEY_F12: return Key::F12;
|
||||||
|
|
||||||
|
// 特殊键
|
||||||
|
case GLFW_KEY_SPACE: return Key::Space;
|
||||||
|
case GLFW_KEY_ENTER: return Key::Enter;
|
||||||
|
case GLFW_KEY_ESCAPE: return Key::Escape;
|
||||||
|
case GLFW_KEY_TAB: return Key::Tab;
|
||||||
|
case GLFW_KEY_BACKSPACE: return Key::Backspace;
|
||||||
|
case GLFW_KEY_INSERT: return Key::Insert;
|
||||||
|
case GLFW_KEY_DELETE: return Key::Delete;
|
||||||
|
case GLFW_KEY_HOME: return Key::Home;
|
||||||
|
case GLFW_KEY_END: return Key::End;
|
||||||
|
case GLFW_KEY_PAGE_UP: return Key::PageUp;
|
||||||
|
case GLFW_KEY_PAGE_DOWN: return Key::PageDown;
|
||||||
|
|
||||||
|
// 方向键
|
||||||
|
case GLFW_KEY_UP: return Key::Up;
|
||||||
|
case GLFW_KEY_DOWN: return Key::Down;
|
||||||
|
case GLFW_KEY_LEFT: return Key::Left;
|
||||||
|
case GLFW_KEY_RIGHT: return Key::Right;
|
||||||
|
|
||||||
|
// 修饰键
|
||||||
|
case GLFW_KEY_LEFT_SHIFT: return Key::LShift;
|
||||||
|
case GLFW_KEY_RIGHT_SHIFT: return Key::RShift;
|
||||||
|
case GLFW_KEY_LEFT_CONTROL: return Key::LCtrl;
|
||||||
|
case GLFW_KEY_RIGHT_CONTROL: return Key::RCtrl;
|
||||||
|
case GLFW_KEY_LEFT_ALT: return Key::LAlt;
|
||||||
|
case GLFW_KEY_RIGHT_ALT: return Key::RAlt;
|
||||||
|
|
||||||
|
// 锁定键
|
||||||
|
case GLFW_KEY_CAPS_LOCK: return Key::CapsLock;
|
||||||
|
case GLFW_KEY_NUM_LOCK: return Key::NumLock;
|
||||||
|
case GLFW_KEY_SCROLL_LOCK: return Key::ScrollLock;
|
||||||
|
|
||||||
|
default: return Key::None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
GLFWInput::GLFWInput() {
|
||||||
|
keyCurrent_.fill(false);
|
||||||
|
keyPrevious_.fill(false);
|
||||||
|
mouseCurrent_.fill(false);
|
||||||
|
mousePrevious_.fill(false);
|
||||||
|
gamepadCurrent_.fill(false);
|
||||||
|
gamepadPrevious_.fill(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
GLFWInput::~GLFWInput() {
|
||||||
|
shutdown();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLFWInput::init() {
|
||||||
|
E2D_LOG_INFO("GLFWInput initialized");
|
||||||
|
openGamepad();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLFWInput::shutdown() {
|
||||||
|
closeGamepad();
|
||||||
|
E2D_LOG_INFO("GLFWInput shutdown");
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLFWInput::update() {
|
||||||
|
// 保存上一帧状态
|
||||||
|
keyPrevious_ = keyCurrent_;
|
||||||
|
mousePrevious_ = mouseCurrent_;
|
||||||
|
gamepadPrevious_ = gamepadCurrent_;
|
||||||
|
|
||||||
|
// 重置增量
|
||||||
|
scrollDelta_ = 0.0f;
|
||||||
|
mouseDelta_ = Vec2{0.0f, 0.0f};
|
||||||
|
|
||||||
|
// 更新游戏手柄
|
||||||
|
updateGamepad();
|
||||||
|
|
||||||
|
// 更新键盘状态(通过轮询 GLFW)
|
||||||
|
if (window_) {
|
||||||
|
for (int i = GLFW_KEY_SPACE; i <= GLFW_KEY_LAST; ++i) {
|
||||||
|
Key key = glfwToKey(i);
|
||||||
|
if (key != Key::None) {
|
||||||
|
int state = glfwGetKey(window_, i);
|
||||||
|
keyCurrent_[static_cast<size_t>(key)] = (state == GLFW_PRESS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新鼠标按钮状态
|
||||||
|
for (int i = 0; i < static_cast<int>(Mouse::Count); ++i) {
|
||||||
|
int glfwButton = GLFW_MOUSE_BUTTON_1 + i;
|
||||||
|
if (glfwButton <= GLFW_MOUSE_BUTTON_LAST) {
|
||||||
|
int state = glfwGetMouseButton(window_, glfwButton);
|
||||||
|
mouseCurrent_[i] = (state == GLFW_PRESS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取鼠标位置
|
||||||
|
double x, y;
|
||||||
|
glfwGetCursorPos(window_, &x, &y);
|
||||||
|
mousePos_ = Vec2{static_cast<float>(x), static_cast<float>(y)};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GLFWInput::down(Key key) const {
|
||||||
|
size_t idx = static_cast<size_t>(key);
|
||||||
|
if (idx < keyCurrent_.size()) {
|
||||||
|
return keyCurrent_[idx];
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GLFWInput::pressed(Key key) const {
|
||||||
|
size_t idx = static_cast<size_t>(key);
|
||||||
|
if (idx < keyCurrent_.size()) {
|
||||||
|
return keyCurrent_[idx] && !keyPrevious_[idx];
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GLFWInput::released(Key key) const {
|
||||||
|
size_t idx = static_cast<size_t>(key);
|
||||||
|
if (idx < keyCurrent_.size()) {
|
||||||
|
return !keyCurrent_[idx] && keyPrevious_[idx];
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GLFWInput::down(Mouse btn) const {
|
||||||
|
size_t idx = static_cast<size_t>(btn);
|
||||||
|
if (idx < mouseCurrent_.size()) {
|
||||||
|
return mouseCurrent_[idx];
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GLFWInput::pressed(Mouse btn) const {
|
||||||
|
size_t idx = static_cast<size_t>(btn);
|
||||||
|
if (idx < mouseCurrent_.size()) {
|
||||||
|
return mouseCurrent_[idx] && !mousePrevious_[idx];
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GLFWInput::released(Mouse btn) const {
|
||||||
|
size_t idx = static_cast<size_t>(btn);
|
||||||
|
if (idx < mouseCurrent_.size()) {
|
||||||
|
return !mouseCurrent_[idx] && mousePrevious_[idx];
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec2 GLFWInput::mouse() const {
|
||||||
|
return mousePos_;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec2 GLFWInput::mouseDelta() const {
|
||||||
|
return mouseDelta_;
|
||||||
|
}
|
||||||
|
|
||||||
|
float GLFWInput::scroll() const {
|
||||||
|
return scroll_;
|
||||||
|
}
|
||||||
|
|
||||||
|
float GLFWInput::scrollDelta() const {
|
||||||
|
return scrollDelta_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLFWInput::setMouse(const Vec2& pos) {
|
||||||
|
if (window_) {
|
||||||
|
glfwSetCursorPos(window_, pos.x, pos.y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GLFWInput::gamepad() const {
|
||||||
|
return gamepadId_ != -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GLFWInput::down(Gamepad btn) const {
|
||||||
|
size_t idx = static_cast<size_t>(btn);
|
||||||
|
if (idx < gamepadCurrent_.size()) {
|
||||||
|
return gamepadCurrent_[idx];
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GLFWInput::pressed(Gamepad btn) const {
|
||||||
|
size_t idx = static_cast<size_t>(btn);
|
||||||
|
if (idx < gamepadCurrent_.size()) {
|
||||||
|
return gamepadCurrent_[idx] && !gamepadPrevious_[idx];
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GLFWInput::released(Gamepad btn) const {
|
||||||
|
size_t idx = static_cast<size_t>(btn);
|
||||||
|
if (idx < gamepadCurrent_.size()) {
|
||||||
|
return !gamepadCurrent_[idx] && gamepadPrevious_[idx];
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec2 GLFWInput::leftStick() const {
|
||||||
|
return leftStick_;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec2 GLFWInput::rightStick() const {
|
||||||
|
return rightStick_;
|
||||||
|
}
|
||||||
|
|
||||||
|
float GLFWInput::leftTrigger() const {
|
||||||
|
return leftTrigger_;
|
||||||
|
}
|
||||||
|
|
||||||
|
float GLFWInput::rightTrigger() const {
|
||||||
|
return rightTrigger_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLFWInput::vibrate(float left, float right) {
|
||||||
|
// GLFW 本身不支持震动,需要平台特定的代码
|
||||||
|
// 这里可以扩展为使用平台特定的 API
|
||||||
|
(void)left;
|
||||||
|
(void)right;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GLFWInput::touching() const {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
int GLFWInput::touchCount() const {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec2 GLFWInput::touch(int index) const {
|
||||||
|
(void)index;
|
||||||
|
return Vec2{0.0f, 0.0f};
|
||||||
|
}
|
||||||
|
|
||||||
|
TouchPoint GLFWInput::touchPoint(int index) const {
|
||||||
|
(void)index;
|
||||||
|
return TouchPoint{};
|
||||||
|
}
|
||||||
|
|
||||||
|
// 事件处理函数
|
||||||
|
void GLFWInput::handleKeyEvent(int key, int scancode, int action, int mods) {
|
||||||
|
(void)scancode;
|
||||||
|
(void)mods;
|
||||||
|
|
||||||
|
Key eKey = glfwToKey(key);
|
||||||
|
if (eKey != Key::None) {
|
||||||
|
size_t idx = static_cast<size_t>(eKey);
|
||||||
|
if (action == GLFW_PRESS) {
|
||||||
|
keyCurrent_[idx] = true;
|
||||||
|
} else if (action == GLFW_RELEASE) {
|
||||||
|
keyCurrent_[idx] = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLFWInput::handleMouseButtonEvent(int button, int action, int mods) {
|
||||||
|
(void)mods;
|
||||||
|
|
||||||
|
if (button >= GLFW_MOUSE_BUTTON_1 && button <= GLFW_MOUSE_BUTTON_LAST) {
|
||||||
|
size_t idx = static_cast<size_t>(button - GLFW_MOUSE_BUTTON_1);
|
||||||
|
if (idx < mouseCurrent_.size()) {
|
||||||
|
if (action == GLFW_PRESS) {
|
||||||
|
mouseCurrent_[idx] = true;
|
||||||
|
} else if (action == GLFW_RELEASE) {
|
||||||
|
mouseCurrent_[idx] = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLFWInput::handleCursorPosEvent(double xpos, double ypos) {
|
||||||
|
Vec2 newPos{static_cast<float>(xpos), static_cast<float>(ypos)};
|
||||||
|
mouseDelta_ = newPos - mousePos_;
|
||||||
|
mousePos_ = newPos;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLFWInput::handleScrollEvent(double xoffset, double yoffset) {
|
||||||
|
(void)xoffset;
|
||||||
|
scroll_ += static_cast<float>(yoffset);
|
||||||
|
scrollDelta_ += static_cast<float>(yoffset);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLFWInput::handleJoystickEvent(int jid, int event) {
|
||||||
|
if (event == GLFW_CONNECTED) {
|
||||||
|
E2D_LOG_INFO("Gamepad connected: {}", jid);
|
||||||
|
if (gamepadId_ == -1) {
|
||||||
|
openGamepad();
|
||||||
|
}
|
||||||
|
} else if (event == GLFW_DISCONNECTED) {
|
||||||
|
if (jid == gamepadId_) {
|
||||||
|
E2D_LOG_INFO("Gamepad disconnected: {}", jid);
|
||||||
|
closeGamepad();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLFWInput::updateGamepad() {
|
||||||
|
if (gamepadId_ == -1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
GLFWgamepadstate state;
|
||||||
|
if (!glfwGetGamepadState(gamepadId_, &state)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 更新按钮状态
|
||||||
|
gamepadCurrent_[static_cast<size_t>(Gamepad::A)] = state.buttons[GLFW_GAMEPAD_BUTTON_A] == GLFW_PRESS;
|
||||||
|
gamepadCurrent_[static_cast<size_t>(Gamepad::B)] = state.buttons[GLFW_GAMEPAD_BUTTON_B] == GLFW_PRESS;
|
||||||
|
gamepadCurrent_[static_cast<size_t>(Gamepad::X)] = state.buttons[GLFW_GAMEPAD_BUTTON_X] == GLFW_PRESS;
|
||||||
|
gamepadCurrent_[static_cast<size_t>(Gamepad::Y)] = state.buttons[GLFW_GAMEPAD_BUTTON_Y] == GLFW_PRESS;
|
||||||
|
gamepadCurrent_[static_cast<size_t>(Gamepad::Back)] = state.buttons[GLFW_GAMEPAD_BUTTON_BACK] == GLFW_PRESS;
|
||||||
|
gamepadCurrent_[static_cast<size_t>(Gamepad::Start)] = state.buttons[GLFW_GAMEPAD_BUTTON_START] == GLFW_PRESS;
|
||||||
|
gamepadCurrent_[static_cast<size_t>(Gamepad::LStick)] = state.buttons[GLFW_GAMEPAD_BUTTON_LEFT_THUMB] == GLFW_PRESS;
|
||||||
|
gamepadCurrent_[static_cast<size_t>(Gamepad::RStick)] = state.buttons[GLFW_GAMEPAD_BUTTON_RIGHT_THUMB] == GLFW_PRESS;
|
||||||
|
gamepadCurrent_[static_cast<size_t>(Gamepad::LB)] = state.buttons[GLFW_GAMEPAD_BUTTON_LEFT_BUMPER] == GLFW_PRESS;
|
||||||
|
gamepadCurrent_[static_cast<size_t>(Gamepad::RB)] = state.buttons[GLFW_GAMEPAD_BUTTON_RIGHT_BUMPER] == GLFW_PRESS;
|
||||||
|
gamepadCurrent_[static_cast<size_t>(Gamepad::DUp)] = state.buttons[GLFW_GAMEPAD_BUTTON_DPAD_UP] == GLFW_PRESS;
|
||||||
|
gamepadCurrent_[static_cast<size_t>(Gamepad::DDown)] = state.buttons[GLFW_GAMEPAD_BUTTON_DPAD_DOWN] == GLFW_PRESS;
|
||||||
|
gamepadCurrent_[static_cast<size_t>(Gamepad::DLeft)] = state.buttons[GLFW_GAMEPAD_BUTTON_DPAD_LEFT] == GLFW_PRESS;
|
||||||
|
gamepadCurrent_[static_cast<size_t>(Gamepad::DRight)] = state.buttons[GLFW_GAMEPAD_BUTTON_DPAD_RIGHT] == GLFW_PRESS;
|
||||||
|
gamepadCurrent_[static_cast<size_t>(Gamepad::Guide)] = state.buttons[GLFW_GAMEPAD_BUTTON_GUIDE] == GLFW_PRESS;
|
||||||
|
|
||||||
|
// 更新摇杆值(应用死区)
|
||||||
|
leftStick_.x = applyDeadzone(state.axes[GLFW_GAMEPAD_AXIS_LEFT_X]);
|
||||||
|
leftStick_.y = applyDeadzone(state.axes[GLFW_GAMEPAD_AXIS_LEFT_Y]);
|
||||||
|
rightStick_.x = applyDeadzone(state.axes[GLFW_GAMEPAD_AXIS_RIGHT_X]);
|
||||||
|
rightStick_.y = applyDeadzone(state.axes[GLFW_GAMEPAD_AXIS_RIGHT_Y]);
|
||||||
|
|
||||||
|
// 更新扳机值(范围 [0, 1])
|
||||||
|
leftTrigger_ = (state.axes[GLFW_GAMEPAD_AXIS_LEFT_TRIGGER] + 1.0f) * 0.5f;
|
||||||
|
rightTrigger_ = (state.axes[GLFW_GAMEPAD_AXIS_RIGHT_TRIGGER] + 1.0f) * 0.5f;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLFWInput::openGamepad() {
|
||||||
|
for (int jid = GLFW_JOYSTICK_1; jid <= GLFW_JOYSTICK_LAST; ++jid) {
|
||||||
|
if (glfwJoystickPresent(jid) && glfwJoystickIsGamepad(jid)) {
|
||||||
|
gamepadId_ = jid;
|
||||||
|
E2D_LOG_INFO("Gamepad opened: {}", glfwGetGamepadName(jid));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLFWInput::closeGamepad() {
|
||||||
|
if (gamepadId_ != -1) {
|
||||||
|
gamepadId_ = -1;
|
||||||
|
gamepadCurrent_.fill(false);
|
||||||
|
gamepadPrevious_.fill(false);
|
||||||
|
leftStick_ = Vec2{0.0f, 0.0f};
|
||||||
|
rightStick_ = Vec2{0.0f, 0.0f};
|
||||||
|
leftTrigger_ = 0.0f;
|
||||||
|
rightTrigger_ = 0.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
float GLFWInput::applyDeadzone(float value) const {
|
||||||
|
if (std::abs(value) < deadzone_) {
|
||||||
|
return 0.0f;
|
||||||
|
}
|
||||||
|
float sign = value >= 0.0f ? 1.0f : -1.0f;
|
||||||
|
return sign * (std::abs(value) - deadzone_) / (1.0f - deadzone_);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,95 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/platform/iinput.h>
|
||||||
|
#include <GLFW/glfw3.h>
|
||||||
|
#include <array>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief GLFW 输入实现
|
||||||
|
*/
|
||||||
|
class GLFWInput : public IInput {
|
||||||
|
public:
|
||||||
|
GLFWInput();
|
||||||
|
~GLFWInput() override;
|
||||||
|
|
||||||
|
void init() override;
|
||||||
|
void shutdown() override;
|
||||||
|
void update() override;
|
||||||
|
|
||||||
|
// Keyboard
|
||||||
|
bool down(Key key) const override;
|
||||||
|
bool pressed(Key key) const override;
|
||||||
|
bool released(Key key) const override;
|
||||||
|
|
||||||
|
// Mouse
|
||||||
|
bool down(Mouse btn) const override;
|
||||||
|
bool pressed(Mouse btn) const override;
|
||||||
|
bool released(Mouse btn) const override;
|
||||||
|
Vec2 mouse() const override;
|
||||||
|
Vec2 mouseDelta() const override;
|
||||||
|
float scroll() const override;
|
||||||
|
float scrollDelta() const override;
|
||||||
|
void setMouse(const Vec2& pos) override;
|
||||||
|
|
||||||
|
// Gamepad
|
||||||
|
bool gamepad() const override;
|
||||||
|
bool down(Gamepad btn) const override;
|
||||||
|
bool pressed(Gamepad btn) const override;
|
||||||
|
bool released(Gamepad btn) const override;
|
||||||
|
Vec2 leftStick() const override;
|
||||||
|
Vec2 rightStick() const override;
|
||||||
|
float leftTrigger() const override;
|
||||||
|
float rightTrigger() const override;
|
||||||
|
void vibrate(float left, float right) override;
|
||||||
|
|
||||||
|
// Touch
|
||||||
|
bool touching() const override;
|
||||||
|
int touchCount() const override;
|
||||||
|
Vec2 touch(int index) const override;
|
||||||
|
TouchPoint touchPoint(int index) const override;
|
||||||
|
|
||||||
|
// GLFW specific
|
||||||
|
void handleKeyEvent(int key, int scancode, int action, int mods);
|
||||||
|
void handleMouseButtonEvent(int button, int action, int mods);
|
||||||
|
void handleCursorPosEvent(double xpos, double ypos);
|
||||||
|
void handleScrollEvent(double xoffset, double yoffset);
|
||||||
|
void handleJoystickEvent(int jid, int event);
|
||||||
|
|
||||||
|
void setWindow(GLFWwindow* window) { window_ = window; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
void updateGamepad();
|
||||||
|
void openGamepad();
|
||||||
|
void closeGamepad();
|
||||||
|
|
||||||
|
float applyDeadzone(float value) const;
|
||||||
|
|
||||||
|
// Keyboard state
|
||||||
|
std::array<bool, static_cast<size_t>(Key::Count)> keyCurrent_;
|
||||||
|
std::array<bool, static_cast<size_t>(Key::Count)> keyPrevious_;
|
||||||
|
|
||||||
|
// Mouse state
|
||||||
|
std::array<bool, static_cast<size_t>(Mouse::Count)> mouseCurrent_;
|
||||||
|
std::array<bool, static_cast<size_t>(Mouse::Count)> mousePrevious_;
|
||||||
|
Vec2 mousePos_;
|
||||||
|
Vec2 mouseDelta_;
|
||||||
|
float scroll_ = 0.0f;
|
||||||
|
float scrollDelta_ = 0.0f;
|
||||||
|
|
||||||
|
// Gamepad state
|
||||||
|
std::array<bool, static_cast<size_t>(Gamepad::Count)> gamepadCurrent_;
|
||||||
|
std::array<bool, static_cast<size_t>(Gamepad::Count)> gamepadPrevious_;
|
||||||
|
Vec2 leftStick_;
|
||||||
|
Vec2 rightStick_;
|
||||||
|
float leftTrigger_ = 0.0f;
|
||||||
|
float rightTrigger_ = 0.0f;
|
||||||
|
int gamepadId_ = -1;
|
||||||
|
|
||||||
|
float deadzone_ = 0.15f;
|
||||||
|
|
||||||
|
GLFWwindow* window_ = nullptr;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,479 @@
|
||||||
|
#include "glfw_window.h"
|
||||||
|
#include "glfw_input.h"
|
||||||
|
#include <extra2d/utils/logger.h>
|
||||||
|
#include <glad/glad.h>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
GLFWWindow::GLFWWindow() {}
|
||||||
|
|
||||||
|
GLFWWindow::~GLFWWindow() {
|
||||||
|
destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GLFWWindow::create(const WindowConfigData& cfg) {
|
||||||
|
if (!initGLFW()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置 OpenGL ES 3.2 上下文
|
||||||
|
glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API);
|
||||||
|
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
|
||||||
|
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
|
||||||
|
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
|
||||||
|
glfwWindowHint(GLFW_DOUBLEBUFFER, GLFW_TRUE);
|
||||||
|
glfwWindowHint(GLFW_DEPTH_BITS, 24);
|
||||||
|
glfwWindowHint(GLFW_STENCIL_BITS, 8);
|
||||||
|
|
||||||
|
#ifdef __SWITCH__
|
||||||
|
// Switch 平台强制全屏
|
||||||
|
glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
|
||||||
|
glfwWindowHint(GLFW_DECORATED, GLFW_FALSE);
|
||||||
|
fullscreen_ = true;
|
||||||
|
#else
|
||||||
|
// 桌面平台配置
|
||||||
|
if (cfg.resizable) {
|
||||||
|
glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE);
|
||||||
|
} else {
|
||||||
|
glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!cfg.decorated) {
|
||||||
|
glfwWindowHint(GLFW_DECORATED, GLFW_FALSE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cfg.isFullscreen()) {
|
||||||
|
glfwWindowHint(GLFW_DECORATED, GLFW_FALSE);
|
||||||
|
fullscreen_ = true;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (cfg.multisamples > 0) {
|
||||||
|
glfwWindowHint(GLFW_SAMPLES, cfg.multisamples);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建窗口
|
||||||
|
GLFWmonitor* monitor = nullptr;
|
||||||
|
#ifdef __SWITCH__
|
||||||
|
monitor = glfwGetPrimaryMonitor();
|
||||||
|
#endif
|
||||||
|
if (fullscreen_ && !monitor) {
|
||||||
|
monitor = glfwGetPrimaryMonitor();
|
||||||
|
}
|
||||||
|
|
||||||
|
glfwWindow_ = glfwCreateWindow(
|
||||||
|
cfg.width, cfg.height,
|
||||||
|
cfg.title.c_str(),
|
||||||
|
monitor,
|
||||||
|
nullptr
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!glfwWindow_) {
|
||||||
|
E2D_LOG_ERROR("Failed to create GLFW window");
|
||||||
|
deinitGLFW();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 窗口居中(非全屏模式下)
|
||||||
|
#ifndef __SWITCH__
|
||||||
|
if (!fullscreen_ && !monitor) {
|
||||||
|
GLFWmonitor* primaryMonitor = glfwGetPrimaryMonitor();
|
||||||
|
if (primaryMonitor) {
|
||||||
|
const GLFWvidmode* mode = glfwGetVideoMode(primaryMonitor);
|
||||||
|
if (mode) {
|
||||||
|
int screenWidth = mode->width;
|
||||||
|
int screenHeight = mode->height;
|
||||||
|
int windowX = (screenWidth - cfg.width) / 2;
|
||||||
|
int windowY = (screenHeight - cfg.height) / 2;
|
||||||
|
glfwSetWindowPos(glfwWindow_, windowX, windowY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
glfwMakeContextCurrent(glfwWindow_);
|
||||||
|
|
||||||
|
// 初始化 GLAD
|
||||||
|
if (!gladLoadGLES2Loader((GLADloadproc)glfwGetProcAddress)) {
|
||||||
|
E2D_LOG_ERROR("Failed to initialize GLAD GLES2");
|
||||||
|
glfwDestroyWindow(glfwWindow_);
|
||||||
|
glfwWindow_ = nullptr;
|
||||||
|
deinitGLFW();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置垂直同步
|
||||||
|
glfwSwapInterval(cfg.vsync ? 1 : 0);
|
||||||
|
vsync_ = cfg.vsync;
|
||||||
|
|
||||||
|
// 获取实际窗口大小
|
||||||
|
glfwGetWindowSize(glfwWindow_, &width_, &height_);
|
||||||
|
updateContentScale();
|
||||||
|
|
||||||
|
// 设置回调函数
|
||||||
|
glfwSetWindowUserPointer(glfwWindow_, this);
|
||||||
|
glfwSetFramebufferSizeCallback(glfwWindow_, framebufferSizeCallback);
|
||||||
|
glfwSetWindowCloseCallback(glfwWindow_, windowCloseCallback);
|
||||||
|
glfwSetWindowFocusCallback(glfwWindow_, windowFocusCallback);
|
||||||
|
glfwSetWindowIconifyCallback(glfwWindow_, windowIconifyCallback);
|
||||||
|
glfwSetCursorPosCallback(glfwWindow_, cursorPosCallback);
|
||||||
|
glfwSetMouseButtonCallback(glfwWindow_, mouseButtonCallback);
|
||||||
|
glfwSetScrollCallback(glfwWindow_, scrollCallback);
|
||||||
|
glfwSetKeyCallback(glfwWindow_, keyCallback);
|
||||||
|
glfwSetJoystickCallback(joystickCallback);
|
||||||
|
|
||||||
|
// 创建输入系统
|
||||||
|
input_ = makeUnique<GLFWInput>();
|
||||||
|
input_->setWindow(glfwWindow_);
|
||||||
|
input_->init();
|
||||||
|
|
||||||
|
E2D_LOG_INFO("GLFW window created: {}x{}", width_, height_);
|
||||||
|
E2D_LOG_INFO(" Platform: OpenGL ES 3.2");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLFWWindow::destroy() {
|
||||||
|
if (input_) {
|
||||||
|
input_->shutdown();
|
||||||
|
input_.reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (glfwWindow_) {
|
||||||
|
glfwDestroyWindow(glfwWindow_);
|
||||||
|
glfwWindow_ = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
deinitGLFW();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLFWWindow::poll() {
|
||||||
|
if (!glfwWindow_) return;
|
||||||
|
|
||||||
|
if (input_) {
|
||||||
|
input_->update();
|
||||||
|
}
|
||||||
|
|
||||||
|
glfwPollEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLFWWindow::swap() {
|
||||||
|
if (glfwWindow_) {
|
||||||
|
glfwSwapBuffers(glfwWindow_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GLFWWindow::shouldClose() const {
|
||||||
|
if (!glfwWindow_) return true;
|
||||||
|
return shouldClose_ || glfwWindowShouldClose(glfwWindow_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLFWWindow::close() {
|
||||||
|
shouldClose_ = true;
|
||||||
|
if (glfwWindow_) {
|
||||||
|
glfwSetWindowShouldClose(glfwWindow_, GLFW_TRUE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLFWWindow::setTitle(const std::string& title) {
|
||||||
|
if (glfwWindow_) {
|
||||||
|
glfwSetWindowTitle(glfwWindow_, title.c_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLFWWindow::setSize(int w, int h) {
|
||||||
|
if (glfwWindow_) {
|
||||||
|
glfwSetWindowSize(glfwWindow_, w, h);
|
||||||
|
width_ = w;
|
||||||
|
height_ = h;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLFWWindow::setPos(int x, int y) {
|
||||||
|
#ifndef __SWITCH__
|
||||||
|
if (glfwWindow_) {
|
||||||
|
glfwSetWindowPos(glfwWindow_, x, y);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
(void)x;
|
||||||
|
(void)y;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLFWWindow::setFullscreen(bool fs) {
|
||||||
|
#ifndef __SWITCH__
|
||||||
|
if (!glfwWindow_) return;
|
||||||
|
|
||||||
|
if (fs == fullscreen_) return;
|
||||||
|
|
||||||
|
if (fs) {
|
||||||
|
GLFWmonitor* monitor = glfwGetPrimaryMonitor();
|
||||||
|
const GLFWvidmode* mode = glfwGetVideoMode(monitor);
|
||||||
|
glfwSetWindowMonitor(glfwWindow_, monitor, 0, 0, mode->width, mode->height, mode->refreshRate);
|
||||||
|
} else {
|
||||||
|
glfwSetWindowMonitor(glfwWindow_, nullptr, 100, 100, 1280, 720, 0);
|
||||||
|
}
|
||||||
|
fullscreen_ = fs;
|
||||||
|
glfwGetWindowSize(glfwWindow_, &width_, &height_);
|
||||||
|
updateContentScale();
|
||||||
|
#else
|
||||||
|
(void)fs;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLFWWindow::setVSync(bool vsync) {
|
||||||
|
if (glfwWindow_) {
|
||||||
|
glfwSwapInterval(vsync ? 1 : 0);
|
||||||
|
vsync_ = vsync;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLFWWindow::setVisible(bool visible) {
|
||||||
|
#ifndef __SWITCH__
|
||||||
|
if (glfwWindow_) {
|
||||||
|
if (visible) {
|
||||||
|
glfwShowWindow(glfwWindow_);
|
||||||
|
} else {
|
||||||
|
glfwHideWindow(glfwWindow_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
(void)visible;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
int GLFWWindow::width() const {
|
||||||
|
return width_;
|
||||||
|
}
|
||||||
|
|
||||||
|
int GLFWWindow::height() const {
|
||||||
|
return height_;
|
||||||
|
}
|
||||||
|
|
||||||
|
Size GLFWWindow::size() const {
|
||||||
|
return Size(static_cast<float>(width_), static_cast<float>(height_));
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec2 GLFWWindow::pos() const {
|
||||||
|
int x = 0, y = 0;
|
||||||
|
#ifndef __SWITCH__
|
||||||
|
if (glfwWindow_) {
|
||||||
|
glfwGetWindowPos(glfwWindow_, &x, &y);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
return Vec2(static_cast<float>(x), static_cast<float>(y));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GLFWWindow::fullscreen() const {
|
||||||
|
return fullscreen_;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GLFWWindow::vsync() const {
|
||||||
|
return vsync_;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GLFWWindow::focused() const {
|
||||||
|
return focused_;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GLFWWindow::minimized() const {
|
||||||
|
return minimized_;
|
||||||
|
}
|
||||||
|
|
||||||
|
float GLFWWindow::scaleX() const {
|
||||||
|
return scaleX_;
|
||||||
|
}
|
||||||
|
|
||||||
|
float GLFWWindow::scaleY() const {
|
||||||
|
return scaleY_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLFWWindow::setCursor(Cursor cursor) {
|
||||||
|
#ifndef __SWITCH__
|
||||||
|
if (!glfwWindow_) return;
|
||||||
|
|
||||||
|
if (cursor == Cursor::Hidden) {
|
||||||
|
glfwSetInputMode(glfwWindow_, GLFW_CURSOR, GLFW_CURSOR_HIDDEN);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
glfwSetInputMode(glfwWindow_, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
|
||||||
|
|
||||||
|
GLFWcursor* glfwCursor = nullptr;
|
||||||
|
switch (cursor) {
|
||||||
|
case Cursor::Arrow:
|
||||||
|
glfwCursor = glfwCreateStandardCursor(GLFW_ARROW_CURSOR);
|
||||||
|
break;
|
||||||
|
case Cursor::IBeam:
|
||||||
|
glfwCursor = glfwCreateStandardCursor(GLFW_IBEAM_CURSOR);
|
||||||
|
break;
|
||||||
|
case Cursor::Crosshair:
|
||||||
|
glfwCursor = glfwCreateStandardCursor(GLFW_CROSSHAIR_CURSOR);
|
||||||
|
break;
|
||||||
|
case Cursor::Hand:
|
||||||
|
glfwCursor = glfwCreateStandardCursor(GLFW_HAND_CURSOR);
|
||||||
|
break;
|
||||||
|
case Cursor::HResize:
|
||||||
|
glfwCursor = glfwCreateStandardCursor(GLFW_HRESIZE_CURSOR);
|
||||||
|
break;
|
||||||
|
case Cursor::VResize:
|
||||||
|
glfwCursor = glfwCreateStandardCursor(GLFW_VRESIZE_CURSOR);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
glfwCursor = glfwCreateStandardCursor(GLFW_ARROW_CURSOR);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (glfwCursor) {
|
||||||
|
glfwSetCursor(glfwWindow_, glfwCursor);
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
(void)cursor;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLFWWindow::showCursor(bool show) {
|
||||||
|
#ifndef __SWITCH__
|
||||||
|
if (glfwWindow_) {
|
||||||
|
glfwSetInputMode(glfwWindow_, GLFW_CURSOR, show ? GLFW_CURSOR_NORMAL : GLFW_CURSOR_HIDDEN);
|
||||||
|
cursorVisible_ = show;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
(void)show;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLFWWindow::lockCursor(bool lock) {
|
||||||
|
#ifndef __SWITCH__
|
||||||
|
if (glfwWindow_) {
|
||||||
|
glfwSetInputMode(glfwWindow_, GLFW_CURSOR, lock ? GLFW_CURSOR_DISABLED : GLFW_CURSOR_NORMAL);
|
||||||
|
cursorLocked_ = lock;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
(void)lock;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
IInput* GLFWWindow::input() const {
|
||||||
|
return input_.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLFWWindow::onResize(ResizeCb cb) {
|
||||||
|
resizeCb_ = cb;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLFWWindow::onClose(CloseCb cb) {
|
||||||
|
closeCb_ = cb;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLFWWindow::onFocus(FocusCb cb) {
|
||||||
|
focusCb_ = cb;
|
||||||
|
}
|
||||||
|
|
||||||
|
void* GLFWWindow::native() const {
|
||||||
|
return glfwWindow_;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GLFWWindow::initGLFW() {
|
||||||
|
static int glfwInitCount = 0;
|
||||||
|
if (glfwInitCount == 0) {
|
||||||
|
if (!glfwInit()) {
|
||||||
|
E2D_LOG_ERROR("Failed to initialize GLFW");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
glfwInitCount++;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLFWWindow::deinitGLFW() {
|
||||||
|
static int glfwInitCount = 1;
|
||||||
|
glfwInitCount--;
|
||||||
|
if (glfwInitCount == 0) {
|
||||||
|
glfwTerminate();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLFWWindow::updateContentScale() {
|
||||||
|
if (glfwWindow_) {
|
||||||
|
int fbWidth, fbHeight;
|
||||||
|
glfwGetFramebufferSize(glfwWindow_, &fbWidth, &fbHeight);
|
||||||
|
scaleX_ = fbWidth > 0 ? static_cast<float>(fbWidth) / width_ : 1.0f;
|
||||||
|
scaleY_ = fbHeight > 0 ? static_cast<float>(fbHeight) / height_ : 1.0f;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 静态回调函数
|
||||||
|
void GLFWWindow::framebufferSizeCallback(GLFWwindow* window, int width, int height) {
|
||||||
|
GLFWWindow* self = static_cast<GLFWWindow*>(glfwGetWindowUserPointer(window));
|
||||||
|
if (self) {
|
||||||
|
self->width_ = width;
|
||||||
|
self->height_ = height;
|
||||||
|
self->updateContentScale();
|
||||||
|
if (self->resizeCb_) {
|
||||||
|
self->resizeCb_(width, height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLFWWindow::windowCloseCallback(GLFWwindow* window) {
|
||||||
|
GLFWWindow* self = static_cast<GLFWWindow*>(glfwGetWindowUserPointer(window));
|
||||||
|
if (self) {
|
||||||
|
self->shouldClose_ = true;
|
||||||
|
if (self->closeCb_) {
|
||||||
|
self->closeCb_();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLFWWindow::windowFocusCallback(GLFWwindow* window, int focused) {
|
||||||
|
GLFWWindow* self = static_cast<GLFWWindow*>(glfwGetWindowUserPointer(window));
|
||||||
|
if (self) {
|
||||||
|
self->focused_ = (focused == GLFW_TRUE);
|
||||||
|
if (self->focusCb_) {
|
||||||
|
self->focusCb_(self->focused_);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLFWWindow::windowIconifyCallback(GLFWwindow* window, int iconified) {
|
||||||
|
GLFWWindow* self = static_cast<GLFWWindow*>(glfwGetWindowUserPointer(window));
|
||||||
|
if (self) {
|
||||||
|
self->minimized_ = (iconified == GLFW_TRUE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLFWWindow::cursorPosCallback(GLFWwindow* window, double xpos, double ypos) {
|
||||||
|
GLFWWindow* self = static_cast<GLFWWindow*>(glfwGetWindowUserPointer(window));
|
||||||
|
if (self && self->input_) {
|
||||||
|
self->input_->handleCursorPosEvent(xpos, ypos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLFWWindow::mouseButtonCallback(GLFWwindow* window, int button, int action, int mods) {
|
||||||
|
GLFWWindow* self = static_cast<GLFWWindow*>(glfwGetWindowUserPointer(window));
|
||||||
|
if (self && self->input_) {
|
||||||
|
self->input_->handleMouseButtonEvent(button, action, mods);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLFWWindow::scrollCallback(GLFWwindow* window, double xoffset, double yoffset) {
|
||||||
|
GLFWWindow* self = static_cast<GLFWWindow*>(glfwGetWindowUserPointer(window));
|
||||||
|
if (self && self->input_) {
|
||||||
|
self->input_->handleScrollEvent(xoffset, yoffset);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLFWWindow::keyCallback(GLFWwindow* window, int key, int scancode, int action, int mods) {
|
||||||
|
GLFWWindow* self = static_cast<GLFWWindow*>(glfwGetWindowUserPointer(window));
|
||||||
|
if (self && self->input_) {
|
||||||
|
self->input_->handleKeyEvent(key, scancode, action, mods);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GLFWWindow::joystickCallback(int jid, int event) {
|
||||||
|
// 通过全局回调找到对应的窗口实例
|
||||||
|
// 由于 GLFW 的 joystick 回调没有窗口参数,我们需要其他方式处理
|
||||||
|
// 这里简化处理,让输入系统在 update() 中轮询
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -0,0 +1,99 @@
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <extra2d/platform/iwindow.h>
|
||||||
|
#include <GLFW/glfw3.h>
|
||||||
|
|
||||||
|
namespace extra2d {
|
||||||
|
|
||||||
|
class GLFWInput;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief GLFW 窗口实现
|
||||||
|
*/
|
||||||
|
class GLFWWindow : public IWindow {
|
||||||
|
public:
|
||||||
|
GLFWWindow();
|
||||||
|
~GLFWWindow() override;
|
||||||
|
|
||||||
|
bool create(const WindowConfigData& cfg) override;
|
||||||
|
void destroy() override;
|
||||||
|
|
||||||
|
void poll() override;
|
||||||
|
void swap() override;
|
||||||
|
bool shouldClose() const override;
|
||||||
|
void close() override;
|
||||||
|
|
||||||
|
void setTitle(const std::string& title) override;
|
||||||
|
void setSize(int w, int h) override;
|
||||||
|
void setPos(int x, int y) override;
|
||||||
|
void setFullscreen(bool fs) override;
|
||||||
|
void setVSync(bool vsync) override;
|
||||||
|
void setVisible(bool visible) override;
|
||||||
|
|
||||||
|
int width() const override;
|
||||||
|
int height() const override;
|
||||||
|
Size size() const override;
|
||||||
|
Vec2 pos() const override;
|
||||||
|
bool fullscreen() const override;
|
||||||
|
bool vsync() const override;
|
||||||
|
bool focused() const override;
|
||||||
|
bool minimized() const override;
|
||||||
|
|
||||||
|
float scaleX() const override;
|
||||||
|
float scaleY() const override;
|
||||||
|
|
||||||
|
void setCursor(Cursor cursor) override;
|
||||||
|
void showCursor(bool show) override;
|
||||||
|
void lockCursor(bool lock) override;
|
||||||
|
|
||||||
|
IInput* input() const override;
|
||||||
|
|
||||||
|
void onResize(ResizeCb cb) override;
|
||||||
|
void onClose(CloseCb cb) override;
|
||||||
|
void onFocus(FocusCb cb) override;
|
||||||
|
|
||||||
|
void* native() const override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief 获取 GLFW 窗口句柄
|
||||||
|
*/
|
||||||
|
GLFWwindow* glfwWindow() const { return glfwWindow_; }
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool initGLFW();
|
||||||
|
void deinitGLFW();
|
||||||
|
void updateContentScale();
|
||||||
|
|
||||||
|
// GLFW 回调函数(静态)
|
||||||
|
static void framebufferSizeCallback(GLFWwindow* window, int width, int height);
|
||||||
|
static void windowCloseCallback(GLFWwindow* window);
|
||||||
|
static void windowFocusCallback(GLFWwindow* window, int focused);
|
||||||
|
static void windowIconifyCallback(GLFWwindow* window, int iconified);
|
||||||
|
static void cursorPosCallback(GLFWwindow* window, double xpos, double ypos);
|
||||||
|
static void mouseButtonCallback(GLFWwindow* window, int button, int action, int mods);
|
||||||
|
static void scrollCallback(GLFWwindow* window, double xoffset, double yoffset);
|
||||||
|
static void keyCallback(GLFWwindow* window, int key, int scancode, int action, int mods);
|
||||||
|
static void joystickCallback(int jid, int event);
|
||||||
|
|
||||||
|
GLFWwindow* glfwWindow_ = nullptr;
|
||||||
|
|
||||||
|
UniquePtr<GLFWInput> input_;
|
||||||
|
|
||||||
|
int width_ = 1280;
|
||||||
|
int height_ = 720;
|
||||||
|
bool fullscreen_ = false;
|
||||||
|
bool vsync_ = true;
|
||||||
|
bool focused_ = true;
|
||||||
|
bool minimized_ = false;
|
||||||
|
bool shouldClose_ = false;
|
||||||
|
float scaleX_ = 1.0f;
|
||||||
|
float scaleY_ = 1.0f;
|
||||||
|
bool cursorVisible_ = true;
|
||||||
|
bool cursorLocked_ = false;
|
||||||
|
|
||||||
|
ResizeCb resizeCb_;
|
||||||
|
CloseCb closeCb_;
|
||||||
|
FocusCb focusCb_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace extra2d
|
||||||
|
|
@ -6,7 +6,9 @@
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
||||||
InputModule::InputModule(const Cfg& cfg) : cfg_(cfg) {}
|
InputModule::InputModule(std::function<void(InputCfg&)> configFn) {
|
||||||
|
configFn(cfg_);
|
||||||
|
}
|
||||||
|
|
||||||
InputModule::~InputModule() {
|
InputModule::~InputModule() {
|
||||||
if (initialized_) {
|
if (initialized_) {
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
#include <extra2d/platform/backend_factory.h>
|
#include <extra2d/platform/backend_factory.h>
|
||||||
#include <extra2d/core/service_locator.h>
|
#include <extra2d/core/service_locator.h>
|
||||||
#include <extra2d/utils/logger.h>
|
#include <extra2d/utils/logger.h>
|
||||||
#include <SDL.h>
|
|
||||||
|
|
||||||
#ifdef __SWITCH__
|
#ifdef __SWITCH__
|
||||||
#include <switch.h>
|
#include <switch.h>
|
||||||
|
|
@ -10,10 +9,16 @@
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
||||||
// 前向声明 SDL2 后端初始化函数
|
// 前向声明后端初始化函数
|
||||||
|
#if defined(E2D_BACKEND_SDL2)
|
||||||
void initSDL2Backend();
|
void initSDL2Backend();
|
||||||
|
#elif defined(E2D_BACKEND_GLFW)
|
||||||
|
void initGLFWBackend();
|
||||||
|
#endif
|
||||||
|
|
||||||
WindowModule::WindowModule(const Cfg& cfg) : cfg_(cfg) {}
|
WindowModule::WindowModule(std::function<void(WindowCfg&)> configFn) {
|
||||||
|
configFn(cfg_);
|
||||||
|
}
|
||||||
|
|
||||||
WindowModule::~WindowModule() {
|
WindowModule::~WindowModule() {
|
||||||
if (initialized_) {
|
if (initialized_) {
|
||||||
|
|
@ -28,21 +33,16 @@ bool WindowModule::init() {
|
||||||
cfg_.mode = WindowMode::Fullscreen;
|
cfg_.mode = WindowMode::Fullscreen;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// 初始化SDL后端(注册到工厂)
|
// 初始化后端(注册到工厂)
|
||||||
|
#if defined(E2D_BACKEND_SDL2)
|
||||||
initSDL2Backend();
|
initSDL2Backend();
|
||||||
|
#elif defined(E2D_BACKEND_GLFW)
|
||||||
// 初始化SDL
|
initGLFWBackend();
|
||||||
Uint32 flags = SDL_INIT_VIDEO | SDL_INIT_EVENTS | SDL_INIT_TIMER;
|
#else
|
||||||
#ifdef __SWITCH__
|
#error "No window backend defined"
|
||||||
flags |= SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER;
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (SDL_Init(flags) != 0) {
|
E2D_LOG_INFO("Window backend initialized");
|
||||||
E2D_LOG_ERROR("SDL_Init failed: {}", SDL_GetError());
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
sdlInited_ = true;
|
|
||||||
E2D_LOG_INFO("SDL initialized successfully");
|
|
||||||
|
|
||||||
// 创建窗口配置
|
// 创建窗口配置
|
||||||
WindowConfigData winCfg;
|
WindowConfigData winCfg;
|
||||||
|
|
@ -57,8 +57,7 @@ bool WindowModule::init() {
|
||||||
// 创建窗口(使用配置的后端)
|
// 创建窗口(使用配置的后端)
|
||||||
win_ = BackendFactory::createWindow(cfg_.backend);
|
win_ = BackendFactory::createWindow(cfg_.backend);
|
||||||
if (!win_) {
|
if (!win_) {
|
||||||
E2D_LOG_ERROR("Failed to create window backend");
|
E2D_LOG_ERROR("Failed to create window backend: {}", cfg_.backend);
|
||||||
shutdown();
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -81,11 +80,6 @@ void WindowModule::shutdown() {
|
||||||
win_.reset();
|
win_.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sdlInited_) {
|
|
||||||
SDL_Quit();
|
|
||||||
sdlInited_ = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
initialized_ = false;
|
initialized_ = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -15,9 +15,7 @@ CameraService::CameraService(float left, float right, float bottom, float top)
|
||||||
info_.enabled = true;
|
info_.enabled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
ServiceInfo CameraService::getServiceInfo() const {
|
ServiceInfo CameraService::getServiceInfo() const { return info_; }
|
||||||
return info_;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool CameraService::initialize() {
|
bool CameraService::initialize() {
|
||||||
camera_.setViewportAdapter(&viewportAdapter_);
|
camera_.setViewportAdapter(&viewportAdapter_);
|
||||||
|
|
@ -25,45 +23,30 @@ bool CameraService::initialize() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void CameraService::shutdown() {
|
void CameraService::shutdown() { setState(ServiceState::Stopped); }
|
||||||
setState(ServiceState::Stopped);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CameraService::setPosition(const Vec2 &position) {
|
void CameraService::setPosition(const Vec2 &position) {
|
||||||
camera_.setPos(position);
|
camera_.setPos(position);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CameraService::setPosition(float x, float y) {
|
void CameraService::setPosition(float x, float y) { camera_.setPos(x, y); }
|
||||||
camera_.setPos(x, y);
|
|
||||||
}
|
|
||||||
|
|
||||||
Vec2 CameraService::getPosition() const {
|
Vec2 CameraService::getPosition() const { return camera_.getPosition(); }
|
||||||
return camera_.getPosition();
|
|
||||||
}
|
|
||||||
|
|
||||||
void CameraService::setRotation(float degrees) {
|
void CameraService::setRotation(float degrees) { camera_.setRotation(degrees); }
|
||||||
camera_.setRotation(degrees);
|
|
||||||
}
|
|
||||||
|
|
||||||
float CameraService::getRotation() const {
|
float CameraService::getRotation() const { return camera_.getRotation(); }
|
||||||
return camera_.getRotation();
|
|
||||||
}
|
|
||||||
|
|
||||||
void CameraService::setZoom(float zoom) {
|
void CameraService::setZoom(float zoom) { camera_.setZoom(zoom); }
|
||||||
camera_.setZoom(zoom);
|
|
||||||
}
|
|
||||||
|
|
||||||
float CameraService::getZoom() const {
|
float CameraService::getZoom() const { return camera_.getZoom(); }
|
||||||
return camera_.getZoom();
|
|
||||||
}
|
|
||||||
|
|
||||||
void CameraService::setViewport(float left, float right, float bottom, float top) {
|
void CameraService::setViewport(float left, float right, float bottom,
|
||||||
|
float top) {
|
||||||
camera_.setViewport(left, right, bottom, top);
|
camera_.setViewport(left, right, bottom, top);
|
||||||
}
|
}
|
||||||
|
|
||||||
Rect CameraService::getViewport() const {
|
Rect CameraService::getViewport() const { return camera_.getViewport(); }
|
||||||
return camera_.getViewport();
|
|
||||||
}
|
|
||||||
|
|
||||||
glm::mat4 CameraService::getViewMatrix() const {
|
glm::mat4 CameraService::getViewMatrix() const {
|
||||||
return camera_.getViewMatrix();
|
return camera_.getViewMatrix();
|
||||||
|
|
@ -85,25 +68,15 @@ Vec2 CameraService::worldToScreen(const Vec2& worldPos) const {
|
||||||
return camera_.worldToScreen(worldPos);
|
return camera_.worldToScreen(worldPos);
|
||||||
}
|
}
|
||||||
|
|
||||||
void CameraService::move(const Vec2& offset) {
|
void CameraService::move(const Vec2 &offset) { camera_.move(offset); }
|
||||||
camera_.move(offset);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CameraService::move(float x, float y) {
|
void CameraService::move(float x, float y) { camera_.move(x, y); }
|
||||||
camera_.move(x, y);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CameraService::setBounds(const Rect& bounds) {
|
void CameraService::setBounds(const Rect &bounds) { camera_.setBounds(bounds); }
|
||||||
camera_.setBounds(bounds);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CameraService::clearBounds() {
|
void CameraService::clearBounds() { camera_.clearBounds(); }
|
||||||
camera_.clearBounds();
|
|
||||||
}
|
|
||||||
|
|
||||||
void CameraService::lookAt(const Vec2& target) {
|
void CameraService::lookAt(const Vec2 &target) { camera_.lookAt(target); }
|
||||||
camera_.lookAt(target);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CameraService::setViewportConfig(const ViewportConfig &config) {
|
void CameraService::setViewportConfig(const ViewportConfig &config) {
|
||||||
viewportAdapter_.setConfig(config);
|
viewportAdapter_.setConfig(config);
|
||||||
|
|
@ -121,8 +94,6 @@ const ViewportResult& CameraService::getViewportResult() const {
|
||||||
return viewportAdapter_.getResult();
|
return viewportAdapter_.getResult();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CameraService::applyViewportAdapter() {
|
void CameraService::applyViewportAdapter() { camera_.applyViewportAdapter(); }
|
||||||
camera_.applyViewportAdapter();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
} // namespace extra2d
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,7 @@ EventService::EventService() {
|
||||||
info_.enabled = true;
|
info_.enabled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
ServiceInfo EventService::getServiceInfo() const {
|
ServiceInfo EventService::getServiceInfo() const { return info_; }
|
||||||
return info_;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool EventService::initialize() {
|
bool EventService::initialize() {
|
||||||
setState(ServiceState::Running);
|
setState(ServiceState::Running);
|
||||||
|
|
@ -29,19 +27,14 @@ void EventService::update(float deltaTime) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void EventService::pushEvent(const Event& event) {
|
void EventService::pushEvent(const Event &event) { queue_.push(event); }
|
||||||
queue_.push(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
void EventService::pushEvent(Event&& event) {
|
void EventService::pushEvent(Event &&event) { queue_.push(std::move(event)); }
|
||||||
queue_.push(std::move(event));
|
|
||||||
}
|
|
||||||
|
|
||||||
bool EventService::pollEvent(Event& event) {
|
bool EventService::pollEvent(Event &event) { return queue_.poll(event); }
|
||||||
return queue_.poll(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
ListenerId EventService::addListener(EventType type, EventDispatcher::EventCallback callback) {
|
ListenerId EventService::addListener(EventType type,
|
||||||
|
EventDispatcher::EventCallback callback) {
|
||||||
return dispatcher_.addListener(type, callback);
|
return dispatcher_.addListener(type, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -53,17 +46,11 @@ void EventService::removeAllListeners(EventType type) {
|
||||||
dispatcher_.removeAllListeners(type);
|
dispatcher_.removeAllListeners(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
void EventService::removeAllListeners() {
|
void EventService::removeAllListeners() { dispatcher_.removeAllListeners(); }
|
||||||
dispatcher_.removeAllListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
void EventService::dispatch(Event& event) {
|
void EventService::dispatch(Event &event) { dispatcher_.dispatch(event); }
|
||||||
dispatcher_.dispatch(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
void EventService::processQueue() {
|
void EventService::processQueue() { dispatcher_.processQueue(queue_); }
|
||||||
dispatcher_.processQueue(queue_);
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t EventService::getListenerCount(EventType type) const {
|
size_t EventService::getListenerCount(EventType type) const {
|
||||||
return dispatcher_.getListenerCount(type);
|
return dispatcher_.getListenerCount(type);
|
||||||
|
|
@ -73,8 +60,6 @@ size_t EventService::getTotalListenerCount() const {
|
||||||
return dispatcher_.getTotalListenerCount();
|
return dispatcher_.getTotalListenerCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t EventService::getQueueSize() const {
|
size_t EventService::getQueueSize() const { return queue_.size(); }
|
||||||
return queue_.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
} // namespace extra2d
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,7 @@
|
||||||
#include <extra2d/services/logger_service.h>
|
|
||||||
#include <extra2d/core/service_locator.h>
|
|
||||||
#include <cstdio>
|
|
||||||
#include <cstdarg>
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <iomanip>
|
#include <cstdarg>
|
||||||
#include <sstream>
|
#include <cstdio>
|
||||||
|
#include <extra2d/services/logger_service.h>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
|
|
||||||
namespace extra2d {
|
namespace extra2d {
|
||||||
|
|
@ -15,7 +12,8 @@ public:
|
||||||
std::mutex mutex_;
|
std::mutex mutex_;
|
||||||
};
|
};
|
||||||
|
|
||||||
ConsoleLogger::ConsoleLogger() : level_(LogLevel::Info), impl_(std::make_unique<Impl>()) {
|
ConsoleLogger::ConsoleLogger()
|
||||||
|
: level_(LogLevel::Info), impl_(std::make_unique<Impl>()) {
|
||||||
info_.name = "ConsoleLogger";
|
info_.name = "ConsoleLogger";
|
||||||
info_.priority = ServicePriority::Core;
|
info_.priority = ServicePriority::Core;
|
||||||
}
|
}
|
||||||
|
|
@ -27,24 +25,19 @@ bool ConsoleLogger::initialize() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConsoleLogger::shutdown() {
|
void ConsoleLogger::shutdown() { setState(ServiceState::Stopped); }
|
||||||
setState(ServiceState::Stopped);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ConsoleLogger::setLevel(LogLevel level) {
|
void ConsoleLogger::setLevel(LogLevel level) { level_ = level; }
|
||||||
level_ = level;
|
|
||||||
}
|
|
||||||
|
|
||||||
LogLevel ConsoleLogger::getLevel() const {
|
LogLevel ConsoleLogger::getLevel() const { return level_; }
|
||||||
return level_;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool ConsoleLogger::isEnabled(LogLevel level) const {
|
bool ConsoleLogger::isEnabled(LogLevel level) const {
|
||||||
return static_cast<int>(level) >= static_cast<int>(level_);
|
return static_cast<int>(level) >= static_cast<int>(level_);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConsoleLogger::log(LogLevel level, const char *fmt, ...) {
|
void ConsoleLogger::log(LogLevel level, const char *fmt, ...) {
|
||||||
if (!isEnabled(level)) return;
|
if (!isEnabled(level))
|
||||||
|
return;
|
||||||
|
|
||||||
char buffer[1024];
|
char buffer[1024];
|
||||||
va_list args;
|
va_list args;
|
||||||
|
|
@ -56,12 +49,14 @@ void ConsoleLogger::log(LogLevel level, const char* fmt, ...) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConsoleLogger::log(LogLevel level, const std::string &msg) {
|
void ConsoleLogger::log(LogLevel level, const std::string &msg) {
|
||||||
if (!isEnabled(level)) return;
|
if (!isEnabled(level))
|
||||||
|
return;
|
||||||
output(level, msg.c_str());
|
output(level, msg.c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConsoleLogger::trace(const char *fmt, ...) {
|
void ConsoleLogger::trace(const char *fmt, ...) {
|
||||||
if (!isEnabled(LogLevel::Trace)) return;
|
if (!isEnabled(LogLevel::Trace))
|
||||||
|
return;
|
||||||
char buffer[1024];
|
char buffer[1024];
|
||||||
va_list args;
|
va_list args;
|
||||||
va_start(args, fmt);
|
va_start(args, fmt);
|
||||||
|
|
@ -71,7 +66,8 @@ void ConsoleLogger::trace(const char* fmt, ...) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConsoleLogger::debug(const char *fmt, ...) {
|
void ConsoleLogger::debug(const char *fmt, ...) {
|
||||||
if (!isEnabled(LogLevel::Debug)) return;
|
if (!isEnabled(LogLevel::Debug))
|
||||||
|
return;
|
||||||
char buffer[1024];
|
char buffer[1024];
|
||||||
va_list args;
|
va_list args;
|
||||||
va_start(args, fmt);
|
va_start(args, fmt);
|
||||||
|
|
@ -81,7 +77,8 @@ void ConsoleLogger::debug(const char* fmt, ...) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConsoleLogger::info(const char *fmt, ...) {
|
void ConsoleLogger::info(const char *fmt, ...) {
|
||||||
if (!isEnabled(LogLevel::Info)) return;
|
if (!isEnabled(LogLevel::Info))
|
||||||
|
return;
|
||||||
char buffer[1024];
|
char buffer[1024];
|
||||||
va_list args;
|
va_list args;
|
||||||
va_start(args, fmt);
|
va_start(args, fmt);
|
||||||
|
|
@ -91,7 +88,8 @@ void ConsoleLogger::info(const char* fmt, ...) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConsoleLogger::warn(const char *fmt, ...) {
|
void ConsoleLogger::warn(const char *fmt, ...) {
|
||||||
if (!isEnabled(LogLevel::Warn)) return;
|
if (!isEnabled(LogLevel::Warn))
|
||||||
|
return;
|
||||||
char buffer[1024];
|
char buffer[1024];
|
||||||
va_list args;
|
va_list args;
|
||||||
va_start(args, fmt);
|
va_start(args, fmt);
|
||||||
|
|
@ -101,7 +99,8 @@ void ConsoleLogger::warn(const char* fmt, ...) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConsoleLogger::error(const char *fmt, ...) {
|
void ConsoleLogger::error(const char *fmt, ...) {
|
||||||
if (!isEnabled(LogLevel::Error)) return;
|
if (!isEnabled(LogLevel::Error))
|
||||||
|
return;
|
||||||
char buffer[1024];
|
char buffer[1024];
|
||||||
va_list args;
|
va_list args;
|
||||||
va_start(args, fmt);
|
va_start(args, fmt);
|
||||||
|
|
@ -111,7 +110,8 @@ void ConsoleLogger::error(const char* fmt, ...) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConsoleLogger::fatal(const char *fmt, ...) {
|
void ConsoleLogger::fatal(const char *fmt, ...) {
|
||||||
if (!isEnabled(LogLevel::Fatal)) return;
|
if (!isEnabled(LogLevel::Fatal))
|
||||||
|
return;
|
||||||
char buffer[1024];
|
char buffer[1024];
|
||||||
va_list args;
|
va_list args;
|
||||||
va_start(args, fmt);
|
va_start(args, fmt);
|
||||||
|
|
@ -126,7 +126,8 @@ void ConsoleLogger::output(LogLevel level, const char* msg) {
|
||||||
auto now = std::chrono::system_clock::now();
|
auto now = std::chrono::system_clock::now();
|
||||||
auto time = std::chrono::system_clock::to_time_t(now);
|
auto time = std::chrono::system_clock::to_time_t(now);
|
||||||
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
|
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||||||
now.time_since_epoch()) % 1000;
|
now.time_since_epoch()) %
|
||||||
|
1000;
|
||||||
|
|
||||||
std::tm tm;
|
std::tm tm;
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
|
|
@ -142,29 +143,48 @@ void ConsoleLogger::output(LogLevel level, const char* msg) {
|
||||||
const char *reset = "\033[0m";
|
const char *reset = "\033[0m";
|
||||||
|
|
||||||
switch (level) {
|
switch (level) {
|
||||||
case LogLevel::Trace: color = "\033[90m"; break;
|
case LogLevel::Trace:
|
||||||
case LogLevel::Debug: color = "\033[36m"; break;
|
color = "\033[90m";
|
||||||
case LogLevel::Info: color = "\033[32m"; break;
|
break;
|
||||||
case LogLevel::Warn: color = "\033[33m"; break;
|
case LogLevel::Debug:
|
||||||
case LogLevel::Error: color = "\033[31m"; break;
|
color = "\033[36m";
|
||||||
case LogLevel::Fatal: color = "\033[35m"; break;
|
break;
|
||||||
default: 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",
|
printf("%s[%02d:%02d:%02d.%03d] [%s] %s%s\n", color, tm.tm_hour, tm.tm_min,
|
||||||
color, tm.tm_hour, tm.tm_min, tm.tm_sec, (int)ms.count(),
|
tm.tm_sec, (int)ms.count(), levelStr, msg, reset);
|
||||||
levelStr, msg, reset);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *ConsoleLogger::getLevelString(LogLevel level) {
|
const char *ConsoleLogger::getLevelString(LogLevel level) {
|
||||||
switch (level) {
|
switch (level) {
|
||||||
case LogLevel::Trace: return "TRACE";
|
case LogLevel::Trace:
|
||||||
case LogLevel::Debug: return "DEBUG";
|
return "TRACE";
|
||||||
case LogLevel::Info: return "INFO";
|
case LogLevel::Debug:
|
||||||
case LogLevel::Warn: return "WARN";
|
return "DEBUG";
|
||||||
case LogLevel::Error: return "ERROR";
|
case LogLevel::Info:
|
||||||
case LogLevel::Fatal: return "FATAL";
|
return "INFO";
|
||||||
default: return "UNKNOWN";
|
case LogLevel::Warn:
|
||||||
|
return "WARN";
|
||||||
|
case LogLevel::Error:
|
||||||
|
return "ERROR";
|
||||||
|
case LogLevel::Fatal:
|
||||||
|
return "FATAL";
|
||||||
|
default:
|
||||||
|
return "UNKNOWN";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,7 @@ SceneService::SceneService() {
|
||||||
info_.enabled = true;
|
info_.enabled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
ServiceInfo SceneService::getServiceInfo() const {
|
ServiceInfo SceneService::getServiceInfo() const { return info_; }
|
||||||
return info_;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SceneService::initialize() {
|
bool SceneService::initialize() {
|
||||||
setState(ServiceState::Running);
|
setState(ServiceState::Running);
|
||||||
|
|
@ -36,17 +34,11 @@ void SceneService::replaceScene(Ptr<Scene> scene) {
|
||||||
manager_.replaceScene(scene);
|
manager_.replaceScene(scene);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SceneService::pushScene(Ptr<Scene> scene) {
|
void SceneService::pushScene(Ptr<Scene> scene) { manager_.pushScene(scene); }
|
||||||
manager_.pushScene(scene);
|
|
||||||
}
|
|
||||||
|
|
||||||
void SceneService::popScene() {
|
void SceneService::popScene() { manager_.popScene(); }
|
||||||
manager_.popScene();
|
|
||||||
}
|
|
||||||
|
|
||||||
void SceneService::popToRootScene() {
|
void SceneService::popToRootScene() { manager_.popToRootScene(); }
|
||||||
manager_.popToRootScene();
|
|
||||||
}
|
|
||||||
|
|
||||||
void SceneService::popToScene(const std::string &name) {
|
void SceneService::popToScene(const std::string &name) {
|
||||||
manager_.popToScene(name);
|
manager_.popToScene(name);
|
||||||
|
|
@ -68,13 +60,9 @@ Ptr<Scene> SceneService::getSceneByName(const std::string& name) const {
|
||||||
return manager_.getSceneByName(name);
|
return manager_.getSceneByName(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t SceneService::getSceneCount() const {
|
size_t SceneService::getSceneCount() const { return manager_.getSceneCount(); }
|
||||||
return manager_.getSceneCount();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SceneService::isEmpty() const {
|
bool SceneService::isEmpty() const { return manager_.isEmpty(); }
|
||||||
return manager_.isEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool SceneService::hasScene(const std::string &name) const {
|
bool SceneService::hasScene(const std::string &name) const {
|
||||||
return manager_.hasScene(name);
|
return manager_.hasScene(name);
|
||||||
|
|
@ -92,20 +80,15 @@ bool SceneService::isTransitioning() const {
|
||||||
return manager_.isTransitioning();
|
return manager_.isTransitioning();
|
||||||
}
|
}
|
||||||
|
|
||||||
void SceneService::setTransitionCallback(SceneManager::TransitionCallback callback) {
|
void SceneService::setTransitionCallback(
|
||||||
|
SceneManager::TransitionCallback callback) {
|
||||||
manager_.setTransitionCallback(callback);
|
manager_.setTransitionCallback(callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
void SceneService::end() {
|
void SceneService::end() { manager_.end(); }
|
||||||
manager_.end();
|
|
||||||
}
|
|
||||||
|
|
||||||
void SceneService::purgeCachedScenes() {
|
void SceneService::purgeCachedScenes() { manager_.purgeCachedScenes(); }
|
||||||
manager_.purgeCachedScenes();
|
|
||||||
}
|
|
||||||
|
|
||||||
void SceneService::enterScene(Ptr<Scene> scene) {
|
void SceneService::enterScene(Ptr<Scene> scene) { manager_.enterScene(scene); }
|
||||||
manager_.enterScene(scene);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
} // namespace extra2d
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,7 @@ TimerService::TimerService() {
|
||||||
info_.enabled = true;
|
info_.enabled = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
ServiceInfo TimerService::getServiceInfo() const {
|
ServiceInfo TimerService::getServiceInfo() const { return info_; }
|
||||||
return info_;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool TimerService::initialize() {
|
bool TimerService::initialize() {
|
||||||
setState(ServiceState::Running);
|
setState(ServiceState::Running);
|
||||||
|
|
@ -32,7 +30,8 @@ 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) {
|
uint32 TimerService::addRepeatingTimer(float interval,
|
||||||
|
Timer::Callback callback) {
|
||||||
return manager_.addRepeatingTimer(interval, callback);
|
return manager_.addRepeatingTimer(interval, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -40,20 +39,14 @@ void TimerService::cancelTimer(uint32 timerId) {
|
||||||
manager_.cancelTimer(timerId);
|
manager_.cancelTimer(timerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TimerService::pauseTimer(uint32 timerId) {
|
void TimerService::pauseTimer(uint32 timerId) { manager_.pauseTimer(timerId); }
|
||||||
manager_.pauseTimer(timerId);
|
|
||||||
}
|
|
||||||
|
|
||||||
void TimerService::resumeTimer(uint32 timerId) {
|
void TimerService::resumeTimer(uint32 timerId) {
|
||||||
manager_.resumeTimer(timerId);
|
manager_.resumeTimer(timerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TimerService::clear() {
|
void TimerService::clear() { manager_.clear(); }
|
||||||
manager_.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t TimerService::getTimerCount() const {
|
size_t TimerService::getTimerCount() const { return manager_.getTimerCount(); }
|
||||||
return manager_.getTimerCount();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
} // namespace extra2d
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue