refactor(图形后端): 重构图形后端系统为基于工厂的注册机制

- 移除BackendType枚举,改为使用字符串标识后端类型
- 实现图形后端工厂类,支持动态注册和创建后端
- 添加自动兼容性检查功能,根据窗口后端选择兼容的渲染器
- 移除WindowConfigData结构,简化窗口创建接口
- 更新示例代码以使用新的后端系统
- 添加OpenGL和Vulkan后端自动注册实现
- 清理无用代码和过时接口
This commit is contained in:
ChestnutYueyue 2026-02-18 17:15:49 +08:00
parent 583e866861
commit 8e06bb0adb
24 changed files with 514 additions and 560 deletions

View File

@ -1,79 +1,124 @@
#pragma once #pragma once
#include <extra2d/core/types.h>
#include <extra2d/graphics/core/render_backend.h> #include <extra2d/graphics/core/render_backend.h>
#include <extra2d/core/smart_ptr.h>
#include <functional>
#include <string>
#include <unordered_map>
#include <vector>
namespace extra2d { namespace extra2d {
namespace graphics {
/** /**
* @brief * @brief
*/ *
enum class BackendType {
OpenGL, // OpenGL 4.x
Vulkan, // Vulkan 1.x
Metal, // Metal (macOS/iOS)
D3D11, // Direct3D 11
D3D12, // Direct3D 12
OpenGLES, // OpenGL ES (移动平台)
Count
};
/**
* @brief
*/ */
class BackendFactory { class BackendFactory {
public: public:
/** using BackendFn = std::function<UniquePtr<RenderBackend>()>;
* @brief
* @return
*/
static BackendFactory& getInstance();
/** /**
* @brief * @brief
* @param type * @param name
* @return * @param backend
* @param windowBackends
*/ */
UniquePtr<RenderBackend> createBackend(BackendType type); static void reg(const std::string &name, BackendFn backend,
const std::vector<std::string> &windowBackends = {});
/**
* @brief
* @param name
* @return nullptr
*/
static UniquePtr<RenderBackend> createBackend(const std::string &name);
/** /**
* @brief * @brief
* @return * @return
*/ */
UniquePtr<RenderBackend> createDefaultBackend(); static UniquePtr<RenderBackend> createDefaultBackend();
/** /**
* @brief * @brief
* @param type * @param windowBackend
* @return truefalse * @return
*/ */
bool isBackendAvailable(BackendType type) const; static UniquePtr<RenderBackend>
createBackendForWindow(const std::string &windowBackend);
/** /**
* @brief * @brief
* @return
*/ */
BackendType getRecommendedBackend() const; static std::vector<std::string> backends();
/** /**
* @brief * @brief
* @param type
* @return
*/ */
const char* getBackendName(BackendType type) const; static bool has(const std::string &name);
/** /**
* @brief * @brief
* @param name * @return
* @return OpenGL
*/ */
BackendType parseBackendType(const char* name) const; static std::string getRecommendedBackend();
/**
* @brief
* @param windowBackend
* @return
*/
static std::string
getRecommendedBackendForWindow(const std::string &windowBackend);
/**
* @brief
* @param graphicsBackend
* @param windowBackend
* @return true
*/
static bool isCompatible(const std::string &graphicsBackend,
const std::string &windowBackend);
/**
* @brief
* @param graphicsBackend
* @return
*/
static std::vector<std::string>
getSupportedWindowBackends(const std::string &graphicsBackend);
private: private:
BackendFactory() = default; struct BackendEntry {
~BackendFactory() = default; BackendFn createFn;
BackendFactory(const BackendFactory&) = delete; std::vector<std::string> windowBackends;
BackendFactory& operator=(const BackendFactory&) = delete; };
static std::unordered_map<std::string, BackendEntry> &registry();
}; };
/**
* @brief
* 使
*
* @example
* E2D_REG_GRAPHICS_BACKEND(opengl, GLRenderer, {"sdl2", "glfw"})
*/
#define E2D_REG_GRAPHICS_BACKEND(name, RendererClass, windowBackends) \
namespace { \
__attribute__((used)) static struct E2D_GRAPHICS_BACKEND_REG_##name { \
E2D_GRAPHICS_BACKEND_REG_##name() { \
::extra2d::graphics::BackendFactory::reg( \
#name, \
[]() -> ::extra2d::UniquePtr<::extra2d::RenderBackend> { \
return ::extra2d::makeUnique<RendererClass>(); \
}, \
windowBackends); \
} \
} e2d_graphics_backend_reg_##name; \
}
} // namespace graphics
} // namespace extra2d } // namespace extra2d

View File

@ -14,17 +14,6 @@ class Texture;
class FontAtlas; class FontAtlas;
class Shader; class Shader;
// ============================================================================
// 渲染后端类型
// ============================================================================
enum class BackendType {
OpenGL,
// Vulkan,
// Metal,
// D3D11,
// D3D12
};
// BlendMode 定义在 pipeline.h 中 // BlendMode 定义在 pipeline.h 中
// ============================================================================ // ============================================================================
@ -131,11 +120,6 @@ public:
}; };
virtual Stats getStats() const = 0; virtual Stats getStats() const = 0;
virtual void resetStats() = 0; virtual void resetStats() = 0;
// ------------------------------------------------------------------------
// 工厂方法
// ------------------------------------------------------------------------
static UniquePtr<RenderBackend> create(BackendType type);
}; };
} // namespace extra2d } // namespace extra2d

View File

