refactor: 移除事件系统、属性绑定和模块配置相关代码

重构模块系统,简化核心架构:
1. 删除EventDispatcher、EventQueue和EventContext相关代码
2. 移除PropertyBinder和模块属性绑定功能
3. 清理冗余的模块配置类(debug_config.h, resource_config.h等)
4. 合并RenderModule配置到模块头文件
5. 移除ServiceRegistry实现
6. 简化Shader系统实现,移除独立缓存和热重载组件
7. 更新文档说明模块配置方式
8. 修复TexturePool的渲染后端依赖问题
This commit is contained in:
ChestnutYueyue 2026-02-16 13:38:48 +08:00
parent 05ef543615
commit ea1bbb891d
32 changed files with 590 additions and 1954 deletions

86
CLAUDE.md Normal file
View File

@ -0,0 +1,86 @@
# CLAUDE.md
本文件为 Claude Code (claude.ai/code) 在此仓库中工作时提供指导。
## 项目概述
Extra2D 是一个使用 C++17 编写的轻量级跨平台 2D 游戏引擎。支持 Windows (MinGW)、Linux、macOS 和 Nintendo Switch。项目注释和文档使用中文。
## 构建系统
项目使用 **xmake**(非 CMake
```bash
# 配置 (Windows/MinGW 示例)
xmake f -p mingw -a x86_64 -m release -y
# 配置调试构建
xmake f -p mingw -a x86_64 -m debug -y
# 构建全部
xmake build
# 构建特定目标
xmake build demo_basic
xmake build demo_hello_module
# 运行示例
xmake run demo_basic
xmake run demo_hello_module
# Nintendo Switch 构建
export DEVKITPRO=/opt/devkitpro
xmake f -p switch -m release -y
xmake build
```
构建目标:`extra2d`(引擎静态库)、`demo_basic`、`demo_hello_module`、`hello_module_lib`。
调试构建定义 `E2D_DEBUG``_DEBUG`。`debug_logs` 选项启用调试日志。
## 架构
### 双系统设计:模块 + 服务
引擎有两个由 `Application`(单例)管理的并行系统:
- **模块**`Module` 基类):处理平台级初始化,具有生命周期钩子(`setupModule`、`destroyModule`、`onUpdate`、`onRender`、`handleEvent`)。通过 `E2D_MODULE` 宏注册,在静态初始化时自动发现。按优先级排序(数值越小越先初始化)。
- **服务**`IService` 基类):提供通过 `ServiceLocator` 访问的运行时功能。通过 `Application` 便捷方法获取:`app.scenes()`、`app.timers()`、`app.events()`、`app.camera()`。
### 模块自动注册
模块使用 `E2D_MODULE(类名, 优先级)` 宏,放置在 `.cpp` 文件末尾、**任何命名空间之外**。这会创建一个静态的 `ModuleAutoRegister<T>` 变量,在静态初始化期间向 `ModuleRegistry` 注册。静态链接需要 `--whole-archive` 链接器标志,以防止链接器剥离这些未引用的符号。
内置模块优先级顺序Logger(-1) → Config(0) → Platform(10) → Window(20) → Input(30) → Render(40)。用户模块应使用优先级 1000+。
### 模块上下文链
模块生命周期方法接收上下文对象(`UpdateContext`、`RenderContext`、`EventContext`)。**必须调用 `ctx.next()`** 以继续链式调用到下一个模块。
### 关键子系统
- **平台层**:统一的 SDL2 后端(`E2D_BACKEND_SDL2` 宏定义)。通过 `IWindow`/`IInput` 接口实现平台抽象。实现在 `Extra2D/src/platform/backends/sdl2/`
- **图形**:通过 glad 使用 OpenGL ES 3.2。渲染器(`gl_renderer`)、精灵批处理(`gl_sprite_batch`)、支持热重载的着色器管理。内置着色器在 `Extra2D/shaders/builtin/`(构建后复制到构建目录)。
- **场景图**:基于树的 `Node` 层级结构,支持变换继承。`Scene` 是根节点。专用节点:`ShapeNode`、`Sprite`。场景过渡效果(淡入淡出、滑动、翻转、缩放、方块)。
- **事件系统**`EventType` 枚举驱动。事件使用 `std::variant` 实现类型化数据(`KeyEvent`、`MouseEvent`、`GamepadEvent`)。输入使用扫描码(非键码)。
## 源码布局
- `Extra2D/include/extra2d/` — 按子系统组织的公共头文件
- `Extra2D/src/` — 实现,镜像 include 结构
- `Extra2D/shaders/` — GLSL 着色器文件(复制到构建输出)
- `examples/` — 示例程序(`basic/`、`hello_module/`
- `xmake/engine.lua` — 引擎静态库目标定义
- `xmake.lua` — 根构建配置,包含示例目标
## 依赖
由 xmake 管理:`glm`、`nlohmann_json`、`libsdl2`。内嵌:`glad`、`stb_image`、`stb_truetype`、`stb_rect_pack`、`KHR`。
## 约定
- 命名空间:`extra2d`
- 智能指针:`Ptr<T>`shared_ptr 别名)、`SharedPtr<T>`,通过 `makeShared<T>()` 创建
- 日志:`E2D_LOG_INFO(...)`、`E2D_LOG_WARN(...)`、`E2D_LOG_ERROR(...)`
- 导出宏:`E2D_API` 用于 DLL 可见符号
- 自定义模块直接编译到可执行文件(静态链接,推荐)或作为独立 DLL

