feat: 添加SDL2后端支持并实现基础示例程序

添加SDL2作为平台后端支持,包括窗口创建、输入处理和GLAD初始化
实现基础示例程序展示引擎基本功能
重构平台配置代码以提高可读性
移除未使用的input_codes.h头文件
添加demo_basic构建目标到xmake配置
This commit is contained in:
ChestnutYueyue 2026-02-15 09:22:57 +08:00
parent 34fe0bafcb
commit 38148a6c54
8 changed files with 433 additions and 292 deletions

View File

@ -48,7 +48,6 @@
#include <extra2d/event/event.h> #include <extra2d/event/event.h>
#include <extra2d/event/event_dispatcher.h> #include <extra2d/event/event_dispatcher.h>
#include <extra2d/event/event_queue.h> #include <extra2d/event/event_queue.h>
#include <extra2d/event/input_codes.h>
// Utils // Utils
#include <extra2d/utils/logger.h> #include <extra2d/utils/logger.h>

View File

@ -79,6 +79,7 @@ private:
*/ */
#define E2D_REG_BACKEND(name, WinClass, InClass) \ #define E2D_REG_BACKEND(name, WinClass, InClass) \
namespace { \ namespace { \
__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::BackendFactory::reg( \

View File

@ -16,10 +16,18 @@
#include <chrono> #include <chrono>
#include <thread> #include <thread>
#include <SDL.h>
#ifdef __SWITCH__ #ifdef __SWITCH__
#include <switch.h> #include <switch.h>
#endif #endif
#ifdef E2D_BACKEND_SDL2
namespace extra2d {
void initSDL2Backend();
}
#endif
namespace extra2d { namespace extra2d {
/** /**
@ -56,10 +64,16 @@ bool Application::init() {
bool Application::init(const AppConfig& config) { bool Application::init(const AppConfig& config) {
if (initialized_) { if (initialized_) {
E2D_LOG_WARN("Application already initialized");
return true; return true;
} }
if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_EVENTS | SDL_INIT_TIMER) != 0) {
E2D_LOG_ERROR("Failed to initialize SDL: {}", SDL_GetError());
return false;
}
Logger::init();
E2D_LOG_INFO("Initializing application with config..."); E2D_LOG_INFO("Initializing application with config...");
if (!ConfigManager::instance().initialize()) { if (!ConfigManager::instance().initialize()) {
@ -92,6 +106,10 @@ bool Application::init(const std::string& configPath) {
} }
bool Application::initImpl() { bool Application::initImpl() {
#ifdef E2D_BACKEND_SDL2
initSDL2Backend();
#endif
auto& configMgr = ConfigManager::instance(); auto& configMgr = ConfigManager::instance();
AppConfig& appConfig = configMgr.appConfig(); AppConfig& appConfig = configMgr.appConfig();

View File

@ -1,5 +1,5 @@
#include <extra2d/config/platform_config.h>
#include <extra2d/config/app_config.h> #include <extra2d/config/app_config.h>
#include <extra2d/config/platform_config.h>
#include <extra2d/utils/logger.h> #include <extra2d/utils/logger.h>
#ifdef _WIN32 #ifdef _WIN32
@ -39,24 +39,28 @@ public:
} }
PlatformType platformType() const override { return PlatformType::Windows; } PlatformType platformType() const override { return PlatformType::Windows; }
const char* platformName() const override { return "Windows"; } const char *platformName() const override { return "Windows"; }
const PlatformCapabilities& capabilities() const override { return caps_; } const PlatformCapabilities &capabilities() const override { return caps_; }
void applyConstraints(AppConfig& config) const override { void applyConstraints(AppConfig &config) const override {
if (config.window.width < 320) config.window.width = 320; if (config.window.width < 320)
if (config.window.height < 240) config.window.height = 240; config.window.width = 320;
if (config.window.width > caps_.maxTextureSize) config.window.width = caps_.maxTextureSize; if (config.window.height < 240)
if (config.window.height > caps_.maxTextureSize) config.window.height = caps_.maxTextureSize; config.window.height = 240;
if (config.window.width > caps_.maxTextureSize)
config.window.width = caps_.maxTextureSize;
if (config.window.height > caps_.maxTextureSize)
config.window.height = caps_.maxTextureSize;
} }
void applyDefaults(AppConfig& config) const override { void applyDefaults(AppConfig &config) const override {
config.window.highDPI = true; config.window.highDPI = true;
config.window.resizable = true; config.window.resizable = true;
config.render.vsync = true; config.render.vsync = true;
config.render.targetFPS = 60; config.render.targetFPS = 60;
} }
bool validateConfig(AppConfig& config) const override { bool validateConfig(AppConfig &config) const override {
if (config.window.width <= 0 || config.window.height <= 0) { if (config.window.width <= 0 || config.window.height <= 0) {
E2D_LOG_ERROR("Windows: Invalid window dimensions"); E2D_LOG_ERROR("Windows: Invalid window dimensions");
return false; return false;
@ -67,8 +71,8 @@ public:
int getRecommendedWidth() const override { return 1920; } int getRecommendedWidth() const override { return 1920; }
int getRecommendedHeight() const override { return 1080; } int getRecommendedHeight() const override { return 1080; }
bool isResolutionSupported(int width, int height) const override { bool isResolutionSupported(int width, int height) const override {
return width >= 320 && height >= 240 && return width >= 320 && height >= 240 && width <= caps_.maxTextureSize &&
width <= caps_.maxTextureSize && height <= caps_.maxTextureSize; height <= caps_.maxTextureSize;
} }
private: private:
@ -100,20 +104,22 @@ public:
} }
PlatformType platformType() const override { return PlatformType::Linux; } PlatformType platformType() const override { return PlatformType::Linux; }
const char* platformName() const override { return "Linux"; } const char *platformName() const override { return "Linux"; }
const PlatformCapabilities& capabilities() const override { return caps_; } const PlatformCapabilities &capabilities() const override { return caps_; }
void applyConstraints(AppConfig& config) const override { void applyConstraints(AppConfig &config) const override {
if (config.window.width < 320) config.window.width = 320; if (config.window.width < 320)
if (config.window.height < 240) config.window.height = 240; config.window.width = 320;
if (config.window.height < 240)
config.window.height = 240;
} }
void applyDefaults(AppConfig& config) const override { void applyDefaults(AppConfig &config) const override {
config.window.resizable = true; config.window.resizable = true;
config.render.vsync = true; config.render.vsync = true;
} }
bool validateConfig(AppConfig& config) const override { bool validateConfig(AppConfig &config) const override {
if (config.window.width <= 0 || config.window.height <= 0) { if (config.window.width <= 0 || config.window.height <= 0) {
E2D_LOG_ERROR("Linux: Invalid window dimensions"); E2D_LOG_ERROR("Linux: Invalid window dimensions");
return false; return false;
@ -156,21 +162,23 @@ public:
} }
PlatformType platformType() const override { return PlatformType::macOS; } PlatformType platformType() const override { return PlatformType::macOS; }
const char* platformName() const override { return "macOS"; } const char *platformName() const override { return "macOS"; }
const PlatformCapabilities& capabilities() const override { return caps_; } const PlatformCapabilities &capabilities() const override { return caps_; }
void applyConstraints(AppConfig& config) const override { void applyConstraints(AppConfig &config) const override {
if (config.window.width < 320) config.window.width = 320; if (config.window.width < 320)
if (config.window.height < 240) config.window.height = 240; config.window.width = 320;
if (config.window.height < 240)
config.window.height = 240;
} }
void applyDefaults(AppConfig& config) const override { void applyDefaults(AppConfig &config) const override {
config.window.highDPI = true; config.window.highDPI = true;
config.window.resizable = true; config.window.resizable = true;
config.render.vsync = true; config.render.vsync = true;
} }
bool validateConfig(AppConfig& config) const override { bool validateConfig(AppConfig &config) const override {
if (config.window.width <= 0 || config.window.height <= 0) { if (config.window.width <= 0 || config.window.height <= 0) {
E2D_LOG_ERROR("macOS: Invalid window dimensions"); E2D_LOG_ERROR("macOS: Invalid window dimensions");
return false; return false;
@ -214,10 +222,10 @@ public:
} }
PlatformType platformType() const override { return PlatformType::Switch; } PlatformType platformType() const override { return PlatformType::Switch; }
const char* platformName() const override { return "Nintendo Switch"; } const char *platformName() const override { return "Nintendo Switch"; }
const PlatformCapabilities& capabilities() const override { return caps_; } const PlatformCapabilities &capabilities() const override { return caps_; }
void applyConstraints(AppConfig& config) const override { void applyConstraints(AppConfig &config) const override {
config.window.width = 1920; config.window.width = 1920;
config.window.height = 1080; config.window.height = 1080;
config.window.mode = WindowMode::Fullscreen; config.window.mode = WindowMode::Fullscreen;
@ -230,7 +238,7 @@ public:
config.input.maxGamepads = 2; config.input.maxGamepads = 2;
} }
void applyDefaults(AppConfig& config) const override { void applyDefaults(AppConfig &config) const override {
config.window.width = 1920; config.window.width = 1920;
config.window.height = 1080; config.window.height = 1080;
config.window.mode = WindowMode::Fullscreen; config.window.mode = WindowMode::Fullscreen;
@ -240,7 +248,7 @@ public:
config.input.enableVibration = true; config.input.enableVibration = true;
} }
bool validateConfig(AppConfig& config) const override { bool validateConfig(AppConfig &config) const override {
if (config.window.mode != WindowMode::Fullscreen) { if (config.window.mode != WindowMode::Fullscreen) {
E2D_LOG_WARN("Switch: Only fullscreen mode is supported"); E2D_LOG_WARN("Switch: Only fullscreen mode is supported");
config.window.mode = WindowMode::Fullscreen; config.window.mode = WindowMode::Fullscreen;
@ -284,10 +292,10 @@ public:
} }
PlatformType platformType() const override { return PlatformType::Switch; } PlatformType platformType() const override { return PlatformType::Switch; }
const char* platformName() const override { return "Nintendo Switch"; } const char *platformName() const override { return "Nintendo Switch"; }
const PlatformCapabilities& capabilities() const override { return caps_; } const PlatformCapabilities &capabilities() const override { return caps_; }
void applyConstraints(AppConfig& config) const override { void applyConstraints(AppConfig &config) const override {
config.window.width = 1920; config.window.width = 1920;
config.window.height = 1080; config.window.height = 1080;
config.window.mode = WindowMode::Fullscreen; config.window.mode = WindowMode::Fullscreen;
@ -300,7 +308,7 @@ public:
config.input.maxGamepads = 2; config.input.maxGamepads = 2;
} }
void applyDefaults(AppConfig& config) const override { void applyDefaults(AppConfig &config) const override {
config.window.width = 1920; config.window.width = 1920;
config.window.height = 1080; config.window.height = 1080;
config.window.mode = WindowMode::Fullscreen; config.window.mode = WindowMode::Fullscreen;
@ -310,7 +318,7 @@ public:
config.input.enableVibration = true; config.input.enableVibration = true;
} }
bool validateConfig(AppConfig& config) const override { bool validateConfig(AppConfig &config) const override {
if (config.window.mode != WindowMode::Fullscreen) { if (config.window.mode != WindowMode::Fullscreen) {
E2D_LOG_WARN("Switch: Only fullscreen mode is supported"); E2D_LOG_WARN("Switch: Only fullscreen mode is supported");
} }
@ -329,7 +337,7 @@ private:
}; };
#endif #endif
} } // namespace
/** /**
* @brief * @brief
@ -377,15 +385,21 @@ UniquePtr<PlatformConfig> createPlatformConfig(PlatformType type) {
* @param type * @param type
* @return * @return
*/ */
const char* getPlatformTypeName(PlatformType type) { const char *getPlatformTypeName(PlatformType type) {
switch (type) { switch (type) {
case PlatformType::Auto: return "Auto"; case PlatformType::Auto:
case PlatformType::Windows: return "Windows"; return "Auto";
case PlatformType::Switch: return "Switch"; case PlatformType::Windows:
case PlatformType::Linux: return "Linux"; return "Windows";
case PlatformType::macOS: return "macOS"; case PlatformType::Switch:
default: return "Unknown"; return "Switch";
case PlatformType::Linux:
return "Linux";
case PlatformType::macOS:
return "macOS";
default:
return "Unknown";
} }
} }
} } // namespace extra2d

View File

@ -4,6 +4,25 @@
namespace extra2d { namespace extra2d {
E2D_REG_BACKEND(sdl2, SDL2Window, SDL2Input) namespace {
static bool s_sdl2BackendRegistered = false;
}
void initSDL2Backend() {
if (s_sdl2BackendRegistered) {
return;
}
s_sdl2BackendRegistered = true;
BackendFactory::reg(
"sdl2",
[]() -> UniquePtr<IWindow> {
return makeUnique<SDL2Window>();
},
[]() -> UniquePtr<IInput> {
return makeUnique<SDL2Input>();
}
);
}
} // namespace extra2d } // namespace extra2d

View File

@ -1,6 +1,7 @@
#include "sdl2_window.h" #include "sdl2_window.h"
#include "sdl2_input.h" #include "sdl2_input.h"
#include <extra2d/utils/logger.h> #include <extra2d/utils/logger.h>
#include <glad/glad.h>
namespace extra2d { namespace extra2d {
@ -73,6 +74,16 @@ bool SDL2Window::create(const WindowConfigData& cfg) {
return false; return false;
} }
if (!gladLoadGLES2Loader((GLADloadproc)SDL_GL_GetProcAddress)) {
E2D_LOG_ERROR("Failed to initialize GLAD");
SDL_GL_DeleteContext(glContext_);
glContext_ = nullptr;
SDL_DestroyWindow(sdlWindow_);
sdlWindow_ = nullptr;
deinitSDL();
return false;
}
SDL_GL_SetSwapInterval(cfg.vsync ? 1 : 0); SDL_GL_SetSwapInterval(cfg.vsync ? 1 : 0);
SDL_GetWindowSize(sdlWindow_, &width_, &height_); SDL_GetWindowSize(sdlWindow_, &width_, &height_);

59
examples/basic/main.cpp Normal file
View File

@ -0,0 +1,59 @@
/**
* @file main.cpp
* @brief Extra2D
*
* 使 Extra2D
*/
#include <extra2d/extra2d.h>
#include <iostream>
using namespace extra2d;
/**
* @brief
*
*
*/
int main(int argc, char* argv[]) {
(void)argc;
(void)argv;
std::cout << "Extra2D Demo - Starting..." << std::endl;
AppConfig config = AppConfig::createDefault();
config.appName = "Extra2D Demo";
config.appVersion = "1.0.0";
config.window.title = "Extra2D Demo";
config.window.width = 800;
config.window.height = 600;
config.window.mode = WindowMode::Windowed;
config.window.resizable = true;
config.window.vsync = true;
config.render.targetFPS = 60;
Application& app = Application::get();
if (!app.init(config)) {
std::cerr << "Failed to initialize application!" << std::endl;
return -1;
}
std::cout << "Application initialized successfully!" << std::endl;
std::cout << "Window: " << app.window().width() << "x" << app.window().height() << std::endl;
std::cout << "Running main loop. Press ESC or close window to exit." << std::endl;
auto scene = Scene::create();
scene->setBackgroundColor(Colors::SkyBlue);
scene->setViewportSize(static_cast<float>(config.window.width),
static_cast<float>(config.window.height));
app.enterScene(scene);
app.run();
std::cout << "Shutting down..." << std::endl;
app.shutdown();
std::cout << "Goodbye!" << std::endl;
return 0;
}

View File

@ -95,3 +95,23 @@ includes("xmake/engine.lua")
-- 定义引擎库 -- 定义引擎库
define_extra2d_engine() define_extra2d_engine()
-- ==============================================
-- 示例程序
-- ==============================================
-- 基础示例
target("demo_basic")
set_kind("binary")
set_default(false)
add_deps("extra2d")
add_files("examples/basic/main.cpp")
-- 平台配置
local target_plat = get_config("plat") or os.host()
if target_plat == "mingw" then
add_packages("glm", "nlohmann_json", "libsdl2")
add_syslinks("opengl32", "glu32", "winmm", "imm32", "version", "setupapi")
end
target_end()