@ -12,14 +12,14 @@ namespace extra2d {
* @brief * @brief
*/ */
struct RenderCfg { struct RenderCfg {
BackendType backend; std::string backend;
int targetFPS; int targetFPS;
bool vsync; bool vsync;
int multisamples; int multisamples;
int priority; int priority;
RenderCfg() RenderCfg()
: backend(BackendType::OpenGL) : backend("")
, targetFPS(60) , targetFPS(60)
, vsync(true) , vsync(true)
, multisamples(0) , multisamples(0)
@ -29,7 +29,7 @@ struct RenderCfg {
/** /**
* @brief * @brief
* *
*/ */
class RenderModule : public Module { class RenderModule : public Module {
public: public:

View File

@ -1,14 +1,15 @@
#pragma once #pragma once
#include <extra2d/core/types.h> #include <extra2d/core/types.h>
#include <extra2d/platform/iwindow.h>
#include <extra2d/platform/iinput.h> #include <extra2d/platform/iinput.h>
#include <extra2d/platform/iwindow.h>
#include <functional> #include <functional>
#include <string>
#include <unordered_map> #include <unordered_map>
#include <vector> #include <vector>
#include <string>
namespace extra2d { namespace extra2d {
namespace platform {
/** /**
* @brief * @brief
@ -25,21 +26,21 @@ public:
* @param win * @param win
* @param in * @param in
*/ */
static void reg(const std::string& name, WindowFn win, InputFn in); static void reg(const std::string &name, WindowFn win, InputFn in);
/** /**
* @brief * @brief
* @param name * @param name
* @return nullptr * @return nullptr
*/ */
static UniquePtr<IWindow> createWindow(const std::string& name); static UniquePtr<IWindow> createWindow(const std::string &name);
/** /**
* @brief * @brief
* @param name * @param name
* @return nullptr * @return nullptr
*/ */
static UniquePtr<IInput> createInput(const std::string& name); static UniquePtr<IInput> createInput(const std::string &name);
/** /**
* @brief * @brief
@ -49,7 +50,7 @@ public:
/** /**
* @brief * @brief
*/ */
static bool has(const std::string& name); static bool has(const std::string &name);
private: private:
struct BackendEntry { struct BackendEntry {
@ -57,7 +58,7 @@ private:
InputFn inputFn; InputFn inputFn;
}; };
static std::unordered_map<std::string, BackendEntry>& registry(); static std::unordered_map<std::string, BackendEntry> &registry();
}; };
/** /**
@ -69,20 +70,19 @@ private:
*/ */
#define E2D_REG_BACKEND(name, WinClass, InClass) \ #define E2D_REG_BACKEND(name, WinClass, InClass) \
namespace { \ namespace { \
__attribute__((used)) \ __attribute__((used)) static struct E2D_BACKEND_REG_##name { \
static struct E2D_BACKEND_REG_##name { \
E2D_BACKEND_REG_##name() { \ E2D_BACKEND_REG_##name() { \
::extra2d::BackendFactory::reg( \ ::extra2d::platform::BackendFactory::reg( \
#name, \ #name, \
[]() -> ::extra2d::UniquePtr<::extra2d::IWindow> { \ []() -> ::extra2d::UniquePtr<::extra2d::IWindow> { \
return ::extra2d::makeUnique<WinClass>(); \ return ::extra2d::makeUnique<WinClass>(); \
}, \ }, \
[]() -> ::extra2d::UniquePtr<::extra2d::IInput> { \ []() -> ::extra2d::UniquePtr<::extra2d::IInput> { \
return ::extra2d::makeUnique<InClass>(); \ return ::extra2d::makeUnique<InClass>(); \
} \ }); \
); \
} \ } \
} e2d_backend_reg_##name; \ } e2d_backend_reg_##name; \
} }
} // namespace platform
} // namespace extra2d } // namespace extra2d

View File

@ -2,7 +2,6 @@
#include <extra2d/core/types.h> #include <extra2d/core/types.h>
#include <extra2d/core/math_types.h> #include <extra2d/core/math_types.h>
#include <extra2d/platform/window_config.h>
#include <functional> #include <functional>
#include <string> #include <string>
@ -33,10 +32,13 @@ public:
/** /**
* @brief * @brief
* @param cfg * @param title
* @param width
* @param height
* @param vsync
* @return * @return
*/ */
virtual bool create(const WindowConfigData& cfg) = 0; virtual bool create(const std::string& title, int width, int height, bool vsync = true) = 0;
/** /**
* @brief * @brief

View File

@ -1,84 +0,0 @@
#pragma once
#include <extra2d/core/math_types.h>
#include <string>
namespace extra2d {
/**
* @file window_config.h
* @brief
*
* WindowModule
*/
/**
* @brief
*/
enum class WindowMode {
Windowed,
Fullscreen,
Borderless
};
/**
* @brief
*/
struct WindowConfigData {
std::string title = "Extra2D Application";
int width = 1280;
int height = 720;
int minWidth = 320;
int minHeight = 240;
int maxWidth = 0;
int maxHeight = 0;
WindowMode mode = WindowMode::Windowed;
bool resizable = true;
bool borderless = false;
bool alwaysOnTop = false;
bool centered = true;
int posX = -1;
int posY = -1;
bool hideOnClose = false;
bool minimizeOnClose = true;
float opacity = 1.0f;
bool transparentFramebuffer = false;
bool highDPI = true;
float contentScale = 1.0f;
bool vsync = true;
int multisamples = 0;
bool visible = true;
bool decorated = true;
/**
* @brief
* @return 0 true
*/
bool isSizeValid() const { return width > 0 && height > 0; }
/**
* @brief
* @return true
*/
bool hasPosition() const { return posX >= 0 && posY >= 0; }
/**
* @brief
* @return
*/
float aspectRatio() const { return static_cast<float>(width) / static_cast<float>(height); }
/**
* @brief
* @return true
*/
bool isFullscreen() const { return mode == WindowMode::Fullscreen; }
/**
* @brief
* @return true
*/
bool isBorderless() const { return mode == WindowMode::Borderless || borderless; }
};
}

View File

@ -2,7 +2,6 @@
#include <extra2d/core/module.h> #include <extra2d/core/module.h>
#include <extra2d/platform/iwindow.h> #include <extra2d/platform/iwindow.h>
#include <extra2d/platform/window_config.h>
#include <functional> #include <functional>
#include <string> #include <string>
@ -15,14 +14,13 @@ struct WindowCfg {
std::string title; std::string title;
int w; int w;
int h; int h;
WindowMode mode;
bool vsync; bool vsync;
int priority; int priority;
std::string backend; std::string backend;
WindowCfg() WindowCfg()
: title("Extra2D"), w(1280), h(720), mode(WindowMode::Windowed), : title("Extra2D"), w(1280), h(720), vsync(true), priority(0),
vsync(true), priority(0), backend("sdl2") {} backend("sdl2") {}
}; };
/** /**
@ -54,11 +52,16 @@ public:
*/ */
IWindow *win() const { return win_.get(); } IWindow *win() const { return win_.get(); }
/**
* @brief
* @return
*/
const std::string &getWindowBackend() const { return cfg_.backend; }
private: private:
WindowCfg cfg_; WindowCfg cfg_;
UniquePtr<IWindow> win_; UniquePtr<IWindow> win_;
bool initialized_ = false; bool initialized_ = false;
bool sdlInited_ = false;
}; };
} // namespace extra2d } // namespace extra2d

