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:
ChestnutYueyue 2026-02-15 11:12:27 +08:00
parent 269f2d907d
commit 475ae50d2a
37 changed files with 4190 additions and 1572 deletions

View File

@ -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

View File

@ -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>

View File

@ -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_;

View File

@ -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 truefalse
*/
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 truefalse
*/
bool compileFromSource(const char* vertexSource, const char* fragmentSource);
/**
* @brief Shader
* @param binary
* @return truefalse
*/
bool compileFromBinary(const std::vector<uint8_t>& binary);
/**
* @brief Shader二进制数据
* @param outBinary
* @return truefalse
*/
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 ID0
*/
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 truefalse
*/
bool getShaderBinary(const IShader& shader,
std::vector<uint8_t>& outBinary) override;
};
} // namespace extra2d

View File

@ -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 truefalse
*/
bool init(const std::string& cacheDir);
/**
* @brief
*/
void shutdown();
/**
* @brief
* @param name Shader名称
* @param sourceHash
* @return truefalse
*/
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 truefalse
*/
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 truefalse
*/
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 truefalse
*/
bool loadCacheIndex();
/**
* @brief
* @return truefalse
*/
bool saveCacheIndex();
/**
* @brief
* @param name Shader名称
* @return
*/
std::string getCachePath(const std::string& name) const;
/**
* @brief
* @return truefalse
*/
bool ensureCacheDirectory();
};
// 便捷宏
#define E2D_SHADER_CACHE() ::extra2d::ShaderCache::getInstance()
} // namespace extra2d

View File

@ -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 truefalse
*/
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 truefalse
*/
bool isEnabled() const { return enabled_; }
/**
* @brief
* @return truefalse
*/
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

View File

@ -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 truefalse
*/
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 truefalse
*/
virtual bool getShaderBinary(const IShader& shader,
std::vector<uint8_t>& outBinary) = 0;
};
} // namespace extra2d

View File

@ -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 truefalse
*/
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 truefalse
*/
bool parseCombinedFile(const std::string& content,
std::string& outVert,
std::string& outFrag,
ShaderMetadata& outMetadata);
/**
* @brief JSON块
* @param jsonContent JSON内容
* @param outMetadata
* @return truefalse
*/
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

View File

@ -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 truefalse
*/
bool init(Ptr<IShaderFactory> factory, const std::string& appName = "extra2d");
/**
* @brief Shader系统
* @param shaderDir Shader文件目录
* @param cacheDir
* @param factory Shader工厂
* @return truefalse
*/
bool init(const std::string& shaderDir,
const std::string& cacheDir,
Ptr<IShaderFactory> factory);
/**
* @brief Shader系统
*/
void shutdown();
/**
* @brief
* @return truefalse
*/
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 truefalse
*/
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 truefalse
*/
bool isHotReloadEnabled() const;
/**
* @brief
*/
void update();
/**
* @brief Shader
* @param name Shader名称
* @return truefalse
*/
bool reload(const std::string& name);
// ------------------------------------------------------------------------
// 内置Shader
// ------------------------------------------------------------------------
/**
* @brief Shader
* @param name Shader名称
* @return Shader实例
*/
Ptr<IShader> getBuiltin(const std::string& name);
/**
* @brief Shader
* @return truefalse
*/
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

View File

@ -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);
};

View File

@ -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 Shadernullptr
*/
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 Shadernullptr
*/
Ptr<GLShader> loadFromSource(const std::string &name,
const std::string &vertSource,
const std::string &fragSource);
/**
* @brief Shader
* @param name Shader名称
* @return Shadernullptr
*/
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

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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

View File

@ -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

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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

View File

@ -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 truefalse
*/
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资源VAOVBO
*/
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),

View File

@ -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 truefalse
*/
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 truefalse
*/
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 truefalse
*/
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 ID0
*/
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 truefalse
*/
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

View File

@ -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 truefalse
*/
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 truefalse
*/
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 truefalse
*/
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 truefalse
*/
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 truefalse
*/
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 truefalse
*/
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

View File

@ -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 truefalse
*/
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

View File

