feat(switch): 添加 Nintendo Switch 平台支持
- 添加 Switch 平台专用的日志输出和初始化处理 - 修改窗口模块以支持 OpenGL ES 3.2 渲染 - 更新构建系统以支持 Switch 平台编译 - 添加 Switch 平台文件系统操作支持 - 移除 Vulkan 相关依赖,改用 OpenGL ES - 更新插件加载器以适配 Switch 平台限制
This commit is contained in:
parent
fb11f2a71e
commit
6717015e28
|
|
@ -20,9 +20,9 @@ using ResizeCb = std::function<void(int32 w, int32 h)>;
|
|||
using CloseCb = std::function<void()>;
|
||||
|
||||
/**
|
||||
* @brief 窗口模块 - Vulkan 版本
|
||||
* @brief 窗口模块 - OpenGL ES 3.2 版本
|
||||
*
|
||||
* 管理 SDL2 窗口,支持 Vulkan 渲染
|
||||
* 管理 SDL2 窗口,支持 OpenGL ES 3.2 渲染
|
||||
* 使用新的 Module 基类,支持自动注册
|
||||
*/
|
||||
class WindowModule : public Module {
|
||||
|
|
@ -122,18 +122,26 @@ public:
|
|||
bool shouldClose() const { return shouldClose_; }
|
||||
|
||||
/**
|
||||
* @brief 获取 Vulkan 所需的窗口扩展
|
||||
* @return 扩展名称列表
|
||||
*/
|
||||
std::vector<const char *> getVulkanExtensions() const;
|
||||
|
||||
/**
|
||||
* @brief 创建 Vulkan 表面
|
||||
* @param instance Vulkan 实例
|
||||
* @param surface 输出的表面句柄
|
||||
* @brief 创建 OpenGL ES 上下文
|
||||
* @return 是否成功
|
||||
*/
|
||||
bool createVulkanSurface(void *instance, void **surface) const;
|
||||
bool createGLContext();
|
||||
|
||||
/**
|
||||
* @brief 销毁 OpenGL ES 上下文
|
||||
*/
|
||||
void destroyGLContext();
|
||||
|
||||
/**
|
||||
* @brief 交换缓冲区(呈现渲染结果)
|
||||
*/
|
||||
void swapBuffers();
|
||||
|
||||
/**
|
||||
* @brief 获取 OpenGL ES 上下文句柄
|
||||
* @return SDL_GLContext 句柄
|
||||
*/
|
||||
void* getGLContext() const;
|
||||
|
||||
private:
|
||||
void handleWindowEvent(const SDL_WindowEvent &evt);
|
||||
|
|
@ -147,6 +155,7 @@ private:
|
|||
void onModuleConfig(const AppConfig &config);
|
||||
|
||||
SDL_Window *window_ = nullptr;
|
||||
SDL_GLContext glContext_ = nullptr;
|
||||
bool shouldClose_ = false;
|
||||
|
||||
CloseCb onClose_;
|
||||
|
|
|
|||
|
|
@ -6,6 +6,11 @@
|
|||
#include <type_traits>
|
||||
#include <types/base/types.h>
|
||||
|
||||
// Switch 平台支持
|
||||
#ifdef __SWITCH__
|
||||
#include <switch.h>
|
||||
#endif
|
||||
|
||||
#ifndef TEST_BUILD
|
||||
// SDL2 日志头文件(测试构建时不使用)
|
||||
#include <SDL.h>
|
||||
|
|
|
|||
|
|
@ -9,6 +9,11 @@
|
|||
#include <direct.h>
|
||||
#include <windows.h>
|
||||
#define mkdir_impl(path, mode) _mkdir(path)
|
||||
#elif defined(__SWITCH__)
|
||||
#include <dirent.h>
|
||||
#include <unistd.h>
|
||||
// Switch 使用 ::mkdir 避免与类成员函数冲突
|
||||
#define mkdir_impl(path, mode) ::mkdir(path, mode)
|
||||
#else
|
||||
#include <dirent.h>
|
||||
#include <unistd.h>
|
||||
|
|
|
|||
|
|
@ -1,246 +1,278 @@
|
|||
#include <platform/window_module.h>
|
||||
#include <config/app_config.h>
|
||||
#include <event/events.h>
|
||||
#include <memory>
|
||||
#include <platform/window_module.h>
|
||||
#include <utils/logger.h>
|
||||
|
||||
// SDL Vulkan support
|
||||
#include <SDL_vulkan.h>
|
||||
|
||||
// Vulkan
|
||||
#include <vulkan/vulkan.h>
|
||||
// OpenGL ES 3.2 with GLAD
|
||||
#include <glad/glad.h>
|
||||
|
||||
namespace extra2d {
|
||||
|
||||
WindowModule::WindowModule() = default;
|
||||
|
||||
WindowModule::~WindowModule() {
|
||||
WindowModule::~WindowModule() { shutdown(); }
|
||||
|
||||
WindowModule::WindowModule(WindowModule &&other) noexcept
|
||||
: window_(other.window_), glContext_(other.glContext_),
|
||||
shouldClose_(other.shouldClose_), onClose_(std::move(other.onClose_)),
|
||||
onResize_(std::move(other.onResize_)),
|
||||
configListener_(std::move(other.configListener_)) {
|
||||
other.window_ = nullptr;
|
||||
other.glContext_ = nullptr;
|
||||
}
|
||||
|
||||
WindowModule &WindowModule::operator=(WindowModule &&other) noexcept {
|
||||
if (this != &other) {
|
||||
shutdown();
|
||||
}
|
||||
|
||||
WindowModule::WindowModule(WindowModule&& other) noexcept
|
||||
: window_(other.window_)
|
||||
, shouldClose_(other.shouldClose_)
|
||||
, onClose_(std::move(other.onClose_))
|
||||
, onResize_(std::move(other.onResize_))
|
||||
, configListener_(std::move(other.configListener_)) {
|
||||
window_ = other.window_;
|
||||
glContext_ = other.glContext_;
|
||||
shouldClose_ = other.shouldClose_;
|
||||
onClose_ = std::move(other.onClose_);
|
||||
onResize_ = std::move(other.onResize_);
|
||||
configListener_ = std::move(other.configListener_);
|
||||
other.window_ = nullptr;
|
||||
}
|
||||
|
||||
WindowModule& WindowModule::operator=(WindowModule&& other) noexcept {
|
||||
if (this != &other) {
|
||||
shutdown();
|
||||
window_ = other.window_;
|
||||
shouldClose_ = other.shouldClose_;
|
||||
onClose_ = std::move(other.onClose_);
|
||||
onResize_ = std::move(other.onResize_);
|
||||
configListener_ = std::move(other.configListener_);
|
||||
other.window_ = nullptr;
|
||||
}
|
||||
return *this;
|
||||
other.glContext_ = nullptr;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool WindowModule::init() {
|
||||
if (SDL_InitSubSystem(SDL_INIT_VIDEO) != 0) {
|
||||
E2D_LOG_ERROR("Failed to initialize SDL video: {}", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
if (SDL_InitSubSystem(SDL_INIT_VIDEO) != 0) {
|
||||
E2D_LOG_ERROR("Failed to initialize SDL video: {}", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
// 监听模块配置事件
|
||||
configListener_ = std::make_unique<events::OnModuleConfig<AppConfig>::Listener>();
|
||||
configListener_->bind([this](const AppConfig& config) {
|
||||
this->onModuleConfig(config);
|
||||
});
|
||||
// 监听模块配置事件
|
||||
configListener_ =
|
||||
std::make_unique<events::OnModuleConfig<AppConfig>::Listener>();
|
||||
configListener_->bind(
|
||||
[this](const AppConfig &config) { this->onModuleConfig(config); });
|
||||
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void WindowModule::shutdown() {
|
||||
if (window_) {
|
||||
SDL_DestroyWindow(window_);
|
||||
window_ = nullptr;
|
||||
}
|
||||
destroyGLContext();
|
||||
if (window_) {
|
||||
SDL_DestroyWindow(window_);
|
||||
window_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool WindowModule::create(const WindowCfg& cfg) {
|
||||
if (window_)
|
||||
return true;
|
||||
|
||||
uint32 flags = SDL_WINDOW_SHOWN | SDL_WINDOW_VULKAN;
|
||||
if (cfg.resizable) {
|
||||
flags |= SDL_WINDOW_RESIZABLE;
|
||||
}
|
||||
if (cfg.fullscreen) {
|
||||
flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
|
||||
}
|
||||
|
||||
window_ = SDL_CreateWindow(cfg.title.c_str(), SDL_WINDOWPOS_CENTERED,
|
||||
SDL_WINDOWPOS_CENTERED, cfg.width, cfg.height, flags);
|
||||
|
||||
if (!window_) {
|
||||
E2D_LOG_ERROR("Failed to create window: {}", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
// 发送窗口显示事件
|
||||
events::OnShow::emit();
|
||||
|
||||
E2D_LOG_INFO("Window created: {}x{}", cfg.width, cfg.height);
|
||||
bool WindowModule::create(const WindowCfg &cfg) {
|
||||
if (window_)
|
||||
return true;
|
||||
|
||||
uint32 flags = SDL_WINDOW_SHOWN | SDL_WINDOW_OPENGL;
|
||||
if (cfg.resizable) {
|
||||
flags |= SDL_WINDOW_RESIZABLE;
|
||||
}
|
||||
if (cfg.fullscreen) {
|
||||
flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
|
||||
}
|
||||
|
||||
window_ =
|
||||
SDL_CreateWindow(cfg.title.c_str(), SDL_WINDOWPOS_CENTERED,
|
||||
SDL_WINDOWPOS_CENTERED, cfg.width, cfg.height, flags);
|
||||
|
||||
if (!window_) {
|
||||
E2D_LOG_ERROR("Failed to create window: {}", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
// 发送窗口显示事件
|
||||
events::OnShow::emit();
|
||||
|
||||
E2D_LOG_INFO("Window created: {}x{}", cfg.width, cfg.height);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WindowModule::pollEvents() {
|
||||
SDL_Event evt;
|
||||
while (SDL_PollEvent(&evt)) {
|
||||
switch (evt.type) {
|
||||
case SDL_QUIT:
|
||||
shouldClose_ = true;
|
||||
// 发送窗口关闭事件
|
||||
events::OnClose::emit();
|
||||
if (onClose_)
|
||||
onClose_();
|
||||
break;
|
||||
case SDL_WINDOWEVENT:
|
||||
handleWindowEvent(evt.window);
|
||||
break;
|
||||
default:
|
||||
// 输入事件由 InputModule 处理
|
||||
break;
|
||||
}
|
||||
SDL_Event evt;
|
||||
while (SDL_PollEvent(&evt)) {
|
||||
switch (evt.type) {
|
||||
case SDL_QUIT:
|
||||
shouldClose_ = true;
|
||||
// 发送窗口关闭事件
|
||||
events::OnClose::emit();
|
||||
if (onClose_)
|
||||
onClose_();
|
||||
break;
|
||||
case SDL_WINDOWEVENT:
|
||||
handleWindowEvent(evt.window);
|
||||
break;
|
||||
default:
|
||||
// 输入事件由 InputModule 处理
|
||||
break;
|
||||
}
|
||||
return !shouldClose_;
|
||||
}
|
||||
|
||||
#ifdef __SWITCH__
|
||||
// Switch 平台:每帧更新控制台,确保日志实时显示
|
||||
consoleUpdate(NULL);
|
||||
#endif
|
||||
|
||||
return !shouldClose_;
|
||||
}
|
||||
|
||||
void WindowModule::handleWindowEvent(const SDL_WindowEvent& evt) {
|
||||
switch (evt.event) {
|
||||
case SDL_WINDOWEVENT_RESIZED:
|
||||
// 发送窗口大小改变事件
|
||||
events::OnResize::emit(evt.data1, evt.data2);
|
||||
if (onResize_) {
|
||||
onResize_(evt.data1, evt.data2);
|
||||
}
|
||||
break;
|
||||
case SDL_WINDOWEVENT_FOCUS_GAINED:
|
||||
// 发送窗口获得焦点事件
|
||||
events::OnFocus::emit();
|
||||
break;
|
||||
case SDL_WINDOWEVENT_FOCUS_LOST:
|
||||
// 发送窗口失去焦点事件
|
||||
events::OnBlur::emit();
|
||||
break;
|
||||
case SDL_WINDOWEVENT_SHOWN:
|
||||
// 发送窗口显示事件
|
||||
events::OnShow::emit();
|
||||
break;
|
||||
case SDL_WINDOWEVENT_HIDDEN:
|
||||
// 发送窗口隐藏事件
|
||||
events::OnHide::emit();
|
||||
break;
|
||||
case SDL_WINDOWEVENT_CLOSE:
|
||||
shouldClose_ = true;
|
||||
// 发送窗口关闭事件
|
||||
events::OnClose::emit();
|
||||
if (onClose_)
|
||||
onClose_();
|
||||
break;
|
||||
void WindowModule::handleWindowEvent(const SDL_WindowEvent &evt) {
|
||||
switch (evt.event) {
|
||||
case SDL_WINDOWEVENT_RESIZED:
|
||||
// 发送窗口大小改变事件
|
||||
events::OnResize::emit(evt.data1, evt.data2);
|
||||
if (onResize_) {
|
||||
onResize_(evt.data1, evt.data2);
|
||||
}
|
||||
break;
|
||||
case SDL_WINDOWEVENT_FOCUS_GAINED:
|
||||
// 发送窗口获得焦点事件
|
||||
events::OnFocus::emit();
|
||||
break;
|
||||
case SDL_WINDOWEVENT_FOCUS_LOST:
|
||||
// 发送窗口失去焦点事件
|
||||
events::OnBlur::emit();
|
||||
break;
|
||||
case SDL_WINDOWEVENT_SHOWN:
|
||||
// 发送窗口显示事件
|
||||
events::OnShow::emit();
|
||||
break;
|
||||
case SDL_WINDOWEVENT_HIDDEN:
|
||||
// 发送窗口隐藏事件
|
||||
events::OnHide::emit();
|
||||
break;
|
||||
case SDL_WINDOWEVENT_CLOSE:
|
||||
shouldClose_ = true;
|
||||
// 发送窗口关闭事件
|
||||
events::OnClose::emit();
|
||||
if (onClose_)
|
||||
onClose_();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void WindowModule::onModuleConfig(const AppConfig &config) {
|
||||
WindowCfg cfg;
|
||||
cfg.title = config.title;
|
||||
cfg.width = config.width;
|
||||
cfg.height = config.height;
|
||||
cfg.fullscreen = config.fullscreen;
|
||||
cfg.resizable = config.resizable;
|
||||
WindowCfg cfg;
|
||||
cfg.title = config.title;
|
||||
cfg.width = config.width;
|
||||
cfg.height = config.height;
|
||||
cfg.fullscreen = config.fullscreen;
|
||||
cfg.resizable = config.resizable;
|
||||
|
||||
if (create(cfg)) {
|
||||
setVisible(true);
|
||||
}
|
||||
if (create(cfg)) {
|
||||
setVisible(true);
|
||||
}
|
||||
}
|
||||
|
||||
Size WindowModule::getSize() const {
|
||||
if (!window_)
|
||||
return Size(0, 0);
|
||||
int w, h;
|
||||
SDL_GetWindowSize(window_, &w, &h);
|
||||
return Size(w, h);
|
||||
if (!window_)
|
||||
return Size(0, 0);
|
||||
int w, h;
|
||||
SDL_GetWindowSize(window_, &w, &h);
|
||||
return Size(w, h);
|
||||
}
|
||||
|
||||
Vec2 WindowModule::getPosition() const {
|
||||
if (!window_)
|
||||
return Vec2(0, 0);
|
||||
int x, y;
|
||||
SDL_GetWindowPosition(window_, &x, &y);
|
||||
return Vec2(static_cast<float>(x), static_cast<float>(y));
|
||||
if (!window_)
|
||||
return Vec2(0, 0);
|
||||
int x, y;
|
||||
SDL_GetWindowPosition(window_, &x, &y);
|
||||
return Vec2(static_cast<float>(x), static_cast<float>(y));
|
||||
}
|
||||
|
||||
void WindowModule::setSize(int32 w, int32 h) {
|
||||
if (window_) {
|
||||
SDL_SetWindowSize(window_, w, h);
|
||||
}
|
||||
if (window_) {
|
||||
SDL_SetWindowSize(window_, w, h);
|
||||
}
|
||||
}
|
||||
|
||||
void WindowModule::setTitle(const std::string& title) {
|
||||
if (window_) {
|
||||
SDL_SetWindowTitle(window_, title.c_str());
|
||||
}
|
||||
void WindowModule::setTitle(const std::string &title) {
|
||||
if (window_) {
|
||||
SDL_SetWindowTitle(window_, title.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void WindowModule::setFullscreen(bool fullscreen) {
|
||||
if (window_) {
|
||||
SDL_SetWindowFullscreen(window_,
|
||||
fullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0);
|
||||
}
|
||||
if (window_) {
|
||||
SDL_SetWindowFullscreen(window_,
|
||||
fullscreen ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0);
|
||||
}
|
||||
}
|
||||
|
||||
bool WindowModule::isFullscreen() const {
|
||||
if (!window_)
|
||||
return false;
|
||||
uint32 flags = SDL_GetWindowFlags(window_);
|
||||
return (flags & SDL_WINDOW_FULLSCREEN_DESKTOP) != 0;
|
||||
if (!window_)
|
||||
return false;
|
||||
uint32 flags = SDL_GetWindowFlags(window_);
|
||||
return (flags & SDL_WINDOW_FULLSCREEN_DESKTOP) != 0;
|
||||
}
|
||||
|
||||
void WindowModule::setVisible(bool visible) {
|
||||
if (window_) {
|
||||
if (visible) {
|
||||
SDL_ShowWindow(window_);
|
||||
// 发送窗口显示事件
|
||||
events::OnShow::emit();
|
||||
} else {
|
||||
SDL_HideWindow(window_);
|
||||
// 发送窗口隐藏事件
|
||||
events::OnHide::emit();
|
||||
}
|
||||
if (window_) {
|
||||
if (visible) {
|
||||
SDL_ShowWindow(window_);
|
||||
// 发送窗口显示事件
|
||||
events::OnShow::emit();
|
||||
} else {
|
||||
SDL_HideWindow(window_);
|
||||
// 发送窗口隐藏事件
|
||||
events::OnHide::emit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool WindowModule::isVisible() const {
|
||||
if (!window_)
|
||||
return false;
|
||||
uint32 flags = SDL_GetWindowFlags(window_);
|
||||
return (flags & SDL_WINDOW_SHOWN) != 0;
|
||||
if (!window_)
|
||||
return false;
|
||||
uint32 flags = SDL_GetWindowFlags(window_);
|
||||
return (flags & SDL_WINDOW_SHOWN) != 0;
|
||||
}
|
||||
|
||||
std::vector<const char*> WindowModule::getVulkanExtensions() const {
|
||||
std::vector<const char*> extensions;
|
||||
if (!window_) {
|
||||
return extensions;
|
||||
}
|
||||
bool WindowModule::createGLContext() {
|
||||
if (!window_) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint32 count = 0;
|
||||
SDL_Vulkan_GetInstanceExtensions(window_, &count, nullptr);
|
||||
extensions.resize(count);
|
||||
SDL_Vulkan_GetInstanceExtensions(window_, &count, extensions.data());
|
||||
// 创建 OpenGL ES 3.2 上下文
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_ES);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
|
||||
SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 2);
|
||||
SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
|
||||
SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
|
||||
SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
|
||||
|
||||
return extensions;
|
||||
glContext_ = SDL_GL_CreateContext(window_);
|
||||
if (!glContext_) {
|
||||
E2D_LOG_ERROR("Failed to create OpenGL ES context: {}", SDL_GetError());
|
||||
return false;
|
||||
}
|
||||
|
||||
// 初始化 GLAD
|
||||
if (!gladLoadGLES2Loader((GLADloadproc)SDL_GL_GetProcAddress)) {
|
||||
E2D_LOG_ERROR("Failed to initialize GLAD");
|
||||
SDL_GL_DeleteContext(glContext_);
|
||||
glContext_ = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
E2D_LOG_INFO("OpenGL ES context created: {}.{}", GLVersion.major,
|
||||
GLVersion.minor);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WindowModule::createVulkanSurface(void* instance, void** surface) const {
|
||||
if (!window_ || !instance || !surface) {
|
||||
return false;
|
||||
}
|
||||
return SDL_Vulkan_CreateSurface(window_, static_cast<VkInstance>(instance), reinterpret_cast<VkSurfaceKHR*>(surface));
|
||||
void WindowModule::destroyGLContext() {
|
||||
if (glContext_) {
|
||||
SDL_GL_DeleteContext(glContext_);
|
||||
glContext_ = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void WindowModule::swapBuffers() {
|
||||
if (window_) {
|
||||
SDL_GL_SwapWindow(window_);
|
||||
}
|
||||
}
|
||||
|
||||
void *WindowModule::getGLContext() const { return glContext_; }
|
||||
|
||||
} // namespace extra2d
|
||||
|
|
|
|||
|
|
@ -1,12 +1,13 @@
|
|||
#include <plugin/plugin_loader.h>
|
||||
#include <event/event_bus.h>
|
||||
#include <algorithm>
|
||||
#include <plugin/plugin_loader.h>
|
||||
#include <queue>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <windows.h>
|
||||
#include <windows.h>
|
||||
#elif defined(__SWITCH__)
|
||||
// Nintendo Switch 平台使用 libnx,不支持传统动态库加载
|
||||
#else
|
||||
#include <dlfcn.h>
|
||||
#include <dlfcn.h>
|
||||
#endif
|
||||
|
||||
namespace extra2d {
|
||||
|
|
@ -14,335 +15,352 @@ namespace extra2d {
|
|||
PluginLoader::PluginLoader() = default;
|
||||
|
||||
PluginLoader::~PluginLoader() {
|
||||
if (inited_) {
|
||||
shutdownAll();
|
||||
}
|
||||
if (inited_) {
|
||||
shutdownAll();
|
||||
}
|
||||
|
||||
// 卸载所有动态库插件
|
||||
for (auto& pair : plugins_) {
|
||||
if (pair.second.isDynamic && pair.second.handle) {
|
||||
if (pair.second.plugin) {
|
||||
pair.second.plugin->unload();
|
||||
if (pair.second.owned) {
|
||||
delete pair.second.plugin;
|
||||
}
|
||||
}
|
||||
unloadDynamicLibrary(pair.second.handle);
|
||||
// 卸载所有动态库插件
|
||||
for (auto &pair : plugins_) {
|
||||
if (pair.second.isDynamic && pair.second.handle) {
|
||||
if (pair.second.plugin) {
|
||||
pair.second.plugin->unload();
|
||||
if (pair.second.owned) {
|
||||
delete pair.second.plugin;
|
||||
}
|
||||
}
|
||||
unloadDynamicLibrary(pair.second.handle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PluginLoader::PluginLoader(PluginLoader&&) noexcept = default;
|
||||
PluginLoader& PluginLoader::operator=(PluginLoader&&) noexcept = default;
|
||||
PluginLoader::PluginLoader(PluginLoader &&) noexcept = default;
|
||||
PluginLoader &PluginLoader::operator=(PluginLoader &&) noexcept = default;
|
||||
|
||||
bool PluginLoader::loadFromLibrary(const char* path) {
|
||||
if (!path) {
|
||||
return false;
|
||||
bool PluginLoader::loadFromLibrary(const char *path) {
|
||||
if (!path) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 加载动态库
|
||||
void *handle = loadDynamicLibrary(path);
|
||||
if (!handle) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 获取创建函数
|
||||
using CreateFunc = IPlugin *(*)();
|
||||
CreateFunc createFunc =
|
||||
reinterpret_cast<CreateFunc>(getSymbol(handle, "extra2d_create_plugin"));
|
||||
|
||||
if (!createFunc) {
|
||||
unloadDynamicLibrary(handle);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 创建插件实例
|
||||
IPlugin *plugin = createFunc();
|
||||
if (!plugin) {
|
||||
unloadDynamicLibrary(handle);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查是否已存在
|
||||
const char *name = plugin->getInfo().name.c_str();
|
||||
if (hasPlugin(name)) {
|
||||
delete plugin;
|
||||
unloadDynamicLibrary(handle);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 添加到插件列表
|
||||
PluginEntry entry;
|
||||
entry.plugin = plugin;
|
||||
entry.handle = handle;
|
||||
entry.isDynamic = true;
|
||||
entry.owned = true;
|
||||
|
||||
plugins_[name] = entry;
|
||||
sortedPlugins_.clear();
|
||||
|
||||
// 插件加载事件(暂不发送,避免依赖 events.h)
|
||||
// event::broadcast<events::OnPluginLoaded>(name,
|
||||
// plugin->getInfo().version.c_str());
|
||||
|
||||
// 如果已经初始化,立即加载插件
|
||||
if (inited_) {
|
||||
if (!plugin->load()) {
|
||||
unloadPlugin(name);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 加载动态库
|
||||
void* handle = loadDynamicLibrary(path);
|
||||
if (!handle) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 获取创建函数
|
||||
using CreateFunc = IPlugin* (*)();
|
||||
CreateFunc createFunc = reinterpret_cast<CreateFunc>(
|
||||
getSymbol(handle, "extra2d_create_plugin")
|
||||
);
|
||||
|
||||
if (!createFunc) {
|
||||
unloadDynamicLibrary(handle);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 创建插件实例
|
||||
IPlugin* plugin = createFunc();
|
||||
if (!plugin) {
|
||||
unloadDynamicLibrary(handle);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 检查是否已存在
|
||||
const char* name = plugin->getInfo().name.c_str();
|
||||
if (hasPlugin(name)) {
|
||||
delete plugin;
|
||||
unloadDynamicLibrary(handle);
|
||||
return false;
|
||||
}
|
||||
|
||||
// 添加到插件列表
|
||||
PluginEntry entry;
|
||||
entry.plugin = plugin;
|
||||
entry.handle = handle;
|
||||
entry.isDynamic = true;
|
||||
entry.owned = true;
|
||||
|
||||
plugins_[name] = entry;
|
||||
sortedPlugins_.clear();
|
||||
|
||||
// 插件加载事件(暂不发送,避免依赖 events.h)
|
||||
// event::broadcast<events::OnPluginLoaded>(name, plugin->getInfo().version.c_str());
|
||||
|
||||
// 如果已经初始化,立即加载插件
|
||||
if (inited_) {
|
||||
if (!plugin->load()) {
|
||||
unloadPlugin(name);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void PluginLoader::registerPlugin(IPlugin* plugin) {
|
||||
if (!plugin) {
|
||||
return;
|
||||
}
|
||||
void PluginLoader::registerPlugin(IPlugin *plugin) {
|
||||
if (!plugin) {
|
||||
return;
|
||||
}
|
||||
|
||||
const char* name = plugin->getInfo().name.c_str();
|
||||
if (!name) {
|
||||
return;
|
||||
}
|
||||
const char *name = plugin->getInfo().name.c_str();
|
||||
if (!name) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 如果已存在同名插件,先卸载旧的
|
||||
if (hasPlugin(name)) {
|
||||
unloadPlugin(name);
|
||||
}
|
||||
// 如果已存在同名插件,先卸载旧的
|
||||
if (hasPlugin(name)) {
|
||||
unloadPlugin(name);
|
||||
}
|
||||
|
||||
PluginEntry entry;
|
||||
entry.plugin = plugin;
|
||||
entry.handle = nullptr;
|
||||
entry.isDynamic = false;
|
||||
entry.owned = false;
|
||||
PluginEntry entry;
|
||||
entry.plugin = plugin;
|
||||
entry.handle = nullptr;
|
||||
entry.isDynamic = false;
|
||||
entry.owned = false;
|
||||
|
||||
plugins_[name] = entry;
|
||||
sortedPlugins_.clear();
|
||||
plugins_[name] = entry;
|
||||
sortedPlugins_.clear();
|
||||
|
||||
// 插件加载事件(暂不发送,避免依赖 events.h)
|
||||
// event::broadcast<events::OnPluginLoaded>(name, plugin->getInfo().version.c_str());
|
||||
// 插件加载事件(暂不发送,避免依赖 events.h)
|
||||
// event::broadcast<events::OnPluginLoaded>(name,
|
||||
// plugin->getInfo().version.c_str());
|
||||
|
||||
// 如果已经初始化,立即加载插件
|
||||
if (inited_) {
|
||||
plugin->load();
|
||||
}
|
||||
// 如果已经初始化,立即加载插件
|
||||
if (inited_) {
|
||||
plugin->load();
|
||||
}
|
||||
}
|
||||
|
||||
void PluginLoader::unloadPlugin(const char* name) {
|
||||
if (!name) {
|
||||
return;
|
||||
}
|
||||
void PluginLoader::unloadPlugin(const char *name) {
|
||||
if (!name) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto it = plugins_.find(name);
|
||||
if (it == plugins_.end()) {
|
||||
return;
|
||||
}
|
||||
auto it = plugins_.find(name);
|
||||
if (it == plugins_.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
PluginEntry& entry = it->second;
|
||||
PluginEntry &entry = it->second;
|
||||
|
||||
// 如果已初始化,先卸载
|
||||
if (inited_ && entry.plugin) {
|
||||
entry.plugin->unload();
|
||||
}
|
||||
// 如果已初始化,先卸载
|
||||
if (inited_ && entry.plugin) {
|
||||
entry.plugin->unload();
|
||||
}
|
||||
|
||||
// 如果是动态加载且由加载器管理,删除实例
|
||||
if (entry.isDynamic && entry.owned && entry.plugin) {
|
||||
delete entry.plugin;
|
||||
}
|
||||
// 如果是动态加载且由加载器管理,删除实例
|
||||
if (entry.isDynamic && entry.owned && entry.plugin) {
|
||||
delete entry.plugin;
|
||||
}
|
||||
|
||||
// 卸载动态库
|
||||
if (entry.handle) {
|
||||
unloadDynamicLibrary(entry.handle);
|
||||
}
|
||||
// 卸载动态库
|
||||
if (entry.handle) {
|
||||
unloadDynamicLibrary(entry.handle);
|
||||
}
|
||||
|
||||
plugins_.erase(it);
|
||||
sortedPlugins_.clear();
|
||||
plugins_.erase(it);
|
||||
sortedPlugins_.clear();
|
||||
}
|
||||
|
||||
IPlugin* PluginLoader::getPlugin(const char* name) const {
|
||||
if (!name) {
|
||||
return nullptr;
|
||||
}
|
||||
IPlugin *PluginLoader::getPlugin(const char *name) const {
|
||||
if (!name) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto it = plugins_.find(name);
|
||||
return it != plugins_.end() ? it->second.plugin : nullptr;
|
||||
auto it = plugins_.find(name);
|
||||
return it != plugins_.end() ? it->second.plugin : nullptr;
|
||||
}
|
||||
|
||||
bool PluginLoader::hasPlugin(const char* name) const {
|
||||
if (!name) {
|
||||
return false;
|
||||
}
|
||||
return plugins_.find(name) != plugins_.end();
|
||||
bool PluginLoader::hasPlugin(const char *name) const {
|
||||
if (!name) {
|
||||
return false;
|
||||
}
|
||||
return plugins_.find(name) != plugins_.end();
|
||||
}
|
||||
|
||||
std::vector<IPlugin*> PluginLoader::getAllPlugins() const {
|
||||
std::vector<IPlugin*> result;
|
||||
for (const auto& pair : plugins_) {
|
||||
if (pair.second.plugin) {
|
||||
result.push_back(pair.second.plugin);
|
||||
}
|
||||
std::vector<IPlugin *> PluginLoader::getAllPlugins() const {
|
||||
std::vector<IPlugin *> result;
|
||||
for (const auto &pair : plugins_) {
|
||||
if (pair.second.plugin) {
|
||||
result.push_back(pair.second.plugin);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
bool PluginLoader::initAll() {
|
||||
if (inited_) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 检查所有插件的依赖是否满足
|
||||
for (const auto& pair : plugins_) {
|
||||
if (pair.second.plugin && !checkDependencies(pair.second.plugin->getDependencies())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 按依赖顺序排序
|
||||
sortPluginsByDependencies();
|
||||
|
||||
// 加载所有插件
|
||||
for (auto* plugin : sortedPlugins_) {
|
||||
if (plugin && !plugin->load()) {
|
||||
// 加载失败,卸载已加载的插件
|
||||
shutdownAll();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
inited_ = true;
|
||||
if (inited_) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 检查所有插件的依赖是否满足
|
||||
for (const auto &pair : plugins_) {
|
||||
if (pair.second.plugin &&
|
||||
!checkDependencies(pair.second.plugin->getDependencies())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// 按依赖顺序排序
|
||||
sortPluginsByDependencies();
|
||||
|
||||
// 加载所有插件
|
||||
for (auto *plugin : sortedPlugins_) {
|
||||
if (plugin && !plugin->load()) {
|
||||
// 加载失败,卸载已加载的插件
|
||||
shutdownAll();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
inited_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
void PluginLoader::shutdownAll() {
|
||||
if (!inited_) {
|
||||
return;
|
||||
}
|
||||
if (!inited_) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 按依赖逆序卸载
|
||||
for (auto it = sortedPlugins_.rbegin(); it != sortedPlugins_.rend(); ++it) {
|
||||
if (*it) {
|
||||
(*it)->unload();
|
||||
}
|
||||
// 按依赖逆序卸载
|
||||
for (auto it = sortedPlugins_.rbegin(); it != sortedPlugins_.rend(); ++it) {
|
||||
if (*it) {
|
||||
(*it)->unload();
|
||||
}
|
||||
}
|
||||
|
||||
inited_ = false;
|
||||
inited_ = false;
|
||||
}
|
||||
|
||||
size_t PluginLoader::getPluginCount() const {
|
||||
return plugins_.size();
|
||||
size_t PluginLoader::getPluginCount() const { return plugins_.size(); }
|
||||
|
||||
void PluginLoader::addSearchPath(const char *path) {
|
||||
if (path) {
|
||||
searchPaths_.push_back(path);
|
||||
}
|
||||
}
|
||||
|
||||
void PluginLoader::addSearchPath(const char* path) {
|
||||
if (path) {
|
||||
searchPaths_.push_back(path);
|
||||
size_t PluginLoader::loadPluginsFromDirectory(const char *directory) {
|
||||
// TODO: 实现目录扫描加载
|
||||
// 需要平台相关的文件系统操作
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool PluginLoader::resolveDependencies(IPlugin *plugin) {
|
||||
if (!plugin) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return checkDependencies(plugin->getDependencies());
|
||||
}
|
||||
|
||||
bool PluginLoader::checkDependencies(
|
||||
const std::vector<std::string> &dependencies) {
|
||||
for (const auto &dep : dependencies) {
|
||||
if (!hasPlugin(dep.c_str())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
size_t PluginLoader::loadPluginsFromDirectory(const char* directory) {
|
||||
// TODO: 实现目录扫描加载
|
||||
// 需要平台相关的文件系统操作
|
||||
return 0;
|
||||
}
|
||||
|
||||
bool PluginLoader::resolveDependencies(IPlugin* plugin) {
|
||||
if (!plugin) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return checkDependencies(plugin->getDependencies());
|
||||
}
|
||||
|
||||
bool PluginLoader::checkDependencies(const std::vector<std::string>& dependencies) {
|
||||
for (const auto& dep : dependencies) {
|
||||
if (!hasPlugin(dep.c_str())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void PluginLoader::sortPluginsByDependencies() {
|
||||
if (!sortedPlugins_.empty()) {
|
||||
return;
|
||||
if (!sortedPlugins_.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 拓扑排序处理依赖关系
|
||||
std::unordered_map<std::string, int> inDegree;
|
||||
std::unordered_map<std::string, std::vector<std::string>> graph;
|
||||
|
||||
// 初始化
|
||||
for (const auto &pair : plugins_) {
|
||||
inDegree[pair.first] = 0;
|
||||
}
|
||||
|
||||
// 构建图
|
||||
for (const auto &pair : plugins_) {
|
||||
if (!pair.second.plugin)
|
||||
continue;
|
||||
|
||||
for (const auto &dep : pair.second.plugin->getDependencies()) {
|
||||
graph[dep].push_back(pair.first);
|
||||
inDegree[pair.first]++;
|
||||
}
|
||||
}
|
||||
|
||||
// 拓扑排序处理依赖关系
|
||||
std::unordered_map<std::string, int> inDegree;
|
||||
std::unordered_map<std::string, std::vector<std::string>> graph;
|
||||
|
||||
// 初始化
|
||||
for (const auto& pair : plugins_) {
|
||||
inDegree[pair.first] = 0;
|
||||
// 拓扑排序
|
||||
std::queue<std::string> q;
|
||||
for (const auto &pair : inDegree) {
|
||||
if (pair.second == 0) {
|
||||
q.push(pair.first);
|
||||
}
|
||||
}
|
||||
|
||||
// 构建图
|
||||
for (const auto& pair : plugins_) {
|
||||
if (!pair.second.plugin) continue;
|
||||
std::vector<std::string> sortedNames;
|
||||
while (!q.empty()) {
|
||||
std::string name = q.front();
|
||||
q.pop();
|
||||
sortedNames.push_back(name);
|
||||
|
||||
for (const auto& dep : pair.second.plugin->getDependencies()) {
|
||||
graph[dep].push_back(pair.first);
|
||||
inDegree[pair.first]++;
|
||||
}
|
||||
for (const auto &next : graph[name]) {
|
||||
inDegree[next]--;
|
||||
if (inDegree[next] == 0) {
|
||||
q.push(next);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 拓扑排序
|
||||
std::queue<std::string> q;
|
||||
for (const auto& pair : inDegree) {
|
||||
if (pair.second == 0) {
|
||||
q.push(pair.first);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> sortedNames;
|
||||
while (!q.empty()) {
|
||||
std::string name = q.front();
|
||||
q.pop();
|
||||
sortedNames.push_back(name);
|
||||
|
||||
for (const auto& next : graph[name]) {
|
||||
inDegree[next]--;
|
||||
if (inDegree[next] == 0) {
|
||||
q.push(next);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 构建排序后的插件列表
|
||||
sortedPlugins_.clear();
|
||||
for (const auto& name : sortedNames) {
|
||||
auto it = plugins_.find(name);
|
||||
if (it != plugins_.end() && it->second.plugin) {
|
||||
sortedPlugins_.push_back(it->second.plugin);
|
||||
}
|
||||
// 构建排序后的插件列表
|
||||
sortedPlugins_.clear();
|
||||
for (const auto &name : sortedNames) {
|
||||
auto it = plugins_.find(name);
|
||||
if (it != plugins_.end() && it->second.plugin) {
|
||||
sortedPlugins_.push_back(it->second.plugin);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void* PluginLoader::loadDynamicLibrary(const char* path) {
|
||||
void *PluginLoader::loadDynamicLibrary(const char *path) {
|
||||
#ifdef _WIN32
|
||||
return LoadLibraryA(path);
|
||||
return LoadLibraryA(path);
|
||||
#elif defined(__SWITCH__)
|
||||
// Nintendo Switch 平台不支持动态库加载
|
||||
(void)path;
|
||||
return nullptr;
|
||||
#else
|
||||
return dlopen(path, RTLD_LAZY);
|
||||
return dlopen(path, RTLD_LAZY);
|
||||
#endif
|
||||
}
|
||||
|
||||
void PluginLoader::unloadDynamicLibrary(void* handle) {
|
||||
if (!handle) return;
|
||||
void PluginLoader::unloadDynamicLibrary(void *handle) {
|
||||
if (!handle)
|
||||
return;
|
||||
|
||||
#ifdef _WIN32
|
||||
FreeLibrary(static_cast<HMODULE>(handle));
|
||||
FreeLibrary(static_cast<HMODULE>(handle));
|
||||
#elif defined(__SWITCH__)
|
||||
// Nintendo Switch 平台不支持动态库加载
|
||||
(void)handle;
|
||||
#else
|
||||
dlclose(handle);
|
||||
dlclose(handle);
|
||||
#endif
|
||||
}
|
||||
|
||||
void* PluginLoader::getSymbol(void* handle, const char* name) {
|
||||
if (!handle || !name) return nullptr;
|
||||
void *PluginLoader::getSymbol(void *handle, const char *name) {
|
||||
if (!handle || !name)
|
||||
return nullptr;
|
||||
|
||||
#ifdef _WIN32
|
||||
return reinterpret_cast<void*>(GetProcAddress(static_cast<HMODULE>(handle), name));
|
||||
return reinterpret_cast<void *>(
|
||||
GetProcAddress(static_cast<HMODULE>(handle), name));
|
||||
#elif defined(__SWITCH__)
|
||||
// Nintendo Switch 平台不支持动态库加载
|
||||
(void)handle;
|
||||
(void)name;
|
||||
return nullptr;
|
||||
#else
|
||||
return dlsym(handle, name);
|
||||
return dlsym(handle, name);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -9,6 +9,25 @@ bool Logger::consoleOutput_ = true;
|
|||
bool Logger::fileOutput_ = false;
|
||||
std::string Logger::logFile_;
|
||||
|
||||
// Switch 平台:自定义 SDL 日志输出函数
|
||||
#ifdef __SWITCH__
|
||||
/**
|
||||
* @brief Switch 平台日志输出回调函数
|
||||
* @param userdata 用户数据
|
||||
* @param category 日志类别
|
||||
* @param priority 日志优先级
|
||||
* @param message 日志消息
|
||||
*/
|
||||
static void SwitchLogOutput(void *userdata, int category,
|
||||
SDL_LogPriority priority, const char *message) {
|
||||
(void)userdata;
|
||||
(void)category;
|
||||
(void)priority;
|
||||
// 输出到 Switch 控制台
|
||||
printf("%s\n", message);
|
||||
}
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief 获取日志级别字符串
|
||||
* @param level 日志级别
|
||||
|
|
@ -41,11 +60,17 @@ void Logger::init() {
|
|||
return;
|
||||
}
|
||||
|
||||
// 设置 SDL 日志级别为详细模式(允许所有级别的日志)
|
||||
#ifdef __SWITCH__
|
||||
// Switch 平台:初始化控制台并设置 SDL 日志重定向
|
||||
consoleInit(NULL);
|
||||
SDL_LogSetOutputFunction(SwitchLogOutput, nullptr);
|
||||
#else
|
||||
// 其他平台:设置 SDL 日志级别为详细模式
|
||||
SDL_LogSetPriority(SDL_LOG_CATEGORY_APPLICATION, SDL_LOG_PRIORITY_VERBOSE);
|
||||
#endif
|
||||
|
||||
initialized_ = true;
|
||||
log(LogLevel::Info, "Logger initialized with SDL2");
|
||||
log(LogLevel::Info, "Logger initialized");
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -54,6 +79,9 @@ void Logger::init() {
|
|||
void Logger::shutdown() {
|
||||
if (initialized_) {
|
||||
log(LogLevel::Info, "Logger shutting down");
|
||||
#ifdef __SWITCH__
|
||||
consoleExit(NULL);
|
||||
#endif
|
||||
}
|
||||
initialized_ = false;
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -78,7 +78,7 @@ end
|
|||
-- ==============================================
|
||||
|
||||
if target_plat == "mingw" then
|
||||
add_requires("glm", "libsdl2", "libsdl2_mixer", "vulkansdk")
|
||||
add_requires("glm", "libsdl2", "libsdl2_mixer")
|
||||
end
|
||||
|
||||
-- ==============================================
|
||||
|
|
|
|||
|
|
@ -13,24 +13,27 @@ function define_extra2d_engine()
|
|||
target("extra2d")
|
||||
set_kind("static")
|
||||
|
||||
add_files("src/**.cpp|core/*.cpp|module/module_manager.cpp|plugin/plugin_manager.cpp|platform/window.cpp|platform/input.cpp")
|
||||
add_files("src/**.cpp")
|
||||
add_files("third_party/glad/src/glad.c")
|
||||
|
||||
add_includedirs("include", {public = true})
|
||||
add_includedirs("third_party", {public = true})
|
||||
add_includedirs("third_party/glad/include", {public = true})
|
||||
|
||||
local plat = get_current_plat()
|
||||
if plat == "mingw" then
|
||||
add_defines("_UNICODE", "UNICODE")
|
||||
-- 使用 xmake 官方包
|
||||
add_packages("glm", "libsdl2", "libsdl2_mixer", "vulkansdk", {public = true})
|
||||
add_packages("glm", "libsdl2", "libsdl2_mixer", {public = true})
|
||||
add_syslinks("winmm", "imm32", "version", "setupapi", {public = true})
|
||||
elseif plat == "switch" then
|
||||
local devkitPro = os.getenv("DEVKITPRO") or "C:/devkitPro"
|
||||
add_includedirs(devkitPro .. "/portlibs/switch/include", {public = true})
|
||||
add_linkdirs(devkitPro .. "/portlibs/switch/lib")
|
||||
-- Switch 平台可能需要不同的图形库
|
||||
-- Switch 平台使用 OpenGL ES + EGL + Mesa 驱动
|
||||
-- 注意:链接顺序很重要,被依赖的库要放在后面
|
||||
add_syslinks("SDL2_mixer", "SDL2", "opusfile", "opus", "vorbisidec", "ogg",
|
||||
"modplug", "mpg123", "FLAC", "drm_nouveau",
|
||||
"modplug", "mpg123", "FLAC", "GLESv2", "EGL", "glapi", "drm_nouveau",
|
||||
{public = true})
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ function define_switch_toolchain()
|
|||
add_includedirs(path.join(devkitPro, "libnx/include"))
|
||||
add_linkdirs(path.join(devkitPro, "libnx/lib"))
|
||||
|
||||
-- portlibs 路径(EGL + 桌面 OpenGL + SDL2)
|
||||
-- portlibs 路径(OpenGL ES + EGL + SDL2)
|
||||
add_includedirs(path.join(devkitPro, "portlibs/switch/include"))
|
||||
add_includedirs(path.join(devkitPro, "portlibs/switch/include/SDL2"))
|
||||
add_linkdirs(path.join(devkitPro, "portlibs/switch/lib"))
|
||||
|
|
|
|||
Loading…
Reference in New Issue