View File

@ -9,12 +9,9 @@ namespace extra2d {
/**
* @file app_config.h
* @brief
*
*
*
* IModuleConfig
*
* ModuleRegistry ConfigManager
*
* RenderModuleConfig
*/
/**

View File

@ -9,9 +9,8 @@ namespace extra2d {
/**
* @file config_loader.h
* @brief
*
* AppConfig
* ModuleRegistry IModuleConfig
*
* AppConfig
*/
/**

View File

@ -12,9 +12,8 @@ namespace extra2d {
/**
* @file config_manager.h
* @brief
*
* AppConfig
* ModuleRegistry IModuleConfig
*
* AppConfig
*/
/**

View File

@ -2,7 +2,6 @@
#include <extra2d/core/export.h>
#include <extra2d/core/types.h>
#include <extra2d/core/property.h>
#include <vector>
namespace extra2d {
@ -10,8 +9,6 @@ namespace extra2d {
class Module;
class UpdateContext;
class RenderContext;
class EventContext;
/**
* @brief
*
@ -112,22 +109,6 @@ private:
Phase phase_;
};
/**
* @brief
*
*/
class EventContext : public ModuleContext {
public:
/**
* @brief
* @param modules
*/
EventContext(std::vector<Module*>& modules);
protected:
void handle(Module* m) override;
};
/**
* @brief
*
@ -179,13 +160,6 @@ public:
*/
virtual void afterRender(RenderContext& ctx) { ctx.next(); }
/**
* @brief
*
* @param ctx
*/
virtual void handleEvent(EventContext& ctx) { ctx.next(); }
/**
* @brief
* @return

View File

@ -2,7 +2,6 @@
#include <extra2d/core/export.h>
#include <extra2d/core/module_meta.h>
#include <extra2d/core/property.h>
#include <extra2d/core/types.h>
#include <initializer_list>
@ -20,20 +19,13 @@ public:
const char* name_ = nullptr;
int priority_ = 0;
std::vector<const char*> dependencies_;
std::function<void(Module*, PropertyBinder&)> bindFunc_;
const char* getName() const override { return name_; }
int getPriority() const override { return priority_; }
std::vector<const char*> getDependencies() const override { return dependencies_; }
T* create() override {
return new T();
}
void bindProperties(Module* instance, PropertyBinder& binder) override {
if (bindFunc_) {
bindFunc_(instance, binder);
}
T* create() override {
return new T();
}
};
@ -107,50 +99,3 @@ struct ModuleAutoRegister {
E2D_DECLARE_FORCE_LINK(ModuleClassName); \
E2D_CALL_FORCE_LINK(ModuleClassName)
/**
* @brief
*/
#define E2D_MODULE_BEGIN(ModuleClassName) \
namespace { \
static ::extra2d::ModuleMeta< ::extra2d::ModuleClassName>& E2D_CONCAT(_e2d_get_meta_, ModuleClassName)() { \
static ::extra2d::ModuleMeta< ::extra2d::ModuleClassName> meta; \
return meta; \
} \
struct E2D_CONCAT(_E2D_ModuleCfg_, ModuleClassName) { \
E2D_CONCAT(_E2D_ModuleCfg_, ModuleClassName)()
/**
* @brief
*/
#define E2D_PRIORITY(value) \
{ auto& m = E2D_CONCAT(_e2d_get_meta_, ModuleClassName)(); m.priority_ = value; }
/**
* @brief
*/
#define E2D_DEPENDENCIES(...) \
{ \
auto& m = E2D_CONCAT(_e2d_get_meta_, ModuleClassName)(); \
m.dependencies_ = { __VA_ARGS__ }; \
}
/**
* @brief
*/
#define E2D_PROPERTY(name, type) \
{ \
auto& m = E2D_CONCAT(_e2d_get_meta_, ModuleClassName)(); \
auto oldFunc = m.bindFunc_; \
m.bindFunc_ = [oldFunc](::extra2d::Module* inst, ::extra2d::PropertyBinder& binder) { \
if (oldFunc) oldFunc(inst, binder); \
auto* module = static_cast< ::extra2d::ModuleClassName*>(inst); \
binder.bind<type>(#name, module->name, #name, ""); \
}; \
}
/**
* @brief
*/
#define E2D_MODULE_END() \
} E2D_CONCAT(_e2d_cfg_inst_, ModuleClassName); \
}

View File

@ -1,7 +1,6 @@
#pragma once
#include <extra2d/core/export.h>
#include <extra2d/core/property.h>
#include <extra2d/core/types.h>
#include <vector>
@ -43,10 +42,6 @@ struct E2D_API ModuleMetaBase {
*/
virtual Module* create() = 0;
/**
* @brief
*/
virtual void bindProperties(Module* instance, PropertyBinder& binder) = 0;
};
/**

View File

@ -1,184 +0,0 @@
#pragma once
#include <extra2d/core/color.h>
#include <extra2d/core/math_types.h>
#include <extra2d/core/types.h>
#include <variant>
#include <vector>
#include <string>
#include <functional>
namespace extra2d {
/**
* @brief
*
*
*/
using PropertyValue = std::variant<
std::monostate,
bool,
int,
float,
double,
std::string,
Vec2,
Vec3,
Color,
Rect,
Size
>;
/**
* @brief
*
*
*/
struct PropertyMeta {
const char* name = nullptr;
const char* displayName = nullptr;
const char* description = nullptr;
PropertyValue defaultValue{};
bool editable = true;
bool serializable = true;
PropertyMeta() = default;
PropertyMeta(const char* n, const char* dn, const char* desc,
PropertyValue def, bool edit = true, bool ser = true)
: name(n), displayName(dn), description(desc),
defaultValue(def), editable(edit), serializable(ser) {}
};
/**
* @brief 访
*
*
*/
struct PropertyAccessor {
std::function<PropertyValue()> getter;
std::function<bool(const PropertyValue&)> setter;
PropertyMeta meta;
};
/**
* @brief
*
*
*/
class PropertyBinder {
public:
virtual ~PropertyBinder() = default;
/**
* @brief
* @return
*/
virtual std::vector<PropertyMeta> getProperties() const = 0;
/**
* @brief
* @param name
* @return
*/
virtual PropertyValue getProperty(const char* name) const = 0;
/**
* @brief
* @param name
* @param value
* @return true
*/
virtual bool setProperty(const char* name, const PropertyValue& value) = 0;
/**
* @brief
* @param name
* @return true
*/
virtual bool hasProperty(const char* name) const = 0;
/**
* @brief
* @param name
* @return nullptr
*/
virtual const PropertyMeta* getPropertyMeta(const char* name) const = 0;
};
/**
* @brief
*
* 使访
*/
class PropertyBinderImpl : public PropertyBinder {
public:
/**
* @brief
* @tparam T
* @param name
* @param value
* @param displayName
* @param description
*/
template<typename T>
void bind(const char* name, T& value,
const char* displayName = nullptr,
const char* description = nullptr) {
PropertyAccessor accessor;
accessor.meta = PropertyMeta(name, displayName ? displayName : name,
description ? description : "", T{});
accessor.getter = [&value]() -> PropertyValue { return value; };
accessor.setter = [&value](const PropertyValue& v) -> bool {
if (auto* ptr = std::get_if<T>(&v)) {
value = *ptr;
return true;
}
return false;
};
accessors_[name] = std::move(accessor);
}
std::vector<PropertyMeta> getProperties() const override {
std::vector<PropertyMeta> result;
result.reserve(accessors_.size());
for (const auto& [name, accessor] : accessors_) {
result.push_back(accessor.meta);
}
return result;
}
PropertyValue getProperty(const char* name) const override {
auto it = accessors_.find(name);
if (it != accessors_.end() && it->second.getter) {
return it->second.getter();
}
return {};
}
bool setProperty(const char* name, const PropertyValue& value) override {
auto it = accessors_.find(name);
if (it != accessors_.end() && it->second.setter) {
return it->second.setter(value);
}
return false;
}
bool hasProperty(const char* name) const override {
return accessors_.find(name) != accessors_.end();
}
const PropertyMeta* getPropertyMeta(const char* name) const override {
auto it = accessors_.find(name);
if (it != accessors_.end()) {
return &it->second.meta;
}
return nullptr;
}
private:
std::unordered_map<std::string, PropertyAccessor> accessors_;
};
} // namespace extra2d

View File

@ -1,137 +0,0 @@
#pragma once
#include <extra2d/core/service_interface.h>
#include <extra2d/core/service_locator.h>
#include <functional>
#include <vector>
#include <string>
namespace extra2d {
/**
* @brief
*/
struct ServiceRegistration {
std::string name;
ServicePriority priority;
std::function<SharedPtr<IService>()> factory;
bool enabled = true;
};
/**
* @brief
*
*/
class ServiceRegistry {
public:
/**
* @brief
* @return
*/
static ServiceRegistry& instance();
ServiceRegistry(const ServiceRegistry&) = delete;
ServiceRegistry& operator=(const ServiceRegistry&) = delete;
/**
* @brief
* @tparam T
* @tparam Impl
* @param name
* @param priority
*/
template<typename T, typename Impl>
void registerService(const std::string& name, ServicePriority priority) {
static_assert(std::is_base_of_v<IService, T>,
"T must derive from IService");
static_assert(std::is_base_of_v<T, Impl>,
"Impl must derive from T");
ServiceRegistration reg;
reg.name = name;
reg.priority = priority;
reg.factory = []() -> SharedPtr<IService> {
return std::static_pointer_cast<IService>(makeShared<Impl>());
};
registrations_.push_back(reg);
}
/**
* @brief
* @tparam T
* @param name
* @param priority
* @param factory
*/
template<typename T>
void registerServiceWithFactory(
const std::string& name,
ServicePriority priority,
std::function<SharedPtr<T>()> factory) {
static_assert(std::is_base_of_v<IService, T>,
"T must derive from IService");
ServiceRegistration reg;
reg.name = name;
reg.priority = priority;
reg.factory = [factory]() -> SharedPtr<IService> {
return std::static_pointer_cast<IService>(factory());
};
registrations_.push_back(reg);
}
/**
* @brief /
* @param name
* @param enabled
*/
void setServiceEnabled(const std::string& name, bool enabled);
/**
* @brief
* ServiceLocator
*/
void createAllServices();
/**
* @brief
* @return
*/
const std::vector<ServiceRegistration>& getRegistrations() const {
return registrations_;
}
/**
* @brief
*/
void clear() {
registrations_.clear();
}
private:
ServiceRegistry() = default;
~ServiceRegistry() = default;
std::vector<ServiceRegistration> registrations_;
};
/**
* @brief
* 使
*/
template<typename Interface, typename Implementation>
class AutoServiceRegistrar {
public:
AutoServiceRegistrar(const std::string& name, ServicePriority priority) {
ServiceRegistry::instance().registerService<Interface, Implementation>(
name, priority);
}
};
}
#define E2D_REGISTER_SERVICE_AUTO(Interface, Implementation, Name, Priority) \
namespace { \
static ::extra2d::AutoServiceRegistrar<Interface, Implementation> \
E2D_CONCAT(auto_service_registrar_, __LINE__)(Name, Priority); \
}

View File

@ -1,53 +0,0 @@
#pragma once
#include <string>
#include <vector>
namespace extra2d {
/**
* @file debug_config.h
* @brief
*
* DebugModule
*/
/**
* @brief
*/
struct DebugConfigData {
bool enabled = false;
bool showFPS = false;
bool showMemoryUsage = false;
bool showRenderStats = false;
bool showColliders = false;
bool showGrid = false;
bool logToFile = false;
bool logToConsole = true;
int logLevel = 2;
bool breakOnAssert = true;
bool enableProfiling = false;
std::string logFilePath;
std::vector<std::string> debugFlags;
/**
* @brief
* @param flag
* @return true
*/
bool hasDebugFlag(const std::string& flag) const;
/**
* @brief
* @param flag
*/
void addDebugFlag(const std::string& flag);
/**
* @brief
* @param flag
*/
void removeDebugFlag(const std::string& flag);
};
}

View File

@ -49,8 +49,6 @@
// Event
#include <extra2d/event/event.h>
#include <extra2d/event/event_dispatcher.h>
#include <extra2d/event/event_queue.h>
// Utils
#include <extra2d/utils/logger.h>

View File

@ -1,51 +0,0 @@
#pragma once
#include <extra2d/core/math_types.h>
#include <extra2d/graphics/render_backend.h>
#include <string>
namespace extra2d {
/**
* @file render_config.h
* @brief
*
* RenderModule
*/
/**
* @brief
*/
struct RenderConfigData {
BackendType backend = BackendType::OpenGL;
int targetFPS = 60;
bool vsync = true;
bool tripleBuffering = false;
int multisamples = 0;
bool sRGBFramebuffer = false;
Color clearColor{0.0f, 0.0f, 0.0f, 1.0f};
int maxTextureSize = 0;
int textureAnisotropy = 1;
bool wireframeMode = false;
bool depthTest = false;
bool blending = true;
bool dithering = false;
int spriteBatchSize = 1000;
int maxRenderTargets = 1;
bool allowShaderHotReload = false;
std::string shaderCachePath;
/**
* @brief
* @return 0 true
*/
bool isMultisampleEnabled() const { return multisamples > 0; }
/**
* @brief
* @return true
*/
bool isFPSCapped() const { return targetFPS > 0; }
};
}

View File

@ -1,131 +0,0 @@
#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

@ -1,137 +0,0 @@
#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

@ -32,136 +32,34 @@ struct ShaderMetadata {
};
// ============================================================================
// ShaderLoader接口 - 支持多种文件格式加载
// ShaderLoader - Shader文件加载
// ============================================================================
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 {
class ShaderLoader {
public:
ShaderLoader();
~ShaderLoader() override = default;
~ShaderLoader() = 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;
const std::string& fragPath);
/**
* @brief Shader (.shader)
* @param path Shader文件路径
* @return
*/
ShaderLoadResult loadFromCombinedFile(const std::string& path) override;
ShaderLoadResult loadFromCombinedFile(const std::string& path);
/**
* @brief Shader
* @param vertSource
* @param fragSource
* @return
*/
ShaderLoadResult loadFromSource(
const std::string& vertSource,
const std::string& fragSource) override;
const std::string& fragSource);
/**
* @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;
std::vector<std::string>& outDependencies);
/**
* @brief
* @param source
* @param defines
* @return
*/
std::string applyDefines(
const std::string& source,
const std::vector<std::string>& defines) override;
const std::vector<std::string>& defines);
/**
* @brief Shader元数据
* @param path Shader文件路径
* @return
*/
ShaderMetadata getMetadata(const std::string& path) override;
ShaderMetadata getMetadata(const std::string& path);
/**
* @brief include搜索路径

View File

@ -1,21 +1,52 @@
#pragma once
#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>
#include <vector>
#ifdef _WIN32
#include <windows.h>
#endif
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;
};
// ============================================================================
// 文件变化事件
// ============================================================================
struct FileChangeEvent {
std::string filepath;
enum class Type {
Created,
Modified,
Deleted,
Renamed
} type;
uint64_t timestamp = 0;
};
// ============================================================================
// Shader重载回调
// ============================================================================
using ShaderReloadCallback = std::function<void(Ptr<IShader> newShader)>;
using FileChangeCallback = std::function<void(const FileChangeEvent&)>;
// ============================================================================
// Shader管理器 - 统一入口
// Shader管理器 - 统一入口(加载/缓存/热重载)
// ============================================================================
class ShaderManager {
public:
@ -31,7 +62,6 @@ public:
/**
* @brief 使Shader系统
* 使romfs/sdmc/
* @param factory Shader工厂
* @param appName
* @return truefalse
@ -56,14 +86,11 @@ public:
/**
* @brief
* @return truefalse
*/
bool isInitialized() const { return initialized_; }
/**
* @brief
* Switch平台使用romfs
* @return true
*/
bool isHotReloadSupported() const { return hotReloadSupported_; }
@ -71,134 +98,73 @@ public:
// 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);
// ------------------------------------------------------------------------
// 热重载文件监视
// ------------------------------------------------------------------------
void watch(const std::string& shaderName,
const std::vector<std::string>& filePaths,
FileChangeCallback callback);
void unwatch(const std::string& shaderName);
// ------------------------------------------------------------------------
// 内置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_; }
// ------------------------------------------------------------------------
// 缓存
// ------------------------------------------------------------------------
bool hasValidCache(const std::string& name, const std::string& sourceHash);
Ptr<ShaderCacheEntry> loadCache(const std::string& name);
bool saveCache(const ShaderCacheEntry& entry);
void invalidateCache(const std::string& name);
void clearAllCache();
static std::string computeHash(const std::string& vertSource,
const std::string& fragSource);
private:
ShaderManager() = default;
~ShaderManager() = default;
ShaderManager(const ShaderManager&) = delete;
ShaderManager& operator=(const ShaderManager&) = delete;
// Shader存储
std::string shaderDir_;
std::string cacheDir_;
Ptr<IShaderFactory> factory_;
@ -218,29 +184,45 @@ private:
bool hotReloadEnabled_ = false;
bool hotReloadSupported_ = true;
/**
* @brief Shader
* @param name Shader名称
* @param sourceHash
* @param vertSource
* @param fragSource
* @return Shader实例
*/
// 缓存(原 ShaderCache
std::unordered_map<std::string, ShaderCacheEntry> cacheMap_;
bool cacheInitialized_ = false;
bool initCache(const std::string& cacheDir);
void shutdownCache();
bool loadCacheIndex();
bool saveCacheIndex();
std::string getCachePath(const std::string& name) const;
bool ensureCacheDirectory();
// 热重载(原 ShaderHotReloader
bool reloaderInitialized_ = 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> watchBuffer_;
std::string watchDir_;
bool watching_ = false;
#endif
bool initReloader();
void shutdownReloader();
void pollChanges();
static uint64_t getFileModifiedTime(const std::string& filepath);
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);
};