@ -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 truefalse
*/
bool ShaderLoader::fileExists(const std::string& filepath) {
return fs::exists(filepath);
}
/**
* @brief Shader文件
* @param content
* @param outVert
* @param outFrag
* @param outMetadata
* @return truefalse
*/
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 truefalse
*/
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

View File

@ -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 truefalse
*/
bool ShaderManager::init(Ptr<IShaderFactory> factory, const std::string& appName) {
std::string shaderDir = PlatformDetector::getShaderPath(appName);
std::string cacheDir = PlatformDetector::getShaderCachePath(appName);
hotReloadSupported_ = PlatformDetector::supportsHotReload();
E2D_LOG_INFO("Platform: {} (HotReload: {})",
PlatformDetector::platformName(),
hotReloadSupported_ ? "supported" : "not supported");
return init(shaderDir, cacheDir, factory);
}
/**
* @brief Shader系统
* @param shaderDir Shader文件目录
* @param cacheDir
* @param factory Shader工厂
* @return truefalse
*/
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 truefalse
*/
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 truefalse
*/
bool ShaderManager::isHotReloadEnabled() const {
return hotReloadEnabled_ && hotReloadSupported_;
}
/**
* @brief
*/
void ShaderManager::update() {
if (hotReloadEnabled_ && hotReloadSupported_) {
ShaderHotReloader::getInstance().update();
}
}
/**
* @brief Shader
* @param name Shader名称
* @return truefalse
*/
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 truefalse
*/
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

View File

@ -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 waveSpeedwaveAmplitude和waveFrequency
* @return nullptr
* @param params
* @return
*/
Ptr<GLShader> ShaderPreset::Water(const WaterParams &params) {
auto shader = std::make_shared<GLShader>();
if (!shader->compileFromSource(ShaderSource::StandardVert,
ShaderSource::WaterFrag)) {
E2D_ERROR("编译水波纹Shader失败");
Ptr<IShader> ShaderPreset::Water(const WaterParams &params) {
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 &params) {
auto shader = std::make_shared<GLShader>();
if (!shader->compileFromSource(ShaderSource::StandardVert,
ShaderSource::OutlineFrag)) {
E2D_ERROR("编译描边Shader失败");
Ptr<IShader> ShaderPreset::Outline(const OutlineParams &params) {
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 &params) {
auto shader = std::make_shared<GLShader>();
if (!shader->compileFromSource(ShaderSource::StandardVert,
ShaderSource::DistortionFrag)) {
E2D_ERROR("编译扭曲Shader失败");
Ptr<IShader> ShaderPreset::Distortion(const DistortionParams &params) {
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 &params) {
auto shader = std::make_shared<GLShader>();
if (!shader->compileFromSource(ShaderSource::StandardVert,
ShaderSource::PixelateFrag)) {
E2D_ERROR("编译像素化Shader失败");
Ptr<IShader> ShaderPreset::Pixelate(const PixelateParams &params) {
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 &params) {
auto shader = std::make_shared<GLShader>();
if (!shader->compileFromSource(ShaderSource::StandardVert,
ShaderSource::InvertFrag)) {
E2D_ERROR("编译反相Shader失败");
Ptr<IShader> ShaderPreset::Invert(const InvertParams &params) {
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 &params) {
auto shader = std::make_shared<GLShader>();
if (!shader->compileFromSource(ShaderSource::StandardVert,
ShaderSource::GrayscaleFrag)) {
E2D_ERROR("编译灰度Shader失败");
Ptr<IShader> ShaderPreset::Grayscale(const GrayscaleParams &params) {
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 &params) {
auto shader = std::make_shared<GLShader>();
if (!shader->compileFromSource(ShaderSource::StandardVert,
ShaderSource::BlurFrag)) {
E2D_ERROR("编译模糊Shader失败");
Ptr<IShader> ShaderPreset::Blur(const BlurParams &params) {
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;
}

View File

@ -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 truefalse
*
*
*/
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 truefalse
*
*
*/
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 truefalse
*/
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 truefalse
*
*
*/
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
View File

@ -1,221 +0,0 @@
<div align="center">
![Extra2D Logo](./logo/logo_text_dark.svg)
<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)