diff --git a/vita3k/android/jni/main_android.cpp b/vita3k/android/jni/main_android.cpp index 4776795b0..dd6253a32 100644 --- a/vita3k/android/jni/main_android.cpp +++ b/vita3k/android/jni/main_android.cpp @@ -147,14 +147,86 @@ void handle_ime_text_input(EmuEnvState &emuenv, const char *text) { ime::notify_ime_state_changed(); } -renderer::WindowSize get_window_size_in_pixels(SDL_Window *window) { - renderer::WindowSize size{ - .width = 960, - .height = 544 - }; - SDL_GetWindowSizeInPixels(window, &size.width, &size.height); - return size; -} +class AndroidFrameHost final : public renderer::FrameHost { +public: + explicit AndroidFrameHost(SDL_Window *window, SDL_GLContext *gl_context) + : m_window(window) + , m_gl_context(gl_context) { + } + + renderer::DisplayHandle handle() const override { + return renderer::AndroidDisplayHandle{ m_window }; + } + + int drawable_width() const override { +#ifdef __ANDROID__ + if (!renderer::vulkan::has_android_surface()) + return 0; +#endif + int width = 960; + int height = 544; + SDL_GetWindowSizeInPixels(m_window, &width, &height); + return width; + } + + int drawable_height() const override { +#ifdef __ANDROID__ + if (!renderer::vulkan::has_android_surface()) + return 0; +#endif + int width = 960; + int height = 544; + SDL_GetWindowSizeInPixels(m_window, &width, &height); + return height; + } + + std::vector font_dirs() const override { + return { "/system/fonts/" }; + } + + void *get_proc_address(const char *name) const override { + return reinterpret_cast(SDL_GL_GetProcAddress(name)); + } + + unsigned int default_fbo() const override { + return 0; + } + + bool make_current() override { + if (!m_gl_context || !*m_gl_context) + return false; + return SDL_GL_MakeCurrent(m_window, *m_gl_context); + } + + void done_current() override { + SDL_GL_MakeCurrent(m_window, nullptr); + } + + void swap_buffers() override { + SDL_GL_SwapWindow(m_window); + } + + bool set_vsync(bool enabled) override { + return SDL_GL_SetSwapInterval(enabled ? 1 : 0); + } + + void prepare_for_render_thread() override { + if (m_gl_context && *m_gl_context) + SDL_GL_MakeCurrent(m_window, nullptr); + } + + void destroy_render_context() override { + if (!m_gl_context || !*m_gl_context) + return; + + SDL_GL_DestroyContext(*m_gl_context); + *m_gl_context = nullptr; + } + +private: + SDL_Window *m_window = nullptr; + SDL_GLContext *m_gl_context = nullptr; +}; } // namespace @@ -231,6 +303,7 @@ SDLMAIN_DECLSPEC int SDL_main(int argc, char *argv[]) { SDL_Window *window = nullptr; SDL_GLContext gl_context = nullptr; + AndroidFrameHost frame_host(nullptr, &gl_context); std::optional pending_launch_request; const auto cleanup_launch = [&](const app::AppSessionStopReason reason) { @@ -320,26 +393,7 @@ SDLMAIN_DECLSPEC int SDL_main(int argc, char *argv[]) { break; } ime::set_sdl_window(window); - - renderer::WindowCallbacks callbacks; - callbacks.native_handle = window; - callbacks.display_protocol = renderer::DisplayProtocol::Android; - callbacks.has_surface = []() -> bool { -#ifdef __ANDROID__ - return renderer::vulkan::has_android_surface(); -#else - return true; -#endif - }; - callbacks.get_native_handle = [window]() -> void * { - return window; - }; - callbacks.get_size = [window]() -> renderer::WindowSize { - return get_window_size_in_pixels(window); - }; - callbacks.get_font_dirs = []() -> std::vector { - return { "/system/fonts/" }; - }; + frame_host = AndroidFrameHost(window, &gl_context); if (emuenv->backend_renderer == renderer::Backend::OpenGL) { gl_context = SDL_GL_CreateContext(window); @@ -359,42 +413,9 @@ SDLMAIN_DECLSPEC int SDL_main(int argc, char *argv[]) { if (!SDL_GL_SetSwapInterval(static_cast(emuenv->cfg.current_config.v_sync))) LOG_WARN("Failed to set OpenGL ES swap interval: {}", SDL_GetError()); - - callbacks.get_proc_address = [](const char *name) -> void * { - return reinterpret_cast(SDL_GL_GetProcAddress(name)); - }; - callbacks.default_fbo = []() -> unsigned int { - return 0; - }; - callbacks.swap = [window]() { - SDL_GL_SwapWindow(window); - }; - callbacks.set_vsync = [](bool enabled) -> bool { - return SDL_GL_SetSwapInterval(enabled ? 1 : 0); - }; - callbacks.set_current = [window, gl_context]() -> bool { - return SDL_GL_MakeCurrent(window, gl_context); - }; - callbacks.done_current = [window]() { - SDL_GL_MakeCurrent(window, nullptr); - }; } - app::AppSessionPlatform platform{ - .window_callbacks = callbacks, - .before_render_thread_start = [window, &gl_context]() { - if (gl_context) - SDL_GL_MakeCurrent(window, nullptr); }, - .after_render_thread_start = {}, - .destroy_render_context = [&gl_context]() { - if (!gl_context) - return; - - SDL_GL_DestroyContext(gl_context); - gl_context = nullptr; } - }; - - if (!session_controller->initialize_renderer(platform)) { + if (!session_controller->initialize_renderer(frame_host)) { LOG_ERROR("Failed to initialise renderer."); exit_code = -1; cleanup_launch(app::AppSessionStopReason::LaunchFailure); diff --git a/vita3k/app/include/app/session_controller.h b/vita3k/app/include/app/session_controller.h index 1896fd1e2..ed9d8c0a5 100644 --- a/vita3k/app/include/app/session_controller.h +++ b/vita3k/app/include/app/session_controller.h @@ -23,6 +23,7 @@ #include #include #include +#include namespace app { @@ -47,13 +48,6 @@ enum class AppSessionStopReason { LaunchFailure }; -struct AppSessionPlatform { - renderer::WindowCallbacks window_callbacks; - std::function before_render_thread_start; - std::function after_render_thread_start; - std::function destroy_render_context; -}; - class AppSessionController { public: explicit AppSessionController(EmuEnvState &emuenv); @@ -63,7 +57,7 @@ public: bool is_paused() const; bool begin_launch(const AppLaunchRequest &launch_request, bool update_last_time_used = true); - bool initialize_renderer(const AppSessionPlatform &platform); + bool initialize_renderer(renderer::FrameHost &frame); bool initialize_runtime(); bool load_and_run(); bool set_pause_reason(AppSessionPauseReason reason, bool enabled); @@ -78,7 +72,7 @@ private: EmuEnvState &emuenv; mutable std::mutex mutex; std::atomic current_phase{ AppSessionPhase::Idle }; - AppSessionPlatform platform; + std::optional> frame_host; AppLaunchRequest active_launch_request; std::atomic active_pause_reasons{ 0 }; bool renderer_initialized = false; diff --git a/vita3k/app/src/session_controller.cpp b/vita3k/app/src/session_controller.cpp index 7164bcded..f05250b92 100644 --- a/vita3k/app/src/session_controller.cpp +++ b/vita3k/app/src/session_controller.cpp @@ -69,7 +69,7 @@ bool AppSessionController::begin_launch(const AppLaunchRequest &launch_request, return true; } -bool AppSessionController::initialize_renderer(const AppSessionPlatform &next_platform) { +bool AppSessionController::initialize_renderer(renderer::FrameHost &frame) { std::lock_guard lock(mutex); const AppSessionPhase phase = current_phase.load(std::memory_order_relaxed); if (phase != AppSessionPhase::Launching || renderer_initialized) { @@ -77,8 +77,9 @@ bool AppSessionController::initialize_renderer(const AppSessionPlatform &next_pl return false; } - platform = next_platform; - if (!renderer::init(platform.window_callbacks, emuenv.renderer, + frame_host = frame; + + if (!renderer::init(frame, emuenv.renderer, emuenv.backend_renderer, emuenv.cfg, emuenv.get_root_paths())) { return false; } @@ -123,13 +124,11 @@ bool AppSessionController::load_and_run() { if (run_app(emuenv, main_module_id, active_launch_request) != Success) return false; - if (platform.before_render_thread_start) - platform.before_render_thread_start(); + frame_host->get().prepare_for_render_thread(); renderer::start_render_thread(*emuenv.renderer, emuenv.display, emuenv.gxm, emuenv.mem, emuenv.cfg); - if (platform.after_render_thread_start) - platform.after_render_thread_start(); + frame_host->get().finalize_render_thread_start(); set_phase(AppSessionPhase::Running); return true; @@ -167,7 +166,7 @@ bool AppSessionController::set_input_intercepted(const bool enabled) { } void AppSessionController::stop(const AppSessionStopReason reason) { - AppSessionPlatform active_platform; + std::optional> active_frame_host; bool renderer_was_initialized = false; bool runtime_was_initialized = false; bool app_started = false; @@ -178,15 +177,14 @@ void AppSessionController::stop(const AppSessionStopReason reason) { if (phase == AppSessionPhase::Idle || phase == AppSessionPhase::Stopping) return; - active_platform = platform; + active_frame_host = frame_host; renderer_was_initialized = renderer_initialized || static_cast(emuenv.renderer); runtime_was_initialized = runtime_initialized; app_started = phase == AppSessionPhase::Running; set_phase(AppSessionPhase::Stopping); } - const bool has_render_context = static_cast(active_platform.destroy_render_context); - const bool needs_renderer_cleanup = renderer_was_initialized || has_render_context; + const bool needs_renderer_cleanup = renderer_was_initialized || active_frame_host.has_value(); if (runtime_was_initialized) { if (app_started && reason != AppSessionStopReason::LaunchFailure) @@ -205,8 +203,8 @@ void AppSessionController::stop(const AppSessionStopReason reason) { abort_game_launch(emuenv); } - if (needs_renderer_cleanup && active_platform.destroy_render_context) - active_platform.destroy_render_context(); + if (needs_renderer_cleanup && active_frame_host) + active_frame_host->get().destroy_render_context(); emuenv.motion.clear_device_motion_support(); @@ -247,7 +245,7 @@ void AppSessionController::set_phase(const AppSessionPhase next_phase) { } void AppSessionController::reset_session_tracking() { - platform = {}; + frame_host.reset(); active_launch_request = {}; active_pause_reasons.store(0, std::memory_order_release); renderer_initialized = false; diff --git a/vita3k/gui-qt/include/gui-qt/game_window.h b/vita3k/gui-qt/include/gui-qt/game_window.h index bb6296f94..eea2a263c 100644 --- a/vita3k/gui-qt/include/gui-qt/game_window.h +++ b/vita3k/gui-qt/include/gui-qt/game_window.h @@ -19,6 +19,7 @@ #include #include +#include #include #include @@ -33,7 +34,7 @@ class QSurfaceFormat; class QThread; class QTimer; class GuiSettings; -class GameWindow : public QWindow { +class GameWindow : public QWindow, public renderer::FrameHost { Q_OBJECT public: @@ -47,18 +48,23 @@ public: const QSurfaceFormat &surface_format() const { return m_format; } bool create_gl_context(); - bool make_current(); - void swap_buffers(); - void done_current(); - void destroy_gl_context(); - void prepare_gl_for_render_thread(); - void complete_gl_migration(); + bool make_current() override; + void swap_buffers() override; + void done_current() override; + void prepare_for_render_thread() override; + void finalize_render_thread_start() override; + void destroy_render_context() override; QOpenGLContext *gl_context() const { return m_gl_context; } - int client_width_px() const; - int client_height_px() const; - bool set_vsync_enabled(bool enabled); + renderer::DisplayHandle handle() const override; + int drawable_width() const override; + int drawable_height() const override; + std::vector font_dirs() const override; + void *get_proc_address(const char *name) const override; + unsigned int default_fbo() const override; + bool set_vsync(bool enabled) override; + void toggle_fullscreen(); signals: @@ -80,6 +86,8 @@ private slots: void ui_tick(); private: + void prepare_gl_for_render_thread(); + void complete_gl_migration(); void sync_native_window_preferences(); void update_window_title(); diff --git a/vita3k/gui-qt/src/game_window.cpp b/vita3k/gui-qt/src/game_window.cpp index d9a469428..bf0072a5d 100644 --- a/vita3k/gui-qt/src/game_window.cpp +++ b/vita3k/gui-qt/src/game_window.cpp @@ -40,11 +40,19 @@ #include #include #include +#include #include #include #include +#include + +#if defined(HAVE_X11) || defined(HAVE_WAYLAND) +#include +#include +#endif + #if QT_CONFIG(xcb_glx_plugin) #include #endif @@ -157,7 +165,7 @@ GameWindow::GameWindow(EmuEnvState &emuenv, std::shared_ptr gui_set GameWindow::~GameWindow() { stop_ui_updates(); - destroy_gl_context(); + destroy_render_context(); } bool GameWindow::create_gl_context() { @@ -217,6 +225,86 @@ bool GameWindow::make_current() { return true; } +renderer::DisplayHandle GameWindow::handle() const { +#ifdef _WIN32 + return renderer::Win32DisplayHandle{ reinterpret_cast(winId()) }; +#elif defined(__APPLE__) + return renderer::MacOSDisplayHandle{ reinterpret_cast(winId()) }; +#elif defined(HAVE_X11) || defined(HAVE_WAYLAND) + QPlatformNativeInterface *native = QGuiApplication::platformNativeInterface(); + if (!native) { + LOG_ERROR("Could not get QPlatformNativeInterface, display protocol unknown"); + return {}; + } + +#if defined(HAVE_WAYLAND) + if (void *wl_display = native->nativeResourceForWindow("display", nullptr)) { + if (void *wl_surface = native->nativeResourceForWindow("surface", const_cast(this))) { + return renderer::WaylandDisplayHandle{ wl_display, wl_surface }; + } + } +#endif + +#if defined(HAVE_X11) + void *display = native->nativeResourceForIntegration("display"); + void *connection = native->nativeResourceForIntegration("connection"); + return renderer::X11DisplayHandle{ + .display = display, + .window = static_cast(winId()), + .connection = connection, + }; +#else + return {}; +#endif +#else + return {}; +#endif +} + +int GameWindow::drawable_width() const { +#ifdef _WIN32 + RECT rect; + if (GetClientRect(reinterpret_cast(winId()), &rect)) + return rect.right - rect.left; +#endif + return static_cast(width() * devicePixelRatio()); +} + +int GameWindow::drawable_height() const { +#ifdef _WIN32 + RECT rect; + if (GetClientRect(reinterpret_cast(winId()), &rect)) + return rect.bottom - rect.top; +#endif + return static_cast(height() * devicePixelRatio()); +} + +std::vector GameWindow::font_dirs() const { + const QStringList locations = QStandardPaths::standardLocations(QStandardPaths::FontsLocation); + std::vector dirs; + dirs.reserve(locations.size()); + + for (const QString &location : locations) { + std::string font_dir = location.toUtf8().constData(); + if (!font_dir.ends_with('/') && !font_dir.ends_with('\\')) + font_dir += '/'; + dirs.push_back(std::move(font_dir)); + } + + return dirs; +} + +void *GameWindow::get_proc_address(const char *name) const { + if (!m_gl_context) + return nullptr; + + return reinterpret_cast(reinterpret_cast(m_gl_context->getProcAddress(name))); +} + +unsigned int GameWindow::default_fbo() const { + return m_gl_context ? m_gl_context->defaultFramebufferObject() : 0; +} + void GameWindow::swap_buffers() { if (m_gl_context && isExposed()) { m_gl_context->swapBuffers(this); @@ -232,7 +320,15 @@ void GameWindow::done_current() { } } -void GameWindow::destroy_gl_context() { +bool GameWindow::set_vsync(bool enabled) { + QOpenGLContext *context = QOpenGLContext::currentContext(); + if (!context) + context = m_gl_context; + + return set_swap_interval(context, enabled ? 1 : 0); +} + +void GameWindow::destroy_render_context() { if (m_gl_context) { m_gl_context->doneCurrent(); delete m_gl_context; @@ -240,6 +336,21 @@ void GameWindow::destroy_gl_context() { } } +void GameWindow::prepare_for_render_thread() { + if (m_backend != renderer::Backend::OpenGL || !m_gl_context) + return; + + done_current(); + prepare_gl_for_render_thread(); +} + +void GameWindow::finalize_render_thread_start() { + if (m_backend != renderer::Backend::OpenGL || !m_gl_context) + return; + + complete_gl_migration(); +} + void GameWindow::prepare_gl_for_render_thread() { m_render_thread_id = std::promise(); m_render_thread_id_future = m_render_thread_id.get_future(); @@ -373,8 +484,8 @@ void GameWindow::keyReleaseEvent(QKeyEvent *e) { void GameWindow::update_mouse_position(QMouseEvent *e) { auto &touch = m_emuenv.touch; - const float scale_x = (width() > 0) ? static_cast(client_width_px()) / width() : 1.0f; - const float scale_y = (height() > 0) ? static_cast(client_height_px()) / height() : 1.0f; + const float scale_x = (width() > 0) ? static_cast(drawable_width()) / width() : 1.0f; + const float scale_y = (height() > 0) ? static_cast(drawable_height()) / height() : 1.0f; touch.mouse_x = static_cast(e->position().x()) * scale_x; touch.mouse_y = static_cast(e->position().y()) * scale_y; } @@ -411,24 +522,6 @@ void GameWindow::focusOutEvent(QFocusEvent *) { m_emuenv.touch.mouse_button_right = false; } -int GameWindow::client_width_px() const { -#ifdef _WIN32 - RECT rect; - if (GetClientRect(reinterpret_cast(winId()), &rect)) - return rect.right - rect.left; -#endif - return static_cast(width() * devicePixelRatio()); -} - -int GameWindow::client_height_px() const { -#ifdef _WIN32 - RECT rect; - if (GetClientRect(reinterpret_cast(winId()), &rect)) - return rect.bottom - rect.top; -#endif - return static_cast(height() * devicePixelRatio()); -} - void GameWindow::toggle_fullscreen() { if (visibility() == QWindow::FullScreen) { showNormal(); @@ -437,14 +530,6 @@ void GameWindow::toggle_fullscreen() { } } -bool GameWindow::set_vsync_enabled(bool enabled) { - QOpenGLContext *context = QOpenGLContext::currentContext(); - if (!context) - context = m_gl_context; - - return set_swap_interval(context, enabled ? 1 : 0); -} - void GameWindow::update_window_title() { if (!app::update_runtime_metrics(m_emuenv, m_runtime_metrics)) return; @@ -455,8 +540,7 @@ void GameWindow::update_window_title() { : ""; const auto x = m_emuenv.display.next_rendered_frame.image_size.x * cc.resolution_multiplier; const auto y = m_emuenv.display.next_rendered_frame.image_size.y * cc.resolution_multiplier; - const std::string title = fmt::format("{} | {} ({}) | {} | {} FPS ({} ms) | {}x{}{} | {}", - window_title, + const std::string title = fmt::format("{} ({}) | {} | {} FPS ({} ms) | {}x{}{} | {}", m_emuenv.current_app_title, m_emuenv.io.title_id, m_title_backend_renderer, m_emuenv.fps, m_emuenv.ms_per_frame, diff --git a/vita3k/gui-qt/src/main_window.cpp b/vita3k/gui-qt/src/main_window.cpp index 62af5c9ca..f8d92d3b4 100644 --- a/vita3k/gui-qt/src/main_window.cpp +++ b/vita3k/gui-qt/src/main_window.cpp @@ -936,9 +936,8 @@ std::optional MainWindow::boot_game_once(const AppLaunchReques } m_game_window = new GameWindow(emuenv, m_gui_settings, emuenv.backend_renderer, screen()); - m_game_window->setTitle(tr("%1 | %2 (%3) | Loading...") - .arg(QString::fromStdString(window_title), - QString::fromStdString(emuenv.current_app_title), + m_game_window->setTitle(tr("%1 (%2) | Loading...") + .arg(QString::fromStdString(emuenv.current_app_title), QString::fromStdString(emuenv.io.title_id))); m_game_window->setIcon(windowIcon()); if (m_live_area_widget) @@ -950,6 +949,8 @@ std::optional MainWindow::boot_game_once(const AppLaunchReques m_live_area_widget = nullptr; } + m_game_window->create(); + m_game_container = m_game_window; m_game_window->show(); @@ -976,78 +977,6 @@ std::optional MainWindow::boot_game_once(const AppLaunchReques m_game_container = nullptr; }; - renderer::WindowCallbacks callbacks; -#ifdef _WIN32 - callbacks.get_native_handle = [win = m_game_window]() -> void * { - return reinterpret_cast(win->winId()); - }; - callbacks.native_handle = reinterpret_cast(m_game_window->winId()); - callbacks.display_protocol = renderer::DisplayProtocol::Win32; -#elif defined(__APPLE__) - callbacks.get_native_handle = [win = m_game_window]() -> void * { - return reinterpret_cast(win->winId()); - }; - callbacks.native_handle = reinterpret_cast(m_game_window->winId()); - callbacks.display_protocol = renderer::DisplayProtocol::MacOS; -#elif defined(HAVE_X11) || defined(HAVE_WAYLAND) - { - QPlatformNativeInterface *native = QGuiApplication::platformNativeInterface(); - if (native) { -#if defined(HAVE_WAYLAND) - auto *wl_dpy = native->nativeResourceForWindow("display", nullptr); - auto *wl_surf = native->nativeResourceForWindow("surface", - const_cast(static_cast(m_game_window))); - if (wl_dpy && wl_surf) { - callbacks.get_native_handle = [native, win = m_game_window]() -> void * { - return native->nativeResourceForWindow("surface", - const_cast(static_cast(win))); - }; - callbacks.native_handle = wl_surf; - callbacks.native_display = wl_dpy; - callbacks.display_protocol = renderer::DisplayProtocol::Wayland; - LOG_INFO("Using Wayland display protocol for Vulkan surface"); - } else -#endif -#if defined(HAVE_X11) - { - void *x11_display = native->nativeResourceForIntegration("display"); - void *xcb_connection = native->nativeResourceForIntegration("connection"); - callbacks.get_native_handle = [win = m_game_window]() -> void * { - return reinterpret_cast(win->winId()); - }; - callbacks.native_handle = reinterpret_cast(m_game_window->winId()); - callbacks.native_display = x11_display; - callbacks.native_connection = xcb_connection; - callbacks.display_protocol = renderer::DisplayProtocol::X11; - LOG_INFO("Using X11 display protocol for Vulkan surface"); - } -#endif - } else { - LOG_ERROR("Could not get QPlatformNativeInterface, display protocol unknown"); - } - } -#endif - callbacks.get_size = [win = m_game_window]() -> renderer::WindowSize { - return { - .width = win->client_width_px(), - .height = win->client_height_px() - }; - }; - callbacks.get_font_dirs = []() -> std::vector { - const QStringList locations = QStandardPaths::standardLocations(QStandardPaths::FontsLocation); - std::vector font_dirs; - for (const QString &location : locations) { - std::string font_dir = location.toUtf8().constData(); - if (!font_dir.ends_with('/') && !font_dir.ends_with('\\')) - font_dir += '/'; - font_dirs.push_back(font_dir); - } - return font_dirs; - }; - - app::AppSessionPlatform platform; - platform.window_callbacks = callbacks; - if (emuenv.backend_renderer == renderer::Backend::OpenGL) { if (!m_game_window->create_gl_context()) { abort_boot(tr("Could not create OpenGL context.\nDoes your GPU support at least OpenGL 4.4?")); @@ -1057,46 +986,15 @@ std::optional MainWindow::boot_game_once(const AppLaunchReques abort_boot(tr("Could not make OpenGL context current.")); return {}; } - auto *gl_ctx = m_game_window->gl_context(); - platform.window_callbacks.get_proc_address = [gl_ctx](const char *name) -> void * { - return reinterpret_cast( - reinterpret_cast(gl_ctx->getProcAddress(name))); - }; - platform.window_callbacks.default_fbo = [gl_ctx]() -> unsigned int { - return gl_ctx->defaultFramebufferObject(); - }; - platform.window_callbacks.swap = [win = m_game_window]() { - win->swap_buffers(); - }; - platform.window_callbacks.set_vsync = [win = m_game_window](bool enabled) -> bool { - return win->set_vsync_enabled(enabled); - }; - platform.window_callbacks.set_current = [win = m_game_window]() -> bool { - return win->make_current(); - }; - platform.window_callbacks.done_current = [win = m_game_window]() { - win->done_current(); - }; - platform.before_render_thread_start = [win = m_game_window]() { - win->done_current(); - win->prepare_gl_for_render_thread(); - }; - platform.after_render_thread_start = [win = m_game_window]() { - win->complete_gl_migration(); - }; - platform.destroy_render_context = [win = m_game_window]() { - win->destroy_gl_context(); - }; } - if (!m_app_session.initialize_renderer(platform)) { + if (!m_app_session.initialize_renderer(*m_game_window)) { abort_boot(tr("Failed to initialise the renderer.")); return {}; } - m_game_window->setTitle(tr("%1 | %2 (%3) | Initializing...") - .arg(QString::fromStdString(window_title), - QString::fromStdString(emuenv.current_app_title), + m_game_window->setTitle(tr("%1 (%2) | Initializing...") + .arg(QString::fromStdString(emuenv.current_app_title), QString::fromStdString(emuenv.io.title_id))); if (!m_app_session.initialize_runtime()) { @@ -1104,9 +1002,8 @@ std::optional MainWindow::boot_game_once(const AppLaunchReques return {}; } - m_game_window->setTitle(tr("%1 | %2 (%3) | Loading modules...") - .arg(QString::fromStdString(window_title), - QString::fromStdString(emuenv.current_app_title), + m_game_window->setTitle(tr("%1 (%2) | Loading modules...") + .arg(QString::fromStdString(emuenv.current_app_title), QString::fromStdString(emuenv.io.title_id))); if (!m_app_session.load_and_run()) { @@ -1140,9 +1037,8 @@ std::optional MainWindow::boot_game_once(const AppLaunchReques m_game_window->start_ui_updates(); - m_game_window->setTitle(tr("%1 | %2 (%3) | Please wait, loading...") - .arg(QString::fromStdString(window_title), - QString::fromStdString(emuenv.current_app_title), + m_game_window->setTitle(tr("%1 (%2) | Please wait, loading...") + .arg(QString::fromStdString(emuenv.current_app_title), QString::fromStdString(emuenv.io.title_id))); LOG_INFO("Game started: {} ({})", emuenv.current_app_title, launch_request.app_path); diff --git a/vita3k/main.cpp b/vita3k/main.cpp index eb0ef0486..020e539d7 100644 --- a/vita3k/main.cpp +++ b/vita3k/main.cpp @@ -67,6 +67,11 @@ #include int main(int argc, char *argv[]) { +#ifdef __APPLE__ + qputenv("QT_MTL_NO_TRANSACTION", "1"); + qputenv("QT_MAC_NO_CONTAINER_LAYER", "1"); +#endif + QApplication app(argc, argv); QCoreApplication::setOrganizationName(QStringLiteral("Vita3K")); QCoreApplication::setApplicationName(QStringLiteral("Vita3K")); diff --git a/vita3k/renderer/include/renderer/frame_host.h b/vita3k/renderer/include/renderer/frame_host.h new file mode 100644 index 000000000..cc3a1cc12 --- /dev/null +++ b/vita3k/renderer/include/renderer/frame_host.h @@ -0,0 +1,99 @@ +// Vita3K emulator project +// Copyright (C) 2026 Vita3K team +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +#pragma once + +#include +#include +#include +#include + +typedef struct SDL_Window SDL_Window; + +namespace renderer { +struct Win32DisplayHandle { + void *hwnd = nullptr; +}; + +struct MacOSDisplayHandle { + void *view = nullptr; +}; + +struct X11DisplayHandle { + void *display = nullptr; + std::uintptr_t window = 0; + void *connection = nullptr; +}; + +struct WaylandDisplayHandle { + void *display = nullptr; + void *surface = nullptr; +}; + +struct AndroidDisplayHandle { + SDL_Window *window = nullptr; +}; + +using DisplayHandle = std::variant; + +class FrameHost { +public: + virtual ~FrameHost() = default; + + virtual DisplayHandle handle() const = 0; + virtual int drawable_width() const = 0; + virtual int drawable_height() const = 0; + virtual std::vector font_dirs() const = 0; + + virtual void *get_proc_address(const char *name) const { + return nullptr; + } + + virtual unsigned int default_fbo() const { + return 0; + } + + virtual bool make_current() { + return false; + } + + virtual void done_current() { + } + + virtual void swap_buffers() { + } + + virtual bool set_vsync(bool enabled) { + return false; + } + + virtual void prepare_for_render_thread() { + } + + virtual void finalize_render_thread_start() { + } + + virtual void destroy_render_context() { + } +}; + +} // namespace renderer diff --git a/vita3k/renderer/include/renderer/functions.h b/vita3k/renderer/include/renderer/functions.h index 4781d2004..f771528b7 100644 --- a/vita3k/renderer/include/renderer/functions.h +++ b/vita3k/renderer/include/renderer/functions.h @@ -30,11 +30,11 @@ struct GxmState; namespace renderer { struct Context; +class FrameHost; struct FragmentProgram; struct RenderTarget; struct State; struct VertexProgram; -struct WindowCallbacks; struct YUVConversionCache; bool create(std::unique_ptr &fp, State &state, const SceGxmProgram &program, const SceGxmBlendInfo *blend, GXPPtrMap &gxp_ptr_map); @@ -71,7 +71,7 @@ void process_batch(State &state, MemState &mem, Config &config, CommandList &com void process_batches(State &state, const FeatureState &features, MemState &mem, Config &config, int64_t max_wait_ms = 500); void start_render_thread(State &state, DisplayState &display, GxmState &gxm, MemState &mem, Config &config); void stop_render_thread(State &state); -bool init(const WindowCallbacks &callbacks, std::unique_ptr &state, Backend backend, const Config &config, const Root &root_paths); +bool init(FrameHost &frame, std::unique_ptr &state, Backend backend, const Config &config, const Root &root_paths); void set_depth_bias(State &state, Context *ctx, bool is_front, int factor, int units); void set_depth_func(State &state, Context *ctx, bool is_front, SceGxmDepthFunc depth_func); diff --git a/vita3k/renderer/include/renderer/gl/overlay_renderer.h b/vita3k/renderer/include/renderer/gl/overlay_renderer.h index 1445c5e8b..134524d0a 100644 --- a/vita3k/renderer/include/renderer/gl/overlay_renderer.h +++ b/vita3k/renderer/include/renderer/gl/overlay_renderer.h @@ -26,10 +26,6 @@ #include #include -namespace renderer { -struct WindowCallbacks; -} - namespace renderer::gl { class OverlayRenderer { @@ -37,8 +33,7 @@ public: OverlayRenderer() = default; ~OverlayRenderer(); - bool init(const fs::path &static_assets, const fs::path &pref_path, - const renderer::WindowCallbacks &callbacks, int sys_lang); + bool init(const fs::path &static_assets, const fs::path &pref_path, int sys_lang); void destroy(); void render(const overlay::display_manager &manager, diff --git a/vita3k/renderer/include/renderer/gl/state.h b/vita3k/renderer/include/renderer/gl/state.h index ef9f02147..73550e3c9 100644 --- a/vita3k/renderer/include/renderer/gl/state.h +++ b/vita3k/renderer/include/renderer/gl/state.h @@ -43,8 +43,6 @@ struct GLState : public renderer::State { bool context_is_current = false; - renderer::WindowSize client_size() const; - bool init() override; void cleanup() override; void late_init(const Config &cfg, const std::string_view game_id, MemState &mem) override; diff --git a/vita3k/renderer/include/renderer/state.h b/vita3k/renderer/include/renderer/state.h index 30ae75a5c..d09d9dd6e 100644 --- a/vita3k/renderer/include/renderer/state.h +++ b/vita3k/renderer/include/renderer/state.h @@ -19,6 +19,7 @@ #include #include +#include #include #include @@ -52,38 +53,6 @@ enum struct MappingMethod : int { namespace renderer { -enum struct DisplayProtocol { - Unknown, - Win32, - MacOS, - X11, - Xcb, - Wayland, - Android -}; - -struct WindowSize { - int width = 0; - int height = 0; -}; - -struct WindowCallbacks { - std::function get_proc_address; - std::function default_fbo; - std::function swap; - std::function set_current; - std::function done_current; - std::function set_vsync; - std::function get_size; - std::function()> get_font_dirs; - std::function has_surface; - std::function get_native_handle; - void *native_handle = nullptr; - void *native_display = nullptr; - void *native_connection = nullptr; - DisplayProtocol display_protocol = DisplayProtocol::Unknown; -}; - struct PerformanceOverlayState { bool enabled = false; int position = 0; @@ -118,7 +87,7 @@ struct State { fs::path shaders_path; fs::path shaders_log_path; - WindowCallbacks window_callbacks; + FrameHost *frame = nullptr; Backend current_backend; FeatureState features; diff --git a/vita3k/renderer/include/renderer/vulkan/state.h b/vita3k/renderer/include/renderer/vulkan/state.h index d3a98f6c5..d5fe8e84a 100644 --- a/vita3k/renderer/include/renderer/vulkan/state.h +++ b/vita3k/renderer/include/renderer/vulkan/state.h @@ -32,6 +32,13 @@ struct Config; namespace renderer::vulkan { +enum class LinuxSurfaceType { + Unknown, + Wayland, + Xlib, + Xcb +}; + struct Viewport { uint32_t offset_x; uint32_t offset_y; @@ -108,6 +115,7 @@ struct VKState : public renderer::State { // support for the VK_KHR_uniform_buffer_standard_layout extension, needed for memory mapping and texture viewport bool support_standard_layout = false; bool support_rasterized_order_access = false; + LinuxSurfaceType linux_surface_type = LinuxSurfaceType::Unknown; #ifdef __ANDROID__ bool support_android_buffer_import = false; diff --git a/vita3k/renderer/src/creation.cpp b/vita3k/renderer/src/creation.cpp index d2d8054e3..ed8ad97de 100644 --- a/vita3k/renderer/src/creation.cpp +++ b/vita3k/renderer/src/creation.cpp @@ -253,11 +253,11 @@ void destroy(SceGxmSyncObject *sync, State &state) { // nothing to do right now } -bool init(const WindowCallbacks &callbacks, std::unique_ptr &state, Backend backend, const Config &config, const Root &root_paths) { +bool init(FrameHost &frame, std::unique_ptr &state, Backend backend, const Config &config, const Root &root_paths) { switch (backend) { case Backend::OpenGL: state = std::make_unique(); - state->window_callbacks = callbacks; + state->frame = &frame; state->init_paths(root_paths); if (!gl::create(state, config)) return false; @@ -265,7 +265,7 @@ bool init(const WindowCallbacks &callbacks, std::unique_ptr &state, Backe case Backend::Vulkan: state = std::make_unique(config.current_config.gpu_idx); - state->window_callbacks = callbacks; + state->frame = &frame; state->init_paths(root_paths); if (!vulkan::create(state, config)) return false; diff --git a/vita3k/renderer/src/gl/overlay_renderer.cpp b/vita3k/renderer/src/gl/overlay_renderer.cpp index 5b2276dc4..33e580b4a 100644 --- a/vita3k/renderer/src/gl/overlay_renderer.cpp +++ b/vita3k/renderer/src/gl/overlay_renderer.cpp @@ -40,8 +40,7 @@ OverlayRenderer::~OverlayRenderer() { destroy(); } -bool OverlayRenderer::init(const fs::path &static_assets, const fs::path &pref_path, - const renderer::WindowCallbacks &callbacks, int sys_lang) { +bool OverlayRenderer::init(const fs::path &static_assets, const fs::path &pref_path, int sys_lang) { const auto overlay_shaders_path = static_assets / "shaders-builtin" / "overlay"; const auto vert_path = overlay_shaders_path / "overlay.vert"; const auto frag_path = overlay_shaders_path / "overlay.frag"; diff --git a/vita3k/renderer/src/gl/renderer.cpp b/vita3k/renderer/src/gl/renderer.cpp index 7b7c737b1..d2a4472c9 100644 --- a/vita3k/renderer/src/gl/renderer.cpp +++ b/vita3k/renderer/src/gl/renderer.cpp @@ -172,14 +172,14 @@ static void debug_output_callback(GLenum source, GLenum type, GLuint id, GLenum bool create(std::unique_ptr &state, const Config &config) { auto &gl_state = dynamic_cast(*state); - static std::function *s_proc_addr; - s_proc_addr = &gl_state.window_callbacks.get_proc_address; + static renderer::FrameHost *s_frame = nullptr; + s_frame = gl_state.frame; #ifdef __ANDROID__ gladLoadGLES2Loader([](const char *name) -> void * { #else gladLoadGLLoader([](const char *name) -> void * { #endif - return (*s_proc_addr)(name); + return s_frame->get_proc_address(name); }); // glad_set_post_callback(after_callback); @@ -251,7 +251,7 @@ bool GLState::init() { init_overlay_font_dirs(); - if (!overlay_renderer.init(static_assets, pref_path, window_callbacks, sys_lang)) { + if (!overlay_renderer.init(static_assets, pref_path, sys_lang)) { LOG_WARN("Failed to initialize overlay renderer, overlays will be disabled"); } @@ -264,10 +264,6 @@ void GLState::late_init(const Config &cfg, const std::string_view game_id, MemSt texture_cache.init(true, texture_folder(), game_id); } -renderer::WindowSize GLState::client_size() const { - return window_callbacks.get_size(); -} - bool create(std::unique_ptr &context) { R_PROFILE(__func__); @@ -647,24 +643,23 @@ void get_surface_data(GLState &renderer, GLContext &context, uint32_t *pixels, S void GLState::render_frame(DisplayState &display, const GxmState &gxm, MemState &mem) { should_display = false; - DisplayFrameInfo frame; + DisplayFrameInfo display_frame; { std::lock_guard guard(display.display_info_mutex); - frame = display.next_rendered_frame; + display_frame = display.next_rendered_frame; } const bool has_overlays = overlay_manager && overlay_manager->has_visible(); - if (!frame.base && !has_overlays) + if (!display_frame.base && !has_overlays) return; SceFVector2 vp_pos = { 0.0f, 0.0f }; SceFVector2 vp_size = { 0.0f, 0.0f }; - GLuint default_fbo = window_callbacks.default_fbo(); - - const renderer::WindowSize fb_size = client_size(); - const float fb_w = static_cast(fb_size.width); - const float fb_h = static_cast(fb_size.height); + auto *frame_host = this->frame; + const GLuint default_fbo = frame_host->default_fbo(); + const float fb_w = static_cast(frame_host->drawable_width()); + const float fb_h = static_cast(frame_host->drawable_height()); if (fb_h > 0.0f) { const float window_aspect = fb_w / fb_h; @@ -697,17 +692,17 @@ void GLState::render_frame(DisplayState &display, const GxmState &gxm, MemState display.viewport_w = vp_size.x; display.viewport_h = vp_size.y; - if (frame.base) { + if (display_frame.base) { // Check if the surface exists float uvs[4] = { 0.0f, 0.0f, 0.0f, 0.0f }; bool need_uv = true; - const std::size_t texture_data_size = static_cast(frame.pitch) * static_cast(frame.image_size.y) * sizeof(uint32_t); + const std::size_t texture_data_size = static_cast(display_frame.pitch) * static_cast(display_frame.image_size.y) * sizeof(uint32_t); SceFVector2 texture_size; uint64_t surface_handle = surface_cache.sourcing_color_surface_for_presentation( - frame.base, frame.image_size.x, frame.image_size.y, frame.pitch, uvs, this->res_multiplier, texture_size); + display_frame.base, display_frame.image_size.x, display_frame.image_size.y, display_frame.pitch, uvs, this->res_multiplier, texture_size); GLint last_texture = 0; glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture); @@ -718,16 +713,16 @@ void GLState::render_frame(DisplayState &display, const GxmState &gxm, MemState glBindTexture(GL_TEXTURE_2D, screen_renderer.get_resident_texture()); // Maybe a victim of surface locking (early from client GXM) when no frame yet renders! - const auto pixels = frame.base.cast().get(mem); + const auto pixels = display_frame.base.cast().get(mem); - glPixelStorei(GL_UNPACK_ROW_LENGTH, frame.pitch); - glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, frame.image_size.x, frame.image_size.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels); + glPixelStorei(GL_UNPACK_ROW_LENGTH, display_frame.pitch); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, display_frame.image_size.x, display_frame.image_size.y, 0, GL_RGBA, GL_UNSIGNED_BYTE, pixels); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); - texture_size.x = static_cast(frame.image_size.x); - texture_size.y = static_cast(frame.image_size.y); + texture_size.x = static_cast(display_frame.image_size.x); + texture_size.y = static_cast(display_frame.image_size.y); surface_handle = screen_renderer.get_resident_texture(); } else { @@ -758,23 +753,20 @@ void GLState::render_frame(DisplayState &display, const GxmState &gxm, MemState void GLState::swap_window() { const int pending_vsync = this->pending_vsync.exchange(-1, std::memory_order_relaxed); - if (pending_vsync >= 0) { - if (!window_callbacks.set_vsync(pending_vsync != 0)) - LOG_WARN("Failed to update OpenGL swap interval"); - } - window_callbacks.swap(); + if (pending_vsync >= 0 && !frame->set_vsync(pending_vsync != 0)) + LOG_WARN("Failed to update OpenGL swap interval"); + + frame->swap_buffers(); } bool GLState::set_current() { -#ifdef __ANDROID__ - if (context_is_current && !window_callbacks.has_surface()) + if (context_is_current && (frame->drawable_width() <= 0 || frame->drawable_height() <= 0)) done_current(); -#endif if (context_is_current) return true; - if (!window_callbacks.set_current()) { + if (!frame->make_current()) { LOG_ERROR("set_current failed"); context_is_current = false; return false; @@ -785,7 +777,7 @@ bool GLState::set_current() { } void GLState::done_current() { - window_callbacks.done_current(); + frame->done_current(); context_is_current = false; } @@ -866,7 +858,7 @@ void GLState::cleanup() { should_display = false; render_abort = false; - window_callbacks = {}; + frame = nullptr; } } // namespace renderer::gl diff --git a/vita3k/renderer/src/renderer.cpp b/vita3k/renderer/src/renderer.cpp index 0705a3371..2c281419d 100644 --- a/vita3k/renderer/src/renderer.cpp +++ b/vita3k/renderer/src/renderer.cpp @@ -114,8 +114,8 @@ void State::update_overlays() { void State::init_overlay_font_dirs() { overlay::fontmgr::set_system_lang(sys_lang); - if (window_callbacks.get_font_dirs) { - overlay::fontmgr::set_system_font_dirs(window_callbacks.get_font_dirs()); + if (frame) { + overlay::fontmgr::set_system_font_dirs(frame->font_dirs()); } if (!pref_path.empty()) { diff --git a/vita3k/renderer/src/vulkan/renderer.cpp b/vita3k/renderer/src/vulkan/renderer.cpp index 76febd57f..4f2fe5bcf 100644 --- a/vita3k/renderer/src/vulkan/renderer.cpp +++ b/vita3k/renderer/src/vulkan/renderer.cpp @@ -221,29 +221,31 @@ static bool has_instance_extension(const std::vector &a }) != available_extensions.end(); } -static bool select_linux_surface_extension(renderer::WindowCallbacks &window_callbacks, std::vector &instance_extensions) { +static bool select_linux_surface_extension(VKState &vk_state, const renderer::DisplayHandle &display_handle, std::vector &instance_extensions) { const auto available_extensions = vk::enumerateInstanceExtensionProperties(); - switch (window_callbacks.display_protocol) { #if defined(HAVE_WAYLAND) - case renderer::DisplayProtocol::Wayland: + if (std::holds_alternative(display_handle)) { if (!has_instance_extension(available_extensions, vk::KHRWaylandSurfaceExtensionName)) { LOG_ERROR("Could not find Vulkan instance extension {}", vk::KHRWaylandSurfaceExtensionName); return false; } + vk_state.linux_surface_type = LinuxSurfaceType::Wayland; instance_extensions.push_back(vk::KHRWaylandSurfaceExtensionName); return true; + } #endif #if defined(HAVE_X11) - case renderer::DisplayProtocol::X11: + if (const auto *x11 = std::get_if(&display_handle)) { if (has_instance_extension(available_extensions, vk::KHRXlibSurfaceExtensionName)) { + vk_state.linux_surface_type = LinuxSurfaceType::Xlib; instance_extensions.push_back(vk::KHRXlibSurfaceExtensionName); return true; } #if defined(VK_USE_PLATFORM_XCB_KHR) - if (window_callbacks.native_connection && has_instance_extension(available_extensions, "VK_KHR_xcb_surface")) { - window_callbacks.display_protocol = renderer::DisplayProtocol::Xcb; + if (x11->connection && has_instance_extension(available_extensions, "VK_KHR_xcb_surface")) { + vk_state.linux_surface_type = LinuxSurfaceType::Xcb; instance_extensions.push_back("VK_KHR_xcb_surface"); LOG_INFO("Falling back to XCB Vulkan surface extension"); return true; @@ -251,29 +253,11 @@ static bool select_linux_surface_extension(renderer::WindowCallbacks &window_cal #endif LOG_ERROR("Could not find a supported Vulkan surface extension for X11 (tried xlib then xcb)"); return false; - - case renderer::DisplayProtocol::Xcb: -#if !defined(VK_USE_PLATFORM_XCB_KHR) - LOG_ERROR("XCB surface requested but Vulkan XCB platform support is not available"); - return false; -#else - if (!window_callbacks.native_connection) { - LOG_ERROR("XCB surface requested without an XCB connection"); - return false; - } - if (!has_instance_extension(available_extensions, "VK_KHR_xcb_surface")) { - LOG_ERROR("Could not find Vulkan instance extension {}", "VK_KHR_xcb_surface"); - return false; - } - instance_extensions.push_back("VK_KHR_xcb_surface"); - return true; -#endif -#endif - - default: - LOG_ERROR("Unsupported display protocol on Linux"); - return false; } +#endif + + LOG_ERROR("Unsupported display handle on Linux"); + return false; } #endif @@ -420,7 +404,8 @@ bool VKState::create(std::unique_ptr &state, const Config &conf #elif defined(__ANDROID__) instance_extensions.push_back(vk::KHRAndroidSurfaceExtensionName); #else - if (!select_linux_surface_extension(window_callbacks, instance_extensions)) + auto *frame_host = this->renderer::State::frame; + if (!select_linux_surface_extension(*this, frame_host->handle(), instance_extensions)) return false; #endif @@ -433,10 +418,15 @@ bool VKState::create(std::unique_ptr &state, const Config &conf vk::EXTLayerSettingsExtensionName, #endif }; + bool has_layer_settings_extension = false; for (const vk::ExtensionProperties &prop : vk::enumerateInstanceExtensionProperties()) { auto ite = optional_instance_extensions.find(prop.extensionName); if (ite != optional_instance_extensions.end()) { instance_extensions.push_back(ite->c_str()); +#ifdef __APPLE__ + if (*ite == vk::EXTLayerSettingsExtensionName) + has_layer_settings_extension = true; +#endif } } @@ -495,12 +485,13 @@ bool VKState::create(std::unique_ptr &state, const Config &conf .settingCount = static_cast(std::size(layer_settings)), .pSettings = layer_settings, }; + const void *instance_create_pnext = has_layer_settings_extension ? &layer_settings_info : nullptr; #endif vk::InstanceCreateInfo instance_info{ #ifdef __APPLE__ .flags = vk::InstanceCreateFlagBits::eEnumeratePortabilityKHR, - .pNext = &layer_settings_info, + .pNext = instance_create_pnext, #endif .pApplicationInfo = &app_info, }; diff --git a/vita3k/renderer/src/vulkan/screen_renderer.cpp b/vita3k/renderer/src/vulkan/screen_renderer.cpp index e58754c5a..5cb0277e9 100644 --- a/vita3k/renderer/src/vulkan/screen_renderer.cpp +++ b/vita3k/renderer/src/vulkan/screen_renderer.cpp @@ -27,8 +27,8 @@ #ifdef _WIN32 #include #elif defined(__APPLE__) +#include #include -extern "C" void *get_metal_layer_from_view(void *nsview); #elif defined(__linux__) && !defined(__ANDROID__) #if defined(HAVE_X11) #include @@ -55,6 +55,15 @@ Java_org_vita3k_emulator_EmuSurface_setSurfaceStatus(JNIEnv *, jobject, jboolean namespace renderer::vulkan { +namespace { + +bool window_has_drawable_size(const VKState &state) { + const auto *frame_host = static_cast(state).frame; + return frame_host->drawable_width() > 0 && frame_host->drawable_height() > 0; +} + +} // namespace + #ifdef __ANDROID__ bool has_android_surface() { return has_surface.load(std::memory_order_acquire); @@ -66,100 +75,90 @@ ScreenRenderer::ScreenRenderer(VKState &state) } bool ScreenRenderer::create() { -#ifdef _WIN32 if (this->surface) { +#ifdef __ANDROID__ + SDL_Vulkan_DestroySurface(state.instance, this->surface, nullptr); +#else state.instance.destroySurfaceKHR(this->surface); +#endif this->surface = nullptr; } - void *native_handle = state.window_callbacks.get_native_handle(); - if (!native_handle) { - LOG_ERROR("Failed to get native window handle from Qt window"); - return false; - } - vk::Win32SurfaceCreateInfoKHR create_info{}; - create_info.hinstance = GetModuleHandle(nullptr); - create_info.hwnd = reinterpret_cast(native_handle); - this->surface = state.instance.createWin32SurfaceKHR(create_info); -#elif defined(__APPLE__) - if (this->surface) { - state.instance.destroySurfaceKHR(this->surface); - this->surface = nullptr; - } - { - void *native_handle = state.window_callbacks.get_native_handle(); - if (!native_handle) { - LOG_ERROR("Failed to get NSView from Qt window"); - return false; - } - void *metal_layer = get_metal_layer_from_view(native_handle); + auto *frame_host = static_cast(state).frame; + + const renderer::DisplayHandle display_handle = frame_host->handle(); + bool surface_created = false; + + if (const auto *handle = std::get_if(&display_handle)) { +#ifdef _WIN32 + vk::Win32SurfaceCreateInfoKHR create_info{}; + create_info.hinstance = GetModuleHandle(nullptr); + create_info.hwnd = reinterpret_cast(handle->hwnd); + this->surface = state.instance.createWin32SurfaceKHR(create_info); + surface_created = true; +#endif + } else if (const auto *handle = std::get_if(&display_handle)) { +#ifdef __APPLE__ + void *metal_layer = get_metal_layer_from_view(handle->view); if (!metal_layer) { LOG_ERROR("Failed to get CAMetalLayer from NSView"); return false; } + vk::MetalSurfaceCreateInfoEXT create_info{}; create_info.pLayer = static_cast(metal_layer); this->surface = state.instance.createMetalSurfaceEXT(create_info); - } -#elif defined(__ANDROID__) - { - auto *window = static_cast(state.window_callbacks.get_native_handle()); - if (!window) { + surface_created = true; +#endif + } else if (const auto *handle = std::get_if(&display_handle)) { +#ifdef __ANDROID__ + if (!handle->window) { LOG_WARN("Android SDL window is not ready yet; deferring Vulkan surface recreation"); return false; } - if (this->surface) { - SDL_Vulkan_DestroySurface(state.instance, this->surface, nullptr); - this->surface = nullptr; - } - VkSurfaceKHR surface_handle = VK_NULL_HANDLE; - if (!SDL_Vulkan_CreateSurface(window, state.instance, nullptr, &surface_handle)) { + if (!SDL_Vulkan_CreateSurface(handle->window, state.instance, nullptr, &surface_handle)) { LOG_WARN("SDL_Vulkan_CreateSurface failed: {}", SDL_GetError()); return false; } this->surface = surface_handle; - } -#else - if (this->surface) { - state.instance.destroySurfaceKHR(this->surface); - this->surface = nullptr; - } - switch (state.window_callbacks.display_protocol) { -#if defined(HAVE_X11) - case renderer::DisplayProtocol::X11: { - vk::XlibSurfaceCreateInfoKHR create_info{}; - create_info.dpy = static_cast(state.window_callbacks.native_display); - create_info.window = static_cast(reinterpret_cast(state.window_callbacks.get_native_handle())); - this->surface = state.instance.createXlibSurfaceKHR(create_info); - break; - } -#if defined(VK_USE_PLATFORM_XCB_KHR) - case renderer::DisplayProtocol::Xcb: { - vk::XcbSurfaceCreateInfoKHR create_info{}; - create_info.connection = static_cast(state.window_callbacks.native_connection); - create_info.window = static_cast(reinterpret_cast(state.window_callbacks.get_native_handle())); - this->surface = state.instance.createXcbSurfaceKHR(create_info); - break; - } -#endif + surface_created = true; #endif + } else if (const auto *handle = std::get_if(&display_handle)) { #if defined(HAVE_WAYLAND) - case renderer::DisplayProtocol::Wayland: { vk::WaylandSurfaceCreateInfoKHR create_info{}; - create_info.display = static_cast(state.window_callbacks.native_display); - create_info.surface = static_cast(state.window_callbacks.get_native_handle()); + create_info.display = static_cast(handle->display); + create_info.surface = static_cast(handle->surface); this->surface = state.instance.createWaylandSurfaceKHR(create_info); - break; - } + surface_created = true; #endif - default: - LOG_ERROR("Unsupported display protocol on Linux"); + } else if (const auto *handle = std::get_if(&display_handle)) { +#if defined(HAVE_X11) + if (state.linux_surface_type == LinuxSurfaceType::Xlib && handle->display) { + vk::XlibSurfaceCreateInfoKHR create_info{}; + create_info.dpy = static_cast(handle->display); + create_info.window = static_cast(handle->window); + this->surface = state.instance.createXlibSurfaceKHR(create_info); + surface_created = true; + } +#if defined(VK_USE_PLATFORM_XCB_KHR) + else if (state.linux_surface_type == LinuxSurfaceType::Xcb && handle->connection) { + vk::XcbSurfaceCreateInfoKHR create_info{}; + create_info.connection = static_cast(handle->connection); + create_info.window = static_cast(handle->window); + this->surface = state.instance.createXcbSurfaceKHR(create_info); + surface_created = true; + } +#endif +#endif + } + + if (!surface_created) { + LOG_ERROR("Failed to create Vulkan surface from frame host"); return false; } -#endif if (!this->surface) { LOG_ERROR("Failed to create Vulkan surface from native window handle"); @@ -248,9 +247,9 @@ void ScreenRenderer::create_swapchain() { if (surface_capabilities.currentExtent.width != std::numeric_limits::max()) { extent = surface_capabilities.currentExtent; } else { - const renderer::WindowSize size = state.window_callbacks.get_size(); - extent.width = std::clamp(static_cast(size.width), surface_capabilities.minImageExtent.width, surface_capabilities.maxImageExtent.width); - extent.height = std::clamp(static_cast(size.height), surface_capabilities.minImageExtent.height, surface_capabilities.maxImageExtent.height); + auto *frame_host = static_cast(state).frame; + extent.width = std::clamp(static_cast(frame_host->drawable_width()), surface_capabilities.minImageExtent.width, surface_capabilities.maxImageExtent.width); + extent.height = std::clamp(static_cast(frame_host->drawable_height()), surface_capabilities.minImageExtent.height, surface_capabilities.maxImageExtent.height); } if (extent.width == 0 || extent.height == 0) @@ -414,14 +413,12 @@ void ScreenRenderer::cleanup() { static constexpr uint64_t next_image_timeout = std::numeric_limits::max(); bool ScreenRenderer::acquire_swapchain_image() { -#ifdef __ANDROID__ - if (!state.window_callbacks.has_surface()) { + if (!window_has_drawable_size(state)) { need_rebuild = true; current_cmd_buffer = nullptr; swapchain_image_idx = 0xDEADBEAF; return false; } -#endif vk::Result acquire_result = vk::Result::eErrorOutOfDateKHR; @@ -441,6 +438,11 @@ bool ScreenRenderer::acquire_swapchain_image() { const bool has_acquired_image = acquire_result == vk::Result::eSuccess || acquire_result == vk::Result::eSuboptimalKHR; if (!has_acquired_image) { + if (acquire_result == vk::Result::eTimeout || acquire_result == vk::Result::eNotReady) { + current_cmd_buffer = nullptr; + swapchain_image_idx = 0xDEADBEAF; + return false; + } if (acquire_result == vk::Result::eErrorOutOfDateKHR || acquire_result == vk::Result::eErrorSurfaceLostKHR) { need_rebuild = true; @@ -461,7 +463,6 @@ bool ScreenRenderer::acquire_swapchain_image() { auto result = state.device.waitForFences(fences[swapchain_image_idx], VK_TRUE, next_image_timeout); if (result != vk::Result::eSuccess) { LOG_ERROR("Could not wait for fences."); - assert(false); return false; } state.device.resetFences(fences[swapchain_image_idx]); @@ -567,7 +568,6 @@ void ScreenRenderer::swap_window() { need_surface_recreate = result == vk::Result::eErrorSurfaceLostKHR; } else if (result != vk::Result::eSuccess) { LOG_ERROR("Could not present KHR."); - assert(false); swapchain_image_idx = ~0; current_cmd_buffer = nullptr; return; @@ -694,6 +694,9 @@ void ScreenRenderer::create_surface_image() { } bool ScreenRenderer::ensure_swapchain() { + if (!window_has_drawable_size(state)) + return false; + if (!need_rebuild && !need_surface_recreate && swapchain && surface_matches_window_size()) return true; @@ -708,10 +711,9 @@ bool ScreenRenderer::ensure_swapchain() { } bool ScreenRenderer::rebuild_swapchain_if_visible() { -#ifdef __ANDROID__ - if (!state.window_callbacks.has_surface()) + if (!window_has_drawable_size(state)) return false; -#endif + state.device.waitIdle(); destroy_swapchain(); @@ -728,11 +730,12 @@ bool ScreenRenderer::rebuild_swapchain_if_visible() { } bool ScreenRenderer::surface_matches_window_size() { - const renderer::WindowSize size = state.window_callbacks.get_size(); - if (size.width == 0 || size.height == 0) + auto *frame_host = static_cast(state).frame; + if (frame_host->drawable_width() == 0 || frame_host->drawable_height() == 0) return true; - return extent.width == static_cast(size.width) && extent.height == static_cast(size.height); + return extent.width == static_cast(frame_host->drawable_width()) + && extent.height == static_cast(frame_host->drawable_height()); } } // namespace renderer::vulkan diff --git a/vita3k/vkutil/CMakeLists.txt b/vita3k/vkutil/CMakeLists.txt index f30dfceb3..a53888e12 100644 --- a/vita3k/vkutil/CMakeLists.txt +++ b/vita3k/vkutil/CMakeLists.txt @@ -1,6 +1,7 @@ add_library( vkutil STATIC + include/vkutil/native_surface.h include/vkutil/objects.h include/vkutil/vkutil.h diff --git a/vita3k/vkutil/include/vkutil/native_surface.h b/vita3k/vkutil/include/vkutil/native_surface.h new file mode 100644 index 000000000..d931e7f61 --- /dev/null +++ b/vita3k/vkutil/include/vkutil/native_surface.h @@ -0,0 +1,22 @@ +// Vita3K emulator project +// Copyright (C) 2026 Vita3K team +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License along +// with this program; if not, write to the Free Software Foundation, Inc., +// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +#pragma once + +#ifdef __APPLE__ +extern "C" void *get_metal_layer_from_view(void *nsview); +#endif