View File

@ -1,127 +1,136 @@
#include <extra2d/graphics/backends/backend_factory.h> #include <extra2d/graphics/backends/backend_factory.h>
// 条件编译包含对应后端实现
#ifdef E2D_BACKEND_OPENGL
#include <extra2d/graphics/backends/opengl/gl_renderer.h>
#endif
#ifdef E2D_BACKEND_VULKAN
#include <extra2d/graphics/backends/vulkan/vk_renderer.h>
#endif
#include <extra2d/utils/logger.h> #include <extra2d/utils/logger.h>
#include <cstring>
namespace extra2d { namespace extra2d {
namespace graphics {
BackendFactory& BackendFactory::getInstance() { std::unordered_map<std::string, BackendFactory::BackendEntry>& BackendFactory::registry() {
static BackendFactory instance; static std::unordered_map<std::string, BackendEntry> reg;
return instance; return reg;
} }
UniquePtr<RenderBackend> BackendFactory::createBackend(BackendType type) { void BackendFactory::reg(const std::string& name, BackendFn backend,
switch (type) { const std::vector<std::string>& windowBackends) {
#ifdef E2D_BACKEND_OPENGL registry()[name] = {backend, windowBackends};
case BackendType::OpenGL: E2D_LOG_DEBUG("Registered graphics backend: {} (window backends: {})",
E2D_LOG_INFO("Creating OpenGL render backend"); name, windowBackends.size());
return makeUnique<GLRenderer>(); }
#endif
#ifdef E2D_BACKEND_VULKAN UniquePtr<RenderBackend> BackendFactory::createBackend(const std::string& name) {
case BackendType::Vulkan: auto& reg = registry();
E2D_LOG_INFO("Creating Vulkan render backend"); auto it = reg.find(name);
return makeUnique<VulkanRenderer>(); if (it != reg.end() && it->second.createFn) {
#endif E2D_LOG_INFO("Creating graphics backend: {}", name);
return it->second.createFn();
default:
E2D_LOG_ERROR("Unsupported render backend type: {}", static_cast<int>(type));
return nullptr;
} }
E2D_LOG_ERROR("Graphics backend '{}' not found", name);
return nullptr;
} }
UniquePtr<RenderBackend> BackendFactory::createDefaultBackend() { UniquePtr<RenderBackend> BackendFactory::createDefaultBackend() {
BackendType recommended = getRecommendedBackend(); std::string recommended = getRecommendedBackend();
if (recommended.empty()) {
E2D_LOG_ERROR("No graphics backend available");
return nullptr;
}
return createBackend(recommended); return createBackend(recommended);
} }
bool BackendFactory::isBackendAvailable(BackendType type) const { UniquePtr<RenderBackend> BackendFactory::createBackendForWindow(const std::string& windowBackend) {
switch (type) { std::string recommended = getRecommendedBackendForWindow(windowBackend);
#ifdef E2D_BACKEND_OPENGL if (recommended.empty()) {
case BackendType::OpenGL: E2D_LOG_ERROR("No compatible graphics backend for window backend: {}", windowBackend);
return true; return nullptr;
#endif }
return createBackend(recommended);
}
#ifdef E2D_BACKEND_VULKAN std::vector<std::string> BackendFactory::backends() {
case BackendType::Vulkan: std::vector<std::string> result;
return true; for (const auto& pair : registry()) {
#endif result.push_back(pair.first);
}
return result;
}
default: bool BackendFactory::has(const std::string& name) {
return registry().find(name) != registry().end();
}
std::string BackendFactory::getRecommendedBackend() {
auto& reg = registry();
static const std::vector<std::string> priority = {
"vulkan", "opengl", "d3d12", "d3d11", "metal", "opengles"
};
for (const auto& name : priority) {
if (reg.find(name) != reg.end()) {
return name;
}
}
if (!reg.empty()) {
return reg.begin()->first;
}
E2D_LOG_WARN("No graphics backend registered");
return "";
}
std::string BackendFactory::getRecommendedBackendForWindow(const std::string& windowBackend) {
auto& reg = registry();
static const std::vector<std::string> priority = {
"vulkan", "opengl", "d3d12", "d3d11", "metal", "opengles"
};
for (const auto& name : priority) {
auto it = reg.find(name);
if (it != reg.end() && isCompatible(name, windowBackend)) {
return name;
}
}
for (const auto& pair : reg) {
if (isCompatible(pair.first, windowBackend)) {
return pair.first;
}
}
E2D_LOG_WARN("No compatible graphics backend for window backend: {}", windowBackend);
return "";
}
bool BackendFactory::isCompatible(const std::string& graphicsBackend, const std::string& windowBackend) {
auto& reg = registry();
auto it = reg.find(graphicsBackend);
if (it == reg.end()) {
return false; return false;
} }
const auto& windowBackends = it->second.windowBackends;
if (windowBackends.empty()) {
return true;
}
for (const auto& wb : windowBackends) {
if (wb == windowBackend) {
return true;
}
}
return false;
} }
BackendType BackendFactory::getRecommendedBackend() const { std::vector<std::string> BackendFactory::getSupportedWindowBackends(const std::string& graphicsBackend) {
// 平台特定的默认后端选择 auto& reg = registry();
// 优先级Vulkan > OpenGL auto it = reg.find(graphicsBackend);
if (it != reg.end()) {
#ifdef E2D_BACKEND_VULKAN return it->second.windowBackends;
return BackendType::Vulkan; }
#endif return {};
#ifdef E2D_BACKEND_OPENGL
return BackendType::OpenGL;
#endif
// 如果没有可用的后端返回OpenGL作为默认值
return BackendType::OpenGL;
}
const char* BackendFactory::getBackendName(BackendType type) const {
switch (type) {
case BackendType::OpenGL:
return "OpenGL";
case BackendType::Vulkan:
return "Vulkan";
case BackendType::Metal:
return "Metal";
case BackendType::D3D11:
return "D3D11";
case BackendType::D3D12:
return "D3D12";
case BackendType::OpenGLES:
return "OpenGL ES";
default:
return "Unknown";
}
}
BackendType BackendFactory::parseBackendType(const char* name) const {
if (!name) {
return BackendType::OpenGL;
}
if (std::strcmp(name, "opengl") == 0 || std::strcmp(name, "OpenGL") == 0) {
return BackendType::OpenGL;
}
if (std::strcmp(name, "vulkan") == 0 || std::strcmp(name, "Vulkan") == 0) {
return BackendType::Vulkan;
}
if (std::strcmp(name, "metal") == 0 || std::strcmp(name, "Metal") == 0) {
return BackendType::Metal;
}
if (std::strcmp(name, "d3d11") == 0 || std::strcmp(name, "D3D11") == 0) {
return BackendType::D3D11;
}
if (std::strcmp(name, "d3d12") == 0 || std::strcmp(name, "D3D12") == 0) {
return BackendType::D3D12;
}
if (std::strcmp(name, "opengles") == 0 || std::strcmp(name, "OpenGLES") == 0) {
return BackendType::OpenGLES;
}
E2D_LOG_WARN("Unknown backend type '{}', defaulting to OpenGL", name);
return BackendType::OpenGL;
} }
} // namespace graphics
} // namespace extra2d } // namespace extra2d

