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
#include <extra2d/core/types.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 graphics {
/**
* @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
* @brief
*
*/
class BackendFactory {
public:
/**
* @brief
* @return
*/
static BackendFactory& getInstance();
using BackendFn = std::function<UniquePtr<RenderBackend>()>;
/**
* @brief
* @param type
* @return
* @brief
* @param name
* @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
* @return
*/
UniquePtr<RenderBackend> createDefaultBackend();
static UniquePtr<RenderBackend> createDefaultBackend();
/**
* @brief
* @param type
* @return truefalse
* @brief
* @param windowBackend
* @return
*/
bool isBackendAvailable(BackendType type) const;
static UniquePtr<RenderBackend>
createBackendForWindow(const std::string &windowBackend);
/**
* @brief
* @return
* @brief
*/
BackendType getRecommendedBackend() const;
static std::vector<std::string> backends();
/**
* @brief
* @param type
* @return
* @brief
*/
const char* getBackendName(BackendType type) const;
static bool has(const std::string &name);
/**
* @brief
* @param name
* @return OpenGL
* @brief
* @return
*/
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:
BackendFactory() = default;
~BackendFactory() = default;
BackendFactory(const BackendFactory&) = delete;
BackendFactory& operator=(const BackendFactory&) = delete;
struct BackendEntry {
BackendFn createFn;
std::vector<std::string> windowBackends;
};
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

View File

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

View File

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

View File

@ -1,14 +1,15 @@
#pragma once
#include <extra2d/core/types.h>
#include <extra2d/platform/iwindow.h>
#include <extra2d/platform/iinput.h>
#include <extra2d/platform/iwindow.h>
#include <functional>
#include <string>
#include <unordered_map>
#include <vector>
#include <string>
namespace extra2d {
namespace platform {
/**
* @brief
@ -69,20 +70,19 @@ private:
*/
#define E2D_REG_BACKEND(name, WinClass, InClass) \
namespace { \
__attribute__((used)) \
static struct E2D_BACKEND_REG_##name { \
__attribute__((used)) static struct E2D_BACKEND_REG_##name { \
E2D_BACKEND_REG_##name() { \
::extra2d::BackendFactory::reg( \
::extra2d::platform::BackendFactory::reg( \
#name, \
[]() -> ::extra2d::UniquePtr<::extra2d::IWindow> { \
return ::extra2d::makeUnique<WinClass>(); \
}, \
[]() -> ::extra2d::UniquePtr<::extra2d::IInput> { \
return ::extra2d::makeUnique<InClass>(); \
} \
); \
}); \
} \
} e2d_backend_reg_##name; \
}
} // namespace platform
} // namespace extra2d

View File

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

View File