View File

@ -1,112 +0,0 @@
#pragma once
#include <extra2d/core/color.h>
#include <extra2d/core/types.h>
#include <extra2d/graphics/shader_interface.h>
#include <glm/vec4.hpp>
namespace extra2d {
struct WaterParams {
float waveSpeed = 1.0f;
float waveAmplitude = 0.02f;
float waveFrequency = 4.0f;
};
struct OutlineParams {
Color color = Colors::Black;
float thickness = 2.0f;
};
struct DistortionParams {
float distortionAmount = 0.02f;
float timeScale = 1.0f;
};
struct PixelateParams {
float pixelSize = 8.0f;
};
struct InvertParams {
float strength = 1.0f;
};
struct GrayscaleParams {
float intensity = 1.0f;
};
struct BlurParams {
float radius = 5.0f;
};
class ShaderPreset {
public:
/**
* @brief
* @param params
* @return
*/
static Ptr<IShader> Water(const WaterParams& params = {});
/**
* @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);
/**
* @brief +
* @param pixParams
* @param invParams
* @return
*/
static Ptr<IShader> PixelateInvert(const PixelateParams& pixParams,
const InvertParams& invParams);
};
} // namespace extra2d

View File

@ -382,6 +382,12 @@ public:
*/
void init(Scene* scene, size_t maxMemoryUsage = 0);
/**
* @brief
* @param backend
*/
void setRenderBackend(RenderBackend* backend) { backend_ = backend; }
// ========================================================================
// 纹理加载
// ========================================================================
@ -554,6 +560,7 @@ private:
void tryAutoEvict();
Scene* scene_; // 场景指针
RenderBackend* backend_ = nullptr; // 渲染后端
mutable std::mutex mutex_; // 互斥锁
std::unordered_map<TextureKey, TexturePoolEntry, TextureKeyHash> cache_; // 纹理缓存