View File

@ -0,0 +1,39 @@
#include <extra2d/graphics/backends/opengl/gl_renderer.h>
#include <extra2d/graphics/backends/backend_factory.h>
namespace extra2d {
namespace graphics {
namespace {
static bool s_openglBackendRegistered = false;
}
/**
* @brief OpenGL
*/
void initOpenGLBackend() {
if (s_openglBackendRegistered) {
return;
}
s_openglBackendRegistered = true;
BackendFactory::reg(
"opengl",
[]() -> UniquePtr<RenderBackend> {
return makeUnique<GLRenderer>();
},
{"sdl2", "glfw"}
);
}
namespace {
struct OpenGLBackendAutoReg {
OpenGLBackendAutoReg() {
initOpenGLBackend();
}
};
static OpenGLBackendAutoReg s_openglAutoReg;
}
} // namespace graphics
} // namespace extra2d

View File

@ -0,0 +1,39 @@
#include <extra2d/graphics/backends/vulkan/vk_renderer.h>
#include <extra2d/graphics/backends/backend_factory.h>
namespace extra2d {
namespace graphics {
namespace {
static bool s_vulkanBackendRegistered = false;
}
/**
* @brief Vulkan
*/
void initVulkanBackend() {
if (s_vulkanBackendRegistered) {
return;
}
s_vulkanBackendRegistered = true;
BackendFactory::reg(
"vulkan",
[]() -> UniquePtr<RenderBackend> {
return makeUnique<VulkanRenderer>();
},
{"sdl2", "glfw"}
);
}
namespace {
struct VulkanBackendAutoReg {
VulkanBackendAutoReg() {
initVulkanBackend();
}
};
static VulkanBackendAutoReg s_vulkanAutoReg;
}
} // namespace graphics
} // namespace extra2d

View File

@ -1,24 +0,0 @@
#include <extra2d/graphics/backends/opengl/gl_renderer.h>
#include <extra2d/graphics/core/render_backend.h>
namespace extra2d {
/**
* @brief
*
*
* OpenGL后端
*
* @param type OpenGL
* @return nullptr
*/
UniquePtr<RenderBackend> RenderBackend::create(BackendType type) {
switch (type) {
case BackendType::OpenGL:
return makeUnique<GLRenderer>();
default:
return nullptr;
}
}
} // namespace extra2d

View File