@ -1,127 +1,136 @@
#include <extra2d/graphics/backends/backend_factory.h>
// 条件编译包含对应后端实现
#ifdef E2D_BACKEND_OPENGL
#include <extra2d/graphics/backends/opengl/gl_renderer.h>
#endif
#ifdef E2D_BACKEND_VULKAN
#include <extra2d/graphics/backends/vulkan/vk_renderer.h>
#endif
#include <extra2d/utils/logger.h>
#include <cstring>
namespace extra2d {
namespace graphics {
BackendFactory& BackendFactory::getInstance() {
static BackendFactory instance;
return instance;
std::unordered_map<std::string, BackendFactory::BackendEntry>& BackendFactory::registry() {
static std::unordered_map<std::string, BackendEntry> reg;
return reg;
}
UniquePtr<RenderBackend> BackendFactory::createBackend(BackendType type) {
switch (type) {
#ifdef E2D_BACKEND_OPENGL
case BackendType::OpenGL:
E2D_LOG_INFO("Creating OpenGL render backend");
return makeUnique<GLRenderer>();
#endif
void BackendFactory::reg(const std::string& name, BackendFn backend,
const std::vector<std::string>& windowBackends) {
registry()[name] = {backend, windowBackends};
E2D_LOG_DEBUG("Registered graphics backend: {} (window backends: {})",
name, windowBackends.size());
}
#ifdef E2D_BACKEND_VULKAN
case BackendType::Vulkan:
E2D_LOG_INFO("Creating Vulkan render backend");
return makeUnique<VulkanRenderer>();
#endif
default:
E2D_LOG_ERROR("Unsupported render backend type: {}", static_cast<int>(type));
UniquePtr<RenderBackend> BackendFactory::createBackend(const std::string& name) {
auto& reg = registry();
auto it = reg.find(name);
if (it != reg.end() && it->second.createFn) {
E2D_LOG_INFO("Creating graphics backend: {}", name);
return it->second.createFn();
}
E2D_LOG_ERROR("Graphics backend '{}' not found", name);
return nullptr;
}
}
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);
}
bool BackendFactory::isBackendAvailable(BackendType type) const {
switch (type) {
#ifdef E2D_BACKEND_OPENGL
case BackendType::OpenGL:
return true;
#endif
UniquePtr<RenderBackend> BackendFactory::createBackendForWindow(const std::string& windowBackend) {
std::string recommended = getRecommendedBackendForWindow(windowBackend);
if (recommended.empty()) {
E2D_LOG_ERROR("No compatible graphics backend for window backend: {}", windowBackend);
return nullptr;
}
return createBackend(recommended);
}
#ifdef E2D_BACKEND_VULKAN
case BackendType::Vulkan:
return true;
#endif
std::vector<std::string> BackendFactory::backends() {
std::vector<std::string> result;
for (const auto& pair : registry()) {
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;
}
const auto& windowBackends = it->second.windowBackends;
if (windowBackends.empty()) {
return true;
}
BackendType BackendFactory::getRecommendedBackend() const {
// 平台特定的默认后端选择
// 优先级Vulkan > OpenGL
#ifdef E2D_BACKEND_VULKAN
return BackendType::Vulkan;
#endif
#ifdef E2D_BACKEND_OPENGL
return BackendType::OpenGL;
#endif
// 如果没有可用的后端返回OpenGL作为默认值
return BackendType::OpenGL;
}
const char* BackendFactory::getBackendName(BackendType type) const {
switch (type) {
case BackendType::OpenGL:
return "OpenGL";
case BackendType::Vulkan:
return "Vulkan";
case BackendType::Metal:
return "Metal";
case BackendType::D3D11:
return "D3D11";
case BackendType::D3D12:
return "D3D12";
case BackendType::OpenGLES:
return "OpenGL ES";
default:
return "Unknown";
for (const auto& wb : windowBackends) {
if (wb == windowBackend) {
return true;
}
}
BackendType BackendFactory::parseBackendType(const char* name) const {
if (!name) {
return BackendType::OpenGL;
return false;
}
if (std::strcmp(name, "opengl") == 0 || std::strcmp(name, "OpenGL") == 0) {
return BackendType::OpenGL;
std::vector<std::string> BackendFactory::getSupportedWindowBackends(const std::string& graphicsBackend) {
auto& reg = registry();
auto it = reg.find(graphicsBackend);
if (it != reg.end()) {
return it->second.windowBackends;
}
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;
return {};
}
} // namespace graphics
} // 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/backends/opengl/gl_renderer.h>
#include <extra2d/graphics/backends/backend_factory.h>
#include <extra2d/graphics/backends/opengl/gl_shader.h>
#include <extra2d/graphics/shader/shader_manager.h>
#include <extra2d/core/registry.h>
@ -11,6 +11,16 @@
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) {
configFn(cfg_);
}
@ -33,13 +43,20 @@ static std::string getExecutableDir() {
bool RenderModule::init() {
if (initialized_) return true;
// 获取WindowModule依赖
auto* winMod = Registry::instance().get<WindowModule>();
if (!winMod || !winMod->win()) {
E2D_LOG_ERROR("WindowModule not available");
return false;
}
// 初始化ShaderManager
// 初始化图形后端(注册到工厂)
#ifdef E2D_BACKEND_OPENGL
graphics::initOpenGLBackend();
#endif
#ifdef E2D_BACKEND_VULKAN
graphics::initVulkanBackend();
#endif
if (!ShaderManager::getInstance().isInitialized()) {
auto factory = makeShared<GLShaderFactory>();
std::string shaderDir = getExecutableDir() + "shaders/";
@ -49,18 +66,31 @@ bool RenderModule::init() {
}
}
// 创建渲染后端
renderer_ = RenderBackend::create(cfg_.backend);
std::string windowBackend = winMod->getWindowBackend();
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_) {
E2D_LOG_ERROR("Failed to create render backend");
return false;
}
// 初始化渲染器
if (!renderer_->init(winMod->win())) {
E2D_LOG_ERROR("Failed to initialize render backend");
renderer_.reset();
return false;
}
E2D_LOG_INFO("Render module initialized successfully");
initialized_ = true;
return true;
}

View File

@ -1,6 +1,7 @@
#include <extra2d/platform/backend_factory.h>
namespace extra2d {
namespace platform {
std::unordered_map<std::string, BackendFactory::BackendEntry>& BackendFactory::registry() {
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();
}
} // namespace platform
} // namespace extra2d

View File

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

View File

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

View File

@ -15,7 +15,7 @@ public:
GLFWWindow();
~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 poll() override;

View File

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

View File

@ -15,27 +15,15 @@ SDL2Window::~SDL2Window() {
destroy();
}
bool SDL2Window::create(const WindowConfigData& cfg) {
bool SDL2Window::create(const std::string& title, int width, int height, bool vsync) {
if (!initSDL()) {
return false;
}
Uint32 flags = SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN;
Uint32 flags = SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE;
#ifdef __SWITCH__
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
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_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(
cfg.title.c_str(),
x, y,
cfg.width, cfg.height,
title.c_str(),
SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED,
width, height,
flags
);
@ -90,11 +66,11 @@ bool SDL2Window::create(const WindowConfigData& cfg) {
return false;
}
SDL_GL_SetSwapInterval(cfg.vsync ? 1 : 0);
SDL_GL_SetSwapInterval(vsync ? 1 : 0);
SDL_GetWindowSize(sdlWindow_, &width_, &height_);
fullscreen_ = (flags & SDL_WINDOW_FULLSCREEN_DESKTOP) != 0;
vsync_ = cfg.vsync;
vsync_ = vsync;
#ifndef __SWITCH__
initCursors();

View File

@ -15,7 +15,7 @@ public:
SDL2Window();
~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 poll() override;

View File

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

View File

@ -3,25 +3,27 @@
* @brief Extra2D
*/
#include <extra2d/core/service_locator.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>
using namespace extra2d;
void createSceneGraph(Scene *scene) {
float width = scene->getWidth();
float height = scene->getHeight();
/**
* @brief
*/
class BasicScene : public Scene {
public:
/**
* @brief
*/
void onEnter() override {
float width = getWidth();
float height = getHeight();
auto root = makeShared<Node>();
root->setName("Root");
root->setPos(width / 2, height / 2);
scene->addChild(root);
addChild(root);
auto parent1 = makeShared<Node>();
parent1->setName("Parent1");
@ -61,7 +63,8 @@ void createSceneGraph(Scene *scene) {
parent2->addChild(child2);
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");
child2->addChild(triangle);
@ -76,23 +79,20 @@ void createSceneGraph(Scene *scene) {
polygon->setName("PurplePolygon");
polygon->setPos(0, -150);
root->addChild(polygon);
std::cout << "\n=== Scene Graph Structure ===" << std::endl;
std::cout << "Scene (root)" << std::endl;
std::cout << " └── Root (center)" << std::endl;
std::cout << " ├── Parent1 (left)" << std::endl;
std::cout << " │ ├── RedRect (100x100)" << std::endl;
std::cout << " │ └── Child1 (rotated 45, scaled 0.5)" << std::endl;
std::cout << " │ └── OrangeRect (60x60)" << std::endl;
std::cout << " ├── Parent2 (right)" << std::endl;
std::cout << " │ ├── BlueCircle (radius 60)" << std::endl;
std::cout << " │ └── Child2 (below)" << std::endl;
std::cout << " │ └── GreenTriangle" << std::endl;
std::cout << " ├── BottomLine" << std::endl;
std::cout << " └── PurplePolygon (pentagon)" << std::endl;
std::cout << "=============================\n" << std::endl;
}
/**
* @brief 退
*/
void onExit() override { clearChildren(); }
/**
* @brief
* @param renderer
*/
void onRender(RenderBackend &renderer) override { Scene::onRender(renderer); }
};
int main(int argc, char *argv[]) {
(void)argc;
(void)argv;
@ -101,7 +101,6 @@ int main(int argc, char *argv[]) {
Application &app = Application::get();
// 注册模块(按优先级顺序)
app.use<WindowModule>([](auto &cfg) {
cfg.w = 1280;
cfg.h = 720;
@ -109,7 +108,10 @@ int main(int argc, char *argv[]) {
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; });
@ -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));
if (win) {
scene->setViewportSize(static_cast<float>(win->width()),
@ -163,7 +165,6 @@ int main(int argc, char *argv[]) {
cameraService->applyViewportAdapter();
}
createSceneGraph(scene.get());
app.enterScene(scene);
std::cout << "\nControls:" << std::endl;

View File

@ -1,12 +1,7 @@
#include "hello_module.h"
#include <extra2d/app/application.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 <extra2d/extra2d.h>
#include <iostream>
using namespace extra2d;
class HelloScene : public Scene {
@ -18,7 +13,7 @@ public:
std::cout << "HelloScene entered" << std::endl;
setBackgroundColor(Color(0.1f, 0.1f, 0.2f, 1.0f));
auto *hello = Application::get().get<HelloModule>();
auto hello = Application::get().get<HelloModule>();
if (hello) {
std::cout << "Scene calling HelloModule from onEnter..." << std::endl;
hello->sayHello();
@ -44,13 +39,6 @@ private:
};
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();
@ -71,21 +59,9 @@ int main(int argc, char *argv[]) {
return 1;
}
std::cout << "" << std::endl;
std::cout << "Application initialized successfully" << std::endl;
std::cout << "" << std::endl;
auto scene = HelloScene::create();
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();
std::cout << "Application shutting down..." << std::endl;
app.shutdown();
std::cout << "Application shutdown complete" << std::endl;
return 0;
}

View File

@ -6,15 +6,7 @@
* OpenGL
*/
#include <extra2d/core/service_locator.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>
using namespace extra2d;
@ -103,9 +95,7 @@ public:
// 注意:无需手动调用 renderer.endSpriteBatch(),帧结束时会自动刷新
}
void setRenderer(RenderBackend* renderer) {
renderer_ = renderer;
}
void setRenderer(RenderBackend *renderer) { renderer_ = renderer; }
private:
void renderText(RenderBackend &renderer, const std::string &text, float x,

View File

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