mirror of
https://github.com/Vita3K/Vita3K.git
synced 2026-07-11 01:34:23 +02:00
renderer: refactor frame host and fix macos vulkan issues
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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,
|
||||
};
|
||||
|
||||
@@ -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,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
|
||||
Reference in New Issue
Block a user