View File

@ -1,8 +1,9 @@
#pragma once
#include <extra2d/core/module.h>
#include <extra2d/graphics/render_config.h>
#include <extra2d/core/math_types.h>
#include <extra2d/graphics/render_backend.h>
#include <string>
namespace extra2d {
@ -16,6 +17,27 @@ struct RenderModuleConfig {
int multisamples = 0;
bool sRGBFramebuffer = false;
int spriteBatchSize = 1000;
bool tripleBuffering = false;
Color clearColor{0.0f, 0.0f, 0.0f, 1.0f};
int maxTextureSize = 0;
int textureAnisotropy = 1;
bool wireframeMode = false;
bool depthTest = false;
bool blending = true;
bool dithering = false;
int maxRenderTargets = 1;
bool allowShaderHotReload = false;
std::string shaderCachePath;
/**
* @brief
*/
bool isMultisampleEnabled() const { return multisamples > 0; }
/**
* @brief
*/
bool isFPSCapped() const { return targetFPS > 0; }
/**
* @brief
@ -25,16 +47,16 @@ struct RenderModuleConfig {
if (targetFPS < 1 || targetFPS > 240) {
return false;
}
if (multisamples != 0 && multisamples != 2 && multisamples != 4 &&
if (multisamples != 0 && multisamples != 2 && multisamples != 4 &&
multisamples != 8 && multisamples != 16) {
return false;
}
if (spriteBatchSize <= 0) {
return false;
}
return true;
}
};

View File

@ -1,52 +0,0 @@
#pragma once
#include <string>
#include <vector>
namespace extra2d {
/**
* @file resource_config.h
* @brief
*
* ResourceModule
*/
/**
* @brief
*/
struct ResourceConfigData {
std::string assetRootPath = "assets";
std::string cachePath = "cache";
std::string savePath = "saves";
std::string configPath = "config";
std::string logPath = "logs";
bool useAssetCache = true;
int maxCacheSize = 512;
bool hotReloadEnabled = false;
float hotReloadInterval = 1.0f;
bool compressTextures = false;
bool preloadCommonAssets = true;
std::vector<std::string> searchPaths;
/**
* @brief
* @param path
*/
void addSearchPath(const std::string& path);
/**
* @brief
* @param path
*/
void removeSearchPath(const std::string& path);
/**
* @brief
* @param path
* @return true
*/
bool hasSearchPath(const std::string& path) const;
};
}

View File

@ -60,11 +60,6 @@ public:
size_t getTotalListenerCount() const override;
size_t getQueueSize() const override;
EventQueue& getQueue() { return queue_; }
const EventQueue& getQueue() const { return queue_; }
EventDispatcher& getDispatcher() { return dispatcher_; }
const EventDispatcher& getDispatcher() const { return dispatcher_; }
private:
EventQueue queue_;
EventDispatcher dispatcher_;

View File

@ -256,9 +256,6 @@ void Application::mainLoop() {
render();
auto renderModule = getModule<RenderModule>();
(void)renderModule;
ConfigManager::instance().update(deltaTime_);
}
@ -331,11 +328,8 @@ RenderBackend &Application::renderer() {
if (renderModule && renderModule->getRenderer()) {
return *renderModule->getRenderer();
}
static RenderBackend *dummy = nullptr;
if (!dummy) {
dummy = RenderBackend::create(BackendType::OpenGL).release();
}
return *dummy;
E2D_LOG_ERROR("RenderModule not initialized - renderer() called too early");
std::abort();
}
SharedPtr<ISceneService> Application::scenes() {

View File

@ -54,16 +54,4 @@ void RenderContext::handle(Module* m) {
}
}
// ---------------------------------------------------------------------------
// EventContext 实现
// ---------------------------------------------------------------------------
EventContext::EventContext(std::vector<Module*>& modules)
: ModuleContext(modules) {
}
void EventContext::handle(Module* m) {
m->handleEvent(*this);
}
} // namespace extra2d

