refactor(shader): 重构着色器系统并添加新功能
- 将shader_system.h重命名为shader_manager.h并重构接口 - 新增shader_interface.h作为跨平台着色器抽象 - 实现GLShaderNew作为OpenGL着色器新实现 - 添加shader_cache支持着色器二进制缓存 - 引入shader_hot_reloader实现热重载功能 - 新增shader_loader支持多种着色器文件格式加载 - 添加内置着色器文件到shaders目录 - 更新gl_renderer.cpp使用新的着色器系统 - 扩展platform_detector.h添加资源路径相关方法 - 添加shaders/common目录包含常用GLSL工具函数 重构后的着色器系统提供更完善的缓存、热重载和跨平台支持,同时优化了性能和维护性。
This commit is contained in:
parent
269f2d907d
commit
475ae50d2a
|
|
@ -141,6 +141,42 @@ public:
|
|||
*/
|
||||
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
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@
|
|||
#include <extra2d/graphics/camera.h>
|
||||
#include <extra2d/graphics/font.h>
|
||||
#include <extra2d/graphics/render_backend.h>
|
||||
#include <extra2d/graphics/shader_system.h>
|
||||
#include <extra2d/graphics/shader_manager.h>
|
||||
#include <extra2d/graphics/texture.h>
|
||||
|
||||
#include <extra2d/graphics/render_target.h>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/graphics/opengl/gl_shader.h>
|
||||
#include <extra2d/graphics/opengl/gl_sprite_batch.h>
|
||||
#include <extra2d/graphics/render_backend.h>
|
||||
#include <extra2d/graphics/shader_interface.h>
|
||||
|
||||
#include <array>
|
||||
#include <glad/glad.h>
|
||||
|
|
@ -90,7 +90,7 @@ private:
|
|||
|
||||
IWindow* window_;
|
||||
GLSpriteBatch spriteBatch_;
|
||||
GLShader shapeShader_;
|
||||
Ptr<IShader> shapeShader_;
|
||||
|
||||
GLuint shapeVao_;
|
||||
GLuint shapeVbo_;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,202 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/core/color.h>
|
||||
#include <extra2d/graphics/shader_interface.h>
|
||||
#include <glad/glad.h>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// ============================================================================
|
||||
// OpenGL Shader实现
|
||||
// ============================================================================
|
||||
class GLShaderNew : public IShader {
|
||||
public:
|
||||
/**
|
||||
* @brief 构造函数
|
||||
*/
|
||||
GLShaderNew();
|
||||
|
||||
/**
|
||||
* @brief 析构函数
|
||||
*/
|
||||
~GLShaderNew() override;
|
||||
|
||||
/**
|
||||
* @brief 绑定Shader程序
|
||||
*/
|
||||
void bind() const override;
|
||||
|
||||
/**
|
||||
* @brief 解绑Shader程序
|
||||
*/
|
||||
void unbind() const override;
|
||||
|
||||
/**
|
||||
* @brief 设置布尔类型uniform变量
|
||||
* @param name uniform变量名
|
||||
* @param value 布尔值
|
||||
*/
|
||||
void setBool(const std::string& name, bool value) override;
|
||||
|
||||
/**
|
||||
* @brief 设置整数类型uniform变量
|
||||
* @param name uniform变量名
|
||||
* @param value 整数值
|
||||
*/
|
||||
void setInt(const std::string& name, int value) override;
|
||||
|
||||
/**
|
||||
* @brief 设置浮点类型uniform变量
|
||||
* @param name uniform变量名
|
||||
* @param value 浮点值
|
||||
*/
|
||||
void setFloat(const std::string& name, float value) override;
|
||||
|
||||
/**
|
||||
* @brief 设置二维向量类型uniform变量
|
||||
* @param name uniform变量名
|
||||
* @param value 二维向量值
|
||||
*/
|
||||
void setVec2(const std::string& name, const glm::vec2& value) override;
|
||||
|
||||
/**
|
||||
* @brief 设置三维向量类型uniform变量
|
||||
* @param name uniform变量名
|
||||
* @param value 三维向量值
|
||||
*/
|
||||
void setVec3(const std::string& name, const glm::vec3& value) override;
|
||||
|
||||
/**
|
||||
* @brief 设置四维向量类型uniform变量
|
||||
* @param name uniform变量名
|
||||
* @param value 四维向量值
|
||||
*/
|
||||
void setVec4(const std::string& name, const glm::vec4& value) override;
|
||||
|
||||
/**
|
||||
* @brief 设置4x4矩阵类型uniform变量
|
||||
* @param name uniform变量名
|
||||
* @param value 4x4矩阵值
|
||||
*/
|
||||
void setMat4(const std::string& name, const glm::mat4& value) override;
|
||||
|
||||
/**
|
||||
* @brief 设置颜色类型uniform变量
|
||||
* @param name uniform变量名
|
||||
* @param color 颜色值
|
||||
*/
|
||||
void setColor(const std::string& name, const Color& color) override;
|
||||
|
||||
/**
|
||||
* @brief 检查Shader是否有效
|
||||
* @return 有效返回true,否则返回false
|
||||
*/
|
||||
bool isValid() const override { return programID_ != 0; }
|
||||
|
||||
/**
|
||||
* @brief 获取原生句柄(OpenGL程序ID)
|
||||
* @return OpenGL程序ID
|
||||
*/
|
||||
uint32_t getNativeHandle() const override { return programID_; }
|
||||
|
||||
/**
|
||||
* @brief 获取Shader名称
|
||||
* @return Shader名称
|
||||
*/
|
||||
const std::string& getName() const override { return name_; }
|
||||
|
||||
/**
|
||||
* @brief 设置Shader名称
|
||||
* @param name Shader名称
|
||||
*/
|
||||
void setName(const std::string& name) override { name_ = name; }
|
||||
|
||||
/**
|
||||
* @brief 从源码编译Shader
|
||||
* @param vertexSource 顶点着色器源码
|
||||
* @param fragmentSource 片段着色器源码
|
||||
* @return 编译成功返回true,失败返回false
|
||||
*/
|
||||
bool compileFromSource(const char* vertexSource, const char* fragmentSource);
|
||||
|
||||
/**
|
||||
* @brief 从二进制数据创建Shader
|
||||
* @param binary 二进制数据
|
||||
* @return 创建成功返回true,失败返回false
|
||||
*/
|
||||
bool compileFromBinary(const std::vector<uint8_t>& binary);
|
||||
|
||||
/**
|
||||
* @brief 获取Shader二进制数据
|
||||
* @param outBinary 输出的二进制数据
|
||||
* @return 成功返回true,失败返回false
|
||||
*/
|
||||
bool getBinary(std::vector<uint8_t>& outBinary);
|
||||
|
||||
/**
|
||||
* @brief 获取OpenGL程序ID
|
||||
* @return OpenGL程序ID
|
||||
*/
|
||||
GLuint getProgramID() const { return programID_; }
|
||||
|
||||
private:
|
||||
GLuint programID_ = 0;
|
||||
std::string name_;
|
||||
std::unordered_map<std::string, GLint> uniformCache_;
|
||||
|
||||
/**
|
||||
* @brief 编译单个着色器
|
||||
* @param type 着色器类型
|
||||
* @param source 着色器源码
|
||||
* @return 着色器ID,失败返回0
|
||||
*/
|
||||
GLuint compileShader(GLenum type, const char* source);
|
||||
|
||||
/**
|
||||
* @brief 获取uniform位置
|
||||
* @param name uniform变量名
|
||||
* @return uniform位置
|
||||
*/
|
||||
GLint getUniformLocation(const std::string& name);
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// OpenGL Shader工厂
|
||||
// ============================================================================
|
||||
class GLShaderFactory : public IShaderFactory {
|
||||
public:
|
||||
/**
|
||||
* @brief 从源码创建Shader
|
||||
* @param name Shader名称
|
||||
* @param vertSource 顶点着色器源码
|
||||
* @param fragSource 片段着色器源码
|
||||
* @return 创建的Shader实例
|
||||
*/
|
||||
Ptr<IShader> createFromSource(
|
||||
const std::string& name,
|
||||
const std::string& vertSource,
|
||||
const std::string& fragSource) override;
|
||||
|
||||
/**
|
||||
* @brief 从缓存二进制创建Shader
|
||||
* @param name Shader名称
|
||||
* @param binary 编译后的二进制数据
|
||||
* @return 创建的Shader实例
|
||||
*/
|
||||
Ptr<IShader> createFromBinary(
|
||||
const std::string& name,
|
||||
const std::vector<uint8_t>& binary) override;
|
||||
|
||||
/**
|
||||
* @brief 获取Shader的二进制数据
|
||||
* @param shader Shader实例
|
||||
* @param outBinary 输出的二进制数据
|
||||
* @return 成功返回true,失败返回false
|
||||
*/
|
||||
bool getShaderBinary(const IShader& shader,
|
||||
std::vector<uint8_t>& outBinary) override;
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -0,0 +1,131 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/core/types.h>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// ============================================================================
|
||||
// Shader缓存条目
|
||||
// ============================================================================
|
||||
struct ShaderCacheEntry {
|
||||
std::string name;
|
||||
std::string sourceHash;
|
||||
uint64_t compileTime = 0;
|
||||
std::vector<uint8_t> binary;
|
||||
std::vector<std::string> dependencies;
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Shader缓存管理器
|
||||
// ============================================================================
|
||||
class ShaderCache {
|
||||
public:
|
||||
/**
|
||||
* @brief 获取单例实例
|
||||
* @return 缓存管理器实例引用
|
||||
*/
|
||||
static ShaderCache& getInstance();
|
||||
|
||||
/**
|
||||
* @brief 初始化缓存系统
|
||||
* @param cacheDir 缓存目录路径
|
||||
* @return 初始化成功返回true,失败返回false
|
||||
*/
|
||||
bool init(const std::string& cacheDir);
|
||||
|
||||
/**
|
||||
* @brief 关闭缓存系统
|
||||
*/
|
||||
void shutdown();
|
||||
|
||||
/**
|
||||
* @brief 检查缓存是否有效
|
||||
* @param name Shader名称
|
||||
* @param sourceHash 源码哈希值
|
||||
* @return 缓存有效返回true,否则返回false
|
||||
*/
|
||||
bool hasValidCache(const std::string& name, const std::string& sourceHash);
|
||||
|
||||
/**
|
||||
* @brief 加载缓存的二进制数据
|
||||
* @param name Shader名称
|
||||
* @return 缓存条目指针,不存在返回nullptr
|
||||
*/
|
||||
Ptr<ShaderCacheEntry> loadCache(const std::string& name);
|
||||
|
||||
/**
|
||||
* @brief 保存编译结果到缓存
|
||||
* @param entry 缓存条目
|
||||
* @return 保存成功返回true,失败返回false
|
||||
*/
|
||||
bool saveCache(const ShaderCacheEntry& entry);
|
||||
|
||||
/**
|
||||
* @brief 使缓存失效
|
||||
* @param name Shader名称
|
||||
*/
|
||||
void invalidate(const std::string& name);
|
||||
|
||||
/**
|
||||
* @brief 清除所有缓存
|
||||
*/
|
||||
void clearAll();
|
||||
|
||||
/**
|
||||
* @brief 计算源码哈希值
|
||||
* @param vertSource 顶点着色器源码
|
||||
* @param fragSource 片段着色器源码
|
||||
* @return 哈希值字符串
|
||||
*/
|
||||
static std::string computeHash(const std::string& vertSource,
|
||||
const std::string& fragSource);
|
||||
|
||||
/**
|
||||
* @brief 检查是否已初始化
|
||||
* @return 已初始化返回true,否则返回false
|
||||
*/
|
||||
bool isInitialized() const { return initialized_; }
|
||||
|
||||
private:
|
||||
ShaderCache() = default;
|
||||
~ShaderCache() = default;
|
||||
ShaderCache(const ShaderCache&) = delete;
|
||||
ShaderCache& operator=(const ShaderCache&) = delete;
|
||||
|
||||
std::string cacheDir_;
|
||||
std::unordered_map<std::string, ShaderCacheEntry> cacheMap_;
|
||||
bool initialized_ = false;
|
||||
|
||||
/**
|
||||
* @brief 加载缓存索引
|
||||
* @return 加载成功返回true,失败返回false
|
||||
*/
|
||||
bool loadCacheIndex();
|
||||
|
||||
/**
|
||||
* @brief 保存缓存索引
|
||||
* @return 保存成功返回true,失败返回false
|
||||
*/
|
||||
bool saveCacheIndex();
|
||||
|
||||
/**
|
||||
* @brief 获取缓存文件路径
|
||||
* @param name Shader名称
|
||||
* @return 缓存文件完整路径
|
||||
*/
|
||||
std::string getCachePath(const std::string& name) const;
|
||||
|
||||
/**
|
||||
* @brief 确保缓存目录存在
|
||||
* @return 目录存在或创建成功返回true,否则返回false
|
||||
*/
|
||||
bool ensureCacheDirectory();
|
||||
};
|
||||
|
||||
// 便捷宏
|
||||
#define E2D_SHADER_CACHE() ::extra2d::ShaderCache::getInstance()
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -0,0 +1,137 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/core/types.h>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// ============================================================================
|
||||
// 文件变化事件
|
||||
// ============================================================================
|
||||
struct FileChangeEvent {
|
||||
std::string filepath;
|
||||
|
||||
enum class Type {
|
||||
Created,
|
||||
Modified,
|
||||
Deleted,
|
||||
Renamed
|
||||
} type;
|
||||
|
||||
uint64_t timestamp = 0;
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// 文件变化回调
|
||||
// ============================================================================
|
||||
using FileChangeCallback = std::function<void(const FileChangeEvent&)>;
|
||||
|
||||
// ============================================================================
|
||||
// Shader热重载管理器
|
||||
// ============================================================================
|
||||
class ShaderHotReloader {
|
||||
public:
|
||||
/**
|
||||
* @brief 获取单例实例
|
||||
* @return 热重载管理器实例引用
|
||||
*/
|
||||
static ShaderHotReloader& getInstance();
|
||||
|
||||
/**
|
||||
* @brief 初始化热重载系统
|
||||
* @return 初始化成功返回true,失败返回false
|
||||
*/
|
||||
bool init();
|
||||
|
||||
/**
|
||||
* @brief 关闭热重载系统
|
||||
*/
|
||||
void shutdown();
|
||||
|
||||
/**
|
||||
* @brief 注册Shader文件监视
|
||||
* @param shaderName Shader名称
|
||||
* @param filePaths 要监视的文件列表
|
||||
* @param callback 文件变化时的回调
|
||||
*/
|
||||
void watch(const std::string& shaderName,
|
||||
const std::vector<std::string>& filePaths,
|
||||
FileChangeCallback callback);
|
||||
|
||||
/**
|
||||
* @brief 取消监视
|
||||
* @param shaderName Shader名称
|
||||
*/
|
||||
void unwatch(const std::string& shaderName);
|
||||
|
||||
/**
|
||||
* @brief 更新文件监视(在主循环中调用)
|
||||
*/
|
||||
void update();
|
||||
|
||||
/**
|
||||
* @brief 启用/禁用热重载
|
||||
* @param enabled 是否启用
|
||||
*/
|
||||
void setEnabled(bool enabled);
|
||||
|
||||
/**
|
||||
* @brief 检查是否启用
|
||||
* @return 启用返回true,否则返回false
|
||||
*/
|
||||
bool isEnabled() const { return enabled_; }
|
||||
|
||||
/**
|
||||
* @brief 检查是否已初始化
|
||||
* @return 已初始化返回true,否则返回false
|
||||
*/
|
||||
bool isInitialized() const { return initialized_; }
|
||||
|
||||
private:
|
||||
ShaderHotReloader() = default;
|
||||
~ShaderHotReloader() = default;
|
||||
ShaderHotReloader(const ShaderHotReloader&) = delete;
|
||||
ShaderHotReloader& operator=(const ShaderHotReloader&) = delete;
|
||||
|
||||
bool enabled_ = false;
|
||||
bool initialized_ = false;
|
||||
|
||||
struct WatchInfo {
|
||||
std::vector<std::string> filePaths;
|
||||
FileChangeCallback callback;
|
||||
std::unordered_map<std::string, uint64_t> modifiedTimes;
|
||||
};
|
||||
std::unordered_map<std::string, WatchInfo> watchMap_;
|
||||
|
||||
#ifdef _WIN32
|
||||
HANDLE watchHandle_ = nullptr;
|
||||
std::vector<uint8_t> buffer_;
|
||||
std::string watchDir_;
|
||||
bool watching_ = false;
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief 轮询检查文件变化
|
||||
*/
|
||||
void pollChanges();
|
||||
|
||||
/**
|
||||
* @brief 获取文件修改时间
|
||||
* @param filepath 文件路径
|
||||
* @return 修改时间戳
|
||||
*/
|
||||
static uint64_t getFileModifiedTime(const std::string& filepath);
|
||||
};
|
||||
|
||||
// 便捷宏
|
||||
#define E2D_SHADER_HOT_RELOADER() ::extra2d::ShaderHotReloader::getInstance()
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -0,0 +1,152 @@
|
|||
#pragma once
|
||||
|
||||
#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 {
|
||||
|
||||
class Color;
|
||||
|
||||
// ============================================================================
|
||||
// Shader抽象接口 - 渲染后端无关
|
||||
// ============================================================================
|
||||
class IShader {
|
||||
public:
|
||||
virtual ~IShader() = default;
|
||||
|
||||
/**
|
||||
* @brief 绑定Shader程序
|
||||
*/
|
||||
virtual void bind() const = 0;
|
||||
|
||||
/**
|
||||
* @brief 解绑Shader程序
|
||||
*/
|
||||
virtual void unbind() const = 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 检查Shader是否有效
|
||||
* @return 有效返回true,否则返回false
|
||||
*/
|
||||
virtual bool isValid() const = 0;
|
||||
|
||||
/**
|
||||
* @brief 获取原生句柄(如OpenGL程序ID)
|
||||
* @return 原生句柄值
|
||||
*/
|
||||
virtual uint32_t getNativeHandle() const = 0;
|
||||
|
||||
/**
|
||||
* @brief 获取Shader名称
|
||||
* @return Shader名称
|
||||
*/
|
||||
virtual const std::string& getName() const = 0;
|
||||
|
||||
/**
|
||||
* @brief 设置Shader名称
|
||||
* @param name Shader名称
|
||||
*/
|
||||
virtual void setName(const std::string& name) = 0;
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Shader工厂接口 - 用于创建渲染后端特定的Shader实例
|
||||
// ============================================================================
|
||||
class IShaderFactory {
|
||||
public:
|
||||
virtual ~IShaderFactory() = default;
|
||||
|
||||
/**
|
||||
* @brief 从源码创建Shader
|
||||
* @param name Shader名称
|
||||
* @param vertSource 顶点着色器源码
|
||||
* @param fragSource 片段着色器源码
|
||||
* @return 创建的Shader实例
|
||||
*/
|
||||
virtual Ptr<IShader> createFromSource(
|
||||
const std::string& name,
|
||||
const std::string& vertSource,
|
||||
const std::string& fragSource) = 0;
|
||||
|
||||
/**
|
||||
* @brief 从缓存二进制创建Shader
|
||||
* @param name Shader名称
|
||||
* @param binary 编译后的二进制数据
|
||||
* @return 创建的Shader实例
|
||||
*/
|
||||
virtual Ptr<IShader> createFromBinary(
|
||||
const std::string& name,
|
||||
const std::vector<uint8_t>& binary) = 0;
|
||||
|
||||
/**
|
||||
* @brief 获取Shader的二进制数据(用于缓存)
|
||||
* @param shader Shader实例
|
||||
* @param outBinary 输出的二进制数据
|
||||
* @return 成功返回true,失败返回false
|
||||
*/
|
||||
virtual bool getShaderBinary(const IShader& shader,
|
||||
std::vector<uint8_t>& outBinary) = 0;
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -0,0 +1,227 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/core/types.h>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// ============================================================================
|
||||
// Shader加载结果
|
||||
// ============================================================================
|
||||
struct ShaderLoadResult {
|
||||
bool success = false;
|
||||
std::string errorMessage;
|
||||
std::string vertSource;
|
||||
std::string fragSource;
|
||||
std::vector<std::string> dependencies;
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Shader元数据
|
||||
// ============================================================================
|
||||
struct ShaderMetadata {
|
||||
std::string name;
|
||||
std::string vertPath;
|
||||
std::string fragPath;
|
||||
std::string combinedPath;
|
||||
uint64_t lastModified = 0;
|
||||
std::vector<std::string> defines;
|
||||
std::unordered_map<std::string, std::string> uniforms;
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// ShaderLoader接口 - 支持多种文件格式加载
|
||||
// ============================================================================
|
||||
class IShaderLoader {
|
||||
public:
|
||||
virtual ~IShaderLoader() = default;
|
||||
|
||||
/**
|
||||
* @brief 从分离文件加载Shader (.vert + .frag)
|
||||
* @param name Shader名称
|
||||
* @param vertPath 顶点着色器文件路径
|
||||
* @param fragPath 片段着色器文件路径
|
||||
* @return 加载结果
|
||||
*/
|
||||
virtual ShaderLoadResult loadFromSeparateFiles(
|
||||
const std::string& name,
|
||||
const std::string& vertPath,
|
||||
const std::string& fragPath) = 0;
|
||||
|
||||
/**
|
||||
* @brief 从组合文件加载Shader (.shader)
|
||||
* @param path 组合Shader文件路径
|
||||
* @return 加载结果
|
||||
*/
|
||||
virtual ShaderLoadResult loadFromCombinedFile(const std::string& path) = 0;
|
||||
|
||||
/**
|
||||
* @brief 从源码字符串加载Shader
|
||||
* @param vertSource 顶点着色器源码
|
||||
* @param fragSource 片段着色器源码
|
||||
* @return 加载结果
|
||||
*/
|
||||
virtual ShaderLoadResult loadFromSource(
|
||||
const std::string& vertSource,
|
||||
const std::string& fragSource) = 0;
|
||||
|
||||
/**
|
||||
* @brief 处理Shader源码中的#include指令
|
||||
* @param source 原始源码
|
||||
* @param baseDir 基础目录
|
||||
* @param outDependencies 输出依赖列表
|
||||
* @return 处理后的源码
|
||||
*/
|
||||
virtual std::string processIncludes(
|
||||
const std::string& source,
|
||||
const std::string& baseDir,
|
||||
std::vector<std::string>& outDependencies) = 0;
|
||||
|
||||
/**
|
||||
* @brief 应用预处理器定义
|
||||
* @param source 原始源码
|
||||
* @param defines 预处理器定义列表
|
||||
* @return 处理后的源码
|
||||
*/
|
||||
virtual std::string applyDefines(
|
||||
const std::string& source,
|
||||
const std::vector<std::string>& defines) = 0;
|
||||
|
||||
/**
|
||||
* @brief 获取Shader元数据
|
||||
* @param path Shader文件路径
|
||||
* @return 元数据
|
||||
*/
|
||||
virtual ShaderMetadata getMetadata(const std::string& path) = 0;
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// 默认ShaderLoader实现
|
||||
// ============================================================================
|
||||
class ShaderLoader : public IShaderLoader {
|
||||
public:
|
||||
ShaderLoader();
|
||||
~ShaderLoader() override = default;
|
||||
|
||||
/**
|
||||
* @brief 从分离文件加载Shader (.vert + .frag)
|
||||
* @param name Shader名称
|
||||
* @param vertPath 顶点着色器文件路径
|
||||
* @param fragPath 片段着色器文件路径
|
||||
* @return 加载结果
|
||||
*/
|
||||
ShaderLoadResult loadFromSeparateFiles(
|
||||
const std::string& name,
|
||||
const std::string& vertPath,
|
||||
const std::string& fragPath) override;
|
||||
|
||||
/**
|
||||
* @brief 从组合文件加载Shader (.shader)
|
||||
* @param path 组合Shader文件路径
|
||||
* @return 加载结果
|
||||
*/
|
||||
ShaderLoadResult loadFromCombinedFile(const std::string& path) override;
|
||||
|
||||
/**
|
||||
* @brief 从源码字符串加载Shader
|
||||
* @param vertSource 顶点着色器源码
|
||||
* @param fragSource 片段着色器源码
|
||||
* @return 加载结果
|
||||
*/
|
||||
ShaderLoadResult loadFromSource(
|
||||
const std::string& vertSource,
|
||||
const std::string& fragSource) override;
|
||||
|
||||
/**
|
||||
* @brief 处理Shader源码中的#include指令
|
||||
* @param source 原始源码
|
||||
* @param baseDir 基础目录
|
||||
* @param outDependencies 输出依赖列表
|
||||
* @return 处理后的源码
|
||||
*/
|
||||
std::string processIncludes(
|
||||
const std::string& source,
|
||||
const std::string& baseDir,
|
||||
std::vector<std::string>& outDependencies) override;
|
||||
|
||||
/**
|
||||
* @brief 应用预处理器定义
|
||||
* @param source 原始源码
|
||||
* @param defines 预处理器定义列表
|
||||
* @return 处理后的源码
|
||||
*/
|
||||
std::string applyDefines(
|
||||
const std::string& source,
|
||||
const std::vector<std::string>& defines) override;
|
||||
|
||||
/**
|
||||
* @brief 获取Shader元数据
|
||||
* @param path Shader文件路径
|
||||
* @return 元数据
|
||||
*/
|
||||
ShaderMetadata getMetadata(const std::string& path) override;
|
||||
|
||||
/**
|
||||
* @brief 添加include搜索路径
|
||||
* @param path 搜索路径
|
||||
*/
|
||||
void addIncludePath(const std::string& path);
|
||||
|
||||
/**
|
||||
* @brief 读取文件内容
|
||||
* @param filepath 文件路径
|
||||
* @return 文件内容字符串
|
||||
*/
|
||||
static std::string readFile(const std::string& filepath);
|
||||
|
||||
/**
|
||||
* @brief 获取文件修改时间
|
||||
* @param filepath 文件路径
|
||||
* @return 修改时间戳
|
||||
*/
|
||||
static uint64_t getFileModifiedTime(const std::string& filepath);
|
||||
|
||||
/**
|
||||
* @brief 检查文件是否存在
|
||||
* @param filepath 文件路径
|
||||
* @return 存在返回true,否则返回false
|
||||
*/
|
||||
static bool fileExists(const std::string& filepath);
|
||||
|
||||
private:
|
||||
std::vector<std::string> includePaths_;
|
||||
std::unordered_map<std::string, std::string> includeCache_;
|
||||
|
||||
/**
|
||||
* @brief 解析组合Shader文件
|
||||
* @param content 文件内容
|
||||
* @param outVert 输出顶点着色器源码
|
||||
* @param outFrag 输出片段着色器源码
|
||||
* @param outMetadata 输出元数据
|
||||
* @return 解析成功返回true,失败返回false
|
||||
*/
|
||||
bool parseCombinedFile(const std::string& content,
|
||||
std::string& outVert,
|
||||
std::string& outFrag,
|
||||
ShaderMetadata& outMetadata);
|
||||
|
||||
/**
|
||||
* @brief 解析元数据JSON块
|
||||
* @param jsonContent JSON内容
|
||||
* @param outMetadata 输出元数据
|
||||
* @return 解析成功返回true,失败返回false
|
||||
*/
|
||||
bool parseMetadata(const std::string& jsonContent, ShaderMetadata& outMetadata);
|
||||
|
||||
/**
|
||||
* @brief 查找include文件路径
|
||||
* @param includeName include文件名
|
||||
* @param baseDir 基础目录
|
||||
* @return 找到的完整路径,未找到返回空字符串
|
||||
*/
|
||||
std::string findIncludeFile(const std::string& includeName, const std::string& baseDir);
|
||||
};
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -0,0 +1,251 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/config/platform_detector.h>
|
||||
#include <extra2d/graphics/shader_cache.h>
|
||||
#include <extra2d/graphics/shader_hot_reloader.h>
|
||||
#include <extra2d/graphics/shader_interface.h>
|
||||
#include <extra2d/graphics/shader_loader.h>
|
||||
#include <functional>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// ============================================================================
|
||||
// Shader重载回调
|
||||
// ============================================================================
|
||||
using ShaderReloadCallback = std::function<void(Ptr<IShader> newShader)>;
|
||||
|
||||
// ============================================================================
|
||||
// Shader管理器 - 统一入口
|
||||
// ============================================================================
|
||||
class ShaderManager {
|
||||
public:
|
||||
/**
|
||||
* @brief 获取单例实例
|
||||
* @return Shader管理器实例引用
|
||||
*/
|
||||
static ShaderManager& getInstance();
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 初始化和关闭
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 使用平台默认路径初始化Shader系统
|
||||
* 自动检测平台并使用正确的路径(romfs/sdmc/相对路径)
|
||||
* @param factory 渲染后端Shader工厂
|
||||
* @param appName 应用名称(用于缓存目录)
|
||||
* @return 初始化成功返回true,失败返回false
|
||||
*/
|
||||
bool init(Ptr<IShaderFactory> factory, const std::string& appName = "extra2d");
|
||||
|
||||
/**
|
||||
* @brief 初始化Shader系统
|
||||
* @param shaderDir Shader文件目录
|
||||
* @param cacheDir 缓存目录
|
||||
* @param factory 渲染后端Shader工厂
|
||||
* @return 初始化成功返回true,失败返回false
|
||||
*/
|
||||
bool init(const std::string& shaderDir,
|
||||
const std::string& cacheDir,
|
||||
Ptr<IShaderFactory> factory);
|
||||
|
||||
/**
|
||||
* @brief 关闭Shader系统
|
||||
*/
|
||||
void shutdown();
|
||||
|
||||
/**
|
||||
* @brief 检查是否已初始化
|
||||
* @return 已初始化返回true,否则返回false
|
||||
*/
|
||||
bool isInitialized() const { return initialized_; }
|
||||
|
||||
/**
|
||||
* @brief 检查当前平台是否支持热重载
|
||||
* Switch平台使用romfs,不支持热重载
|
||||
* @return 支持热重载返回true
|
||||
*/
|
||||
bool isHotReloadSupported() const { return hotReloadSupported_; }
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Shader加载
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 从分离文件加载Shader
|
||||
* @param name Shader名称
|
||||
* @param vertPath 顶点着色器文件路径
|
||||
* @param fragPath 片段着色器文件路径
|
||||
* @return 加载的Shader实例
|
||||
*/
|
||||
Ptr<IShader> loadFromFiles(const std::string& name,
|
||||
const std::string& vertPath,
|
||||
const std::string& fragPath);
|
||||
|
||||
/**
|
||||
* @brief 从组合文件加载Shader
|
||||
* @param path 组合Shader文件路径
|
||||
* @return 加载的Shader实例
|
||||
*/
|
||||
Ptr<IShader> loadFromCombinedFile(const std::string& path);
|
||||
|
||||
/**
|
||||
* @brief 从源码加载Shader
|
||||
* @param name Shader名称
|
||||
* @param vertSource 顶点着色器源码
|
||||
* @param fragSource 片段着色器源码
|
||||
* @return 加载的Shader实例
|
||||
*/
|
||||
Ptr<IShader> loadFromSource(const std::string& name,
|
||||
const std::string& vertSource,
|
||||
const std::string& fragSource);
|
||||
|
||||
/**
|
||||
* @brief 获取已加载的Shader
|
||||
* @param name Shader名称
|
||||
* @return Shader实例,不存在返回nullptr
|
||||
*/
|
||||
Ptr<IShader> get(const std::string& name) const;
|
||||
|
||||
/**
|
||||
* @brief 检查Shader是否存在
|
||||
* @param name Shader名称
|
||||
* @return 存在返回true,否则返回false
|
||||
*/
|
||||
bool has(const std::string& name) const;
|
||||
|
||||
/**
|
||||
* @brief 移除Shader
|
||||
* @param name Shader名称
|
||||
*/
|
||||
void remove(const std::string& name);
|
||||
|
||||
/**
|
||||
* @brief 清除所有Shader
|
||||
*/
|
||||
void clear();
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 热重载
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 注册重载回调
|
||||
* @param name Shader名称
|
||||
* @param callback 重载回调函数
|
||||
*/
|
||||
void setReloadCallback(const std::string& name, ShaderReloadCallback callback);
|
||||
|
||||
/**
|
||||
* @brief 启用/禁用热重载
|
||||
* @param enabled 是否启用
|
||||
*/
|
||||
void setHotReloadEnabled(bool enabled);
|
||||
|
||||
/**
|
||||
* @brief 检查热重载是否启用
|
||||
* @return 启用返回true,否则返回false
|
||||
*/
|
||||
bool isHotReloadEnabled() const;
|
||||
|
||||
/**
|
||||
* @brief 更新热重载系统(主循环调用)
|
||||
*/
|
||||
void update();
|
||||
|
||||
/**
|
||||
* @brief 手动重载Shader
|
||||
* @param name Shader名称
|
||||
* @return 重载成功返回true,失败返回false
|
||||
*/
|
||||
bool reload(const std::string& name);
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 内置Shader
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 获取内置Shader
|
||||
* @param name 内置Shader名称
|
||||
* @return Shader实例
|
||||
*/
|
||||
Ptr<IShader> getBuiltin(const std::string& name);
|
||||
|
||||
/**
|
||||
* @brief 加载所有内置Shader
|
||||
* @return 加载成功返回true,失败返回false
|
||||
*/
|
||||
bool loadBuiltinShaders();
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 工具方法
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 获取Shader目录
|
||||
* @return Shader目录路径
|
||||
*/
|
||||
const std::string& getShaderDir() const { return shaderDir_; }
|
||||
|
||||
/**
|
||||
* @brief 获取ShaderLoader
|
||||
* @return ShaderLoader引用
|
||||
*/
|
||||
ShaderLoader& getLoader() { return loader_; }
|
||||
|
||||
private:
|
||||
ShaderManager() = default;
|
||||
~ShaderManager() = default;
|
||||
ShaderManager(const ShaderManager&) = delete;
|
||||
ShaderManager& operator=(const ShaderManager&) = delete;
|
||||
|
||||
std::string shaderDir_;
|
||||
std::string cacheDir_;
|
||||
Ptr<IShaderFactory> factory_;
|
||||
ShaderLoader loader_;
|
||||
|
||||
struct ShaderInfo {
|
||||
Ptr<IShader> shader;
|
||||
ShaderMetadata metadata;
|
||||
ShaderReloadCallback reloadCallback;
|
||||
std::string vertSource;
|
||||
std::string fragSource;
|
||||
std::vector<std::string> filePaths;
|
||||
};
|
||||
std::unordered_map<std::string, ShaderInfo> shaders_;
|
||||
|
||||
bool initialized_ = false;
|
||||
bool hotReloadEnabled_ = false;
|
||||
bool hotReloadSupported_ = true;
|
||||
|
||||
/**
|
||||
* @brief 从缓存加载Shader
|
||||
* @param name Shader名称
|
||||
* @param sourceHash 源码哈希值
|
||||
* @param vertSource 顶点着色器源码
|
||||
* @param fragSource 片段着色器源码
|
||||
* @return Shader实例
|
||||
*/
|
||||
Ptr<IShader> loadFromCache(const std::string& name,
|
||||
const std::string& sourceHash,
|
||||
const std::string& vertSource,
|
||||
const std::string& fragSource);
|
||||
|
||||
/**
|
||||
* @brief 创建内置Shader源码
|
||||
*/
|
||||
void createBuiltinShaderSources();
|
||||
|
||||
/**
|
||||
* @brief 处理文件变化事件
|
||||
* @param shaderName Shader名称
|
||||
* @param event 文件变化事件
|
||||
*/
|
||||
void handleFileChange(const std::string& shaderName, const FileChangeEvent& event);
|
||||
};
|
||||
|
||||
// 便捷宏
|
||||
#define E2D_SHADER_MANAGER() ::extra2d::ShaderManager::getInstance()
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/graphics/opengl/gl_shader.h>
|
||||
#include <extra2d/core/types.h>
|
||||
#include <extra2d/core/color.h>
|
||||
#include <extra2d/core/types.h>
|
||||
#include <extra2d/graphics/shader_interface.h>
|
||||
#include <glm/vec4.hpp>
|
||||
|
||||
namespace extra2d {
|
||||
|
|
@ -39,280 +39,73 @@ struct BlurParams {
|
|||
float radius = 5.0f;
|
||||
};
|
||||
|
||||
namespace ShaderSource {
|
||||
|
||||
static const char* StandardVert = R"(
|
||||
#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;
|
||||
}
|
||||
)";
|
||||
|
||||
static const char* StandardFrag = R"(
|
||||
#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;
|
||||
}
|
||||
}
|
||||
)";
|
||||
|
||||
static const char* WaterFrag = R"(
|
||||
#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;
|
||||
}
|
||||
}
|
||||
)";
|
||||
|
||||
static const char* OutlineFrag = R"(
|
||||
#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;
|
||||
}
|
||||
}
|
||||
)";
|
||||
|
||||
static const char* DistortionFrag = R"(
|
||||
#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;
|
||||
}
|
||||
}
|
||||
)";
|
||||
|
||||
static const char* PixelateFrag = R"(
|
||||
#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;
|
||||
}
|
||||
}
|
||||
)";
|
||||
|
||||
static const char* InvertFrag = R"(
|
||||
#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;
|
||||
}
|
||||
}
|
||||
)";
|
||||
|
||||
static const char* GrayscaleFrag = R"(
|
||||
#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;
|
||||
}
|
||||
}
|
||||
)";
|
||||
|
||||
static const char* BlurFrag = R"(
|
||||
#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;
|
||||
}
|
||||
}
|
||||
)";
|
||||
|
||||
} // namespace ShaderSource
|
||||
|
||||
class ShaderPreset {
|
||||
public:
|
||||
static Ptr<GLShader> Water(const WaterParams& params);
|
||||
static Ptr<GLShader> Outline(const OutlineParams& params);
|
||||
static Ptr<GLShader> Distortion(const DistortionParams& params);
|
||||
static Ptr<GLShader> Pixelate(const PixelateParams& params);
|
||||
static Ptr<GLShader> Invert(const InvertParams& params);
|
||||
static Ptr<GLShader> Grayscale(const GrayscaleParams& params);
|
||||
static Ptr<GLShader> Blur(const BlurParams& params);
|
||||
/**
|
||||
* @brief 创建水波纹效果着色器
|
||||
* @param params 水波纹效果参数
|
||||
* @return 配置好的着色器
|
||||
*/
|
||||
static Ptr<IShader> Water(const WaterParams& params = {});
|
||||
|
||||
static Ptr<GLShader> GrayscaleOutline(const GrayscaleParams& grayParams,
|
||||
/**
|
||||
* @brief 创建描边效果着色器
|
||||
* @param params 描边效果参数
|
||||
* @return 配置好的着色器
|
||||
*/
|
||||
static Ptr<IShader> Outline(const OutlineParams& params = {});
|
||||
|
||||
/**
|
||||
* @brief 创建扭曲效果着色器
|
||||
* @param params 扭曲效果参数
|
||||
* @return 配置好的着色器
|
||||
*/
|
||||
static Ptr<IShader> Distortion(const DistortionParams& params = {});
|
||||
|
||||
/**
|
||||
* @brief 创建像素化效果着色器
|
||||
* @param params 像素化效果参数
|
||||
* @return 配置好的着色器
|
||||
*/
|
||||
static Ptr<IShader> Pixelate(const PixelateParams& params = {});
|
||||
|
||||
/**
|
||||
* @brief 创建反相效果着色器
|
||||
* @param params 反相效果参数
|
||||
* @return 配置好的着色器
|
||||
*/
|
||||
static Ptr<IShader> Invert(const InvertParams& params = {});
|
||||
|
||||
/**
|
||||
* @brief 创建灰度效果着色器
|
||||
* @param params 灰度效果参数
|
||||
* @return 配置好的着色器
|
||||
*/
|
||||
static Ptr<IShader> Grayscale(const GrayscaleParams& params = {});
|
||||
|
||||
/**
|
||||
* @brief 创建模糊效果着色器
|
||||
* @param params 模糊效果参数
|
||||
* @return 配置好的着色器
|
||||
*/
|
||||
static Ptr<IShader> Blur(const BlurParams& params = {});
|
||||
|
||||
/**
|
||||
* @brief 创建灰度+描边组合效果着色器
|
||||
* @param grayParams 灰度效果参数
|
||||
* @param outlineParams 描边效果参数
|
||||
* @return 配置好的着色器
|
||||
*/
|
||||
static Ptr<IShader> GrayscaleOutline(const GrayscaleParams& grayParams,
|
||||
const OutlineParams& outlineParams);
|
||||
static Ptr<GLShader> PixelateInvert(const PixelateParams& pixParams,
|
||||
|
||||
/**
|
||||
* @brief 创建像素化+反相组合效果着色器
|
||||
* @param pixParams 像素化效果参数
|
||||
* @param invParams 反相效果参数
|
||||
* @return 配置好的着色器
|
||||
*/
|
||||
static Ptr<IShader> PixelateInvert(const PixelateParams& pixParams,
|
||||
const InvertParams& invParams);
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,179 +0,0 @@
|
|||
#pragma once
|
||||
|
||||
#include <extra2d/core/color.h>
|
||||
#include <extra2d/core/types.h>
|
||||
#include <extra2d/graphics/opengl/gl_shader.h>
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// ============================================================================
|
||||
// Shader参数绑定回调
|
||||
// ============================================================================
|
||||
using ShaderBindCallback = std::function<void(GLShader &)>;
|
||||
|
||||
// ============================================================================
|
||||
// Shader系统 - 管理所有Shader的加载、缓存和热重载
|
||||
// ============================================================================
|
||||
class ShaderSystem {
|
||||
public:
|
||||
// ------------------------------------------------------------------------
|
||||
// 单例访问
|
||||
// ------------------------------------------------------------------------
|
||||
static ShaderSystem &get();
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 初始化和关闭
|
||||
// ------------------------------------------------------------------------
|
||||
bool init();
|
||||
void shutdown();
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Shader加载
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 从文件加载Shader
|
||||
* @param name Shader名称(用于缓存)
|
||||
* @param vertPath 顶点着色器文件路径
|
||||
* @param fragPath 片段着色器文件路径
|
||||
* @return 加载的Shader,失败返回nullptr
|
||||
*/
|
||||
Ptr<GLShader> loadFromFile(const std::string &name,
|
||||
const std::string &vertPath,
|
||||
const std::string &fragPath);
|
||||
|
||||
/**
|
||||
* @brief 从源码字符串加载Shader
|
||||
* @param name Shader名称(用于缓存)
|
||||
* @param vertSource 顶点着色器源码
|
||||
* @param fragSource 片段着色器源码
|
||||
* @return 加载的Shader,失败返回nullptr
|
||||
*/
|
||||
Ptr<GLShader> loadFromSource(const std::string &name,
|
||||
const std::string &vertSource,
|
||||
const std::string &fragSource);
|
||||
|
||||
/**
|
||||
* @brief 从已编译的程序获取Shader
|
||||
* @param name Shader名称
|
||||
* @return 缓存的Shader,不存在返回nullptr
|
||||
*/
|
||||
Ptr<GLShader> get(const std::string &name);
|
||||
|
||||
/**
|
||||
* @brief 检查Shader是否存在
|
||||
*/
|
||||
bool has(const std::string &name) const;
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// Shader移除
|
||||
// ------------------------------------------------------------------------
|
||||
void remove(const std::string &name);
|
||||
void clear();
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 热重载支持
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 启用/禁用文件监视
|
||||
*/
|
||||
void setFileWatching(bool enable);
|
||||
bool isFileWatching() const { return fileWatching_; }
|
||||
|
||||
/**
|
||||
* @brief 更新文件监视(在主循环中调用)
|
||||
*/
|
||||
void updateFileWatching();
|
||||
|
||||
/**
|
||||
* @brief 重载指定Shader
|
||||
*/
|
||||
bool reload(const std::string &name);
|
||||
|
||||
/**
|
||||
* @brief 重载所有Shader
|
||||
*/
|
||||
void reloadAll();
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 内置Shader获取
|
||||
// ------------------------------------------------------------------------
|
||||
Ptr<GLShader> getBuiltinSpriteShader();
|
||||
Ptr<GLShader> getBuiltinParticleShader();
|
||||
Ptr<GLShader> getBuiltinPostProcessShader();
|
||||
Ptr<GLShader> getBuiltinShapeShader();
|
||||
|
||||
// ------------------------------------------------------------------------
|
||||
// 工具方法
|
||||
// ------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @brief 从文件读取文本内容
|
||||
*/
|
||||
static std::string readFile(const std::string &filepath);
|
||||
|
||||
/**
|
||||
* @brief 获取Shader文件的最后修改时间
|
||||
*/
|
||||
static uint64_t getFileModifiedTime(const std::string &filepath);
|
||||
|
||||
private:
|
||||
ShaderSystem() = default;
|
||||
~ShaderSystem() = default;
|
||||
ShaderSystem(const ShaderSystem &) = delete;
|
||||
ShaderSystem &operator=(const ShaderSystem &) = delete;
|
||||
|
||||
struct ShaderInfo {
|
||||
Ptr<GLShader> shader;
|
||||
std::string vertPath;
|
||||
std::string fragPath;
|
||||
uint64_t vertModifiedTime;
|
||||
uint64_t fragModifiedTime;
|
||||
bool isBuiltin;
|
||||
};
|
||||
|
||||
std::unordered_map<std::string, ShaderInfo> shaders_;
|
||||
bool fileWatching_ = false;
|
||||
float watchTimer_ = 0.0f;
|
||||
static constexpr float WATCH_INTERVAL = 1.0f; // 检查间隔(秒)
|
||||
|
||||
// 内置Shader缓存
|
||||
Ptr<GLShader> builtinSpriteShader_;
|
||||
Ptr<GLShader> builtinParticleShader_;
|
||||
Ptr<GLShader> builtinPostProcessShader_;
|
||||
Ptr<GLShader> builtinShapeShader_;
|
||||
|
||||
bool loadBuiltinShaders();
|
||||
void checkAndReload();
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// Shader参数包装器 - 简化Uniform设置
|
||||
// ============================================================================
|
||||
class ShaderParams {
|
||||
public:
|
||||
explicit ShaderParams(GLShader &shader);
|
||||
|
||||
ShaderParams &setBool(const std::string &name, bool value);
|
||||
ShaderParams &setInt(const std::string &name, int value);
|
||||
ShaderParams &setFloat(const std::string &name, float value);
|
||||
ShaderParams &setVec2(const std::string &name, const glm::vec2 &value);
|
||||
ShaderParams &setVec3(const std::string &name, const glm::vec3 &value);
|
||||
ShaderParams &setVec4(const std::string &name, const glm::vec4 &value);
|
||||
ShaderParams &setMat4(const std::string &name, const glm::mat4 &value);
|
||||
ShaderParams &setColor(const std::string &name, const Color &color);
|
||||
|
||||
private:
|
||||
GLShader &shader_;
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
// 便捷宏
|
||||
// ============================================================================
|
||||
#define E2D_SHADER_SYSTEM() ::extra2d::ShaderSystem::get()
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
// ============================================
|
||||
// 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 = 0.1;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
// ============================================
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
// ============================================
|
||||
// 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);
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
// ============================================
|
||||
// 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;
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
// ============================================
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,137 @@
|
|||
// ============================================
|
||||
// Common Color Functions
|
||||
// ============================================
|
||||
|
||||
#ifndef E2D_COLOR_GLSL
|
||||
#define E2D_COLOR_GLSL
|
||||
|
||||
/**
|
||||
* @brief RGB转灰度
|
||||
* @param color RGB颜色
|
||||
* @return 灰度值
|
||||
*/
|
||||
float rgbToGrayscale(vec3 color) {
|
||||
return dot(color, vec3(0.299, 0.587, 0.114));
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief RGB转HSV
|
||||
* @param c RGB颜色
|
||||
* @return HSV颜色
|
||||
*/
|
||||
vec3 rgbToHsv(vec3 c) {
|
||||
vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
|
||||
vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g));
|
||||
vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r));
|
||||
float d = q.x - min(q.w, q.y);
|
||||
float e = 1.0e-10;
|
||||
return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief HSV转RGB
|
||||
* @param c HSV颜色
|
||||
* @return RGB颜色
|
||||
*/
|
||||
vec3 hsvToRgb(vec3 c) {
|
||||
vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
|
||||
vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
|
||||
return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 调整亮度
|
||||
* @param color 原始颜色
|
||||
* @param amount 亮度调整量
|
||||
* @return 调整后的颜色
|
||||
*/
|
||||
vec3 adjustBrightness(vec3 color, float amount) {
|
||||
return color + amount;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 调整对比度
|
||||
* @param color 原始颜色
|
||||
* @param amount 对比度调整量
|
||||
* @return 调整后的颜色
|
||||
*/
|
||||
vec3 adjustContrast(vec3 color, float amount) {
|
||||
return (color - 0.5) * amount + 0.5;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 调整饱和度
|
||||
* @param color 原始颜色
|
||||
* @param amount 饱和度调整量
|
||||
* @return 调整后的颜色
|
||||
*/
|
||||
vec3 adjustSaturation(vec3 color, float amount) {
|
||||
float gray = rgbToGrayscale(color);
|
||||
return mix(vec3(gray), color, amount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 颜色混合(正片叠底)
|
||||
* @param a 底色
|
||||
* @param b 混合色
|
||||
* @return 混合结果
|
||||
*/
|
||||
vec3 blendMultiply(vec3 a, vec3 b) {
|
||||
return a * b;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 颜色混合(滤色)
|
||||
* @param a 底色
|
||||
* @param b 混合色
|
||||
* @return 混合结果
|
||||
*/
|
||||
vec3 blendScreen(vec3 a, vec3 b) {
|
||||
return 1.0 - (1.0 - a) * (1.0 - b);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 颜色混合(叠加)
|
||||
* @param a 底色
|
||||
* @param b 混合色
|
||||
* @return 混合结果
|
||||
*/
|
||||
vec3 blendOverlay(vec3 a, vec3 b) {
|
||||
return mix(
|
||||
2.0 * a * b,
|
||||
1.0 - 2.0 * (1.0 - a) * (1.0 - b),
|
||||
step(0.5, a)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 颜色调色
|
||||
* @param color 原始颜色
|
||||
* @param tintColor 色调颜色
|
||||
* @param amount 色调强度
|
||||
* @return 调色结果
|
||||
*/
|
||||
vec3 tint(vec3 color, vec3 tintColor, float amount) {
|
||||
return mix(color, tintColor, amount);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 预乘Alpha
|
||||
* @param color RGBA颜色
|
||||
* @return 预乘后的RGB颜色
|
||||
*/
|
||||
vec3 premultiplyAlpha(vec4 color) {
|
||||
return color.rgb * color.a;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 取消预乘Alpha
|
||||
* @param color RGB颜色
|
||||
* @param alpha Alpha值
|
||||
* @return 未预乘的RGB颜色
|
||||
*/
|
||||
vec3 unpremultiplyAlpha(vec3 color, float alpha) {
|
||||
return alpha > 0.0 ? color / alpha : color;
|
||||
}
|
||||
|
||||
#endif // E2D_COLOR_GLSL
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
// ============================================
|
||||
// Common Math Functions
|
||||
// ============================================
|
||||
|
||||
#ifndef E2D_MATH_GLSL
|
||||
#define E2D_MATH_GLSL
|
||||
|
||||
const float PI = 3.14159265359;
|
||||
const float E = 2.71828182846;
|
||||
|
||||
/**
|
||||
* @brief 角度转弧度
|
||||
* @param deg 角度值
|
||||
* @return 弧度值
|
||||
*/
|
||||
float degToRad(float deg) {
|
||||
return deg * PI / 180.0;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 弧度转角度
|
||||
* @param rad 弧度值
|
||||
* @return 角度值
|
||||
*/
|
||||
float radToDeg(float rad) {
|
||||
return rad * 180.0 / PI;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 线性插值
|
||||
* @param a 起始值
|
||||
* @param b 结束值
|
||||
* @param t 插值因子 [0, 1]
|
||||
* @return 插值结果
|
||||
*/
|
||||
float lerp(float a, float b, float t) {
|
||||
return a + (b - a) * t;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 平滑插值
|
||||
* @param edge0 下边界
|
||||
* @param edge1 上边界
|
||||
* @param x 输入值
|
||||
* @return 平滑插值结果
|
||||
*/
|
||||
float smoothStep(float edge0, float edge1, float x) {
|
||||
float t = clamp((x - edge0) / (edge1 - edge0), 0.0, 1.0);
|
||||
return t * t * (3.0 - 2.0 * t);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 2D向量线性插值
|
||||
*/
|
||||
vec2 lerpVec2(vec2 a, vec2 b, float t) {
|
||||
return a + (b - a) * t;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 计算两点之间的距离
|
||||
*/
|
||||
float distance2D(vec2 a, vec2 b) {
|
||||
return length(b - a);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 计算两点之间的距离平方
|
||||
*/
|
||||
float distance2DSquared(vec2 a, vec2 b) {
|
||||
vec2 diff = b - a;
|
||||
return dot(diff, diff);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 将值限制在范围内
|
||||
*/
|
||||
float clamp01(float x) {
|
||||
return clamp(x, 0.0, 1.0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 重复平铺
|
||||
*/
|
||||
float repeat(float x, float period) {
|
||||
return mod(x, period);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 镜像重复
|
||||
*/
|
||||
float mirrorRepeat(float x, float period) {
|
||||
float m = mod(x, period * 2.0);
|
||||
return m > period ? period * 2.0 - m : m;
|
||||
}
|
||||
|
||||
#endif // E2D_MATH_GLSL
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
// ============================================
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
// ============================================
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
// ============================================
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
// ============================================
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
// ============================================
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
// ============================================
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,61 @@
|
|||
// ============================================
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
// ============================================
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
// ============================================
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
|
|
@ -432,6 +432,77 @@ std::string PlatformDetector::getLogPath(const std::string& appName) {
|
|||
#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
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
#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_manager.h>
|
||||
#include <extra2d/graphics/vram_manager.h>
|
||||
#include <extra2d/platform/iwindow.h>
|
||||
#include <extra2d/utils/logger.h>
|
||||
|
|
@ -13,30 +14,6 @@
|
|||
|
||||
namespace extra2d {
|
||||
|
||||
// 形状渲染着色器 - 支持顶点颜色批处理 (GLES 3.2)
|
||||
static const char *SHAPE_VERTEX_SHADER = R"(
|
||||
#version 300 es
|
||||
precision highp float;
|
||||
layout(location = 0) in vec2 aPosition;
|
||||
layout(location = 1) in vec4 aColor;
|
||||
uniform mat4 uViewProjection;
|
||||
out vec4 vColor;
|
||||
void main() {
|
||||
gl_Position = uViewProjection * vec4(aPosition, 0.0, 1.0);
|
||||
vColor = aColor;
|
||||
}
|
||||
)";
|
||||
|
||||
static const char *SHAPE_FRAGMENT_SHADER = R"(
|
||||
#version 300 es
|
||||
precision highp float;
|
||||
in vec4 vColor;
|
||||
out vec4 fragColor;
|
||||
void main() {
|
||||
fragColor = vColor;
|
||||
}
|
||||
)";
|
||||
|
||||
// VBO 初始大小(用于 VRAM 跟踪)
|
||||
static constexpr size_t SHAPE_VBO_SIZE = 1024 * sizeof(float);
|
||||
|
||||
|
|
@ -85,7 +62,7 @@ GLRenderer::~GLRenderer() { shutdown(); }
|
|||
* @param window 窗口指针
|
||||
* @return 初始化成功返回true,失败返回false
|
||||
*/
|
||||
bool GLRenderer::init(IWindow* window) {
|
||||
bool GLRenderer::init(IWindow *window) {
|
||||
window_ = window;
|
||||
|
||||
// Switch: GL 上下文已通过 SDL2 + EGL 初始化,无需 glewInit()
|
||||
|
|
@ -125,8 +102,7 @@ void GLRenderer::shutdown() {
|
|||
|
||||
if (lineVbo_ != 0) {
|
||||
glDeleteBuffers(1, &lineVbo_);
|
||||
VRAMMgr::get().freeBuffer(MAX_LINE_VERTICES *
|
||||
sizeof(ShapeVertex));
|
||||
VRAMMgr::get().freeBuffer(MAX_LINE_VERTICES * sizeof(ShapeVertex));
|
||||
lineVbo_ = 0;
|
||||
}
|
||||
if (lineVao_ != 0) {
|
||||
|
|
@ -135,8 +111,7 @@ void GLRenderer::shutdown() {
|
|||
}
|
||||
if (shapeVbo_ != 0) {
|
||||
glDeleteBuffers(1, &shapeVbo_);
|
||||
VRAMMgr::get().freeBuffer(MAX_SHAPE_VERTICES *
|
||||
sizeof(ShapeVertex));
|
||||
VRAMMgr::get().freeBuffer(MAX_SHAPE_VERTICES * sizeof(ShapeVertex));
|
||||
shapeVbo_ = 0;
|
||||
}
|
||||
if (shapeVao_ != 0) {
|
||||
|
|
@ -657,8 +632,14 @@ void GLRenderer::resetStats() { stats_ = Stats{}; }
|
|||
* @brief 初始化形状渲染所需的OpenGL资源(VAO、VBO、着色器)
|
||||
*/
|
||||
void GLRenderer::initShapeRendering() {
|
||||
// 编译形状着色器
|
||||
shapeShader_.compileFromSource(SHAPE_VERTEX_SHADER, SHAPE_FRAGMENT_SHADER);
|
||||
// 从ShaderManager获取形状着色器
|
||||
shapeShader_ = ShaderManager::getInstance().getBuiltin("builtin_shape");
|
||||
if (!shapeShader_) {
|
||||
E2D_LOG_WARN("Failed to get builtin shape shader, loading from manager");
|
||||
if (!ShaderManager::getInstance().isInitialized()) {
|
||||
E2D_LOG_ERROR("ShaderManager not initialized, shape rendering may fail");
|
||||
}
|
||||
}
|
||||
|
||||
// 创建形状 VAO 和 VBO
|
||||
glGenVertexArrays(1, &shapeVao_);
|
||||
|
|
@ -703,10 +684,8 @@ void GLRenderer::initShapeRendering() {
|
|||
glBindVertexArray(0);
|
||||
|
||||
// VRAM 跟踪
|
||||
VRAMMgr::get().allocBuffer(MAX_SHAPE_VERTICES *
|
||||
sizeof(ShapeVertex));
|
||||
VRAMMgr::get().allocBuffer(MAX_LINE_VERTICES *
|
||||
sizeof(ShapeVertex));
|
||||
VRAMMgr::get().allocBuffer(MAX_SHAPE_VERTICES * sizeof(ShapeVertex));
|
||||
VRAMMgr::get().allocBuffer(MAX_LINE_VERTICES * sizeof(ShapeVertex));
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -769,8 +748,10 @@ void GLRenderer::flushShapeBatch() {
|
|||
if (shapeVertexCount_ == 0)
|
||||
return;
|
||||
|
||||
shapeShader_.bind();
|
||||
shapeShader_.setMat4("uViewProjection", viewProjection_);
|
||||
if (shapeShader_) {
|
||||
shapeShader_->bind();
|
||||
shapeShader_->setMat4("u_viewProjection", viewProjection_);
|
||||
}
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, shapeVbo_);
|
||||
glBufferSubData(GL_ARRAY_BUFFER, 0, shapeVertexCount_ * sizeof(ShapeVertex),
|
||||
|
|
@ -796,8 +777,10 @@ void GLRenderer::flushLineBatch() {
|
|||
flushShapeBatch();
|
||||
|
||||
glLineWidth(currentLineWidth_);
|
||||
shapeShader_.bind();
|
||||
shapeShader_.setMat4("uViewProjection", viewProjection_);
|
||||
if (shapeShader_) {
|
||||
shapeShader_->bind();
|
||||
shapeShader_->setMat4("u_viewProjection", viewProjection_);
|
||||
}
|
||||
|
||||
glBindBuffer(GL_ARRAY_BUFFER, lineVbo_);
|
||||
glBufferSubData(GL_ARRAY_BUFFER, 0, lineVertexCount_ * sizeof(ShapeVertex),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,325 @@
|
|||
#include <extra2d/graphics/opengl/gl_shader_new.h>
|
||||
#include <extra2d/utils/logger.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
/**
|
||||
* @brief 构造函数,初始化着色器程序ID为0
|
||||
*/
|
||||
GLShaderNew::GLShaderNew() : programID_(0) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 析构函数,删除OpenGL着色器程序
|
||||
*/
|
||||
GLShaderNew::~GLShaderNew() {
|
||||
if (programID_ != 0) {
|
||||
glDeleteProgram(programID_);
|
||||
programID_ = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 绑定Shader程序
|
||||
*/
|
||||
void GLShaderNew::bind() const {
|
||||
glUseProgram(programID_);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 解绑Shader程序
|
||||
*/
|
||||
void GLShaderNew::unbind() const {
|
||||
glUseProgram(0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 设置布尔类型uniform变量
|
||||
* @param name uniform变量名
|
||||
* @param value 布尔值
|
||||
*/
|
||||
void GLShaderNew::setBool(const std::string& name, bool value) {
|
||||
glUniform1i(getUniformLocation(name), value ? 1 : 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 设置整数类型uniform变量
|
||||
* @param name uniform变量名
|
||||
* @param value 整数值
|
||||
*/
|
||||
void GLShaderNew::setInt(const std::string& name, int value) {
|
||||
glUniform1i(getUniformLocation(name), value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 设置浮点类型uniform变量
|
||||
* @param name uniform变量名
|
||||
* @param value 浮点值
|
||||
*/
|
||||
void GLShaderNew::setFloat(const std::string& name, float value) {
|
||||
glUniform1f(getUniformLocation(name), value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 设置二维向量类型uniform变量
|
||||
* @param name uniform变量名
|
||||
* @param value 二维向量值
|
||||
*/
|
||||
void GLShaderNew::setVec2(const std::string& name, const glm::vec2& value) {
|
||||
glUniform2fv(getUniformLocation(name), 1, &value[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 设置三维向量类型uniform变量
|
||||
* @param name uniform变量名
|
||||
* @param value 三维向量值
|
||||
*/
|
||||
void GLShaderNew::setVec3(const std::string& name, const glm::vec3& value) {
|
||||
glUniform3fv(getUniformLocation(name), 1, &value[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 设置四维向量类型uniform变量
|
||||
* @param name uniform变量名
|
||||
* @param value 四维向量值
|
||||
*/
|
||||
void GLShaderNew::setVec4(const std::string& name, const glm::vec4& value) {
|
||||
glUniform4fv(getUniformLocation(name), 1, &value[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 设置4x4矩阵类型uniform变量
|
||||
* @param name uniform变量名
|
||||
* @param value 4x4矩阵值
|
||||
*/
|
||||
void GLShaderNew::setMat4(const std::string& name, const glm::mat4& value) {
|
||||
glUniformMatrix4fv(getUniformLocation(name), 1, GL_FALSE, &value[0][0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 设置颜色类型uniform变量
|
||||
* @param name uniform变量名
|
||||
* @param color 颜色值
|
||||
*/
|
||||
void GLShaderNew::setColor(const std::string& name, const Color& color) {
|
||||
glUniform4f(getUniformLocation(name), color.r, color.g, color.b, color.a);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 从源码编译Shader
|
||||
* @param vertexSource 顶点着色器源码
|
||||
* @param fragmentSource 片段着色器源码
|
||||
* @return 编译成功返回true,失败返回false
|
||||
*/
|
||||
bool GLShaderNew::compileFromSource(const char* vertexSource, const char* fragmentSource) {
|
||||
GLuint vertexShader = compileShader(GL_VERTEX_SHADER, vertexSource);
|
||||
if (vertexShader == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
GLuint fragmentShader = compileShader(GL_FRAGMENT_SHADER, fragmentSource);
|
||||
if (fragmentShader == 0) {
|
||||
glDeleteShader(vertexShader);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (programID_ != 0) {
|
||||
glDeleteProgram(programID_);
|
||||
uniformCache_.clear();
|
||||
}
|
||||
|
||||
programID_ = glCreateProgram();
|
||||
glAttachShader(programID_, vertexShader);
|
||||
glAttachShader(programID_, fragmentShader);
|
||||
glLinkProgram(programID_);
|
||||
|
||||
GLint success;
|
||||
glGetProgramiv(programID_, GL_LINK_STATUS, &success);
|
||||
if (!success) {
|
||||
char infoLog[512];
|
||||
glGetProgramInfoLog(programID_, 512, nullptr, infoLog);
|
||||
E2D_LOG_ERROR("Shader program linking failed: {}", infoLog);
|
||||
glDeleteProgram(programID_);
|
||||
programID_ = 0;
|
||||
}
|
||||
|
||||
glDeleteShader(vertexShader);
|
||||
glDeleteShader(fragmentShader);
|
||||
|
||||
return success == GL_TRUE;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 从二进制数据创建Shader
|
||||
* @param binary 二进制数据
|
||||
* @return 创建成功返回true,失败返回false
|
||||
*/
|
||||
bool GLShaderNew::compileFromBinary(const std::vector<uint8_t>& binary) {
|
||||
if (binary.empty()) {
|
||||
E2D_LOG_ERROR("Binary data is empty");
|
||||
return false;
|
||||
}
|
||||
|
||||
GLint numFormats = 0;
|
||||
glGetIntegerv(GL_NUM_PROGRAM_BINARY_FORMATS, &numFormats);
|
||||
if (numFormats == 0) {
|
||||
E2D_LOG_ERROR("Program binary formats not supported");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (programID_ != 0) {
|
||||
glDeleteProgram(programID_);
|
||||
uniformCache_.clear();
|
||||
}
|
||||
|
||||
programID_ = glCreateProgram();
|
||||
|
||||
GLenum binaryFormat = 0;
|
||||
glGetIntegerv(GL_PROGRAM_BINARY_FORMATS, reinterpret_cast<GLint*>(&binaryFormat));
|
||||
|
||||
glProgramBinary(programID_, binaryFormat, binary.data(), static_cast<GLsizei>(binary.size()));
|
||||
|
||||
GLint success = 0;
|
||||
glGetProgramiv(programID_, GL_LINK_STATUS, &success);
|
||||
if (!success) {
|
||||
char infoLog[512];
|
||||
glGetProgramInfoLog(programID_, 512, nullptr, infoLog);
|
||||
E2D_LOG_ERROR("Failed to load shader from binary: {}", infoLog);
|
||||
glDeleteProgram(programID_);
|
||||
programID_ = 0;
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取Shader二进制数据
|
||||
* @param outBinary 输出的二进制数据
|
||||
* @return 成功返回true,失败返回false
|
||||
*/
|
||||
bool GLShaderNew::getBinary(std::vector<uint8_t>& outBinary) {
|
||||
if (programID_ == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
GLint binaryLength = 0;
|
||||
glGetProgramiv(programID_, GL_PROGRAM_BINARY_LENGTH, &binaryLength);
|
||||
|
||||
if (binaryLength <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
outBinary.resize(binaryLength);
|
||||
|
||||
GLenum binaryFormat = 0;
|
||||
glGetProgramBinary(programID_, binaryLength, nullptr, &binaryFormat, outBinary.data());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 编译单个着色器
|
||||
* @param type 着色器类型
|
||||
* @param source 着色器源码
|
||||
* @return 着色器ID,失败返回0
|
||||
*/
|
||||
GLuint GLShaderNew::compileShader(GLenum type, const char* source) {
|
||||
GLuint shader = glCreateShader(type);
|
||||
glShaderSource(shader, 1, &source, nullptr);
|
||||
glCompileShader(shader);
|
||||
|
||||
GLint success;
|
||||
glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
|
||||
if (!success) {
|
||||
char infoLog[512];
|
||||
glGetShaderInfoLog(shader, 512, nullptr, infoLog);
|
||||
E2D_LOG_ERROR("Shader compilation failed: {}", infoLog);
|
||||
glDeleteShader(shader);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return shader;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取uniform位置
|
||||
* @param name uniform变量名
|
||||
* @return uniform位置
|
||||
*/
|
||||
GLint GLShaderNew::getUniformLocation(const std::string& name) {
|
||||
auto it = uniformCache_.find(name);
|
||||
if (it != uniformCache_.end()) {
|
||||
return it->second;
|
||||
}
|
||||
|
||||
GLint location = glGetUniformLocation(programID_, name.c_str());
|
||||
uniformCache_[name] = location;
|
||||
return location;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// GLShaderFactory 实现
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* @brief 从源码创建Shader
|
||||
* @param name Shader名称
|
||||
* @param vertSource 顶点着色器源码
|
||||
* @param fragSource 片段着色器源码
|
||||
* @return 创建的Shader实例
|
||||
*/
|
||||
Ptr<IShader> GLShaderFactory::createFromSource(
|
||||
const std::string& name,
|
||||
const std::string& vertSource,
|
||||
const std::string& fragSource) {
|
||||
|
||||
auto shader = std::make_shared<GLShaderNew>();
|
||||
shader->setName(name);
|
||||
|
||||
if (!shader->compileFromSource(vertSource.c_str(), fragSource.c_str())) {
|
||||
E2D_LOG_ERROR("Failed to compile shader from source: {}", name);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return shader;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 从缓存二进制创建Shader
|
||||
* @param name Shader名称
|
||||
* @param binary 编译后的二进制数据
|
||||
* @return 创建的Shader实例
|
||||
*/
|
||||
Ptr<IShader> GLShaderFactory::createFromBinary(
|
||||
const std::string& name,
|
||||
const std::vector<uint8_t>& binary) {
|
||||
|
||||
auto shader = std::make_shared<GLShaderNew>();
|
||||
shader->setName(name);
|
||||
|
||||
if (!shader->compileFromBinary(binary)) {
|
||||
E2D_LOG_ERROR("Failed to create shader from binary: {}", name);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return shader;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取Shader的二进制数据
|
||||
* @param shader Shader实例
|
||||
* @param outBinary 输出的二进制数据
|
||||
* @return 成功返回true,失败返回false
|
||||
*/
|
||||
bool GLShaderFactory::getShaderBinary(const IShader& shader, std::vector<uint8_t>& outBinary) {
|
||||
const GLShaderNew* glShader = dynamic_cast<const GLShaderNew*>(&shader);
|
||||
if (!glShader) {
|
||||
E2D_LOG_ERROR("Shader is not a GLShaderNew instance");
|
||||
return false;
|
||||
}
|
||||
|
||||
return const_cast<GLShaderNew*>(glShader)->getBinary(outBinary);
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -0,0 +1,278 @@
|
|||
#include <extra2d/graphics/shader_cache.h>
|
||||
#include <extra2d/utils/logger.h>
|
||||
#include <chrono>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
/**
|
||||
* @brief 获取单例实例
|
||||
* @return 缓存管理器实例引用
|
||||
*/
|
||||
ShaderCache& ShaderCache::getInstance() {
|
||||
static ShaderCache instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 初始化缓存系统
|
||||
* @param cacheDir 缓存目录路径
|
||||
* @return 初始化成功返回true,失败返回false
|
||||
*/
|
||||
bool ShaderCache::init(const std::string& cacheDir) {
|
||||
cacheDir_ = cacheDir;
|
||||
|
||||
if (!ensureCacheDirectory()) {
|
||||
E2D_LOG_ERROR("Failed to create cache directory: {}", cacheDir);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!loadCacheIndex()) {
|
||||
E2D_LOG_WARN("Failed to load cache index, starting fresh");
|
||||
}
|
||||
|
||||
initialized_ = true;
|
||||
E2D_LOG_INFO("Shader cache initialized at: {}", cacheDir);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 关闭缓存系统
|
||||
*/
|
||||
void ShaderCache::shutdown() {
|
||||
if (!initialized_) {
|
||||
return;
|
||||
}
|
||||
|
||||
saveCacheIndex();
|
||||
cacheMap_.clear();
|
||||
initialized_ = false;
|
||||
E2D_LOG_INFO("Shader cache shutdown");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 检查缓存是否有效
|
||||
* @param name Shader名称
|
||||
* @param sourceHash 源码哈希值
|
||||
* @return 缓存有效返回true,否则返回false
|
||||
*/
|
||||
bool ShaderCache::hasValidCache(const std::string& name, const std::string& sourceHash) {
|
||||
auto it = cacheMap_.find(name);
|
||||
if (it == cacheMap_.end()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return it->second.sourceHash == sourceHash;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 加载缓存的二进制数据
|
||||
* @param name Shader名称
|
||||
* @return 缓存条目指针,不存在返回nullptr
|
||||
*/
|
||||
Ptr<ShaderCacheEntry> ShaderCache::loadCache(const std::string& name) {
|
||||
auto it = cacheMap_.find(name);
|
||||
if (it == cacheMap_.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::string cachePath = getCachePath(name);
|
||||
std::ifstream file(cachePath, std::ios::binary);
|
||||
if (!file.is_open()) {
|
||||
E2D_LOG_WARN("Failed to open cache file: {}", cachePath);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto entry = std::make_shared<ShaderCacheEntry>(it->second);
|
||||
entry->binary.clear();
|
||||
|
||||
file.seekg(0, std::ios::end);
|
||||
size_t fileSize = static_cast<size_t>(file.tellg());
|
||||
file.seekg(0, std::ios::beg);
|
||||
|
||||
entry->binary.resize(fileSize);
|
||||
file.read(reinterpret_cast<char*>(entry->binary.data()), fileSize);
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 保存编译结果到缓存
|
||||
* @param entry 缓存条目
|
||||
* @return 保存成功返回true,失败返回false
|
||||
*/
|
||||
bool ShaderCache::saveCache(const ShaderCacheEntry& entry) {
|
||||
if (!initialized_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string cachePath = getCachePath(entry.name);
|
||||
std::ofstream file(cachePath, std::ios::binary);
|
||||
if (!file.is_open()) {
|
||||
E2D_LOG_ERROR("Failed to create cache file: {}", cachePath);
|
||||
return false;
|
||||
}
|
||||
|
||||
file.write(reinterpret_cast<const char*>(entry.binary.data()), entry.binary.size());
|
||||
file.close();
|
||||
|
||||
cacheMap_[entry.name] = entry;
|
||||
saveCacheIndex();
|
||||
|
||||
E2D_LOG_DEBUG("Shader cache saved: {}", entry.name);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 使缓存失效
|
||||
* @param name Shader名称
|
||||
*/
|
||||
void ShaderCache::invalidate(const std::string& name) {
|
||||
auto it = cacheMap_.find(name);
|
||||
if (it == cacheMap_.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::string cachePath = getCachePath(name);
|
||||
fs::remove(cachePath);
|
||||
|
||||
cacheMap_.erase(it);
|
||||
saveCacheIndex();
|
||||
|
||||
E2D_LOG_DEBUG("Shader cache invalidated: {}", name);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 清除所有缓存
|
||||
*/
|
||||
void ShaderCache::clearAll() {
|
||||
for (const auto& pair : cacheMap_) {
|
||||
std::string cachePath = getCachePath(pair.first);
|
||||
fs::remove(cachePath);
|
||||
}
|
||||
|
||||
cacheMap_.clear();
|
||||
saveCacheIndex();
|
||||
|
||||
E2D_LOG_INFO("All shader caches cleared");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 计算源码哈希值
|
||||
* @param vertSource 顶点着色器源码
|
||||
* @param fragSource 片段着色器源码
|
||||
* @return 哈希值字符串
|
||||
*/
|
||||
std::string ShaderCache::computeHash(const std::string& vertSource,
|
||||
const std::string& fragSource) {
|
||||
std::string combined = vertSource + fragSource;
|
||||
|
||||
uint32_t hash = 5381;
|
||||
for (char c : combined) {
|
||||
hash = ((hash << 5) + hash) + static_cast<uint32_t>(c);
|
||||
}
|
||||
|
||||
std::stringstream ss;
|
||||
ss << std::hex << hash;
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 加载缓存索引
|
||||
* @return 加载成功返回true,失败返回false
|
||||
*/
|
||||
bool ShaderCache::loadCacheIndex() {
|
||||
std::string indexPath = cacheDir_ + "/.cache_index";
|
||||
|
||||
if (!fs::exists(indexPath)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::ifstream file(indexPath);
|
||||
if (!file.is_open()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string line;
|
||||
while (std::getline(file, line)) {
|
||||
if (line.empty() || line[0] == '#') {
|
||||
continue;
|
||||
}
|
||||
|
||||
size_t pos = line.find('=');
|
||||
if (pos == std::string::npos) {
|
||||
continue;
|
||||
}
|
||||
|
||||
std::string name = line.substr(0, pos);
|
||||
std::string hash = line.substr(pos + 1);
|
||||
|
||||
std::string cachePath = getCachePath(name);
|
||||
if (fs::exists(cachePath)) {
|
||||
ShaderCacheEntry entry;
|
||||
entry.name = name;
|
||||
entry.sourceHash = hash;
|
||||
entry.compileTime = static_cast<uint64_t>(
|
||||
std::chrono::system_clock::now().time_since_epoch().count());
|
||||
cacheMap_[name] = entry;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 保存缓存索引
|
||||
* @return 保存成功返回true,失败返回false
|
||||
*/
|
||||
bool ShaderCache::saveCacheIndex() {
|
||||
std::string indexPath = cacheDir_ + "/.cache_index";
|
||||
|
||||
std::ofstream file(indexPath);
|
||||
if (!file.is_open()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
file << "# Extra2D Shader Cache Index\n";
|
||||
file << "# Format: name=hash\n";
|
||||
|
||||
for (const auto& pair : cacheMap_) {
|
||||
file << pair.first << "=" << pair.second.sourceHash << "\n";
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取缓存文件路径
|
||||
* @param name Shader名称
|
||||
* @return 缓存文件完整路径
|
||||
*/
|
||||
std::string ShaderCache::getCachePath(const std::string& name) const {
|
||||
return cacheDir_ + "/" + name + ".cache";
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 确保缓存目录存在
|
||||
* @return 目录存在或创建成功返回true,否则返回false
|
||||
*/
|
||||
bool ShaderCache::ensureCacheDirectory() {
|
||||
if (cacheDir_.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::error_code ec;
|
||||
if (!fs::exists(cacheDir_)) {
|
||||
if (!fs::create_directories(cacheDir_, ec)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -0,0 +1,164 @@
|
|||
#include <extra2d/graphics/shader_hot_reloader.h>
|
||||
#include <extra2d/utils/logger.h>
|
||||
#include <chrono>
|
||||
#include <filesystem>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
/**
|
||||
* @brief 获取单例实例
|
||||
* @return 热重载管理器实例引用
|
||||
*/
|
||||
ShaderHotReloader& ShaderHotReloader::getInstance() {
|
||||
static ShaderHotReloader instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 初始化热重载系统
|
||||
* @return 初始化成功返回true,失败返回false
|
||||
*/
|
||||
bool ShaderHotReloader::init() {
|
||||
if (initialized_) {
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
buffer_.resize(4096);
|
||||
#endif
|
||||
|
||||
initialized_ = true;
|
||||
E2D_LOG_INFO("Shader hot reloader initialized");
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 关闭热重载系统
|
||||
*/
|
||||
void ShaderHotReloader::shutdown() {
|
||||
if (!initialized_) {
|
||||
return;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
if (watchHandle_ != nullptr) {
|
||||
FindCloseChangeNotification(watchHandle_);
|
||||
watchHandle_ = nullptr;
|
||||
}
|
||||
#endif
|
||||
|
||||
watchMap_.clear();
|
||||
initialized_ = false;
|
||||
enabled_ = false;
|
||||
E2D_LOG_INFO("Shader hot reloader shutdown");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 注册Shader文件监视
|
||||
* @param shaderName Shader名称
|
||||
* @param filePaths 要监视的文件列表
|
||||
* @param callback 文件变化时的回调
|
||||
*/
|
||||
void ShaderHotReloader::watch(const std::string& shaderName,
|
||||
const std::vector<std::string>& filePaths,
|
||||
FileChangeCallback callback) {
|
||||
if (!initialized_) {
|
||||
E2D_LOG_WARN("Hot reloader not initialized");
|
||||
return;
|
||||
}
|
||||
|
||||
WatchInfo info;
|
||||
info.filePaths = filePaths;
|
||||
info.callback = callback;
|
||||
|
||||
for (const auto& path : filePaths) {
|
||||
info.modifiedTimes[path] = getFileModifiedTime(path);
|
||||
}
|
||||
|
||||
watchMap_[shaderName] = std::move(info);
|
||||
E2D_LOG_DEBUG("Watching shader: {} ({} files)", shaderName, filePaths.size());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 取消监视
|
||||
* @param shaderName Shader名称
|
||||
*/
|
||||
void ShaderHotReloader::unwatch(const std::string& shaderName) {
|
||||
auto it = watchMap_.find(shaderName);
|
||||
if (it != watchMap_.end()) {
|
||||
watchMap_.erase(it);
|
||||
E2D_LOG_DEBUG("Stopped watching shader: {}", shaderName);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 更新文件监视(在主循环中调用)
|
||||
*/
|
||||
void ShaderHotReloader::update() {
|
||||
if (!initialized_ || !enabled_) {
|
||||
return;
|
||||
}
|
||||
|
||||
pollChanges();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 启用/禁用热重载
|
||||
* @param enabled 是否启用
|
||||
*/
|
||||
void ShaderHotReloader::setEnabled(bool enabled) {
|
||||
enabled_ = enabled;
|
||||
E2D_LOG_DEBUG("Hot reload {}", enabled ? "enabled" : "disabled");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 轮询检查文件变化
|
||||
*/
|
||||
void ShaderHotReloader::pollChanges() {
|
||||
auto now = static_cast<uint64_t>(
|
||||
std::chrono::system_clock::now().time_since_epoch().count());
|
||||
|
||||
for (auto& pair : watchMap_) {
|
||||
WatchInfo& info = pair.second;
|
||||
|
||||
for (const auto& filePath : info.filePaths) {
|
||||
uint64_t currentModTime = getFileModifiedTime(filePath);
|
||||
uint64_t lastModTime = info.modifiedTimes[filePath];
|
||||
|
||||
if (currentModTime != 0 && lastModTime != 0 && currentModTime != lastModTime) {
|
||||
info.modifiedTimes[filePath] = currentModTime;
|
||||
|
||||
FileChangeEvent event;
|
||||
event.filepath = filePath;
|
||||
event.type = FileChangeEvent::Type::Modified;
|
||||
event.timestamp = now;
|
||||
|
||||
E2D_LOG_DEBUG("Shader file changed: {}", filePath);
|
||||
|
||||
if (info.callback) {
|
||||
info.callback(event);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取文件修改时间
|
||||
* @param filepath 文件路径
|
||||
* @return 修改时间戳
|
||||
*/
|
||||
uint64_t ShaderHotReloader::getFileModifiedTime(const std::string& filepath) {
|
||||
try {
|
||||
auto ftime = fs::last_write_time(filepath);
|
||||
auto sctp = std::chrono::time_point_cast<std::chrono::seconds>(
|
||||
ftime - fs::file_time_type::clock::now() + std::chrono::system_clock::now());
|
||||
return static_cast<uint64_t>(sctp.time_since_epoch().count());
|
||||
} catch (...) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -0,0 +1,451 @@
|
|||
#include <extra2d/graphics/shader_loader.h>
|
||||
#include <extra2d/utils/logger.h>
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
/**
|
||||
* @brief 构造函数,初始化Shader加载器
|
||||
*/
|
||||
ShaderLoader::ShaderLoader() {
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 从分离文件加载Shader (.vert + .frag)
|
||||
* @param name Shader名称
|
||||
* @param vertPath 顶点着色器文件路径
|
||||
* @param fragPath 片段着色器文件路径
|
||||
* @return 加载结果
|
||||
*/
|
||||
ShaderLoadResult ShaderLoader::loadFromSeparateFiles(
|
||||
const std::string& name,
|
||||
const std::string& vertPath,
|
||||
const std::string& fragPath) {
|
||||
|
||||
ShaderLoadResult result;
|
||||
|
||||
if (!fileExists(vertPath)) {
|
||||
result.errorMessage = "Vertex shader file not found: " + vertPath;
|
||||
E2D_LOG_ERROR("{}", result.errorMessage);
|
||||
return result;
|
||||
}
|
||||
|
||||
if (!fileExists(fragPath)) {
|
||||
result.errorMessage = "Fragment shader file not found: " + fragPath;
|
||||
E2D_LOG_ERROR("{}", result.errorMessage);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string vertSource = readFile(vertPath);
|
||||
std::string fragSource = readFile(fragPath);
|
||||
|
||||
if (vertSource.empty()) {
|
||||
result.errorMessage = "Failed to read vertex shader file: " + vertPath;
|
||||
E2D_LOG_ERROR("{}", result.errorMessage);
|
||||
return result;
|
||||
}
|
||||
|
||||
if (fragSource.empty()) {
|
||||
result.errorMessage = "Failed to read fragment shader file: " + fragPath;
|
||||
E2D_LOG_ERROR("{}", result.errorMessage);
|
||||
return result;
|
||||
}
|
||||
|
||||
fs::path vertDir = fs::path(vertPath).parent_path();
|
||||
fs::path fragDir = fs::path(fragPath).parent_path();
|
||||
|
||||
vertSource = processIncludes(vertSource, vertDir.string(), result.dependencies);
|
||||
fragSource = processIncludes(fragSource, fragDir.string(), result.dependencies);
|
||||
|
||||
result.vertSource = vertSource;
|
||||
result.fragSource = fragSource;
|
||||
result.success = true;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 从组合文件加载Shader (.shader)
|
||||
* @param path 组合Shader文件路径
|
||||
* @return 加载结果
|
||||
*/
|
||||
ShaderLoadResult ShaderLoader::loadFromCombinedFile(const std::string& path) {
|
||||
ShaderLoadResult result;
|
||||
|
||||
if (!fileExists(path)) {
|
||||
result.errorMessage = "Shader file not found: " + path;
|
||||
E2D_LOG_ERROR("{}", result.errorMessage);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::string content = readFile(path);
|
||||
if (content.empty()) {
|
||||
result.errorMessage = "Failed to read shader file: " + path;
|
||||
E2D_LOG_ERROR("{}", result.errorMessage);
|
||||
return result;
|
||||
}
|
||||
|
||||
ShaderMetadata metadata;
|
||||
std::string vertSource, fragSource;
|
||||
|
||||
if (!parseCombinedFile(content, vertSource, fragSource, metadata)) {
|
||||
result.errorMessage = "Failed to parse combined shader file: " + path;
|
||||
E2D_LOG_ERROR("{}", result.errorMessage);
|
||||
return result;
|
||||
}
|
||||
|
||||
fs::path baseDir = fs::path(path).parent_path();
|
||||
vertSource = processIncludes(vertSource, baseDir.string(), result.dependencies);
|
||||
fragSource = processIncludes(fragSource, baseDir.string(), result.dependencies);
|
||||
|
||||
result.vertSource = vertSource;
|
||||
result.fragSource = fragSource;
|
||||
result.success = true;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 从源码字符串加载Shader
|
||||
* @param vertSource 顶点着色器源码
|
||||
* @param fragSource 片段着色器源码
|
||||
* @return 加载结果
|
||||
*/
|
||||
ShaderLoadResult ShaderLoader::loadFromSource(
|
||||
const std::string& vertSource,
|
||||
const std::string& fragSource) {
|
||||
|
||||
ShaderLoadResult result;
|
||||
result.vertSource = vertSource;
|
||||
result.fragSource = fragSource;
|
||||
result.success = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 处理Shader源码中的#include指令
|
||||
* @param source 原始源码
|
||||
* @param baseDir 基础目录
|
||||
* @param outDependencies 输出依赖列表
|
||||
* @return 处理后的源码
|
||||
*/
|
||||
std::string ShaderLoader::processIncludes(
|
||||
const std::string& source,
|
||||
const std::string& baseDir,
|
||||
std::vector<std::string>& outDependencies) {
|
||||
|
||||
std::string result;
|
||||
std::istringstream stream(source);
|
||||
std::string line;
|
||||
|
||||
while (std::getline(stream, line)) {
|
||||
size_t includePos = line.find("#include");
|
||||
if (includePos != std::string::npos) {
|
||||
size_t startQuote = line.find('"', includePos);
|
||||
size_t endQuote = line.find('"', startQuote + 1);
|
||||
|
||||
if (startQuote != std::string::npos && endQuote != std::string::npos) {
|
||||
std::string includeName = line.substr(startQuote + 1, endQuote - startQuote - 1);
|
||||
std::string includePath = findIncludeFile(includeName, baseDir);
|
||||
|
||||
if (!includePath.empty()) {
|
||||
auto cacheIt = includeCache_.find(includePath);
|
||||
std::string includeContent;
|
||||
|
||||
if (cacheIt != includeCache_.end()) {
|
||||
includeContent = cacheIt->second;
|
||||
} else {
|
||||
includeContent = readFile(includePath);
|
||||
includeCache_[includePath] = includeContent;
|
||||
}
|
||||
|
||||
outDependencies.push_back(includePath);
|
||||
result += includeContent;
|
||||
result += "\n";
|
||||
continue;
|
||||
} else {
|
||||
E2D_LOG_WARN("Include file not found: {}", includeName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
result += line;
|
||||
result += "\n";
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 应用预处理器定义
|
||||
* @param source 原始源码
|
||||
* @param defines 预处理器定义列表
|
||||
* @return 处理后的源码
|
||||
*/
|
||||
std::string ShaderLoader::applyDefines(
|
||||
const std::string& source,
|
||||
const std::vector<std::string>& defines) {
|
||||
|
||||
if (defines.empty()) {
|
||||
return source;
|
||||
}
|
||||
|
||||
std::string defineBlock;
|
||||
for (const auto& def : defines) {
|
||||
defineBlock += "#define " + def + "\n";
|
||||
}
|
||||
|
||||
std::string result;
|
||||
std::istringstream stream(source);
|
||||
std::string line;
|
||||
bool inserted = false;
|
||||
|
||||
while (std::getline(stream, line)) {
|
||||
if (!inserted && (line.find("#version") != std::string::npos ||
|
||||
line.find("precision") != std::string::npos)) {
|
||||
result += line + "\n";
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!inserted) {
|
||||
result += defineBlock;
|
||||
inserted = true;
|
||||
}
|
||||
|
||||
result += line + "\n";
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取Shader元数据
|
||||
* @param path Shader文件路径
|
||||
* @return 元数据
|
||||
*/
|
||||
ShaderMetadata ShaderLoader::getMetadata(const std::string& path) {
|
||||
ShaderMetadata metadata;
|
||||
|
||||
if (!fileExists(path)) {
|
||||
return metadata;
|
||||
}
|
||||
|
||||
metadata.combinedPath = path;
|
||||
metadata.lastModified = getFileModifiedTime(path);
|
||||
|
||||
fs::path p(path);
|
||||
metadata.name = p.stem().string();
|
||||
|
||||
return metadata;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 添加include搜索路径
|
||||
* @param path 搜索路径
|
||||
*/
|
||||
void ShaderLoader::addIncludePath(const std::string& path) {
|
||||
if (std::find(includePaths_.begin(), includePaths_.end(), path) == includePaths_.end()) {
|
||||
includePaths_.push_back(path);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 读取文件内容
|
||||
* @param filepath 文件路径
|
||||
* @return 文件内容字符串
|
||||
*/
|
||||
std::string ShaderLoader::readFile(const std::string& filepath) {
|
||||
std::ifstream file(filepath, std::ios::binary);
|
||||
if (!file.is_open()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
std::ostringstream content;
|
||||
content << file.rdbuf();
|
||||
return content.str();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取文件修改时间
|
||||
* @param filepath 文件路径
|
||||
* @return 修改时间戳
|
||||
*/
|
||||
uint64_t ShaderLoader::getFileModifiedTime(const std::string& filepath) {
|
||||
#ifdef __SWITCH__
|
||||
(void)filepath;
|
||||
return 1;
|
||||
#else
|
||||
try {
|
||||
auto ftime = fs::last_write_time(filepath);
|
||||
auto sctp = std::chrono::time_point_cast<std::chrono::seconds>(
|
||||
ftime - fs::file_time_type::clock::now() + std::chrono::system_clock::now());
|
||||
return static_cast<uint64_t>(sctp.time_since_epoch().count());
|
||||
} catch (...) {
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 检查文件是否存在
|
||||
* @param filepath 文件路径
|
||||
* @return 存在返回true,否则返回false
|
||||
*/
|
||||
bool ShaderLoader::fileExists(const std::string& filepath) {
|
||||
return fs::exists(filepath);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 解析组合Shader文件
|
||||
* @param content 文件内容
|
||||
* @param outVert 输出顶点着色器源码
|
||||
* @param outFrag 输出片段着色器源码
|
||||
* @param outMetadata 输出元数据
|
||||
* @return 解析成功返回true,失败返回false
|
||||
*/
|
||||
bool ShaderLoader::parseCombinedFile(const std::string& content,
|
||||
std::string& outVert,
|
||||
std::string& outFrag,
|
||||
ShaderMetadata& outMetadata) {
|
||||
enum class Section {
|
||||
None,
|
||||
Meta,
|
||||
Vertex,
|
||||
Fragment
|
||||
};
|
||||
|
||||
Section currentSection = Section::None;
|
||||
std::string metaContent;
|
||||
std::string vertContent;
|
||||
std::string fragContent;
|
||||
|
||||
std::istringstream stream(content);
|
||||
std::string line;
|
||||
|
||||
while (std::getline(stream, line)) {
|
||||
std::string trimmedLine = line;
|
||||
size_t start = trimmedLine.find_first_not_of(" \t\r\n");
|
||||
if (start != std::string::npos) {
|
||||
trimmedLine = trimmedLine.substr(start);
|
||||
}
|
||||
size_t end = trimmedLine.find_last_not_of(" \t\r\n");
|
||||
if (end != std::string::npos) {
|
||||
trimmedLine = trimmedLine.substr(0, end + 1);
|
||||
}
|
||||
|
||||
if (trimmedLine == "#meta") {
|
||||
currentSection = Section::Meta;
|
||||
continue;
|
||||
} else if (trimmedLine == "#vertex") {
|
||||
currentSection = Section::Vertex;
|
||||
continue;
|
||||
} else if (trimmedLine == "#fragment") {
|
||||
currentSection = Section::Fragment;
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (currentSection) {
|
||||
case Section::Meta:
|
||||
metaContent += line + "\n";
|
||||
break;
|
||||
case Section::Vertex:
|
||||
vertContent += line + "\n";
|
||||
break;
|
||||
case Section::Fragment:
|
||||
fragContent += line + "\n";
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (vertContent.empty() || fragContent.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!metaContent.empty()) {
|
||||
parseMetadata(metaContent, outMetadata);
|
||||
}
|
||||
|
||||
outVert = vertContent;
|
||||
outFrag = fragContent;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 解析元数据JSON块
|
||||
* @param jsonContent JSON内容
|
||||
* @param outMetadata 输出元数据
|
||||
* @return 解析成功返回true,失败返回false
|
||||
*/
|
||||
bool ShaderLoader::parseMetadata(const std::string& jsonContent, ShaderMetadata& outMetadata) {
|
||||
std::string content = jsonContent;
|
||||
|
||||
size_t start = content.find('{');
|
||||
size_t end = content.rfind('}');
|
||||
if (start == std::string::npos || end == std::string::npos || end <= start) {
|
||||
return false;
|
||||
}
|
||||
|
||||
content = content.substr(start, end - start + 1);
|
||||
|
||||
auto extractString = [&content](const std::string& key) -> std::string {
|
||||
std::string searchKey = "\"" + key + "\"";
|
||||
size_t keyPos = content.find(searchKey);
|
||||
if (keyPos == std::string::npos) {
|
||||
return "";
|
||||
}
|
||||
|
||||
size_t colonPos = content.find(':', keyPos);
|
||||
if (colonPos == std::string::npos) {
|
||||
return "";
|
||||
}
|
||||
|
||||
size_t quoteStart = content.find('"', colonPos);
|
||||
if (quoteStart == std::string::npos) {
|
||||
return "";
|
||||
}
|
||||
|
||||
size_t quoteEnd = content.find('"', quoteStart + 1);
|
||||
if (quoteEnd == std::string::npos) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return content.substr(quoteStart + 1, quoteEnd - quoteStart - 1);
|
||||
};
|
||||
|
||||
outMetadata.name = extractString("name");
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 查找include文件路径
|
||||
* @param includeName include文件名
|
||||
* @param baseDir 基础目录
|
||||
* @return 找到的完整路径,未找到返回空字符串
|
||||
*/
|
||||
std::string ShaderLoader::findIncludeFile(const std::string& includeName, const std::string& baseDir) {
|
||||
fs::path basePath(baseDir);
|
||||
fs::path includePath = basePath / includeName;
|
||||
|
||||
if (fs::exists(includePath)) {
|
||||
return includePath.string();
|
||||
}
|
||||
|
||||
for (const auto& searchPath : includePaths_) {
|
||||
includePath = fs::path(searchPath) / includeName;
|
||||
if (fs::exists(includePath)) {
|
||||
return includePath.string();
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -0,0 +1,525 @@
|
|||
#include <extra2d/graphics/shader_manager.h>
|
||||
#include <extra2d/utils/logger.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
/**
|
||||
* @brief 获取单例实例
|
||||
* @return Shader管理器实例引用
|
||||
*/
|
||||
ShaderManager& ShaderManager::getInstance() {
|
||||
static ShaderManager instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 使用平台默认路径初始化Shader系统
|
||||
* 自动检测平台并使用正确的路径(romfs/sdmc/相对路径)
|
||||
* @param factory 渲染后端Shader工厂
|
||||
* @param appName 应用名称(用于缓存目录)
|
||||
* @return 初始化成功返回true,失败返回false
|
||||
*/
|
||||
bool ShaderManager::init(Ptr<IShaderFactory> factory, const std::string& appName) {
|
||||
std::string shaderDir = PlatformDetector::getShaderPath(appName);
|
||||
std::string cacheDir = PlatformDetector::getShaderCachePath(appName);
|
||||
|
||||
hotReloadSupported_ = PlatformDetector::supportsHotReload();
|
||||
|
||||
E2D_LOG_INFO("Platform: {} (HotReload: {})",
|
||||
PlatformDetector::platformName(),
|
||||
hotReloadSupported_ ? "supported" : "not supported");
|
||||
|
||||
return init(shaderDir, cacheDir, factory);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 初始化Shader系统
|
||||
* @param shaderDir Shader文件目录
|
||||
* @param cacheDir 缓存目录
|
||||
* @param factory 渲染后端Shader工厂
|
||||
* @return 初始化成功返回true,失败返回false
|
||||
*/
|
||||
bool ShaderManager::init(const std::string& shaderDir,
|
||||
const std::string& cacheDir,
|
||||
Ptr<IShaderFactory> factory) {
|
||||
if (initialized_) {
|
||||
E2D_LOG_WARN("ShaderManager already initialized");
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!factory) {
|
||||
E2D_LOG_ERROR("Shader factory is null");
|
||||
return false;
|
||||
}
|
||||
|
||||
shaderDir_ = shaderDir;
|
||||
cacheDir_ = cacheDir;
|
||||
factory_ = factory;
|
||||
|
||||
hotReloadSupported_ = PlatformDetector::supportsHotReload();
|
||||
|
||||
#ifdef __SWITCH__
|
||||
if (!ShaderCache::getInstance().init(cacheDir_)) {
|
||||
E2D_LOG_WARN("Failed to initialize shader cache on Switch");
|
||||
}
|
||||
#else
|
||||
if (!ShaderCache::getInstance().init(cacheDir_)) {
|
||||
E2D_LOG_WARN("Failed to initialize shader cache, caching disabled");
|
||||
}
|
||||
#endif
|
||||
|
||||
if (hotReloadSupported_) {
|
||||
if (!ShaderHotReloader::getInstance().init()) {
|
||||
E2D_LOG_WARN("Failed to initialize hot reloader");
|
||||
}
|
||||
}
|
||||
|
||||
loader_.addIncludePath(shaderDir_ + "common");
|
||||
|
||||
initialized_ = true;
|
||||
E2D_LOG_INFO("ShaderManager initialized");
|
||||
E2D_LOG_INFO(" Shader directory: {}", shaderDir_);
|
||||
E2D_LOG_INFO(" Cache directory: {}", cacheDir_);
|
||||
E2D_LOG_INFO(" Hot reload: {}", hotReloadSupported_ ? "supported" : "not supported");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 关闭Shader系统
|
||||
*/
|
||||
void ShaderManager::shutdown() {
|
||||
if (!initialized_) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (hotReloadSupported_) {
|
||||
ShaderHotReloader::getInstance().shutdown();
|
||||
}
|
||||
ShaderCache::getInstance().shutdown();
|
||||
|
||||
shaders_.clear();
|
||||
factory_.reset();
|
||||
initialized_ = false;
|
||||
|
||||
E2D_LOG_INFO("ShaderManager shutdown");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 从分离文件加载Shader
|
||||
* @param name Shader名称
|
||||
* @param vertPath 顶点着色器文件路径
|
||||
* @param fragPath 片段着色器文件路径
|
||||
* @return 加载的Shader实例
|
||||
*/
|
||||
Ptr<IShader> ShaderManager::loadFromFiles(const std::string& name,
|
||||
const std::string& vertPath,
|
||||
const std::string& fragPath) {
|
||||
if (!initialized_) {
|
||||
E2D_LOG_ERROR("ShaderManager not initialized");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto it = shaders_.find(name);
|
||||
if (it != shaders_.end()) {
|
||||
return it->second.shader;
|
||||
}
|
||||
|
||||
ShaderLoadResult result = loader_.loadFromSeparateFiles(name, vertPath, fragPath);
|
||||
if (!result.success) {
|
||||
E2D_LOG_ERROR("Failed to load shader files: {} - {}", vertPath, fragPath);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::string sourceHash = ShaderCache::computeHash(result.vertSource, result.fragSource);
|
||||
Ptr<IShader> shader = loadFromCache(name, sourceHash, result.vertSource, result.fragSource);
|
||||
|
||||
if (!shader) {
|
||||
shader = factory_->createFromSource(name, result.vertSource, result.fragSource);
|
||||
if (!shader) {
|
||||
E2D_LOG_ERROR("Failed to create shader from source: {}", name);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> binary;
|
||||
if (factory_->getShaderBinary(*shader, binary)) {
|
||||
ShaderCacheEntry entry;
|
||||
entry.name = name;
|
||||
entry.sourceHash = sourceHash;
|
||||
entry.binary = binary;
|
||||
entry.dependencies = result.dependencies;
|
||||
ShaderCache::getInstance().saveCache(entry);
|
||||
}
|
||||
}
|
||||
|
||||
ShaderInfo info;
|
||||
info.shader = shader;
|
||||
info.vertSource = result.vertSource;
|
||||
info.fragSource = result.fragSource;
|
||||
info.filePaths = {vertPath, fragPath};
|
||||
info.filePaths.insert(info.filePaths.end(), result.dependencies.begin(), result.dependencies.end());
|
||||
|
||||
info.metadata.name = name;
|
||||
info.metadata.vertPath = vertPath;
|
||||
info.metadata.fragPath = fragPath;
|
||||
|
||||
shaders_[name] = std::move(info);
|
||||
|
||||
if (hotReloadEnabled_ && hotReloadSupported_) {
|
||||
auto callback = [this, name](const FileChangeEvent& event) {
|
||||
this->handleFileChange(name, event);
|
||||
};
|
||||
ShaderHotReloader::getInstance().watch(name, shaders_[name].filePaths, callback);
|
||||
}
|
||||
|
||||
E2D_LOG_DEBUG("Shader loaded: {}", name);
|
||||
return shader;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 从组合文件加载Shader
|
||||
* @param path 组合Shader文件路径
|
||||
* @return 加载的Shader实例
|
||||
*/
|
||||
Ptr<IShader> ShaderManager::loadFromCombinedFile(const std::string& path) {
|
||||
if (!initialized_) {
|
||||
E2D_LOG_ERROR("ShaderManager not initialized");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ShaderMetadata metadata = loader_.getMetadata(path);
|
||||
std::string name = metadata.name.empty() ? path : metadata.name;
|
||||
|
||||
auto it = shaders_.find(name);
|
||||
if (it != shaders_.end()) {
|
||||
return it->second.shader;
|
||||
}
|
||||
|
||||
ShaderLoadResult result = loader_.loadFromCombinedFile(path);
|
||||
if (!result.success) {
|
||||
E2D_LOG_ERROR("Failed to load combined shader file: {}", path);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::string sourceHash = ShaderCache::computeHash(result.vertSource, result.fragSource);
|
||||
Ptr<IShader> shader = loadFromCache(name, sourceHash, result.vertSource, result.fragSource);
|
||||
|
||||
if (!shader) {
|
||||
shader = factory_->createFromSource(name, result.vertSource, result.fragSource);
|
||||
if (!shader) {
|
||||
E2D_LOG_ERROR("Failed to create shader from source: {}", name);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::vector<uint8_t> binary;
|
||||
if (factory_->getShaderBinary(*shader, binary)) {
|
||||
ShaderCacheEntry entry;
|
||||
entry.name = name;
|
||||
entry.sourceHash = sourceHash;
|
||||
entry.binary = binary;
|
||||
entry.dependencies = result.dependencies;
|
||||
ShaderCache::getInstance().saveCache(entry);
|
||||
}
|
||||
}
|
||||
|
||||
ShaderInfo info;
|
||||
info.shader = shader;
|
||||
info.vertSource = result.vertSource;
|
||||
info.fragSource = result.fragSource;
|
||||
info.filePaths = {path};
|
||||
info.filePaths.insert(info.filePaths.end(), result.dependencies.begin(), result.dependencies.end());
|
||||
info.metadata = metadata;
|
||||
|
||||
shaders_[name] = std::move(info);
|
||||
|
||||
if (hotReloadEnabled_ && hotReloadSupported_) {
|
||||
auto callback = [this, name](const FileChangeEvent& event) {
|
||||
this->handleFileChange(name, event);
|
||||
};
|
||||
ShaderHotReloader::getInstance().watch(name, shaders_[name].filePaths, callback);
|
||||
}
|
||||
|
||||
E2D_LOG_DEBUG("Shader loaded from combined file: {}", name);
|
||||
return shader;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 从源码加载Shader
|
||||
* @param name Shader名称
|
||||
* @param vertSource 顶点着色器源码
|
||||
* @param fragSource 片段着色器源码
|
||||
* @return 加载的Shader实例
|
||||
*/
|
||||
Ptr<IShader> ShaderManager::loadFromSource(const std::string& name,
|
||||
const std::string& vertSource,
|
||||
const std::string& fragSource) {
|
||||
if (!initialized_) {
|
||||
E2D_LOG_ERROR("ShaderManager not initialized");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto it = shaders_.find(name);
|
||||
if (it != shaders_.end()) {
|
||||
return it->second.shader;
|
||||
}
|
||||
|
||||
Ptr<IShader> shader = factory_->createFromSource(name, vertSource, fragSource);
|
||||
if (!shader) {
|
||||
E2D_LOG_ERROR("Failed to create shader from source: {}", name);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ShaderInfo info;
|
||||
info.shader = shader;
|
||||
info.vertSource = vertSource;
|
||||
info.fragSource = fragSource;
|
||||
info.metadata.name = name;
|
||||
|
||||
shaders_[name] = std::move(info);
|
||||
|
||||
E2D_LOG_DEBUG("Shader loaded from source: {}", name);
|
||||
return shader;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取已加载的Shader
|
||||
* @param name Shader名称
|
||||
* @return Shader实例,不存在返回nullptr
|
||||
*/
|
||||
Ptr<IShader> ShaderManager::get(const std::string& name) const {
|
||||
auto it = shaders_.find(name);
|
||||
if (it != shaders_.end()) {
|
||||
return it->second.shader;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 检查Shader是否存在
|
||||
* @param name Shader名称
|
||||
* @return 存在返回true,否则返回false
|
||||
*/
|
||||
bool ShaderManager::has(const std::string& name) const {
|
||||
return shaders_.find(name) != shaders_.end();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 移除Shader
|
||||
* @param name Shader名称
|
||||
*/
|
||||
void ShaderManager::remove(const std::string& name) {
|
||||
auto it = shaders_.find(name);
|
||||
if (it != shaders_.end()) {
|
||||
ShaderHotReloader::getInstance().unwatch(name);
|
||||
shaders_.erase(it);
|
||||
E2D_LOG_DEBUG("Shader removed: {}", name);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 清除所有Shader
|
||||
*/
|
||||
void ShaderManager::clear() {
|
||||
if (hotReloadSupported_) {
|
||||
for (const auto& pair : shaders_) {
|
||||
ShaderHotReloader::getInstance().unwatch(pair.first);
|
||||
}
|
||||
}
|
||||
shaders_.clear();
|
||||
E2D_LOG_DEBUG("All shaders cleared");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 注册重载回调
|
||||
* @param name Shader名称
|
||||
* @param callback 重载回调函数
|
||||
*/
|
||||
void ShaderManager::setReloadCallback(const std::string& name, ShaderReloadCallback callback) {
|
||||
auto it = shaders_.find(name);
|
||||
if (it != shaders_.end()) {
|
||||
it->second.reloadCallback = callback;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 启用/禁用热重载
|
||||
* @param enabled 是否启用
|
||||
*/
|
||||
void ShaderManager::setHotReloadEnabled(bool enabled) {
|
||||
if (!hotReloadSupported_) {
|
||||
E2D_LOG_WARN("Hot reload not supported on this platform");
|
||||
return;
|
||||
}
|
||||
hotReloadEnabled_ = enabled;
|
||||
ShaderHotReloader::getInstance().setEnabled(enabled);
|
||||
E2D_LOG_INFO("Hot reload {}", enabled ? "enabled" : "disabled");
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 检查热重载是否启用
|
||||
* @return 启用返回true,否则返回false
|
||||
*/
|
||||
bool ShaderManager::isHotReloadEnabled() const {
|
||||
return hotReloadEnabled_ && hotReloadSupported_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 更新热重载系统(主循环调用)
|
||||
*/
|
||||
void ShaderManager::update() {
|
||||
if (hotReloadEnabled_ && hotReloadSupported_) {
|
||||
ShaderHotReloader::getInstance().update();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 手动重载Shader
|
||||
* @param name Shader名称
|
||||
* @return 重载成功返回true,失败返回false
|
||||
*/
|
||||
bool ShaderManager::reload(const std::string& name) {
|
||||
auto it = shaders_.find(name);
|
||||
if (it == shaders_.end()) {
|
||||
E2D_LOG_WARN("Shader not found for reload: {}", name);
|
||||
return false;
|
||||
}
|
||||
|
||||
ShaderInfo& info = it->second;
|
||||
|
||||
std::string vertSource = info.vertSource;
|
||||
std::string fragSource = info.fragSource;
|
||||
|
||||
if (!info.metadata.vertPath.empty() && !info.metadata.fragPath.empty()) {
|
||||
ShaderLoadResult result = loader_.loadFromSeparateFiles(name, info.metadata.vertPath, info.metadata.fragPath);
|
||||
if (result.success) {
|
||||
vertSource = result.vertSource;
|
||||
fragSource = result.fragSource;
|
||||
}
|
||||
} else if (!info.metadata.combinedPath.empty()) {
|
||||
ShaderLoadResult result = loader_.loadFromCombinedFile(info.metadata.combinedPath);
|
||||
if (result.success) {
|
||||
vertSource = result.vertSource;
|
||||
fragSource = result.fragSource;
|
||||
}
|
||||
}
|
||||
|
||||
Ptr<IShader> newShader = factory_->createFromSource(name, vertSource, fragSource);
|
||||
if (!newShader) {
|
||||
E2D_LOG_ERROR("Failed to reload shader: {}", name);
|
||||
return false;
|
||||
}
|
||||
|
||||
info.shader = newShader;
|
||||
info.vertSource = vertSource;
|
||||
info.fragSource = fragSource;
|
||||
|
||||
if (info.reloadCallback) {
|
||||
info.reloadCallback(newShader);
|
||||
}
|
||||
|
||||
E2D_LOG_INFO("Shader reloaded: {}", name);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取内置Shader
|
||||
* @param name 内置Shader名称
|
||||
* @return Shader实例
|
||||
*/
|
||||
Ptr<IShader> ShaderManager::getBuiltin(const std::string& name) {
|
||||
Ptr<IShader> shader = get(name);
|
||||
if (shader) {
|
||||
return shader;
|
||||
}
|
||||
|
||||
std::string path = shaderDir_ + "builtin/" + name + ".shader";
|
||||
return loadFromCombinedFile(path);
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 加载所有内置Shader
|
||||
* @return 加载成功返回true,失败返回false
|
||||
*/
|
||||
bool ShaderManager::loadBuiltinShaders() {
|
||||
if (!initialized_) {
|
||||
E2D_LOG_ERROR("ShaderManager not initialized");
|
||||
return false;
|
||||
}
|
||||
|
||||
bool allSuccess = true;
|
||||
|
||||
const char* builtinNames[] = {
|
||||
"sprite",
|
||||
"particle",
|
||||
"shape",
|
||||
"postprocess",
|
||||
"font"
|
||||
};
|
||||
|
||||
for (const char* name : builtinNames) {
|
||||
std::string path = shaderDir_ + "builtin/" + name + ".shader";
|
||||
std::string shaderName = std::string("builtin_") + name;
|
||||
|
||||
if (!loadFromCombinedFile(path)) {
|
||||
E2D_LOG_ERROR("Failed to load builtin {} shader from: {}", name, path);
|
||||
allSuccess = false;
|
||||
} else {
|
||||
auto it = shaders_.find(name);
|
||||
if (it != shaders_.end()) {
|
||||
shaders_[shaderName] = it->second;
|
||||
shaders_.erase(it);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (allSuccess) {
|
||||
E2D_LOG_INFO("All builtin shaders loaded");
|
||||
}
|
||||
|
||||
return allSuccess;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 从缓存加载Shader
|
||||
* @param name Shader名称
|
||||
* @param sourceHash 源码哈希值
|
||||
* @param vertSource 顶点着色器源码
|
||||
* @param fragSource 片段着色器源码
|
||||
* @return Shader实例
|
||||
*/
|
||||
Ptr<IShader> ShaderManager::loadFromCache(const std::string& name,
|
||||
const std::string& sourceHash,
|
||||
const std::string& vertSource,
|
||||
const std::string& fragSource) {
|
||||
if (!ShaderCache::getInstance().isInitialized()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (!ShaderCache::getInstance().hasValidCache(name, sourceHash)) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Ptr<ShaderCacheEntry> entry = ShaderCache::getInstance().loadCache(name);
|
||||
if (!entry || entry->binary.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Ptr<IShader> shader = factory_->createFromBinary(name, entry->binary);
|
||||
if (shader) {
|
||||
E2D_LOG_DEBUG("Shader loaded from cache: {}", name);
|
||||
}
|
||||
|
||||
return shader;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 处理文件变化事件
|
||||
* @param shaderName Shader名称
|
||||
* @param event 文件变化事件
|
||||
*/
|
||||
void ShaderManager::handleFileChange(const std::string& shaderName, const FileChangeEvent& event) {
|
||||
E2D_LOG_DEBUG("Shader file changed: {} -> {}", shaderName, event.filepath);
|
||||
reload(shaderName);
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
@ -1,304 +1,183 @@
|
|||
#include <extra2d/graphics/shader_manager.h>
|
||||
#include <extra2d/graphics/shader_preset.h>
|
||||
#include <extra2d/utils/logger.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// ============================================================================
|
||||
// ShaderPreset实现
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* @brief 创建水波纹效果着色器
|
||||
*
|
||||
* 创建并配置一个水波纹效果的GLShader对象,包含波动速度、振幅和频率参数
|
||||
*
|
||||
* @param params 水波纹效果参数,包含waveSpeed、waveAmplitude和waveFrequency
|
||||
* @return 成功返回配置好的着色器智能指针,失败返回nullptr
|
||||
* @param params 水波纹效果参数
|
||||
* @return 配置好的着色器
|
||||
*/
|
||||
Ptr<GLShader> ShaderPreset::Water(const WaterParams ¶ms) {
|
||||
auto shader = std::make_shared<GLShader>();
|
||||
|
||||
if (!shader->compileFromSource(ShaderSource::StandardVert,
|
||||
ShaderSource::WaterFrag)) {
|
||||
E2D_ERROR("编译水波纹Shader失败");
|
||||
Ptr<IShader> ShaderPreset::Water(const WaterParams ¶ms) {
|
||||
Ptr<IShader> shader = ShaderManager::getInstance().get("water");
|
||||
if (!shader) {
|
||||
E2D_LOG_ERROR("Failed to get water shader");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// 设置默认参数
|
||||
shader->setFloat("u_waveSpeed", params.waveSpeed);
|
||||
shader->setFloat("u_waveAmplitude", params.waveAmplitude);
|
||||
shader->setFloat("u_waveFrequency", params.waveFrequency);
|
||||
|
||||
E2D_INFO("创建水波纹Shader预设");
|
||||
return shader;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 创建描边效果着色器
|
||||
*
|
||||
* 创建并配置一个描边效果的GLShader对象,包含描边颜色和厚度参数
|
||||
*
|
||||
* @param params 描边效果参数,包含color和thickness
|
||||
* @return 成功返回配置好的着色器智能指针,失败返回nullptr
|
||||
* @param params 描边效果参数
|
||||
* @return 配置好的着色器
|
||||
*/
|
||||
Ptr<GLShader> ShaderPreset::Outline(const OutlineParams ¶ms) {
|
||||
auto shader = std::make_shared<GLShader>();
|
||||
|
||||
if (!shader->compileFromSource(ShaderSource::StandardVert,
|
||||
ShaderSource::OutlineFrag)) {
|
||||
E2D_ERROR("编译描边Shader失败");
|
||||
Ptr<IShader> ShaderPreset::Outline(const OutlineParams ¶ms) {
|
||||
Ptr<IShader> shader = ShaderManager::getInstance().get("outline");
|
||||
if (!shader) {
|
||||
E2D_LOG_ERROR("Failed to get outline shader");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// 设置默认参数
|
||||
shader->setVec4("u_outlineColor", glm::vec4(params.color.r, params.color.g,
|
||||
params.color.b, params.color.a));
|
||||
shader->setFloat("u_thickness", params.thickness);
|
||||
|
||||
E2D_INFO("创建描边Shader预设");
|
||||
return shader;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 创建扭曲效果着色器
|
||||
*
|
||||
* 创建并配置一个扭曲效果的GLShader对象,包含扭曲强度和时间缩放参数
|
||||
*
|
||||
* @param params 扭曲效果参数,包含distortionAmount和timeScale
|
||||
* @return 成功返回配置好的着色器智能指针,失败返回nullptr
|
||||
* @param params 扭曲效果参数
|
||||
* @return 配置好的着色器
|
||||
*/
|
||||
Ptr<GLShader> ShaderPreset::Distortion(const DistortionParams ¶ms) {
|
||||
auto shader = std::make_shared<GLShader>();
|
||||
|
||||
if (!shader->compileFromSource(ShaderSource::StandardVert,
|
||||
ShaderSource::DistortionFrag)) {
|
||||
E2D_ERROR("编译扭曲Shader失败");
|
||||
Ptr<IShader> ShaderPreset::Distortion(const DistortionParams ¶ms) {
|
||||
Ptr<IShader> shader = ShaderManager::getInstance().get("distortion");
|
||||
if (!shader) {
|
||||
E2D_LOG_ERROR("Failed to get distortion shader");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// 设置默认参数
|
||||
shader->setFloat("u_distortionAmount", params.distortionAmount);
|
||||
shader->setFloat("u_timeScale", params.timeScale);
|
||||
|
||||
E2D_INFO("创建扭曲Shader预设");
|
||||
return shader;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 创建像素化效果着色器
|
||||
*
|
||||
* 创建并配置一个像素化效果的GLShader对象,包含像素大小参数
|
||||
*
|
||||
* @param params 像素化效果参数,包含pixelSize
|
||||
* @return 成功返回配置好的着色器智能指针,失败返回nullptr
|
||||
* @param params 像素化效果参数
|
||||
* @return 配置好的着色器
|
||||
*/
|
||||
Ptr<GLShader> ShaderPreset::Pixelate(const PixelateParams ¶ms) {
|
||||
auto shader = std::make_shared<GLShader>();
|
||||
|
||||
if (!shader->compileFromSource(ShaderSource::StandardVert,
|
||||
ShaderSource::PixelateFrag)) {
|
||||
E2D_ERROR("编译像素化Shader失败");
|
||||
Ptr<IShader> ShaderPreset::Pixelate(const PixelateParams ¶ms) {
|
||||
Ptr<IShader> shader = ShaderManager::getInstance().get("pixelate");
|
||||
if (!shader) {
|
||||
E2D_LOG_ERROR("Failed to get pixelate shader");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// 设置默认参数
|
||||
shader->setFloat("u_pixelSize", params.pixelSize);
|
||||
|
||||
E2D_INFO("创建像素化Shader预设");
|
||||
return shader;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 创建反相效果着色器
|
||||
*
|
||||
* 创建并配置一个颜色反相效果的GLShader对象,包含反相强度参数
|
||||
*
|
||||
* @param params 反相效果参数,包含strength
|
||||
* @return 成功返回配置好的着色器智能指针,失败返回nullptr
|
||||
* @param params 反相效果参数
|
||||
* @return 配置好的着色器
|
||||
*/
|
||||
Ptr<GLShader> ShaderPreset::Invert(const InvertParams ¶ms) {
|
||||
auto shader = std::make_shared<GLShader>();
|
||||
|
||||
if (!shader->compileFromSource(ShaderSource::StandardVert,
|
||||
ShaderSource::InvertFrag)) {
|
||||
E2D_ERROR("编译反相Shader失败");
|
||||
Ptr<IShader> ShaderPreset::Invert(const InvertParams ¶ms) {
|
||||
Ptr<IShader> shader = ShaderManager::getInstance().get("invert");
|
||||
if (!shader) {
|
||||
E2D_LOG_ERROR("Failed to get invert shader");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// 设置默认参数
|
||||
shader->setFloat("u_strength", params.strength);
|
||||
|
||||
E2D_INFO("创建反相Shader预设");
|
||||
return shader;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 创建灰度效果着色器
|
||||
*
|
||||
* 创建并配置一个灰度效果的GLShader对象,包含灰度强度参数
|
||||
*
|
||||
* @param params 灰度效果参数,包含intensity
|
||||
* @return 成功返回配置好的着色器智能指针,失败返回nullptr
|
||||
* @param params 灰度效果参数
|
||||
* @return 配置好的着色器
|
||||
*/
|
||||
Ptr<GLShader> ShaderPreset::Grayscale(const GrayscaleParams ¶ms) {
|
||||
auto shader = std::make_shared<GLShader>();
|
||||
|
||||
if (!shader->compileFromSource(ShaderSource::StandardVert,
|
||||
ShaderSource::GrayscaleFrag)) {
|
||||
E2D_ERROR("编译灰度Shader失败");
|
||||
Ptr<IShader> ShaderPreset::Grayscale(const GrayscaleParams ¶ms) {
|
||||
Ptr<IShader> shader = ShaderManager::getInstance().get("grayscale");
|
||||
if (!shader) {
|
||||
E2D_LOG_ERROR("Failed to get grayscale shader");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// 设置默认参数
|
||||
shader->setFloat("u_intensity", params.intensity);
|
||||
|
||||
E2D_INFO("创建灰度Shader预设");
|
||||
return shader;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 创建模糊效果着色器
|
||||
*
|
||||
* 创建并配置一个模糊效果的GLShader对象,包含模糊半径参数
|
||||
*
|
||||
* @param params 模糊效果参数,包含radius
|
||||
* @return 成功返回配置好的着色器智能指针,失败返回nullptr
|
||||
* @param params 模糊效果参数
|
||||
* @return 配置好的着色器
|
||||
*/
|
||||
Ptr<GLShader> ShaderPreset::Blur(const BlurParams ¶ms) {
|
||||
auto shader = std::make_shared<GLShader>();
|
||||
|
||||
if (!shader->compileFromSource(ShaderSource::StandardVert,
|
||||
ShaderSource::BlurFrag)) {
|
||||
E2D_ERROR("编译模糊Shader失败");
|
||||
Ptr<IShader> ShaderPreset::Blur(const BlurParams ¶ms) {
|
||||
Ptr<IShader> shader = ShaderManager::getInstance().get("blur");
|
||||
if (!shader) {
|
||||
E2D_LOG_ERROR("Failed to get blur shader");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// 设置默认参数
|
||||
shader->setFloat("u_radius", params.radius);
|
||||
|
||||
E2D_INFO("创建模糊Shader预设");
|
||||
return shader;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 创建灰度+描边组合效果着色器
|
||||
*
|
||||
* 创建并配置一个同时应用灰度和描边效果的GLShader对象
|
||||
*
|
||||
* @param grayParams 灰度效果参数,包含intensity
|
||||
* @param outlineParams 描边效果参数,包含color和thickness
|
||||
* @return 成功返回配置好的着色器智能指针,失败返回nullptr
|
||||
* @param grayParams 灰度效果参数
|
||||
* @param outlineParams 描边效果参数
|
||||
* @return 配置好的着色器
|
||||
*/
|
||||
Ptr<GLShader>
|
||||
Ptr<IShader>
|
||||
ShaderPreset::GrayscaleOutline(const GrayscaleParams &grayParams,
|
||||
const OutlineParams &outlineParams) {
|
||||
// 创建组合效果的片段着色器 (GLES 3.2)
|
||||
const char *combinedFrag = R"(
|
||||
#version 300 es
|
||||
precision highp float;
|
||||
in vec2 v_texCoord;
|
||||
std::string shaderDir = ShaderManager::getInstance().getShaderDir();
|
||||
std::string shaderPath = shaderDir + "effects/grayscale_outline.shader";
|
||||
|
||||
uniform sampler2D u_texture;
|
||||
uniform float u_grayIntensity;
|
||||
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 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;
|
||||
}
|
||||
}
|
||||
)";
|
||||
|
||||
auto shader = std::make_shared<GLShader>();
|
||||
|
||||
if (!shader->compileFromSource(ShaderSource::StandardVert, combinedFrag)) {
|
||||
E2D_ERROR("编译灰度+描边Shader失败");
|
||||
Ptr<IShader> shader =
|
||||
ShaderManager::getInstance().loadFromCombinedFile(shaderPath);
|
||||
if (!shader) {
|
||||
E2D_LOG_ERROR("Failed to load grayscale_outline shader from: {}",
|
||||
shaderPath);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// 设置默认参数
|
||||
shader->setFloat("u_grayIntensity", grayParams.intensity);
|
||||
shader->setVec4("u_outlineColor",
|
||||
glm::vec4(outlineParams.color.r, outlineParams.color.g,
|
||||
outlineParams.color.b, outlineParams.color.a));
|
||||
shader->setFloat("u_thickness", outlineParams.thickness);
|
||||
|
||||
E2D_INFO("创建灰度+描边组合Shader预设");
|
||||
return shader;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 创建像素化+反相组合效果着色器
|
||||
*
|
||||
* 创建并配置一个同时应用像素化和反相效果的GLShader对象
|
||||
*
|
||||
* @param pixParams 像素化效果参数,包含pixelSize
|
||||
* @param invParams 反相效果参数,包含strength
|
||||
* @return 成功返回配置好的着色器智能指针,失败返回nullptr
|
||||
* @param pixParams 像素化效果参数
|
||||
* @param invParams 反相效果参数
|
||||
* @return 配置好的着色器
|
||||
*/
|
||||
Ptr<GLShader> ShaderPreset::PixelateInvert(const PixelateParams &pixParams,
|
||||
Ptr<IShader> ShaderPreset::PixelateInvert(const PixelateParams &pixParams,
|
||||
const InvertParams &invParams) {
|
||||
// 创建组合效果的片段着色器 (GLES 3.2)
|
||||
const char *combinedFrag = R"(
|
||||
#version 300 es
|
||||
precision highp float;
|
||||
in vec2 v_texCoord;
|
||||
std::string shaderDir = ShaderManager::getInstance().getShaderDir();
|
||||
std::string shaderPath = shaderDir + "effects/pixelate_invert.shader";
|
||||
|
||||
uniform sampler2D u_texture;
|
||||
uniform float u_pixelSize;
|
||||
uniform vec2 u_textureSize;
|
||||
uniform float u_invertStrength;
|
||||
|
||||
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);
|
||||
|
||||
// 反相
|
||||
vec3 inverted = 1.0 - color.rgb;
|
||||
color.rgb = mix(color.rgb, inverted, u_invertStrength);
|
||||
|
||||
fragColor = color;
|
||||
}
|
||||
)";
|
||||
|
||||
auto shader = std::make_shared<GLShader>();
|
||||
|
||||
if (!shader->compileFromSource(ShaderSource::StandardVert, combinedFrag)) {
|
||||
E2D_ERROR("编译像素化+反相Shader失败");
|
||||
Ptr<IShader> shader =
|
||||
ShaderManager::getInstance().loadFromCombinedFile(shaderPath);
|
||||
if (!shader) {
|
||||
E2D_LOG_ERROR("Failed to load pixelate_invert shader from: {}", shaderPath);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// 设置默认参数
|
||||
shader->setFloat("u_pixelSize", pixParams.pixelSize);
|
||||
shader->setFloat("u_invertStrength", invParams.strength);
|
||||
|
||||
E2D_INFO("创建像素化+反相组合Shader预设");
|
||||
return shader;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,667 +0,0 @@
|
|||
#include <extra2d/core/color.h>
|
||||
#include <extra2d/graphics/shader_system.h>
|
||||
#include <extra2d/utils/logger.h>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <sys/stat.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
// ============================================================================
|
||||
// 内置Shader源码
|
||||
// ============================================================================
|
||||
|
||||
// 标准精灵着色器 (GLES 3.2)
|
||||
static const char *BUILTIN_SPRITE_VERT = R"(
|
||||
#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;
|
||||
}
|
||||
)";
|
||||
|
||||
static const char *BUILTIN_SPRITE_FRAG = R"(
|
||||
#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;
|
||||
}
|
||||
}
|
||||
)";
|
||||
|
||||
// 粒子着色器 (GLES 3.2)
|
||||
static const char *BUILTIN_PARTICLE_VERT = R"(
|
||||
#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;
|
||||
layout(location = 3) in float a_size;
|
||||
layout(location = 4) in float a_rotation;
|
||||
|
||||
uniform mat4 u_viewProjection;
|
||||
|
||||
out vec2 v_texCoord;
|
||||
out vec4 v_color;
|
||||
|
||||
void main() {
|
||||
float c = cos(a_rotation);
|
||||
float s = sin(a_rotation);
|
||||
mat2 rot = mat2(c, -s, s, c);
|
||||
|
||||
vec2 pos = rot * a_position * a_size;
|
||||
gl_Position = u_viewProjection * vec4(pos, 0.0, 1.0);
|
||||
|
||||
v_texCoord = a_texCoord;
|
||||
v_color = a_color;
|
||||
}
|
||||
)";
|
||||
|
||||
static const char *BUILTIN_PARTICLE_FRAG = R"(
|
||||
#version 300 es
|
||||
precision highp float;
|
||||
in vec2 v_texCoord;
|
||||
in vec4 v_color;
|
||||
|
||||
uniform sampler2D u_texture;
|
||||
uniform int u_textureEnabled;
|
||||
|
||||
out vec4 fragColor;
|
||||
|
||||
void main() {
|
||||
vec4 color = v_color;
|
||||
|
||||
if (u_textureEnabled > 0) {
|
||||
color *= texture(u_texture, v_texCoord);
|
||||
}
|
||||
|
||||
// 圆形粒子
|
||||
vec2 center = v_texCoord - vec2(0.5);
|
||||
float dist = length(center);
|
||||
float alpha = 1.0 - smoothstep(0.4, 0.5, dist);
|
||||
color.a *= alpha;
|
||||
|
||||
if (color.a < 0.01) {
|
||||
discard;
|
||||
}
|
||||
|
||||
fragColor = color;
|
||||
}
|
||||
)";
|
||||
|
||||
// 后处理着色器 (GLES 3.2)
|
||||
static const char *BUILTIN_POSTPROCESS_VERT = R"(
|
||||
#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;
|
||||
}
|
||||
)";
|
||||
|
||||
static const char *BUILTIN_POSTPROCESS_FRAG = R"(
|
||||
#version 300 es
|
||||
precision highp float;
|
||||
in vec2 v_texCoord;
|
||||
|
||||
uniform sampler2D u_texture;
|
||||
uniform vec2 u_resolution;
|
||||
uniform float u_time;
|
||||
|
||||
out vec4 fragColor;
|
||||
|
||||
void main() {
|
||||
fragColor = texture(u_texture, v_texCoord);
|
||||
}
|
||||
)";
|
||||
|
||||
// 形状渲染着色器 (GLES 3.2)
|
||||
static const char *BUILTIN_SHAPE_VERT = R"(
|
||||
#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;
|
||||
}
|
||||
)";
|
||||
|
||||
static const char *BUILTIN_SHAPE_FRAG = R"(
|
||||
#version 300 es
|
||||
precision highp float;
|
||||
in vec4 v_color;
|
||||
|
||||
out vec4 fragColor;
|
||||
|
||||
void main() {
|
||||
fragColor = v_color;
|
||||
}
|
||||
)";
|
||||
|
||||
// ============================================================================
|
||||
// ShaderSystem实现
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* @brief 获取ShaderSystem单例实例
|
||||
* @return ShaderSystem单例的引用
|
||||
*
|
||||
* 使用静态局部变量实现线程安全的单例模式
|
||||
*/
|
||||
ShaderSystem &ShaderSystem::get() {
|
||||
static ShaderSystem instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 初始化Shader系统
|
||||
* @return 初始化成功返回true,失败返回false
|
||||
*
|
||||
* 加载所有内置着色器,包括精灵、粒子、后处理和形状着色器
|
||||
*/
|
||||
bool ShaderSystem::init() {
|
||||
E2D_INFO("初始化Shader系统...");
|
||||
|
||||
if (!loadBuiltinShaders()) {
|
||||
E2D_ERROR("加载内置Shader失败!");
|
||||
return false;
|
||||
}
|
||||
|
||||
E2D_INFO("Shader系统初始化完成");
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 关闭Shader系统
|
||||
*
|
||||
* 清理所有着色器资源,释放内置着色器
|
||||
*/
|
||||
void ShaderSystem::shutdown() {
|
||||
E2D_INFO("关闭Shader系统...");
|
||||
clear();
|
||||
|
||||
builtinSpriteShader_.reset();
|
||||
builtinParticleShader_.reset();
|
||||
builtinPostProcessShader_.reset();
|
||||
builtinShapeShader_.reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 加载内置着色器
|
||||
* @return 加载成功返回true,失败返回false
|
||||
*
|
||||
* 编译并加载所有内置着色器:精灵、粒子、后处理和形状着色器
|
||||
*/
|
||||
bool ShaderSystem::loadBuiltinShaders() {
|
||||
// 加载精灵Shader
|
||||
builtinSpriteShader_ = std::make_shared<GLShader>();
|
||||
if (!builtinSpriteShader_->compileFromSource(BUILTIN_SPRITE_VERT,
|
||||
BUILTIN_SPRITE_FRAG)) {
|
||||
E2D_ERROR("编译内置精灵Shader失败!");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 加载粒子Shader
|
||||
builtinParticleShader_ = std::make_shared<GLShader>();
|
||||
if (!builtinParticleShader_->compileFromSource(BUILTIN_PARTICLE_VERT,
|
||||
BUILTIN_PARTICLE_FRAG)) {
|
||||
E2D_ERROR("编译内置粒子Shader失败!");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 加载后处理Shader
|
||||
builtinPostProcessShader_ = std::make_shared<GLShader>();
|
||||
if (!builtinPostProcessShader_->compileFromSource(BUILTIN_POSTPROCESS_VERT,
|
||||
BUILTIN_POSTPROCESS_FRAG)) {
|
||||
E2D_ERROR("编译内置后处理Shader失败!");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 加载形状Shader
|
||||
builtinShapeShader_ = std::make_shared<GLShader>();
|
||||
if (!builtinShapeShader_->compileFromSource(BUILTIN_SHAPE_VERT,
|
||||
BUILTIN_SHAPE_FRAG)) {
|
||||
E2D_ERROR("编译内置形状Shader失败!");
|
||||
return false;
|
||||
}
|
||||
|
||||
E2D_INFO("内置Shader加载成功");
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 从文件加载着色器
|
||||
* @param name 着色器名称
|
||||
* @param vertPath 顶点着色器文件路径
|
||||
* @param fragPath 片段着色器文件路径
|
||||
* @return 加载成功返回着色器指针,失败返回nullptr
|
||||
*
|
||||
* 从指定路径读取顶点和片段着色器源码并编译
|
||||
*/
|
||||
Ptr<GLShader> ShaderSystem::loadFromFile(const std::string &name,
|
||||
const std::string &vertPath,
|
||||
const std::string &fragPath) {
|
||||
// 读取文件内容
|
||||
std::string vertSource = readFile(vertPath);
|
||||
std::string fragSource = readFile(fragPath);
|
||||
|
||||
if (vertSource.empty()) {
|
||||
E2D_ERROR("无法读取顶点着色器文件: {}", vertPath);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (fragSource.empty()) {
|
||||
E2D_ERROR("无法读取片段着色器文件: {}", fragPath);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// 编译Shader
|
||||
auto shader = std::make_shared<GLShader>();
|
||||
if (!shader->compileFromSource(vertSource.c_str(), fragSource.c_str())) {
|
||||
E2D_ERROR("编译Shader '{}' 失败", name);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// 存储Shader信息
|
||||
ShaderInfo info;
|
||||
info.shader = shader;
|
||||
info.vertPath = vertPath;
|
||||
info.fragPath = fragPath;
|
||||
info.vertModifiedTime = getFileModifiedTime(vertPath);
|
||||
info.fragModifiedTime = getFileModifiedTime(fragPath);
|
||||
info.isBuiltin = false;
|
||||
|
||||
shaders_[name] = std::move(info);
|
||||
|
||||
E2D_INFO("加载Shader '{}' 成功", name);
|
||||
return shader;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 从源码加载着色器
|
||||
* @param name 着色器名称
|
||||
* @param vertSource 顶点着色器源码
|
||||
* @param fragSource 片段着色器源码
|
||||
* @return 加载成功返回着色器指针,失败返回nullptr
|
||||
*
|
||||
* 直接从源码字符串编译着色器
|
||||
*/
|
||||
Ptr<GLShader> ShaderSystem::loadFromSource(const std::string &name,
|
||||
const std::string &vertSource,
|
||||
const std::string &fragSource) {
|
||||
auto shader = std::make_shared<GLShader>();
|
||||
if (!shader->compileFromSource(vertSource.c_str(), fragSource.c_str())) {
|
||||
E2D_ERROR("编译Shader '{}' 失败", name);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ShaderInfo info;
|
||||
info.shader = shader;
|
||||
info.vertModifiedTime = 0;
|
||||
info.fragModifiedTime = 0;
|
||||
info.isBuiltin = false;
|
||||
|
||||
shaders_[name] = std::move(info);
|
||||
|
||||
E2D_INFO("加载Shader '{}' 成功", name);
|
||||
return shader;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取指定名称的着色器
|
||||
* @param name 着色器名称
|
||||
* @return 找到返回着色器指针,未找到返回nullptr
|
||||
*/
|
||||
Ptr<GLShader> ShaderSystem::get(const std::string &name) {
|
||||
auto it = shaders_.find(name);
|
||||
if (it != shaders_.end()) {
|
||||
return it->second.shader;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 检查指定名称的着色器是否存在
|
||||
* @param name 着色器名称
|
||||
* @return 存在返回true,不存在返回false
|
||||
*/
|
||||
bool ShaderSystem::has(const std::string &name) const {
|
||||
return shaders_.find(name) != shaders_.end();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 移除指定名称的着色器
|
||||
* @param name 着色器名称
|
||||
*
|
||||
* 从着色器管理器中移除指定着色器
|
||||
*/
|
||||
void ShaderSystem::remove(const std::string &name) { shaders_.erase(name); }
|
||||
|
||||
/**
|
||||
* @brief 清空所有着色器
|
||||
*
|
||||
* 移除所有已加载的着色器
|
||||
*/
|
||||
void ShaderSystem::clear() { shaders_.clear(); }
|
||||
|
||||
/**
|
||||
* @brief 设置文件监视功能
|
||||
* @param enable 是否启用
|
||||
*
|
||||
* 启用或禁用着色器文件变化监视功能
|
||||
*/
|
||||
void ShaderSystem::setFileWatching(bool enable) {
|
||||
fileWatching_ = enable;
|
||||
if (enable) {
|
||||
E2D_INFO("启用Shader文件监视");
|
||||
} else {
|
||||
E2D_INFO("禁用Shader文件监视");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 更新文件监视
|
||||
*
|
||||
* 定期检查着色器文件是否发生变化,如有变化则自动重载
|
||||
*/
|
||||
void ShaderSystem::updateFileWatching() {
|
||||
if (!fileWatching_)
|
||||
return;
|
||||
|
||||
watchTimer_ += 0.016f; // 假设60fps
|
||||
if (watchTimer_ >= WATCH_INTERVAL) {
|
||||
watchTimer_ = 0.0f;
|
||||
checkAndReload();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 检查并重载变化的着色器
|
||||
*
|
||||
* 检查所有着色器文件的修改时间,如有变化则自动重载
|
||||
*/
|
||||
void ShaderSystem::checkAndReload() {
|
||||
for (auto &[name, info] : shaders_) {
|
||||
if (info.isBuiltin)
|
||||
continue;
|
||||
if (info.vertPath.empty() || info.fragPath.empty())
|
||||
continue;
|
||||
|
||||
uint64_t vertTime = getFileModifiedTime(info.vertPath);
|
||||
uint64_t fragTime = getFileModifiedTime(info.fragPath);
|
||||
|
||||
if (vertTime > info.vertModifiedTime || fragTime > info.fragModifiedTime) {
|
||||
E2D_INFO("检测到Shader '{}' 文件变化,正在重载...", name);
|
||||
reload(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 重载指定着色器
|
||||
* @param name 着色器名称
|
||||
* @return 重载成功返回true,失败返回false
|
||||
*
|
||||
* 重新从文件加载并编译指定着色器
|
||||
*/
|
||||
bool ShaderSystem::reload(const std::string &name) {
|
||||
auto it = shaders_.find(name);
|
||||
if (it == shaders_.end()) {
|
||||
E2D_ERROR("无法重载不存在的Shader '{}'", name);
|
||||
return false;
|
||||
}
|
||||
|
||||
auto &info = it->second;
|
||||
if (info.isBuiltin) {
|
||||
E2D_WARN("无法重载内置Shader '{}'", name);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (info.vertPath.empty() || info.fragPath.empty()) {
|
||||
E2D_ERROR("Shader '{}' 没有关联的文件路径", name);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 重新加载
|
||||
auto newShader = loadFromFile(name, info.vertPath, info.fragPath);
|
||||
if (newShader) {
|
||||
E2D_INFO("重载Shader '{}' 成功", name);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 重载所有着色器
|
||||
*
|
||||
* 重载所有非内置着色器
|
||||
*/
|
||||
void ShaderSystem::reloadAll() {
|
||||
for (const auto &[name, info] : shaders_) {
|
||||
if (!info.isBuiltin) {
|
||||
reload(name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取内置精灵着色器
|
||||
* @return 精灵着色器指针
|
||||
*/
|
||||
Ptr<GLShader> ShaderSystem::getBuiltinSpriteShader() {
|
||||
return builtinSpriteShader_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取内置粒子着色器
|
||||
* @return 粒子着色器指针
|
||||
*/
|
||||
Ptr<GLShader> ShaderSystem::getBuiltinParticleShader() {
|
||||
return builtinParticleShader_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取内置后处理着色器
|
||||
* @return 后处理着色器指针
|
||||
*/
|
||||
Ptr<GLShader> ShaderSystem::getBuiltinPostProcessShader() {
|
||||
return builtinPostProcessShader_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取内置形状着色器
|
||||
* @return 形状着色器指针
|
||||
*/
|
||||
Ptr<GLShader> ShaderSystem::getBuiltinShapeShader() {
|
||||
return builtinShapeShader_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 读取文件内容
|
||||
* @param filepath 文件路径
|
||||
* @return 文件内容字符串,读取失败返回空字符串
|
||||
*
|
||||
* 以二进制模式读取文件全部内容
|
||||
*/
|
||||
std::string ShaderSystem::readFile(const std::string &filepath) {
|
||||
std::ifstream file(filepath, std::ios::in | std::ios::binary);
|
||||
if (!file.is_open()) {
|
||||
return "";
|
||||
}
|
||||
|
||||
std::stringstream buffer;
|
||||
buffer << file.rdbuf();
|
||||
return buffer.str();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 获取文件修改时间
|
||||
* @param filepath 文件路径
|
||||
* @return 文件最后修改时间戳,失败返回0
|
||||
*
|
||||
* 获取文件的最后修改时间,用于文件监视功能
|
||||
*/
|
||||
uint64_t ShaderSystem::getFileModifiedTime(const std::string &filepath) {
|
||||
#ifdef _WIN32
|
||||
struct _stat64 statBuf;
|
||||
if (_stat64(filepath.c_str(), &statBuf) == 0) {
|
||||
return static_cast<uint64_t>(statBuf.st_mtime);
|
||||
}
|
||||
#else
|
||||
struct stat statBuf;
|
||||
if (stat(filepath.c_str(), &statBuf) == 0) {
|
||||
return static_cast<uint64_t>(statBuf.st_mtime);
|
||||
}
|
||||
#endif
|
||||
return 0;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// ShaderParams实现
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* @brief 构造函数
|
||||
* @param shader 关联的着色器对象
|
||||
*
|
||||
* 创建一个着色器参数设置器
|
||||
*/
|
||||
ShaderParams::ShaderParams(GLShader &shader) : shader_(shader) {}
|
||||
|
||||
/**
|
||||
* @brief 设置布尔类型uniform变量
|
||||
* @param name 变量名
|
||||
* @param value 布尔值
|
||||
* @return 当前对象引用,支持链式调用
|
||||
*/
|
||||
ShaderParams &ShaderParams::setBool(const std::string &name, bool value) {
|
||||
shader_.setBool(name, value);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 设置整数类型uniform变量
|
||||
* @param name 变量名
|
||||
* @param value 整数值
|
||||
* @return 当前对象引用,支持链式调用
|
||||
*/
|
||||
ShaderParams &ShaderParams::setInt(const std::string &name, int value) {
|
||||
shader_.setInt(name, value);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 设置浮点类型uniform变量
|
||||
* @param name 变量名
|
||||
* @param value 浮点值
|
||||
* @return 当前对象引用,支持链式调用
|
||||
*/
|
||||
ShaderParams &ShaderParams::setFloat(const std::string &name, float value) {
|
||||
shader_.setFloat(name, value);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 设置二维向量类型uniform变量
|
||||
* @param name 变量名
|
||||
* @param value 二维向量值
|
||||
* @return 当前对象引用,支持链式调用
|
||||
*/
|
||||
ShaderParams &ShaderParams::setVec2(const std::string &name,
|
||||
const glm::vec2 &value) {
|
||||
shader_.setVec2(name, value);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 设置三维向量类型uniform变量
|
||||
* @param name 变量名
|
||||
* @param value 三维向量值
|
||||
* @return 当前对象引用,支持链式调用
|
||||
*/
|
||||
ShaderParams &ShaderParams::setVec3(const std::string &name,
|
||||
const glm::vec3 &value) {
|
||||
shader_.setVec3(name, value);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 设置四维向量类型uniform变量
|
||||
* @param name 变量名
|
||||
* @param value 四维向量值
|
||||
* @return 当前对象引用,支持链式调用
|
||||
*/
|
||||
ShaderParams &ShaderParams::setVec4(const std::string &name,
|
||||
const glm::vec4 &value) {
|
||||
shader_.setVec4(name, value);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 设置4x4矩阵类型uniform变量
|
||||
* @param name 变量名
|
||||
* @param value 4x4矩阵值
|
||||
* @return 当前对象引用,支持链式调用
|
||||
*/
|
||||
ShaderParams &ShaderParams::setMat4(const std::string &name,
|
||||
const glm::mat4 &value) {
|
||||
shader_.setMat4(name, value);
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief 设置颜色类型uniform变量
|
||||
* @param name 变量名
|
||||
* @param color 颜色值
|
||||
* @return 当前对象引用,支持链式调用
|
||||
*
|
||||
* 将颜色对象转换为四维向量并设置
|
||||
*/
|
||||
ShaderParams &ShaderParams::setColor(const std::string &name,
|
||||
const Color &color) {
|
||||
shader_.setVec4(name, glm::vec4(color.r, color.g, color.b, color.a));
|
||||
return *this;
|
||||
}
|
||||
|
||||
} // namespace extra2d
|
||||
221
README.md
221
README.md
|
|
@ -1,221 +0,0 @@
|
|||
<div align="center">
|
||||
|
||||

|
||||
|
||||
<p align="center">
|
||||
<a href="https://github.com/ChestnutYueyue/extra2d/releases/latest">
|
||||
<img src="https://img.shields.io/github/release/ChestnutYueyue/extra2d?style=for-the-badge&color=blue&logo=github" alt="Release">
|
||||
</a>
|
||||
<a href="https://github.com/ChestnutYueyue/extra2d/blob/master/LICENSE">
|
||||
<img src="https://img.shields.io/github/license/ChestnutYueyue/extra2d?style=for-the-badge&color=green&logo=opensourceinitiative" alt="License">
|
||||
</a>
|
||||
<a href="#">
|
||||
<img src="https://img.shields.io/badge/build-passing-brightgreen?style=for-the-badge&logo=appveyor" alt="Build Status">
|
||||
</a>
|
||||
<a href="#">
|
||||
<img src="https://img.shields.io/badge/C++-17-00599C?style=for-the-badge&logo=c%2B%2B" alt="C++17">
|
||||
</a>
|
||||
<a href="#">
|
||||
<img src="https://img.shields.io/badge/Nintendo%20Switch-E60012?style=for-the-badge&logo=nintendo-switch&logoColor=white" alt="Nintendo Switch">
|
||||
</a>
|
||||
</p>
|
||||
|
||||
<p align="center">
|
||||
<b>🎮 专为 Nintendo Switch 打造的轻量级 2D 游戏引擎</b><br>
|
||||
<i>高性能、易用、原生支持 Switch 平台</i>
|
||||
</p>
|
||||
|
||||
[📖 构建指南](./docs/Extra2D%20构建系统文档.md) | [🚀 快速开始](#快速开始) | [📦 示例程序](#示例程序) | [📚 API 教程](./docs/API_Tutorial/01_Quick_Start.md)
|
||||
|
||||
</div>
|
||||
|
||||
---
|
||||
|
||||
## 🌟 简介
|
||||
|
||||
**Extra2D** 是一个专为 **Nintendo Switch** 平台设计的轻量级 2D 游戏引擎,采用现代 C++17 架构,充分利用 Switch 硬件特性,为开发者提供流畅的游戏开发体验。
|
||||
|
||||
> 💡 Extra2D 的诞生是为了让 Switch 独立游戏开发变得更加简单高效。无论是复古风格的像素游戏,还是现代化的 2D 作品,Extra2D 都能提供强大的支持。
|
||||
|
||||
### ✨ 核心特性
|
||||
|
||||
- **🎯 Switch 原生支持**:专为 Nintendo Switch 硬件优化,支持掌机/主机双模式
|
||||
- **🎬 高级动画系统**:支持骨骼动画、精灵动画、补间动画
|
||||
- **🎵 音频系统**:基于 SDL2_mixer 的高质量音频播放,支持 BGM 和音效
|
||||
- **🎨 渲染系统**:基于 OpenGL ES 的 2D 渲染,支持自定义着色器
|
||||
- **💾 数据持久化**:游戏存档、配置文件的便捷读写
|
||||
- **🔧 空间索引**:内置四叉树和空间哈希碰撞检测系统
|
||||
- **🖱️ UI 系统**:完整的 UI 控件支持(按钮、文本、滑块等)
|
||||
|
||||
---
|
||||
|
||||
## 🚀 快速开始
|
||||
|
||||
### 环境要求
|
||||
|
||||
| 组件 | 要求 |
|
||||
|:----:|:-----|
|
||||
| 开发环境 | devkitPro + devkitA64 (Switch) / MinGW-w64 (Windows) |
|
||||
| C++ 标准 | C++17 |
|
||||
| 构建工具 | xmake |
|
||||
| 目标平台 | Nintendo Switch / Windows (MinGW) |
|
||||
|
||||
### 安装 xmake
|
||||
|
||||
```bash
|
||||
# Windows (PowerShell)
|
||||
Invoke-Expression (Invoke-WebRequest 'https://xmake.io/psget.text' -UseBasicParsing).Content
|
||||
|
||||
# macOS
|
||||
brew install xmake
|
||||
|
||||
# Linux
|
||||
sudo add-apt-repository ppa:xmake-io/xmake
|
||||
sudo apt update
|
||||
sudo apt install xmake
|
||||
```
|
||||
|
||||
## 📚 文档
|
||||
|
||||
- [📖 API 教程](./docs/API_Tutorial/01_Quick_Start.md) - 完整的 API 使用教程
|
||||
- [01. 快速开始](./docs/API_Tutorial/01_Quick_Start.md)
|
||||
- [02. 场景系统](./docs/API_Tutorial/02_Scene_System.md)
|
||||
- [03. 节点系统](./docs/API_Tutorial/03_Node_System.md)
|
||||
- [04. 资源管理](./docs/API_Tutorial/04_Resource_Management.md)
|
||||
- [05. 输入处理](./docs/API_Tutorial/05_Input_Handling.md)
|
||||
- [06. 碰撞检测](./docs/API_Tutorial/06_Collision_Detection.md)
|
||||
- [07. UI 系统](./docs/API_Tutorial/07_UI_System.md)
|
||||
- [08. 音频系统](./docs/API_Tutorial/08_Audio_System.md)
|
||||
- [🔧 构建系统文档](./docs/Extra2D%20构建系统文档.md) - 详细的构建系统说明
|
||||
|
||||
---
|
||||
|
||||
## 🏗️ 架构概览
|
||||
|
||||
```mermaid
|
||||
mindmap
|
||||
root((Extra2D<br/>引擎架构))
|
||||
核心基础层
|
||||
数学库
|
||||
向量 Vec2/Vec3
|
||||
矩形 Rect
|
||||
矩阵 glm::mat4
|
||||
颜色 Color
|
||||
大小 Size
|
||||
类型系统
|
||||
智能指针 Ptr/UniquePtr
|
||||
字符串 String
|
||||
平台抽象层
|
||||
窗口管理 Window
|
||||
输入处理 Input
|
||||
键盘 Keyboard
|
||||
鼠标 Mouse
|
||||
手柄 Gamepad
|
||||
触摸 Touch
|
||||
输入码 InputCodes
|
||||
渲染系统
|
||||
渲染后端 RenderBackend
|
||||
OpenGL 实现
|
||||
GLRenderer
|
||||
GLShader
|
||||
GLTexture
|
||||
GLSpriteBatch
|
||||
相机 Camera
|
||||
纹理 Texture
|
||||
字体 Font
|
||||
着色器系统 ShaderSystem
|
||||
渲染目标 RenderTarget
|
||||
场景系统
|
||||
场景管理器 SceneManager
|
||||
场景 Scene
|
||||
过渡动画 Transition
|
||||
节点系统 Node
|
||||
精灵 Sprite
|
||||
动画精灵 AnimatedSprite
|
||||
形状 ShapeNode
|
||||
粒子系统 ParticleSystem
|
||||
空间索引
|
||||
空间管理器 SpatialManager
|
||||
四叉树 QuadTree
|
||||
空间哈希 SpatialHash
|
||||
动画系统
|
||||
动画控制器 AnimationController
|
||||
动画剪辑 AnimationClip
|
||||
关键帧 AnimationFrame
|
||||
插值引擎 InterpolationEngine
|
||||
精灵帧 SpriteFrame
|
||||
动作系统
|
||||
动作基类 Action
|
||||
位移动作 MoveBy/MoveTo
|
||||
缩放动作 ScaleBy/ScaleTo
|
||||
旋转动作 RotateBy/RotateTo
|
||||
跳跃动作 JumpBy/JumpTo
|
||||
淡入淡出 FadeIn/FadeOut
|
||||
组合动作 Sequence/Spawn/Repeat
|
||||
缓动函数 Ease
|
||||
事件系统
|
||||
事件队列 EventQueue
|
||||
事件分发 EventDispatcher
|
||||
事件类型
|
||||
窗口事件
|
||||
键盘事件
|
||||
鼠标事件
|
||||
UI事件
|
||||
音频系统
|
||||
音频引擎 AudioEngine
|
||||
音效 Sound
|
||||
资源管理
|
||||
资源管理器 ResourceManager
|
||||
纹理池 TexturePool
|
||||
显存管理 VRAMManager
|
||||
UI系统
|
||||
基础控件 Widget
|
||||
按钮 Button
|
||||
标签 Label
|
||||
文本 Text
|
||||
进度条 ProgressBar
|
||||
滑块 Slider
|
||||
复选框 CheckBox
|
||||
单选框 RadioButton
|
||||
特效系统
|
||||
粒子系统 ParticleSystem
|
||||
后处理 PostProcess
|
||||
自定义效果 CustomEffectManager
|
||||
工具库
|
||||
日志 Logger
|
||||
计时器 Timer
|
||||
随机数 Random
|
||||
数据持久化 Data
|
||||
```
|
||||
|
||||
## 🛠️ 技术栈
|
||||
|
||||
| 技术 | 用途 | 版本 |
|
||||
|:----:|:-----|:----:|
|
||||
| OpenGL ES | 2D 图形渲染 | 3.0+ |
|
||||
| GLFW | 窗口和输入管理 | 3.3+ |
|
||||
| GLM | 数学库 | 0.9.9+ |
|
||||
| SDL2_mixer | 音频播放 | 2.0+ |
|
||||
| spdlog | 日志系统 | 最新版 |
|
||||
| stb_image | 图像加载 | 最新版 |
|
||||
| freetype | 字体渲染 | 最新版 |
|
||||
| xmake | 构建系统 | 2.5+ |
|
||||
|
||||
---
|
||||
|
||||
## 🤝 贡献
|
||||
|
||||
欢迎提交 Issue 和 Pull Request!
|
||||
|
||||
---
|
||||
|
||||
## 📄 许可证
|
||||
|
||||
Extra2D 使用 [MIT](LICENSE) 许可证。
|
||||
|
||||
---
|
||||
|
||||
## 联系方式
|
||||
|
||||
- GitHub Issues: https://github.com/ChestnutYueyue/extra2d/issues
|
||||
- 作者: [ChestnutYueyue](https://github.com/ChestnutYueyue)
|
||||
Loading…
Reference in New Issue