2026-02-25 06:23:53 +08:00
|
|
|
|
#include <app/application.h>
|
|
|
|
|
|
#include <audio/audio_engine.h>
|
|
|
|
|
|
#include <event/event_dispatcher.h>
|
|
|
|
|
|
#include <event/event_queue.h>
|
|
|
|
|
|
#include <graphics/vram_manager.h>
|
|
|
|
|
|
#include <platform/input.h>
|
|
|
|
|
|
#include <platform/window.h>
|
2026-02-26 20:08:04 +08:00
|
|
|
|
#include <renderer/camera.h>
|
|
|
|
|
|
#include <renderer/renderer.h>
|
2026-02-25 06:23:53 +08:00
|
|
|
|
#include <resource/resource_manager.h>
|
|
|
|
|
|
#include <scene/scene_manager.h>
|
|
|
|
|
|
#include <utils/logger.h>
|
|
|
|
|
|
#include <utils/timer.h>
|
2026-02-11 19:40:26 +08:00
|
|
|
|
|
2026-02-26 20:08:04 +08:00
|
|
|
|
|
2026-02-11 19:40:26 +08:00
|
|
|
|
#include <chrono>
|
|
|
|
|
|
#include <thread>
|
|
|
|
|
|
|
|
|
|
|
|
#ifdef __SWITCH__
|
|
|
|
|
|
#include <switch.h>
|
|
|
|
|
|
#endif
|
|
|
|
|
|
|
|
|
|
|
|
namespace extra2d {
|
|
|
|
|
|
|
|
|
|
|
|
// 获取当前时间(秒)
|
|
|
|
|
|
static double getTimeSeconds() {
|
|
|
|
|
|
#ifdef __SWITCH__
|
|
|
|
|
|
struct timespec ts;
|
|
|
|
|
|
clock_gettime(CLOCK_MONOTONIC, &ts);
|
|
|
|
|
|
return static_cast<double>(ts.tv_sec) +
|
|
|
|
|
|
static_cast<double>(ts.tv_nsec) / 1000000000.0;
|
|
|
|
|
|
#else
|
|
|
|
|
|
// PC 平台使用 chrono
|
|
|
|
|
|
using namespace std::chrono;
|
|
|
|
|
|
auto now = steady_clock::now();
|
|
|
|
|
|
auto duration = now.time_since_epoch();
|
|
|
|
|
|
return duration_cast<std::chrono::duration<double>>(duration).count();
|
|
|
|
|
|
#endif
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Application &Application::instance() {
|
|
|
|
|
|
static Application instance;
|
|
|
|
|
|
return instance;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Application::~Application() { shutdown(); }
|
|
|
|
|
|
|
|
|
|
|
|
bool Application::init(const AppConfig &config) {
|
|
|
|
|
|
if (initialized_) {
|
|
|
|
|
|
E2D_LOG_WARN("Application already initialized");
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
config_ = config;
|
|
|
|
|
|
|
|
|
|
|
|
// 确定平台类型
|
|
|
|
|
|
PlatformType platform = config_.platform;
|
|
|
|
|
|
if (platform == PlatformType::Auto) {
|
|
|
|
|
|
#ifdef __SWITCH__
|
|
|
|
|
|
platform = PlatformType::Switch;
|
|
|
|
|
|
#else
|
|
|
|
|
|
platform = PlatformType::PC;
|
|
|
|
|
|
#endif
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (platform == PlatformType::Switch) {
|
|
|
|
|
|
#ifdef __SWITCH__
|
|
|
|
|
|
// ========================================
|
|
|
|
|
|
// 1. 初始化 RomFS 文件系统(Switch 平台)
|
|
|
|
|
|
// ========================================
|
|
|
|
|
|
Result rc;
|
|
|
|
|
|
rc = romfsInit();
|
|
|
|
|
|
if (R_SUCCEEDED(rc)) {
|
|
|
|
|
|
E2D_LOG_INFO("RomFS initialized successfully");
|
|
|
|
|
|
} else {
|
2026-02-13 18:46:42 +08:00
|
|
|
|
E2D_LOG_WARN("romfsInit failed: {:#08X}, will use regular filesystem",
|
|
|
|
|
|
rc);
|
2026-02-11 19:40:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ========================================
|
|
|
|
|
|
// 2. 初始化 nxlink 调试输出(Switch 平台)
|
|
|
|
|
|
// ========================================
|
|
|
|
|
|
rc = socketInitializeDefault();
|
|
|
|
|
|
if (R_FAILED(rc)) {
|
|
|
|
|
|
E2D_LOG_WARN(
|
|
|
|
|
|
"socketInitializeDefault failed, nxlink will not be available");
|
|
|
|
|
|
}
|
|
|
|
|
|
#endif
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ========================================
|
|
|
|
|
|
// 3. 创建窗口(包含 SDL_Init + GLES 3.2 上下文创建)
|
|
|
|
|
|
// ========================================
|
2026-02-26 20:08:04 +08:00
|
|
|
|
window_ = unique<Window>();
|
2026-02-11 19:40:26 +08:00
|
|
|
|
WindowConfig winConfig;
|
|
|
|
|
|
winConfig.title = config.title;
|
2026-02-12 14:29:50 +08:00
|
|
|
|
winConfig.width = config.width;
|
|
|
|
|
|
winConfig.height = config.height;
|
2026-02-11 19:40:26 +08:00
|
|
|
|
if (platform == PlatformType::Switch) {
|
|
|
|
|
|
winConfig.fullscreen = true;
|
2026-02-13 18:46:42 +08:00
|
|
|
|
winConfig.fullscreenDesktop = false; // Switch 使用固定分辨率全屏
|
2026-02-11 19:40:26 +08:00
|
|
|
|
winConfig.resizable = false;
|
2026-02-12 14:32:51 +08:00
|
|
|
|
winConfig.enableCursors = false;
|
|
|
|
|
|
winConfig.enableDpiScale = false;
|
2026-02-11 19:40:26 +08:00
|
|
|
|
} else {
|
|
|
|
|
|
// PC 平台默认窗口模式
|
|
|
|
|
|
winConfig.fullscreen = config.fullscreen;
|
2026-02-12 14:29:50 +08:00
|
|
|
|
winConfig.resizable = config.resizable;
|
|
|
|
|
|
winConfig.enableCursors = config.enableCursors;
|
|
|
|
|
|
winConfig.enableDpiScale = config.enableDpiScale;
|
2026-02-11 19:40:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
winConfig.vsync = config.vsync;
|
|
|
|
|
|
winConfig.msaaSamples = config.msaaSamples;
|
|
|
|
|
|
|
|
|
|
|
|
if (!window_->create(winConfig)) {
|
|
|
|
|
|
E2D_LOG_ERROR("Failed to create window");
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ========================================
|
|
|
|
|
|
// 4. 初始化渲染器
|
|
|
|
|
|
// ========================================
|
2026-02-26 00:59:16 +08:00
|
|
|
|
renderer_ = Renderer::create(config.Renderer);
|
2026-02-11 19:40:26 +08:00
|
|
|
|
if (!renderer_ || !renderer_->init(window_.get())) {
|
|
|
|
|
|
E2D_LOG_ERROR("Failed to initialize renderer");
|
|
|
|
|
|
window_->destroy();
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ========================================
|
|
|
|
|
|
// 5. 初始化其他子系统
|
|
|
|
|
|
// ========================================
|
2026-02-26 20:08:04 +08:00
|
|
|
|
sceneManager_ = unique<SceneManager>();
|
|
|
|
|
|
resourceManager_ = unique<ResourceManager>();
|
|
|
|
|
|
timerManager_ = unique<TimerManager>();
|
|
|
|
|
|
eventQueue_ = unique<EventQueue>();
|
|
|
|
|
|
eventDispatcher_ = unique<EventDispatcher>();
|
|
|
|
|
|
camera_ = unique<Camera>(0, static_cast<float>(window_->width()),
|
|
|
|
|
|
static_cast<float>(window_->height()), 0);
|
2026-02-11 19:40:26 +08:00
|
|
|
|
|
|
|
|
|
|
// 窗口大小回调
|
|
|
|
|
|
window_->setResizeCallback([this](int width, int height) {
|
|
|
|
|
|
if (camera_) {
|
|
|
|
|
|
camera_->setViewport(0, static_cast<float>(width),
|
|
|
|
|
|
static_cast<float>(height), 0);
|
|
|
|
|
|
}
|
|
|
|
|
|
if (sceneManager_) {
|
|
|
|
|
|
auto currentScene = sceneManager_->getCurrentScene();
|
|
|
|
|
|
if (currentScene) {
|
|
|
|
|
|
currentScene->setViewportSize(static_cast<float>(width),
|
|
|
|
|
|
static_cast<float>(height));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// 初始化音频引擎
|
|
|
|
|
|
AudioEngine::getInstance().initialize();
|
|
|
|
|
|
|
|
|
|
|
|
initialized_ = true;
|
|
|
|
|
|
running_ = true;
|
|
|
|
|
|
|
|
|
|
|
|
E2D_LOG_INFO("Application initialized successfully");
|
|
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Application::shutdown() {
|
|
|
|
|
|
if (!initialized_)
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
|
|
E2D_LOG_INFO("Shutting down application...");
|
|
|
|
|
|
|
|
|
|
|
|
// 打印 VRAM 统计
|
|
|
|
|
|
VRAMManager::getInstance().printStats();
|
2026-02-13 18:46:42 +08:00
|
|
|
|
|
2026-02-11 19:40:26 +08:00
|
|
|
|
// 先结束所有场景,确保 onExit() 被正确调用
|
|
|
|
|
|
if (sceneManager_) {
|
|
|
|
|
|
sceneManager_->end();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-12 12:20:14 +08:00
|
|
|
|
// ========================================
|
|
|
|
|
|
// 1. 先清理所有持有 GPU 资源的子系统
|
|
|
|
|
|
// 必须在渲染器关闭前释放纹理等资源
|
|
|
|
|
|
// ========================================
|
2026-02-13 18:46:42 +08:00
|
|
|
|
sceneManager_.reset(); // 场景持有纹理引用
|
|
|
|
|
|
resourceManager_.reset(); // 纹理缓存持有 GPU 纹理
|
|
|
|
|
|
camera_.reset(); // 相机可能持有渲染目标
|
2026-02-12 12:20:14 +08:00
|
|
|
|
|
|
|
|
|
|
// ========================================
|
|
|
|
|
|
// 2. 关闭音频(不依赖 GPU)
|
|
|
|
|
|
// ========================================
|
|
|
|
|
|
AudioEngine::getInstance().shutdown();
|
|
|
|
|
|
|
|
|
|
|
|
// ========================================
|
|
|
|
|
|
// 3. 清理其他子系统
|
|
|
|
|
|
// ========================================
|
2026-02-11 19:40:26 +08:00
|
|
|
|
timerManager_.reset();
|
|
|
|
|
|
eventQueue_.reset();
|
|
|
|
|
|
eventDispatcher_.reset();
|
|
|
|
|
|
|
2026-02-12 12:20:14 +08:00
|
|
|
|
// ========================================
|
|
|
|
|
|
// 4. 最后关闭渲染器和窗口
|
|
|
|
|
|
// 必须在所有 GPU 资源释放后才能关闭 OpenGL 上下文
|
|
|
|
|
|
// ========================================
|
2026-02-11 19:40:26 +08:00
|
|
|
|
if (renderer_) {
|
|
|
|
|
|
renderer_->shutdown();
|
|
|
|
|
|
renderer_.reset();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-12 12:20:14 +08:00
|
|
|
|
// 销毁窗口(包含 SDL_Quit,会销毁 OpenGL 上下文)
|
2026-02-11 19:40:26 +08:00
|
|
|
|
if (window_) {
|
|
|
|
|
|
window_->destroy();
|
|
|
|
|
|
window_.reset();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Switch 平台清理
|
|
|
|
|
|
PlatformType platform = config_.platform;
|
|
|
|
|
|
if (platform == PlatformType::Auto) {
|
|
|
|
|
|
#ifdef __SWITCH__
|
|
|
|
|
|
platform = PlatformType::Switch;
|
|
|
|
|
|
#else
|
|
|
|
|
|
platform = PlatformType::PC;
|
|
|
|
|
|
#endif
|
|
|
|
|
|
}
|
|
|
|
|
|
if (platform == PlatformType::Switch) {
|
|
|
|
|
|
#ifdef __SWITCH__
|
|
|
|
|
|
romfsExit();
|
|
|
|
|
|
socketExit();
|
|
|
|
|
|
#endif
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
initialized_ = false;
|
|
|
|
|
|
running_ = false;
|
|
|
|
|
|
|
|
|
|
|
|
E2D_LOG_INFO("Application shutdown complete");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Application::run() {
|
|
|
|
|
|
if (!initialized_) {
|
|
|
|
|
|
E2D_LOG_ERROR("Application not initialized");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
lastFrameTime_ = getTimeSeconds();
|
|
|
|
|
|
|
|
|
|
|
|
#ifdef __SWITCH__
|
|
|
|
|
|
// SDL2 on Switch 内部已处理 appletMainLoop
|
|
|
|
|
|
while (running_ && !window_->shouldClose()) {
|
|
|
|
|
|
mainLoop();
|
|
|
|
|
|
}
|
|
|
|
|
|
#else
|
|
|
|
|
|
// PC 平台主循环
|
|
|
|
|
|
while (running_ && !window_->shouldClose()) {
|
|
|
|
|
|
mainLoop();
|
|
|
|
|
|
}
|
|
|
|
|
|
#endif
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Application::quit() {
|
|
|
|
|
|
shouldQuit_ = true;
|
|
|
|
|
|
running_ = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Application::pause() {
|
|
|
|
|
|
if (!paused_) {
|
|
|
|
|
|
paused_ = true;
|
|
|
|
|
|
E2D_LOG_INFO("Application paused");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Application::resume() {
|
|
|
|
|
|
if (paused_) {
|
|
|
|
|
|
paused_ = false;
|
|
|
|
|
|
lastFrameTime_ = getTimeSeconds();
|
|
|
|
|
|
E2D_LOG_INFO("Application resumed");
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Application::mainLoop() {
|
|
|
|
|
|
// 计算 delta time
|
|
|
|
|
|
double currentTime = getTimeSeconds();
|
|
|
|
|
|
deltaTime_ = static_cast<float>(currentTime - lastFrameTime_);
|
|
|
|
|
|
lastFrameTime_ = currentTime;
|
|
|
|
|
|
|
|
|
|
|
|
totalTime_ += deltaTime_;
|
|
|
|
|
|
|
|
|
|
|
|
// 计算 FPS
|
|
|
|
|
|
frameCount_++;
|
|
|
|
|
|
fpsTimer_ += deltaTime_;
|
|
|
|
|
|
if (fpsTimer_ >= 1.0f) {
|
|
|
|
|
|
currentFps_ = frameCount_;
|
|
|
|
|
|
frameCount_ = 0;
|
|
|
|
|
|
fpsTimer_ -= 1.0f;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 处理窗口事件(SDL_PollEvent + 输入更新)
|
|
|
|
|
|
window_->pollEvents();
|
|
|
|
|
|
|
|
|
|
|
|
// 处理事件队列
|
|
|
|
|
|
if (eventDispatcher_ && eventQueue_) {
|
|
|
|
|
|
eventDispatcher_->processQueue(*eventQueue_);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 更新
|
|
|
|
|
|
if (!paused_) {
|
|
|
|
|
|
update();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 渲染
|
|
|
|
|
|
render();
|
|
|
|
|
|
|
|
|
|
|
|
if (!config_.vsync && config_.fpsLimit > 0) {
|
|
|
|
|
|
double frameEndTime = getTimeSeconds();
|
|
|
|
|
|
double frameTime = frameEndTime - currentTime;
|
|
|
|
|
|
double target = 1.0 / static_cast<double>(config_.fpsLimit);
|
|
|
|
|
|
if (frameTime < target) {
|
|
|
|
|
|
auto sleepSeconds = target - frameTime;
|
|
|
|
|
|
std::this_thread::sleep_for(std::chrono::duration<double>(sleepSeconds));
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Application::update() {
|
|
|
|
|
|
if (timerManager_) {
|
|
|
|
|
|
timerManager_->update(deltaTime_);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (sceneManager_) {
|
|
|
|
|
|
sceneManager_->update(deltaTime_);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void Application::render() {
|
|
|
|
|
|
if (!renderer_) {
|
|
|
|
|
|
E2D_LOG_ERROR("Render failed: renderer is null");
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-02-26 00:59:16 +08:00
|
|
|
|
renderer_->setViewport(0, 0, window_->width(), window_->height());
|
2026-02-11 19:40:26 +08:00
|
|
|
|
|
|
|
|
|
|
if (sceneManager_) {
|
|
|
|
|
|
sceneManager_->render(*renderer_);
|
|
|
|
|
|
} else {
|
|
|
|
|
|
E2D_LOG_WARN("Render: sceneManager is null");
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
window_->swapBuffers();
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
Input &Application::input() { return *window_->getInput(); }
|
|
|
|
|
|
|
|
|
|
|
|
AudioEngine &Application::audio() { return AudioEngine::getInstance(); }
|
|
|
|
|
|
|
|
|
|
|
|
SceneManager &Application::scenes() { return *sceneManager_; }
|
|
|
|
|
|
|
|
|
|
|
|
ResourceManager &Application::resources() { return *resourceManager_; }
|
|
|
|
|
|
|
|
|
|
|
|
TimerManager &Application::timers() { return *timerManager_; }
|
|
|
|
|
|
|
|
|
|
|
|
EventQueue &Application::eventQueue() { return *eventQueue_; }
|
|
|
|
|
|
|
|
|
|
|
|
EventDispatcher &Application::eventDispatcher() { return *eventDispatcher_; }
|
|
|
|
|
|
|
|
|
|
|
|
Camera &Application::camera() { return *camera_; }
|
|
|
|
|
|
|
2026-02-26 21:17:11 +08:00
|
|
|
|
void Application::enterScene(IntrusivePtr<Scene> scene) {
|
2026-02-11 19:40:26 +08:00
|
|
|
|
if (sceneManager_ && scene) {
|
2026-02-26 00:59:16 +08:00
|
|
|
|
scene->setViewportSize(static_cast<float>(window_->width()),
|
|
|
|
|
|
static_cast<float>(window_->height()));
|
2026-02-26 19:57:16 +08:00
|
|
|
|
sceneManager_->enterScene(scene);
|
2026-02-11 19:40:26 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
} // namespace extra2d
|