View File

@ -205,9 +205,6 @@ bool ModuleRegistry::createAndInitAll() {
continue;
}
PropertyBinderImpl binder;
meta->bindProperties(instance, binder);
instance->setupModule();
instances_.emplace_back(instance);

View File

@ -1,37 +0,0 @@
#include <extra2d/core/service_registry.h>
namespace extra2d {
ServiceRegistry& ServiceRegistry::instance() {
static ServiceRegistry instance;
return instance;
}
void ServiceRegistry::setServiceEnabled(const std::string& name, bool enabled) {
for (auto& reg : registrations_) {
if (reg.name == name) {
reg.enabled = enabled;
break;
}
}
}
void ServiceRegistry::createAllServices() {
std::sort(registrations_.begin(), registrations_.end(),
[](const ServiceRegistration& a, const ServiceRegistration& b) {
return static_cast<int>(a.priority) < static_cast<int>(b.priority);
});
for (const auto& reg : registrations_) {
if (!reg.enabled) {
continue;
}
auto service = reg.factory();
if (service) {
ServiceLocator::instance().registerService<IService>(service);
}
}
}
}

View File

@ -1,23 +0,0 @@
#include <extra2d/debug/debug_config.h>
#include <algorithm>
namespace extra2d {
bool DebugConfigData::hasDebugFlag(const std::string& flag) const {
return std::find(debugFlags.begin(), debugFlags.end(), flag) != debugFlags.end();
}
void DebugConfigData::addDebugFlag(const std::string& flag) {
if (!hasDebugFlag(flag)) {
debugFlags.push_back(flag);
}
}
void DebugConfigData::removeDebugFlag(const std::string& flag) {
auto it = std::find(debugFlags.begin(), debugFlags.end(), flag);
if (it != debugFlags.end()) {
debugFlags.erase(it);
}
}
}

