renderer: refactor frame host and fix macos vulkan issues

This commit is contained in:
KorewaWatchful
2026-05-23 00:12:32 -04:00
committed by Gamid
parent f3a0d0abc4
commit 905ac6a5a6
21 changed files with 520 additions and 437 deletions
+82 -61
View File
@@ -147,15 +147,87 @@ 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<std::string> font_dirs() const override {
return { "/system/fonts/" };
}
void *get_proc_address(const char *name) const override {
return reinterpret_cast<void *>(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
extern "C" {
@@ -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<AppLaunchRequest> 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<std::string> {
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<int>(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<void *>(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);
+3 -9
View File
@@ -23,6 +23,7 @@
#include <atomic>
#include <functional>
#include <mutex>
#include <optional>
namespace app {
@@ -47,13 +48,6 @@ enum class AppSessionStopReason {
LaunchFailure
};
struct AppSessionPlatform {
renderer::WindowCallbacks window_callbacks;
std::function<void()> before_render_thread_start;
std::function<void()> after_render_thread_start;
std::function<void()> 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<AppSessionPhase> current_phase{ AppSessionPhase::Idle };
AppSessionPlatform platform;
std::optional<std::reference_wrapper<renderer::FrameHost>> frame_host;
AppLaunchRequest active_launch_request;
std::atomic<uint32_t> active_pause_reasons{ 0 };
bool renderer_initialized = false;
+12 -14
View File
@@ -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<std::mutex> 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<std::reference_wrapper<renderer::FrameHost>> 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<bool>(emuenv.renderer);
runtime_was_initialized = runtime_initialized;
app_started = phase == AppSessionPhase::Running;
set_phase(AppSessionPhase::Stopping);
}
const bool has_render_context = static_cast<bool>(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;
+18 -10
View File
@@ -19,6 +19,7 @@
#include <app/functions.h>
#include <emuenv/state.h>
#include <renderer/frame_host.h>
#include <renderer/types.h>
#include <QWindow>
@@ -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<std::string> 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();
+116 -32
View File
@@ -40,11 +40,19 @@
#include <QMouseEvent>
#include <QOpenGLContext>
#include <QScreen>
#include <QStandardPaths>
#include <QThread>
#include <QTimer>
#include <QtGui/qopenglcontext_platform.h>
#include <cstdint>
#if defined(HAVE_X11) || defined(HAVE_WAYLAND)
#include <QGuiApplication>
#include <qpa/qplatformnativeinterface.h>
#endif
#if QT_CONFIG(xcb_glx_plugin)
#include <GL/glx.h>
#endif
@@ -157,7 +165,7 @@ GameWindow::GameWindow(EmuEnvState &emuenv, std::shared_ptr<GuiSettings> 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<void *>(winId()) };
#elif defined(__APPLE__)
return renderer::MacOSDisplayHandle{ reinterpret_cast<void *>(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<GameWindow *>(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<std::uintptr_t>(winId()),
.connection = connection,
};
#else
return {};
#endif
#else
return {};
#endif
}
int GameWindow::drawable_width() const {
#ifdef _WIN32
RECT rect;
if (GetClientRect(reinterpret_cast<HWND>(winId()), &rect))
return rect.right - rect.left;
#endif
return static_cast<int>(width() * devicePixelRatio());
}
int GameWindow::drawable_height() const {
#ifdef _WIN32
RECT rect;
if (GetClientRect(reinterpret_cast<HWND>(winId()), &rect))
return rect.bottom - rect.top;
#endif
return static_cast<int>(height() * devicePixelRatio());
}
std::vector<std::string> GameWindow::font_dirs() const {
const QStringList locations = QStandardPaths::standardLocations(QStandardPaths::FontsLocation);
std::vector<std::string> 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<void *>(reinterpret_cast<uintptr_t>(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<QThread *>();
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<float>(client_width_px()) / width() : 1.0f;
const float scale_y = (height() > 0) ? static_cast<float>(client_height_px()) / height() : 1.0f;
const float scale_x = (width() > 0) ? static_cast<float>(drawable_width()) / width() : 1.0f;
const float scale_y = (height() > 0) ? static_cast<float>(drawable_height()) / height() : 1.0f;
touch.mouse_x = static_cast<float>(e->position().x()) * scale_x;
touch.mouse_y = static_cast<float>(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<HWND>(winId()), &rect))
return rect.right - rect.left;
#endif
return static_cast<int>(width() * devicePixelRatio());
}
int GameWindow::client_height_px() const {
#ifdef _WIN32
RECT rect;
if (GetClientRect(reinterpret_cast<HWND>(winId()), &rect))
return rect.bottom - rect.top;
#endif
return static_cast<int>(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,
+11 -115
View File
@@ -936,9 +936,8 @@ std::optional<AppLaunchRequest> 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<AppLaunchRequest> 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<AppLaunchRequest> 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<void *>(win->winId());
};
callbacks.native_handle = reinterpret_cast<void *>(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<void *>(win->winId());
};
callbacks.native_handle = reinterpret_cast<void *>(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<QWindow *>(static_cast<const QWindow *>(m_game_window)));
if (wl_dpy && wl_surf) {
callbacks.get_native_handle = [native, win = m_game_window]() -> void * {
return native->nativeResourceForWindow("surface",
const_cast<QWindow *>(static_cast<const QWindow *>(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<void *>(win->winId());
};
callbacks.native_handle = reinterpret_cast<void *>(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<std::string> {
const QStringList locations = QStandardPaths::standardLocations(QStandardPaths::FontsLocation);
std::vector<std::string> 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<AppLaunchRequest> 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<void *>(
reinterpret_cast<uintptr_t>(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<AppLaunchRequest> 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<AppLaunchRequest> 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);
+5
View File
@@ -67,6 +67,11 @@
#include <optional>
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"));
@@ -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 <cstdint>
#include <string>
#include <variant>
#include <vector>
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<std::monostate,
Win32DisplayHandle,
MacOSDisplayHandle,
X11DisplayHandle,
WaylandDisplayHandle,
AndroidDisplayHandle>;
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<std::string> 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
+2 -2
View File
@@ -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<FragmentProgram> &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> &state, Backend backend, const Config &config, const Root &root_paths);
bool init(FrameHost &frame, std::unique_ptr<State> &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);
@@ -26,10 +26,6 @@
#include <unordered_map>
#include <vector>
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,
@@ -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;
+2 -33
View File
@@ -19,6 +19,7 @@
#include <features/state.h>
#include <renderer/commands.h>
#include <renderer/frame_host.h>
#include <renderer/types.h>
#include <threads/queue.h>
@@ -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<void *(const char *)> get_proc_address;
std::function<unsigned int()> default_fbo;
std::function<void()> swap;
std::function<bool()> set_current;
std::function<void()> done_current;
std::function<bool(bool)> set_vsync;
std::function<WindowSize()> get_size;
std::function<std::vector<std::string>()> get_font_dirs;
std::function<bool()> has_surface;
std::function<void *()> 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;
@@ -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;
+3 -3
View File
@@ -253,11 +253,11 @@ void destroy(SceGxmSyncObject *sync, State &state) {
// nothing to do right now
}
bool init(const WindowCallbacks &callbacks, std::unique_ptr<State> &state, Backend backend, const Config &config, const Root &root_paths) {
bool init(FrameHost &frame, std::unique_ptr<State> &state, Backend backend, const Config &config, const Root &root_paths) {
switch (backend) {
case Backend::OpenGL:
state = std::make_unique<gl::GLState>();
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> &state, Backe
case Backend::Vulkan:
state = std::make_unique<vulkan::VKState>(config.current_config.gpu_idx);
state->window_callbacks = callbacks;
state->frame = &frame;
state->init_paths(root_paths);
if (!vulkan::create(state, config))
return false;
+1 -2
View File
@@ -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";
+26 -34
View File
@@ -172,14 +172,14 @@ static void debug_output_callback(GLenum source, GLenum type, GLuint id, GLenum
bool create(std::unique_ptr<State> &state, const Config &config) {
auto &gl_state = dynamic_cast<GLState &>(*state);
static std::function<void *(const char *)> *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> &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<std::mutex> 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<float>(fb_size.width);
const float fb_h = static_cast<float>(fb_size.height);
auto *frame_host = this->frame;
const GLuint default_fbo = frame_host->default_fbo();
const float fb_w = static_cast<float>(frame_host->drawable_width());
const float fb_h = static_cast<float>(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<size_t>(frame.pitch) * static_cast<size_t>(frame.image_size.y) * sizeof(uint32_t);
const std::size_t texture_data_size = static_cast<size_t>(display_frame.pitch) * static_cast<size_t>(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<void>().get(mem);
const auto pixels = display_frame.base.cast<void>().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<float>(frame.image_size.x);
texture_size.y = static_cast<float>(frame.image_size.y);
texture_size.x = static_cast<float>(display_frame.image_size.x);
texture_size.y = static_cast<float>(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))
if (pending_vsync >= 0 && !frame->set_vsync(pending_vsync != 0))
LOG_WARN("Failed to update OpenGL swap interval");
}
window_callbacks.swap();
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
+2 -2
View File
@@ -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()) {
+18 -27
View File
@@ -221,29 +221,31 @@ static bool has_instance_extension(const std::vector<vk::ExtensionProperties> &a
}) != available_extensions.end();
}
static bool select_linux_surface_extension(renderer::WindowCallbacks &window_callbacks, std::vector<const char *> &instance_extensions) {
static bool select_linux_surface_extension(VKState &vk_state, const renderer::DisplayHandle &display_handle, std::vector<const char *> &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<renderer::WaylandDisplayHandle>(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<renderer::X11DisplayHandle>(&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,30 +253,12 @@ 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");
LOG_ERROR("Unsupported display handle on Linux");
return false;
}
}
#endif
static bool device_is_compatible(const vk::PhysicalDevice &device) {
@@ -420,7 +404,8 @@ bool VKState::create(std::unique_ptr<renderer::State> &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<renderer::State> &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<renderer::State> &state, const Config &conf
.settingCount = static_cast<uint32_t>(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,
};
+77 -74
View File
@@ -27,8 +27,8 @@
#ifdef _WIN32
#include <vulkan/vulkan_win32.h>
#elif defined(__APPLE__)
#include <vkutil/native_surface.h>
#include <vulkan/vulkan_metal.h>
extern "C" void *get_metal_layer_from_view(void *nsview);
#elif defined(__linux__) && !defined(__ANDROID__)
#if defined(HAVE_X11)
#include <vulkan/vulkan_xlib.h>
@@ -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<const renderer::State &>(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<HWND>(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<renderer::State &>(state).frame;
const renderer::DisplayHandle display_handle = frame_host->handle();
bool surface_created = false;
if (const auto *handle = std::get_if<renderer::Win32DisplayHandle>(&display_handle)) {
#ifdef _WIN32
vk::Win32SurfaceCreateInfoKHR create_info{};
create_info.hinstance = GetModuleHandle(nullptr);
create_info.hwnd = reinterpret_cast<HWND>(handle->hwnd);
this->surface = state.instance.createWin32SurfaceKHR(create_info);
surface_created = true;
#endif
} else if (const auto *handle = std::get_if<renderer::MacOSDisplayHandle>(&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<const CAMetalLayer *>(metal_layer);
this->surface = state.instance.createMetalSurfaceEXT(create_info);
}
#elif defined(__ANDROID__)
{
auto *window = static_cast<SDL_Window *>(state.window_callbacks.get_native_handle());
if (!window) {
surface_created = true;
#endif
} else if (const auto *handle = std::get_if<renderer::AndroidDisplayHandle>(&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) {
surface_created = true;
#endif
} else if (const auto *handle = std::get_if<renderer::WaylandDisplayHandle>(&display_handle)) {
#if defined(HAVE_WAYLAND)
vk::WaylandSurfaceCreateInfoKHR create_info{};
create_info.display = static_cast<struct wl_display *>(handle->display);
create_info.surface = static_cast<struct wl_surface *>(handle->surface);
this->surface = state.instance.createWaylandSurfaceKHR(create_info);
surface_created = true;
#endif
} else if (const auto *handle = std::get_if<renderer::X11DisplayHandle>(&display_handle)) {
#if defined(HAVE_X11)
case renderer::DisplayProtocol::X11: {
if (state.linux_surface_type == LinuxSurfaceType::Xlib && handle->display) {
vk::XlibSurfaceCreateInfoKHR create_info{};
create_info.dpy = static_cast<Display *>(state.window_callbacks.native_display);
create_info.window = static_cast<Window>(reinterpret_cast<uintptr_t>(state.window_callbacks.get_native_handle()));
create_info.dpy = static_cast<Display *>(handle->display);
create_info.window = static_cast<Window>(handle->window);
this->surface = state.instance.createXlibSurfaceKHR(create_info);
break;
surface_created = true;
}
#if defined(VK_USE_PLATFORM_XCB_KHR)
case renderer::DisplayProtocol::Xcb: {
else if (state.linux_surface_type == LinuxSurfaceType::Xcb && handle->connection) {
vk::XcbSurfaceCreateInfoKHR create_info{};
create_info.connection = static_cast<xcb_connection_t *>(state.window_callbacks.native_connection);
create_info.window = static_cast<xcb_window_t>(reinterpret_cast<uintptr_t>(state.window_callbacks.get_native_handle()));
create_info.connection = static_cast<xcb_connection_t *>(handle->connection);
create_info.window = static_cast<xcb_window_t>(handle->window);
this->surface = state.instance.createXcbSurfaceKHR(create_info);
break;
surface_created = true;
}
#endif
#endif
#if defined(HAVE_WAYLAND)
case renderer::DisplayProtocol::Wayland: {
vk::WaylandSurfaceCreateInfoKHR create_info{};
create_info.display = static_cast<struct wl_display *>(state.window_callbacks.native_display);
create_info.surface = static_cast<struct wl_surface *>(state.window_callbacks.get_native_handle());
this->surface = state.instance.createWaylandSurfaceKHR(create_info);
break;
}
#endif
default:
LOG_ERROR("Unsupported display protocol on Linux");
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<uint32_t>::max()) {
extent = surface_capabilities.currentExtent;
} else {
const renderer::WindowSize size = state.window_callbacks.get_size();
extent.width = std::clamp<uint32_t>(static_cast<uint32_t>(size.width), surface_capabilities.minImageExtent.width, surface_capabilities.maxImageExtent.width);
extent.height = std::clamp<uint32_t>(static_cast<uint32_t>(size.height), surface_capabilities.minImageExtent.height, surface_capabilities.maxImageExtent.height);
auto *frame_host = static_cast<renderer::State &>(state).frame;
extent.width = std::clamp<uint32_t>(static_cast<uint32_t>(frame_host->drawable_width()), surface_capabilities.minImageExtent.width, surface_capabilities.maxImageExtent.width);
extent.height = std::clamp<uint32_t>(static_cast<uint32_t>(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<uint64_t>::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<renderer::State &>(state).frame;
if (frame_host->drawable_width() == 0 || frame_host->drawable_height() == 0)
return true;
return extent.width == static_cast<uint32_t>(size.width) && extent.height == static_cast<uint32_t>(size.height);
return extent.width == static_cast<uint32_t>(frame_host->drawable_width())
&& extent.height == static_cast<uint32_t>(frame_host->drawable_height());
}
} // namespace renderer::vulkan
+1
View File
@@ -1,6 +1,7 @@
add_library(
vkutil
STATIC
include/vkutil/native_surface.h
include/vkutil/objects.h
include/vkutil/vkutil.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