@ -1,5 +1,5 @@
#include <extra2d/graphics/core/render_module.h> #include <extra2d/graphics/core/render_module.h>
#include <extra2d/graphics/backends/opengl/gl_renderer.h> #include <extra2d/graphics/backends/backend_factory.h>
#include <extra2d/graphics/backends/opengl/gl_shader.h> #include <extra2d/graphics/backends/opengl/gl_shader.h>
#include <extra2d/graphics/shader/shader_manager.h> #include <extra2d/graphics/shader/shader_manager.h>
#include <extra2d/core/registry.h> #include <extra2d/core/registry.h>
@ -11,6 +11,16 @@
namespace extra2d { namespace extra2d {
// 前向声明后端初始化函数
namespace graphics {
#ifdef E2D_BACKEND_OPENGL
void initOpenGLBackend();
#endif
#ifdef E2D_BACKEND_VULKAN
void initVulkanBackend();
#endif
}
RenderModule::RenderModule(std::function<void(RenderCfg&)> configFn) { RenderModule::RenderModule(std::function<void(RenderCfg&)> configFn) {
configFn(cfg_); configFn(cfg_);
} }
@ -33,13 +43,20 @@ static std::string getExecutableDir() {
bool RenderModule::init() { bool RenderModule::init() {
if (initialized_) return true; if (initialized_) return true;
// 获取WindowModule依赖
auto* winMod = Registry::instance().get<WindowModule>(); auto* winMod = Registry::instance().get<WindowModule>();
if (!winMod || !winMod->win()) { if (!winMod || !winMod->win()) {
E2D_LOG_ERROR("WindowModule not available");
return false; return false;
} }
// 初始化ShaderManager // 初始化图形后端(注册到工厂)
#ifdef E2D_BACKEND_OPENGL
graphics::initOpenGLBackend();
#endif
#ifdef E2D_BACKEND_VULKAN
graphics::initVulkanBackend();
#endif
if (!ShaderManager::getInstance().isInitialized()) { if (!ShaderManager::getInstance().isInitialized()) {
auto factory = makeShared<GLShaderFactory>(); auto factory = makeShared<GLShaderFactory>();
std::string shaderDir = getExecutableDir() + "shaders/"; std::string shaderDir = getExecutableDir() + "shaders/";
@ -49,18 +66,31 @@ bool RenderModule::init() {
} }
} }
// 创建渲染后端 std::string windowBackend = winMod->getWindowBackend();
renderer_ = RenderBackend::create(cfg_.backend);
if (cfg_.backend.empty()) {
E2D_LOG_INFO("No graphics backend specified, auto-selecting for window backend: {}", windowBackend);
renderer_ = graphics::BackendFactory::createBackendForWindow(windowBackend);
} else {
if (!graphics::BackendFactory::isCompatible(cfg_.backend, windowBackend)) {
E2D_LOG_WARN("Graphics backend '{}' is not compatible with window backend '{}'",
cfg_.backend, windowBackend);
}
renderer_ = graphics::BackendFactory::createBackend(cfg_.backend);
}
if (!renderer_) { if (!renderer_) {
E2D_LOG_ERROR("Failed to create render backend");
return false; return false;
} }
// 初始化渲染器
if (!renderer_->init(winMod->win())) { if (!renderer_->init(winMod->win())) {
E2D_LOG_ERROR("Failed to initialize render backend");
renderer_.reset(); renderer_.reset();
return false; return false;
} }
E2D_LOG_INFO("Render module initialized successfully");
initialized_ = true; initialized_ = true;
return true; return true;
} }

View File

@ -1,6 +1,7 @@
#include <extra2d/platform/backend_factory.h> #include <extra2d/platform/backend_factory.h>
namespace extra2d { namespace extra2d {
namespace platform {
std::unordered_map<std::string, BackendFactory::BackendEntry>& BackendFactory::registry() { std::unordered_map<std::string, BackendFactory::BackendEntry>& BackendFactory::registry() {
static std::unordered_map<std::string, BackendEntry> reg; static std::unordered_map<std::string, BackendEntry> reg;
@ -41,4 +42,5 @@ bool BackendFactory::has(const std::string& name) {
return registry().find(name) != registry().end(); return registry().find(name) != registry().end();
} }
} // namespace platform
} // namespace extra2d } // namespace extra2d

View File

@ -3,6 +3,7 @@
#include <extra2d/platform/backend_factory.h> #include <extra2d/platform/backend_factory.h>
namespace extra2d { namespace extra2d {
namespace platform {
namespace { namespace {
static bool s_glfwBackendRegistered = false; static bool s_glfwBackendRegistered = false;
@ -25,4 +26,5 @@ void initGLFWBackend() {
); );
} }
} // namespace platform
} // namespace extra2d } // namespace extra2d

View File

@ -11,12 +11,11 @@ GLFWWindow::~GLFWWindow() {
destroy(); destroy();
} }
bool GLFWWindow::create(const WindowConfigData& cfg) { bool GLFWWindow::create(const std::string& title, int width, int height, bool vsync) {
if (!initGLFW()) { if (!initGLFW()) {
return false; return false;
} }
// 设置 OpenGL ES 3.2 上下文
glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API); glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API);
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2); glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
@ -26,44 +25,21 @@ bool GLFWWindow::create(const WindowConfigData& cfg) {
glfwWindowHint(GLFW_STENCIL_BITS, 8); glfwWindowHint(GLFW_STENCIL_BITS, 8);
#ifdef __SWITCH__ #ifdef __SWITCH__
// Switch 平台强制全屏
glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
glfwWindowHint(GLFW_DECORATED, GLFW_FALSE); glfwWindowHint(GLFW_DECORATED, GLFW_FALSE);
fullscreen_ = true; fullscreen_ = true;
#else #else
// 桌面平台配置
if (cfg.resizable) {
glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE);
} else {
glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
}
if (!cfg.decorated) {
glfwWindowHint(GLFW_DECORATED, GLFW_FALSE);
}
if (cfg.isFullscreen()) {
glfwWindowHint(GLFW_DECORATED, GLFW_FALSE);
fullscreen_ = true;
}
#endif #endif
if (cfg.multisamples > 0) {
glfwWindowHint(GLFW_SAMPLES, cfg.multisamples);
}
// 创建窗口
GLFWmonitor* monitor = nullptr; GLFWmonitor* monitor = nullptr;
#ifdef __SWITCH__ #ifdef __SWITCH__
monitor = glfwGetPrimaryMonitor(); monitor = glfwGetPrimaryMonitor();
#endif #endif
if (fullscreen_ && !monitor) {
monitor = glfwGetPrimaryMonitor();
}
glfwWindow_ = glfwCreateWindow( glfwWindow_ = glfwCreateWindow(
cfg.width, cfg.height, width, height,
cfg.title.c_str(), title.c_str(),
monitor, monitor,
nullptr nullptr
); );
@ -74,7 +50,6 @@ bool GLFWWindow::create(const WindowConfigData& cfg) {
return false; return false;
} }
// 窗口居中(非全屏模式下)
#ifndef __SWITCH__ #ifndef __SWITCH__
if (!fullscreen_ && !monitor) { if (!fullscreen_ && !monitor) {
GLFWmonitor* primaryMonitor = glfwGetPrimaryMonitor(); GLFWmonitor* primaryMonitor = glfwGetPrimaryMonitor();
@ -83,8 +58,8 @@ bool GLFWWindow::create(const WindowConfigData& cfg) {
if (mode) { if (mode) {
int screenWidth = mode->width; int screenWidth = mode->width;
int screenHeight = mode->height; int screenHeight = mode->height;
int windowX = (screenWidth - cfg.width) / 2; int windowX = (screenWidth - width) / 2;
int windowY = (screenHeight - cfg.height) / 2; int windowY = (screenHeight - height) / 2;
glfwSetWindowPos(glfwWindow_, windowX, windowY); glfwSetWindowPos(glfwWindow_, windowX, windowY);
} }
} }
@ -93,7 +68,6 @@ bool GLFWWindow::create(const WindowConfigData& cfg) {
glfwMakeContextCurrent(glfwWindow_); glfwMakeContextCurrent(glfwWindow_);
// 初始化 GLAD
if (!gladLoadGLES2Loader((GLADloadproc)glfwGetProcAddress)) { if (!gladLoadGLES2Loader((GLADloadproc)glfwGetProcAddress)) {
E2D_LOG_ERROR("Failed to initialize GLAD GLES2"); E2D_LOG_ERROR("Failed to initialize GLAD GLES2");
glfwDestroyWindow(glfwWindow_); glfwDestroyWindow(glfwWindow_);
@ -102,15 +76,12 @@ bool GLFWWindow::create(const WindowConfigData& cfg) {
return false; return false;
} }
// 设置垂直同步 glfwSwapInterval(vsync ? 1 : 0);
glfwSwapInterval(cfg.vsync ? 1 : 0); vsync_ = vsync;
vsync_ = cfg.vsync;
// 获取实际窗口大小
glfwGetWindowSize(glfwWindow_, &width_, &height_); glfwGetWindowSize(glfwWindow_, &width_, &height_);
updateContentScale(); updateContentScale();
// 设置回调函数
glfwSetWindowUserPointer(glfwWindow_, this); glfwSetWindowUserPointer(glfwWindow_, this);
glfwSetFramebufferSizeCallback(glfwWindow_, framebufferSizeCallback); glfwSetFramebufferSizeCallback(glfwWindow_, framebufferSizeCallback);
glfwSetWindowCloseCallback(glfwWindow_, windowCloseCallback); glfwSetWindowCloseCallback(glfwWindow_, windowCloseCallback);
@ -122,7 +93,6 @@ bool GLFWWindow::create(const WindowConfigData& cfg) {
glfwSetKeyCallback(glfwWindow_, keyCallback); glfwSetKeyCallback(glfwWindow_, keyCallback);
glfwSetJoystickCallback(joystickCallback); glfwSetJoystickCallback(joystickCallback);
// 创建输入系统
input_ = makeUnique<GLFWInput>(); input_ = makeUnique<GLFWInput>();
input_->setWindow(glfwWindow_); input_->setWindow(glfwWindow_);
input_->init(); input_->init();

View File

@ -15,7 +15,7 @@ public:
GLFWWindow(); GLFWWindow();
~GLFWWindow() override; ~GLFWWindow() override;
bool create(const WindowConfigData& cfg) override; bool create(const std::string& title, int width, int height, bool vsync = true) override;
void destroy() override; void destroy() override;
void poll() override; void poll() override;

View File

@ -3,6 +3,7 @@
#include <extra2d/platform/backend_factory.h> #include <extra2d/platform/backend_factory.h>
namespace extra2d { namespace extra2d {
namespace platform {
namespace { namespace {
static bool s_sdl2BackendRegistered = false; static bool s_sdl2BackendRegistered = false;
@ -25,4 +26,5 @@ void initSDL2Backend() {
); );
} }
} // namespace platform
} // namespace extra2d } // namespace extra2d

View File

@ -15,27 +15,15 @@ SDL2Window::~SDL2Window() {
destroy(); destroy();
} }
bool SDL2Window::create(const WindowConfigData& cfg) { bool SDL2Window::create(const std::string& title, int width, int height, bool vsync) {
if (!initSDL()) { if (!initSDL()) {
return false; return false;
} }
Uint32 flags = SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN; Uint32 flags = SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE;
#ifdef __SWITCH__ #ifdef __SWITCH__
flags |= SDL_WINDOW_FULLSCREEN_DESKTOP; flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
#else
if (cfg.isFullscreen()) {
flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
} else if (cfg.isBorderless()) {
flags |= SDL_WINDOW_BORDERLESS;
}
if (cfg.resizable) {
flags |= SDL_WINDOW_RESIZABLE;
}
if (!cfg.decorated) {
flags |= SDL_WINDOW_BORDERLESS;
}
#endif #endif
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3); SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
@ -46,22 +34,10 @@ bool SDL2Window::create(const WindowConfigData& cfg) {
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
if (cfg.multisamples > 0) {
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1);
SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, cfg.multisamples);
}
int x = SDL_WINDOWPOS_CENTERED;
int y = SDL_WINDOWPOS_CENTERED;
if (!cfg.centered) {
x = SDL_WINDOWPOS_UNDEFINED;
y = SDL_WINDOWPOS_UNDEFINED;
}
sdlWindow_ = SDL_CreateWindow( sdlWindow_ = SDL_CreateWindow(
cfg.title.c_str(), title.c_str(),
x, y, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
cfg.width, cfg.height, width, height,
flags flags
); );
@ -90,11 +66,11 @@ bool SDL2Window::create(const WindowConfigData& cfg) {
return false; return false;
} }
SDL_GL_SetSwapInterval(cfg.vsync ? 1 : 0); SDL_GL_SetSwapInterval(vsync ? 1 : 0);
SDL_GetWindowSize(sdlWindow_, &width_, &height_); SDL_GetWindowSize(sdlWindow_, &width_, &height_);
fullscreen_ = (flags & SDL_WINDOW_FULLSCREEN_DESKTOP) != 0; fullscreen_ = (flags & SDL_WINDOW_FULLSCREEN_DESKTOP) != 0;
vsync_ = cfg.vsync; vsync_ = vsync;
#ifndef __SWITCH__ #ifndef __SWITCH__
initCursors(); initCursors();

View File

@ -15,7 +15,7 @@ public:
SDL2Window(); SDL2Window();
~SDL2Window() override; ~SDL2Window() override;
bool create(const WindowConfigData& cfg) override; bool create(const std::string& title, int width, int height, bool vsync = true) override;
void destroy() override; void destroy() override;
void poll() override; void poll() override;

View File

@ -10,11 +10,13 @@
namespace extra2d { namespace extra2d {
// 前向声明后端初始化函数 // 前向声明后端初始化函数
namespace platform {
#if defined(E2D_BACKEND_SDL2) #if defined(E2D_BACKEND_SDL2)
void initSDL2Backend(); void initSDL2Backend();
#elif defined(E2D_BACKEND_GLFW) #elif defined(E2D_BACKEND_GLFW)
void initGLFWBackend(); void initGLFWBackend();
#endif #endif
}
WindowModule::WindowModule(std::function<void(WindowCfg&)> configFn) { WindowModule::WindowModule(std::function<void(WindowCfg&)> configFn) {
configFn(cfg_); configFn(cfg_);
@ -29,39 +31,27 @@ WindowModule::~WindowModule() {
bool WindowModule::init() { bool WindowModule::init() {
if (initialized_) return true; if (initialized_) return true;
#ifdef __SWITCH__
cfg_.mode = WindowMode::Fullscreen;
#endif
// 初始化后端(注册到工厂) // 初始化后端(注册到工厂)
#if defined(E2D_BACKEND_SDL2) #if defined(E2D_BACKEND_SDL2)
initSDL2Backend(); platform::initSDL2Backend();
#elif defined(E2D_BACKEND_GLFW) #elif defined(E2D_BACKEND_GLFW)
initGLFWBackend(); platform::initGLFWBackend();
#else #else
#error "No window backend defined" #error "No window backend defined"
#endif #endif
E2D_LOG_INFO("Window backend initialized"); E2D_LOG_INFO("Window backend initialized");
// 创建窗口配置
WindowConfigData winCfg;
winCfg.title = cfg_.title;
winCfg.width = cfg_.w;
winCfg.height = cfg_.h;
winCfg.mode = cfg_.mode;
winCfg.vsync = cfg_.vsync;
E2D_LOG_INFO("Creating window with size {}x{}", cfg_.w, cfg_.h); E2D_LOG_INFO("Creating window with size {}x{}", cfg_.w, cfg_.h);
// 创建窗口(使用配置的后端) // 创建窗口(使用配置的后端)
win_ = BackendFactory::createWindow(cfg_.backend); win_ = platform::BackendFactory::createWindow(cfg_.backend);
if (!win_) { if (!win_) {
E2D_LOG_ERROR("Failed to create window backend: {}", cfg_.backend); E2D_LOG_ERROR("Failed to create window backend: {}", cfg_.backend);
return false; return false;
} }
if (!win_->create(winCfg)) { if (!win_->create(cfg_.title, cfg_.w, cfg_.h, cfg_.vsync)) {
E2D_LOG_ERROR("Failed to create window"); E2D_LOG_ERROR("Failed to create window");
shutdown(); shutdown();
return false; return false;

View File

@ -3,25 +3,27 @@
* @brief Extra2D * @brief Extra2D
*/ */
#include <extra2d/core/service_locator.h>
#include <extra2d/extra2d.h> #include <extra2d/extra2d.h>
#include <extra2d/graphics/core/render_module.h>
#include <extra2d/platform/input_module.h>
#include <extra2d/platform/window_module.h>
#include <extra2d/services/camera_service.h>
#include <extra2d/services/event_service.h>
#include <iostream> #include <iostream>
using namespace extra2d; using namespace extra2d;
void createSceneGraph(Scene *scene) { /**
float width = scene->getWidth(); * @brief
float height = scene->getHeight(); */
class BasicScene : public Scene {
public:
/**
* @brief
*/
void onEnter() override {
float width = getWidth();
float height = getHeight();
auto root = makeShared<Node>(); auto root = makeShared<Node>();
root->setName("Root"); root->setName("Root");
root->setPos(width / 2, height / 2); root->setPos(width / 2, height / 2);
scene->addChild(root); addChild(root);
auto parent1 = makeShared<Node>(); auto parent1 = makeShared<Node>();
parent1->setName("Parent1"); parent1->setName("Parent1");
@ -61,7 +63,8 @@ void createSceneGraph(Scene *scene) {
parent2->addChild(child2); parent2->addChild(child2);
auto triangle = ShapeNode::createFilledTriangle( auto triangle = ShapeNode::createFilledTriangle(
Vec2(0, -40), Vec2(-35, 30), Vec2(35, 30), Color(0.4f, 1.0f, 0.4f, 1.0f)); Vec2(0, -40), Vec2(-35, 30), Vec2(35, 30),
Color(0.4f, 1.0f, 0.4f, 1.0f));
triangle->setName("GreenTriangle"); triangle->setName("GreenTriangle");
child2->addChild(triangle); child2->addChild(triangle);
@ -76,22 +79,19 @@ void createSceneGraph(Scene *scene) {
polygon->setName("PurplePolygon"); polygon->setName("PurplePolygon");
polygon->setPos(0, -150); polygon->setPos(0, -150);
root->addChild(polygon); root->addChild(polygon);
}
std::cout << "\n=== Scene Graph Structure ===" << std::endl; /**
std::cout << "Scene (root)" << std::endl; * @brief 退
std::cout << " └── Root (center)" << std::endl; */
std::cout << " ├── Parent1 (left)" << std::endl; void onExit() override { clearChildren(); }
std::cout << " │ ├── RedRect (100x100)" << std::endl;
std::cout << " │ └── Child1 (rotated 45, scaled 0.5)" << std::endl; /**
std::cout << " │ └── OrangeRect (60x60)" << std::endl; * @brief
std::cout << " ├── Parent2 (right)" << std::endl; * @param renderer
std::cout << " │ ├── BlueCircle (radius 60)" << std::endl; */
std::cout << " │ └── Child2 (below)" << std::endl; void onRender(RenderBackend &renderer) override { Scene::onRender(renderer); }
std::cout << " │ └── GreenTriangle" << std::endl; };
std::cout << " ├── BottomLine" << std::endl;
std::cout << " └── PurplePolygon (pentagon)" << std::endl;
std::cout << "=============================\n" << std::endl;
}
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
(void)argc; (void)argc;
@ -101,7 +101,6 @@ int main(int argc, char *argv[]) {
Application &app = Application::get(); Application &app = Application::get();
// 注册模块(按优先级顺序)
app.use<WindowModule>([](auto &cfg) { app.use<WindowModule>([](auto &cfg) {
cfg.w = 1280; cfg.w = 1280;
cfg.h = 720; cfg.h = 720;
@ -109,7 +108,10 @@ int main(int argc, char *argv[]) {
cfg.backend = "glfw"; cfg.backend = "glfw";
}); });
app.use<RenderModule>([](auto &cfg) { cfg.priority = 10; }); app.use<RenderModule>([](auto &cfg) {
cfg.priority = 10;
cfg.backend = "opengl";
});
app.use<InputModule>([](auto &cfg) { cfg.priority = 20; }); app.use<InputModule>([](auto &cfg) { cfg.priority = 20; });
@ -145,7 +147,7 @@ int main(int argc, char *argv[]) {
}); });
} }
auto scene = Scene::create(); auto scene = makeShared<BasicScene>();
scene->setBackgroundColor(Color(0.12f, 0.12f, 0.16f, 1.0f)); scene->setBackgroundColor(Color(0.12f, 0.12f, 0.16f, 1.0f));
if (win) { if (win) {
scene->setViewportSize(static_cast<float>(win->width()), scene->setViewportSize(static_cast<float>(win->width()),
@ -163,7 +165,6 @@ int main(int argc, char *argv[]) {
cameraService->applyViewportAdapter(); cameraService->applyViewportAdapter();
} }
createSceneGraph(scene.get());
app.enterScene(scene); app.enterScene(scene);
std::cout << "\nControls:" << std::endl; std::cout << "\nControls:" << std::endl;

View File

@ -1,12 +1,7 @@
#include "hello_module.h" #include "hello_module.h"
#include <extra2d/app/application.h> #include <extra2d/extra2d.h>
#include <extra2d/graphics/core/render_module.h>
#include <extra2d/platform/window_module.h>
#include <extra2d/scene/scene.h>
#include <extra2d/services/scene_service.h>
#include <iostream> #include <iostream>
using namespace extra2d; using namespace extra2d;
class HelloScene : public Scene { class HelloScene : public Scene {
@ -18,7 +13,7 @@ public:
std::cout << "HelloScene entered" << std::endl; std::cout << "HelloScene entered" << std::endl;
setBackgroundColor(Color(0.1f, 0.1f, 0.2f, 1.0f)); setBackgroundColor(Color(0.1f, 0.1f, 0.2f, 1.0f));
auto *hello = Application::get().get<HelloModule>(); auto hello = Application::get().get<HelloModule>();
if (hello) { if (hello) {
std::cout << "Scene calling HelloModule from onEnter..." << std::endl; std::cout << "Scene calling HelloModule from onEnter..." << std::endl;
hello->sayHello(); hello->sayHello();
@ -44,13 +39,6 @@ private:
}; };
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
(void)argc;
(void)argv;
std::cout << "=== Hello Module Example ===" << std::endl;
std::cout << "This example demonstrates how to create a custom module"
<< std::endl;
std::cout << "" << std::endl;
Application &app = Application::get(); Application &app = Application::get();
@ -71,21 +59,9 @@ int main(int argc, char *argv[]) {
return 1; return 1;
} }
std::cout << "" << std::endl;
std::cout << "Application initialized successfully" << std::endl;
std::cout << "" << std::endl;
auto scene = HelloScene::create(); auto scene = HelloScene::create();
app.enterScene(scene); app.enterScene(scene);
std::cout << "Starting main loop..." << std::endl;
std::cout << "Press ESC or close window to exit" << std::endl;
std::cout << "" << std::endl;
app.run(); app.run();
std::cout << "Application shutting down..." << std::endl;
app.shutdown(); app.shutdown();
std::cout << "Application shutdown complete" << std::endl;
return 0; return 0;
} }

View File

@ -6,15 +6,7 @@
* OpenGL * OpenGL
*/ */
#include <extra2d/core/service_locator.h>
#include <extra2d/extra2d.h> #include <extra2d/extra2d.h>
#include <extra2d/graphics/core/render_backend.h>
#include <extra2d/graphics/core/render_module.h>
#include <extra2d/graphics/texture/font.h>
#include <extra2d/platform/input_module.h>
#include <extra2d/platform/window_module.h>
#include <extra2d/services/camera_service.h>
#include <extra2d/services/event_service.h>
#include <iostream> #include <iostream>
using namespace extra2d; using namespace extra2d;
@ -103,9 +95,7 @@ public:
// 注意:无需手动调用 renderer.endSpriteBatch(),帧结束时会自动刷新 // 注意:无需手动调用 renderer.endSpriteBatch(),帧结束时会自动刷新
} }
void setRenderer(RenderBackend* renderer) { void setRenderer(RenderBackend *renderer) { renderer_ = renderer; }
renderer_ = renderer;
}
private: private:
void renderText(RenderBackend &renderer, const std::string &text, float x, void renderText(RenderBackend &renderer, const std::string &text, float x,
@ -119,7 +109,7 @@ private:
} }
Ptr<FontAtlas> font_; Ptr<FontAtlas> font_;
RenderBackend* renderer_ = nullptr; RenderBackend *renderer_ = nullptr;
}; };
int main(int argc, char *argv[]) { int main(int argc, char *argv[]) {
@ -170,7 +160,7 @@ int main(int argc, char *argv[]) {
} }
// 获取渲染器 // 获取渲染器
RenderBackend* renderer = app.renderer(); RenderBackend *renderer = app.renderer();
// 创建并配置场景 // 创建并配置场景
auto scene = makeShared<TextRenderingScene>(); auto scene = makeShared<TextRenderingScene>();

View File

@ -34,6 +34,8 @@ function define_extra2d_engine()
-- 渲染后端源文件 -- 渲染后端源文件
local render_backend = get_render_backend() local render_backend = get_render_backend()
-- 图形后端工厂(始终编译)
add_files("Extra2D/src/graphics/backends/backend_factory.cpp")
if render_backend == "vulkan" then if render_backend == "vulkan" then
add_files("Extra2D/src/graphics/backends/vulkan/*.cpp") add_files("Extra2D/src/graphics/backends/vulkan/*.cpp")
add_defines("E2D_BACKEND_VULKAN") add_defines("E2D_BACKEND_VULKAN")