View File

@ -1,286 +0,0 @@
#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_) {
E2D_LOG_WARN("ShaderCache not initialized, cannot save cache");
return false;
}
if (entry.binary.empty()) {
E2D_LOG_WARN("Shader binary is empty, skipping cache save for: {}", entry.name);
return false;
}
std::string cachePath = getCachePath(entry.name);
E2D_LOG_DEBUG("Saving shader cache to: {} ({} bytes)", cachePath, entry.binary.size());
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_INFO("Shader cache saved: {} ({} bytes)", entry.name, entry.binary.size());
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

@ -1,164 +0,0 @@
#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

@ -1,8 +1,18 @@
#include <extra2d/graphics/shader_manager.h>
#include <extra2d/utils/logger.h>
#include <chrono>
#include <filesystem>
#include <fstream>
#include <sstream>
namespace extra2d {
namespace fs = std::filesystem;
// ============================================================================
// ShaderManager 核心
// ============================================================================
ShaderManager& ShaderManager::getInstance() {
static ShaderManager instance;
return instance;
@ -11,7 +21,7 @@ ShaderManager& ShaderManager::getInstance() {
bool ShaderManager::init(Ptr<IShaderFactory> factory, const std::string& appName) {
std::string shaderDir;
std::string cacheDir;
#ifdef __SWITCH__
shaderDir = "romfs:/shaders/";
cacheDir = "sdmc:/config/" + appName + "/shader_cache/";
@ -23,7 +33,7 @@ bool ShaderManager::init(Ptr<IShaderFactory> factory, const std::string& appName
hotReloadSupported_ = true;
E2D_LOG_INFO("Platform: Desktop (HotReload: supported)");
#endif
return init(shaderDir, cacheDir, factory);
}
@ -46,18 +56,16 @@ bool ShaderManager::init(const std::string& shaderDir,
#ifdef __SWITCH__
hotReloadSupported_ = false;
if (!ShaderCache::getInstance().init(cacheDir_)) {
E2D_LOG_WARN("Failed to initialize shader cache on Switch");
}
#else
hotReloadSupported_ = true;
if (!ShaderCache::getInstance().init(cacheDir_)) {
E2D_LOG_WARN("Failed to initialize shader cache, caching disabled");
}
#endif
if (!initCache(cacheDir_)) {
E2D_LOG_WARN("Failed to initialize shader cache, caching disabled");
}
if (hotReloadSupported_) {
if (!ShaderHotReloader::getInstance().init()) {
if (!initReloader()) {
E2D_LOG_WARN("Failed to initialize hot reloader");
}
}
@ -66,7 +74,7 @@ bool ShaderManager::init(const std::string& shaderDir,
initialized_ = true;
E2D_LOG_INFO("ShaderManager initialized");
return true;
}
@ -76,9 +84,9 @@ void ShaderManager::shutdown() {
}
if (hotReloadSupported_) {
ShaderHotReloader::getInstance().shutdown();
shutdownReloader();
}
ShaderCache::getInstance().shutdown();
shutdownCache();
shaders_.clear();
factory_.reset();
@ -87,6 +95,10 @@ void ShaderManager::shutdown() {
E2D_LOG_INFO("ShaderManager shutdown");
}
// ============================================================================
// Shader 加载
// ============================================================================
Ptr<IShader> ShaderManager::loadFromFiles(const std::string& name,
const std::string& vertPath,
const std::string& fragPath) {
@ -97,7 +109,7 @@ Ptr<IShader> ShaderManager::loadFromFiles(const std::string& name,
auto vertSource = loader_.readFile(vertPath);
auto fragSource = loader_.readFile(fragPath);
if (vertSource.empty() || fragSource.empty()) {
E2D_LOG_ERROR("Failed to load shader sources: {} / {}", vertPath, fragPath);
return nullptr;
@ -145,7 +157,7 @@ Ptr<IShader> ShaderManager::loadFromSource(const std::string& name,
info.shader = shader;
info.vertSource = vertSource;
info.fragSource = fragSource;
shaders_[name] = std::move(info);
E2D_LOG_INFO("Shader loaded: {}", name);
@ -177,6 +189,10 @@ void ShaderManager::clear() {
E2D_LOG_INFO("All shaders cleared");
}
// ============================================================================
// 热重载
// ============================================================================
void ShaderManager::setReloadCallback(const std::string& name, ShaderReloadCallback callback) {
auto it = shaders_.find(name);
if (it != shaders_.end()) {
@ -200,7 +216,9 @@ void ShaderManager::update() {
if (!hotReloadEnabled_ || !hotReloadSupported_) {
return;
}
ShaderHotReloader::getInstance().update();
if (reloaderInitialized_) {
pollChanges();
}
}
bool ShaderManager::reload(const std::string& name) {
@ -217,7 +235,7 @@ bool ShaderManager::reload(const std::string& name) {
}
it->second.shader = shader;
if (it->second.reloadCallback) {
it->second.reloadCallback(shader);
}
@ -226,13 +244,17 @@ bool ShaderManager::reload(const std::string& name) {
return true;
}
// ============================================================================
// 内置Shader
// ============================================================================
Ptr<IShader> ShaderManager::getBuiltin(const std::string& name) {
return get("builtin_" + name);
}
bool ShaderManager::loadBuiltinShaders() {
E2D_LOG_INFO("Loading builtin shaders...");
const std::vector<std::string> builtinShaders = {
"sprite",
"shape",
@ -240,7 +262,7 @@ bool ShaderManager::loadBuiltinShaders() {
"particle",
"postprocess"
};
int loaded = 0;
for (const auto& name : builtinShaders) {
std::string path = shaderDir_ + "builtin/" + name + ".shader";
@ -260,7 +282,7 @@ bool ShaderManager::loadBuiltinShaders() {
}
}
}
E2D_LOG_INFO("Builtin shaders loaded: {}/{}", loaded, builtinShaders.size());
return loaded > 0;
}
@ -274,4 +296,321 @@ void ShaderManager::handleFileChange(const std::string& shaderName, const FileCh
}
}
// ============================================================================
// 缓存(原 ShaderCache
// ============================================================================
bool ShaderManager::initCache(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");
}
cacheInitialized_ = true;
E2D_LOG_INFO("Shader cache initialized at: {}", cacheDir);
return true;
}
void ShaderManager::shutdownCache() {
if (!cacheInitialized_) {
return;
}
saveCacheIndex();
cacheMap_.clear();
cacheInitialized_ = false;
E2D_LOG_INFO("Shader cache shutdown");
}
bool ShaderManager::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;
}
Ptr<ShaderCacheEntry> ShaderManager::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;
}
bool ShaderManager::saveCache(const ShaderCacheEntry& entry) {
if (!cacheInitialized_) {
E2D_LOG_WARN("Shader cache not initialized, cannot save cache");
return false;
}
if (entry.binary.empty()) {
E2D_LOG_WARN("Shader binary is empty, skipping cache save for: {}", entry.name);
return false;
}
std::string cachePath = getCachePath(entry.name);
E2D_LOG_DEBUG("Saving shader cache to: {} ({} bytes)", cachePath, entry.binary.size());
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_INFO("Shader cache saved: {} ({} bytes)", entry.name, entry.binary.size());
return true;
}
void ShaderManager::invalidateCache(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);
}
void ShaderManager::clearAllCache() {
for (const auto& pair : cacheMap_) {
std::string cachePath = getCachePath(pair.first);
fs::remove(cachePath);
}
cacheMap_.clear();
saveCacheIndex();
E2D_LOG_INFO("All shader caches cleared");
}
std::string ShaderManager::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();
}
bool ShaderManager::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;
}
bool ShaderManager::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;
}
std::string ShaderManager::getCachePath(const std::string& name) const {
return cacheDir_ + "/" + name + ".cache";
}
bool ShaderManager::ensureCacheDirectory() {
if (cacheDir_.empty()) {
return false;
}
std::error_code ec;
if (!fs::exists(cacheDir_)) {
if (!fs::create_directories(cacheDir_, ec)) {
return false;
}
}
return true;
}
// ============================================================================
// 热重载(原 ShaderHotReloader
// ============================================================================
bool ShaderManager::initReloader() {
if (reloaderInitialized_) {
return true;
}
#ifdef _WIN32
watchBuffer_.resize(4096);
#endif
reloaderInitialized_ = true;
E2D_LOG_INFO("Shader hot reloader initialized");
return true;
}
void ShaderManager::shutdownReloader() {
if (!reloaderInitialized_) {
return;
}
#ifdef _WIN32
if (watchHandle_ != nullptr) {
FindCloseChangeNotification(watchHandle_);
watchHandle_ = nullptr;
}
#endif
watchMap_.clear();
reloaderInitialized_ = false;
E2D_LOG_INFO("Shader hot reloader shutdown");
}
void ShaderManager::watch(const std::string& shaderName,
const std::vector<std::string>& filePaths,
FileChangeCallback callback) {
if (!reloaderInitialized_) {
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());
}
void ShaderManager::unwatch(const std::string& shaderName) {
auto it = watchMap_.find(shaderName);
if (it != watchMap_.end()) {
watchMap_.erase(it);
E2D_LOG_DEBUG("Stopped watching shader: {}", shaderName);
}
}
void ShaderManager::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);
}
}
}
}
}
uint64_t ShaderManager::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

@ -1,184 +0,0 @@
#include <extra2d/graphics/shader_manager.h>
#include <extra2d/graphics/shader_preset.h>
#include <extra2d/utils/logger.h>
namespace extra2d {
/**
* @brief
* @param params
* @return
*/
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);
return shader;
}
/**
* @brief
* @param params
* @return
*/
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);
return shader;
}
/**
* @brief
* @param params
* @return
*/
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);
return shader;
}
/**
* @brief
* @param params
* @return
*/
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);
return shader;
}
/**
* @brief
* @param params
* @return
*/
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);
return shader;
}
/**
* @brief
* @param params
* @return
*/
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);
return shader;
}
/**
* @brief
* @param params
* @return
*/
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);
return shader;
}
/**
* @brief +
* @param grayParams
* @param outlineParams
* @return
*/
Ptr<IShader>
ShaderPreset::GrayscaleOutline(const GrayscaleParams &grayParams,
const OutlineParams &outlineParams) {
std::string shaderDir = ShaderManager::getInstance().getShaderDir();
std::string shaderPath = shaderDir + "effects/grayscale_outline.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);
return shader;
}
/**
* @brief +
* @param pixParams
* @param invParams
* @return
*/
Ptr<IShader> ShaderPreset::PixelateInvert(const PixelateParams &pixParams,
const InvertParams &invParams) {
std::string shaderDir = ShaderManager::getInstance().getShaderDir();
std::string shaderPath = shaderDir + "effects/pixelate_invert.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);
return shader;
}
} // namespace extra2d

View File

@ -112,15 +112,10 @@ TextureRef TexturePool::load(const std::string& path, const Rect& region,
cacheMisses_.fetch_add(1, std::memory_order_relaxed);
// 获取渲染后端
RenderBackend* backend = nullptr;
if (scene_) {
// 假设 Scene 有获取 RenderBackend 的方法
// 这里需要根据实际接口调整
backend = nullptr; // TODO: 从 Scene 获取 RenderBackend
}
RenderBackend* backend = backend_;
if (!backend) {
E2D_LOG_ERROR("TexturePool: RenderBackend not available");
E2D_LOG_ERROR("TexturePool: RenderBackend not available, call setRenderBackend() first");
return TextureRef();
}

View File

@ -1,23 +0,0 @@
#include <extra2d/resource/resource_config.h>
#include <algorithm>
namespace extra2d {
void ResourceConfigData::addSearchPath(const std::string& path) {
if (!hasSearchPath(path)) {
searchPaths.push_back(path);
}
}
void ResourceConfigData::removeSearchPath(const std::string& path) {
auto it = std::find(searchPaths.begin(), searchPaths.end(), path);
if (it != searchPaths.end()) {
searchPaths.erase(it);
}
}
bool ResourceConfigData::hasSearchPath(const std::string& path) const {
return std::find(searchPaths.begin(), searchPaths.end(), path) != searchPaths.end();
}
}