diff --git a/CMake/FindWayland.cmake b/CMake/FindWayland.cmake new file mode 100644 index 0000000000..8b7e1863ca --- /dev/null +++ b/CMake/FindWayland.cmake @@ -0,0 +1,68 @@ +# Try to find Wayland on a Unix system +# +# This will define: +# +# Wayland_FOUND - True if Wayland is found +# +# The following imported targets: +# Wayland::Client - Imported Client +# Wayland::Server - Imported Server +# Wayland::Egl - Imported Egl +# Wayland::Cursor - Imported Cursor +# +# Copyright (c) 2013 Martin Gräßlin +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + +IF (NOT WIN32) + IF (WAYLAND_INCLUDE_DIR AND WAYLAND_LIBRARIES) + # In the cache already + SET(WAYLAND_FIND_QUIETLY TRUE) + ENDIF () + + FIND_PATH(WAYLAND_CLIENT_INCLUDE_DIR NAMES wayland-client.h) + FIND_PATH(WAYLAND_SERVER_INCLUDE_DIR NAMES wayland-server.h) + FIND_PATH(WAYLAND_EGL_INCLUDE_DIR NAMES wayland-egl.h) + FIND_PATH(WAYLAND_CURSOR_INCLUDE_DIR NAMES wayland-cursor.h) + + FIND_LIBRARY(WAYLAND_CLIENT_LIBRARIES NAMES wayland-client) + FIND_LIBRARY(WAYLAND_SERVER_LIBRARIES NAMES wayland-server) + FIND_LIBRARY(WAYLAND_EGL_LIBRARIES NAMES wayland-egl) + FIND_LIBRARY(WAYLAND_CURSOR_LIBRARIES NAMES wayland-cursor) + + include(FindPackageHandleStandardArgs) + + # FIND_PACKAGE_HANDLE_STANDARD_ARGS is just meant to find the main package and set package found. Not set variables or find individual libs + FIND_PACKAGE_HANDLE_STANDARD_ARGS(Wayland REQUIRED_VARS + WAYLAND_CLIENT_LIBRARIES WAYLAND_CLIENT_INCLUDE_DIR + WAYLAND_SERVER_LIBRARIES WAYLAND_SERVER_INCLUDE_DIR + WAYLAND_EGL_LIBRARIES WAYLAND_EGL_INCLUDE_DIR + WAYLAND_CURSOR_LIBRARIES WAYLAND_CURSOR_INCLUDE_DIR + ) + + if (WAYLAND_CLIENT_INCLUDE_DIR AND WAYLAND_CLIENT_LIBRARIES AND NOT TARGET Wayland::Client) + add_library(Wayland::Client UNKNOWN IMPORTED) + set_target_properties(Wayland::Client PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${WAYLAND_CLIENT_INCLUDE_DIR}" IMPORTED_LOCATION "${WAYLAND_CLIENT_LIBRARIES}") + endif() + if (WAYLAND_SERVER_INCLUDE_DIR AND WAYLAND_SERVER_LIBRARIES AND NOT TARGET Wayland::Server) + add_library(Wayland::Server UNKNOWN IMPORTED) + set_target_properties(Wayland::Server PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${WAYLAND_SERVER_INCLUDE_DIR}" IMPORTED_LOCATION "${WAYLAND_SERVER_LIBRARIES}") + endif() + if (WAYLAND_EGL_INCLUDE_DIR AND WAYLAND_EGL_LIBRARIES AND NOT TARGET Wayland::Egl) + add_library(Wayland::Egl UNKNOWN IMPORTED) + set_target_properties(Wayland::Egl PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${WAYLAND_EGL_INCLUDE_DIR}" IMPORTED_LOCATION "${WAYLAND_EGL_LIBRARIES}") + endif() + if (WAYLAND_CURSOR_INCLUDE_DIR AND WAYLAND_CURSOR_LIBRARIES AND NOT TARGET Wayland::Cursor) + add_library(Wayland::Cursor UNKNOWN IMPORTED) + set_target_properties(Wayland::Cursor PROPERTIES INTERFACE_INCLUDE_DIRECTORIES "${WAYLAND_CURSOR_INCLUDE_DIR}" IMPORTED_LOCATION "${WAYLAND_CURSOR_LIBRARIES}") + endif() + + MARK_AS_ADVANCED( + WAYLAND_CLIENT_INCLUDE_DIR WAYLAND_CLIENT_LIBRARIES + WAYLAND_SERVER_INCLUDE_DIR WAYLAND_SERVER_LIBRARIES + WAYLAND_EGL_INCLUDE_DIR WAYLAND_EGL_LIBRARIES + WAYLAND_CURSOR_INCLUDE_DIR WAYLAND_CURSOR_LIBRARIES + ) + +ENDIF () \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index dacb51c382..1a6afca347 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -67,6 +67,7 @@ set(DOLPHIN_DEFAULT_UPDATE_TRACK "" CACHE STRING "Name of the default update tra if(UNIX AND NOT APPLE AND NOT ANDROID) option(ENABLE_X11 "Enables X11 Support" ON) + option(ENABLE_WAYLAND "Enables Wayland support" ON) endif() if(NOT WIN32 AND NOT APPLE AND NOT HAIKU) option(ENABLE_EGL "Enables EGL OpenGL Interface" ON) @@ -508,6 +509,16 @@ if(ENABLE_X11) endif() endif() +if(ENABLE_WAYLAND) + find_package(Wayland) + if(Wayland_FOUND) + add_definitions(-DHAVE_WAYLAND=1) + message(STATUS "Wayland support enabled") + else() + message(WARNING "Wayland support enabled but not found. This build will not support Wayland.") + endif() +endif() + if(ENABLE_EGL) find_package(EGL) if(EGL_FOUND) diff --git a/Source/Android/jni/MainAndroid.cpp b/Source/Android/jni/MainAndroid.cpp index bbf65230f2..0aaefcea23 100644 --- a/Source/Android/jni/MainAndroid.cpp +++ b/Source/Android/jni/MainAndroid.cpp @@ -86,6 +86,8 @@ Common::Flag s_is_booting; bool s_game_metadata_is_valid = false; } // Anonymous namespace +static float GetRenderSurfaceScale(JNIEnv* env); + void UpdatePointer() { // Update touch pointer @@ -448,7 +450,11 @@ JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_SurfaceChang __android_log_print(ANDROID_LOG_ERROR, DOLPHIN_TAG, "Error: Surface is null."); if (g_presenter) - g_presenter->ChangeSurface(s_surf); + { + const u32 width = s_surf ? static_cast(ANativeWindow_getWidth(s_surf)) : 0; + const u32 height = s_surf ? static_cast(ANativeWindow_getHeight(s_surf)) : 0; + g_presenter->ChangeSurface(s_surf, width, height, GetRenderSurfaceScale(env)); + } s_surface_cv.notify_all(); } @@ -471,7 +477,11 @@ JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_NativeLibrary_SurfaceDestr std::lock_guard surface_guard(s_surface_lock); if (g_presenter) - g_presenter->ChangeSurface(nullptr); + { + g_presenter->ChangeSurface(nullptr, g_presenter->GetBackbufferWidth(), + g_presenter->GetBackbufferHeight(), + g_presenter->GetBackbufferScale()); + } if (s_surf) { diff --git a/Source/Core/Common/CMakeLists.txt b/Source/Core/Common/CMakeLists.txt index c7459b0853..0af05ca064 100644 --- a/Source/Core/Common/CMakeLists.txt +++ b/Source/Core/Common/CMakeLists.txt @@ -306,6 +306,13 @@ if(ENABLE_EGL AND EGL_FOUND) target_link_libraries(common PUBLIC ${EGL_LIBRARIES}) endif() +if(ENABLE_WAYLAND AND WAYLAND_FOUND AND ENABLE_EGL AND EGL_FOUND) + target_sources(common PRIVATE + GL/GLInterface/EGLWayland.cpp + GL/GLInterface/EGLWayland.h + ) +endif() + if(WIN32) target_sources(common PRIVATE CompatPatches.cpp diff --git a/Source/Core/Common/GL/GLContext.cpp b/Source/Core/Common/GL/GLContext.cpp index eef689616e..3c0f251bfb 100644 --- a/Source/Core/Common/GL/GLContext.cpp +++ b/Source/Core/Common/GL/GLContext.cpp @@ -19,6 +19,9 @@ #if HAVE_X11 #include "Common/GL/GLInterface/EGLX11.h" #endif +#if HAVE_EGL && HAVE_WAYLAND +#include "Common/GL/GLInterface/EGLWayland.h" +#endif #if defined(ANDROID) #include "Common/GL/GLInterface/EGLAndroid.h" #endif @@ -54,12 +57,15 @@ bool GLContext::ClearCurrent() return false; } -void GLContext::Update() +void GLContext::Update(u32 width, u32 height) { + m_backbuffer_width = width; + m_backbuffer_height = height; } -void GLContext::UpdateSurface(void* window_handle) +void GLContext::UpdateSurface(void* window_handle, u32 width, u32 height) { + Update(width, height); } void GLContext::Swap() @@ -100,6 +106,12 @@ std::unique_ptr GLContext::Create(const WindowSystemInfo& wsi, bool s if (wsi.type == WindowSystemType::X11) context = std::make_unique(); #endif +#endif +#if HAVE_EGL && HAVE_WAYLAND + if (wsi.type == WindowSystemType::Wayland) + context = std::make_unique(); +#endif +#if HAVE_EGL if (wsi.type == WindowSystemType::Headless || wsi.type == WindowSystemType::FBDev) context = std::make_unique(); #endif diff --git a/Source/Core/Common/GL/GLContext.h b/Source/Core/Common/GL/GLContext.h index b1619a647f..78f9083992 100644 --- a/Source/Core/Common/GL/GLContext.h +++ b/Source/Core/Common/GL/GLContext.h @@ -36,8 +36,8 @@ public: virtual bool MakeCurrent(); virtual bool ClearCurrent(); - virtual void Update(); - virtual void UpdateSurface(void* window_handle); + virtual void Update(u32 width, u32 height); + virtual void UpdateSurface(void* window_handle, u32 width, u32 height); virtual void Swap(); virtual void SwapInterval(int interval); diff --git a/Source/Core/Common/GL/GLInterface/AGL.h b/Source/Core/Common/GL/GLInterface/AGL.h index dda9fee0e3..41d5e199aa 100644 --- a/Source/Core/Common/GL/GLInterface/AGL.h +++ b/Source/Core/Common/GL/GLInterface/AGL.h @@ -28,7 +28,7 @@ public: bool MakeCurrent() override; bool ClearCurrent() override; - void Update() override; + void Update(u32 width, u32 height) override; void Swap() override; void SwapInterval(int interval) override; diff --git a/Source/Core/Common/GL/GLInterface/AGL.mm b/Source/Core/Common/GL/GLInterface/AGL.mm index 1b8ff224d1..e3d8d216e5 100644 --- a/Source/Core/Common/GL/GLInterface/AGL.mm +++ b/Source/Core/Common/GL/GLInterface/AGL.mm @@ -5,27 +5,7 @@ #include "Common/Logging/Log.h" -// UpdateCachedDimensions and AttachContextToView contain calls to UI APIs, so they must only be -// called from the main thread or they risk crashing! - -static bool UpdateCachedDimensions(NSView* view, u32* width, u32* height) -{ - NSWindow* window = [view window]; - NSSize size = [view frame].size; - - const CGFloat scale = [window backingScaleFactor]; - u32 new_width = static_cast(size.width * scale); - u32 new_height = static_cast(size.height * scale); - - if (*width == new_width && *height == new_height) - return false; - - *width = new_width; - *height = new_height; - return true; -} - -static bool AttachContextToView(NSOpenGLContext* context, NSView* view, u32* width, u32* height) +static bool AttachContextToView(NSOpenGLContext* context, NSView* view) { // Enable high-resolution display support. [view setWantsBestResolutionOpenGLSurface:YES]; @@ -37,8 +17,6 @@ static bool AttachContextToView(NSOpenGLContext* context, NSView* view, u32* wid return false; } - (void)UpdateCachedDimensions(view, width, height); - [context setView:view]; return true; @@ -98,16 +76,18 @@ bool GLContextAGL::Initialize(const WindowSystemInfo& wsi, bool stereo, bool cor m_view = static_cast(wsi.render_surface); m_opengl_mode = Mode::OpenGL; + m_backbuffer_width = wsi.render_surface_width; + m_backbuffer_height = wsi.render_surface_height; __block bool success; if ([NSThread isMainThread]) { - success = AttachContextToView(m_context, m_view, &m_backbuffer_width, &m_backbuffer_height); + success = AttachContextToView(m_context, m_view); } else { dispatch_sync(dispatch_get_main_queue(), ^{ - success = AttachContextToView(m_context, m_view, &m_backbuffer_width, &m_backbuffer_height); + success = AttachContextToView(m_context, m_view); }); } @@ -150,16 +130,11 @@ bool GLContextAGL::ClearCurrent() return true; } -void GLContextAGL::Update() +void GLContextAGL::Update(u32 width, u32 height) { - if (!m_view) - return; - + GLContext::Update(width, height); dispatch_sync(dispatch_get_main_queue(), ^{ - if (UpdateCachedDimensions(m_view, &m_backbuffer_width, &m_backbuffer_height)) - { - [m_context update]; - } + [m_context update]; }); } diff --git a/Source/Core/Common/GL/GLInterface/BGL.cpp b/Source/Core/Common/GL/GLInterface/BGL.cpp index ac03e698a4..f9a06f36dd 100644 --- a/Source/Core/Common/GL/GLInterface/BGL.cpp +++ b/Source/Core/Common/GL/GLInterface/BGL.cpp @@ -70,7 +70,7 @@ void GLContextBGL::Swap() m_gl->SwapBuffers(); } -void GLContextBGL::Update() +void GLContextBGL::Update(u32, u32) { m_gl->LockLooper(); BRect size = m_gl->Frame(); diff --git a/Source/Core/Common/GL/GLInterface/BGL.h b/Source/Core/Common/GL/GLInterface/BGL.h index c36f2602dc..ef7c378f27 100644 --- a/Source/Core/Common/GL/GLInterface/BGL.h +++ b/Source/Core/Common/GL/GLInterface/BGL.h @@ -18,7 +18,7 @@ public: bool MakeCurrent() override; bool ClearCurrent() override; - void Update() override; + void Update(u32, u32) override; void Swap() override; diff --git a/Source/Core/Common/GL/GLInterface/EGL.cpp b/Source/Core/Common/GL/GLInterface/EGL.cpp index a0bf6dcbab..0d2017c9a3 100644 --- a/Source/Core/Common/GL/GLInterface/EGL.cpp +++ b/Source/Core/Common/GL/GLInterface/EGL.cpp @@ -145,6 +145,8 @@ bool GLContextEGL::Initialize(const WindowSystemInfo& wsi, bool stereo, bool cor bool supports_core_profile = false; m_wsi = wsi; + m_backbuffer_width = wsi.render_surface_width; + m_backbuffer_height = wsi.render_surface_height; m_egl_display = OpenEGLDisplay(); if (!m_egl_display) { @@ -346,8 +348,9 @@ bool GLContextEGL::MakeCurrent() return eglMakeCurrent(m_egl_display, m_egl_surface, m_egl_surface, m_egl_context); } -void GLContextEGL::UpdateSurface(void* window_handle) +void GLContextEGL::UpdateSurface(void* window_handle, u32 width, u32 height) { + GLContext::UpdateSurface(window_handle, width, height); m_wsi.render_surface = window_handle; ClearCurrent(); DestroyWindowSurface(); diff --git a/Source/Core/Common/GL/GLInterface/EGL.h b/Source/Core/Common/GL/GLInterface/EGL.h index bed1b31ecc..54cc5d11fe 100644 --- a/Source/Core/Common/GL/GLInterface/EGL.h +++ b/Source/Core/Common/GL/GLInterface/EGL.h @@ -22,7 +22,7 @@ public: bool MakeCurrent() override; bool ClearCurrent() override; - void UpdateSurface(void* window_handle) override; + void UpdateSurface(void* window_handle, u32 width, u32 height) override; void Swap() override; void SwapInterval(int interval) override; diff --git a/Source/Core/Common/GL/GLInterface/EGLAndroid.cpp b/Source/Core/Common/GL/GLInterface/EGLAndroid.cpp index 873387f7e9..d5419df352 100644 --- a/Source/Core/Common/GL/GLInterface/EGLAndroid.cpp +++ b/Source/Core/Common/GL/GLInterface/EGLAndroid.cpp @@ -15,7 +15,5 @@ EGLNativeWindowType GLContextEGLAndroid::GetEGLNativeWindow(EGLConfig config) EGLint format; eglGetConfigAttrib(m_egl_display, config, EGL_NATIVE_VISUAL_ID, &format); ANativeWindow_setBuffersGeometry(static_cast(m_wsi.render_surface), 0, 0, format); - m_backbuffer_width = ANativeWindow_getWidth(static_cast(m_wsi.render_surface)); - m_backbuffer_height = ANativeWindow_getHeight(static_cast(m_wsi.render_surface)); return static_cast(m_wsi.render_surface); } diff --git a/Source/Core/Common/GL/GLInterface/EGLWayland.cpp b/Source/Core/Common/GL/GLInterface/EGLWayland.cpp new file mode 100644 index 0000000000..1571f2de6f --- /dev/null +++ b/Source/Core/Common/GL/GLInterface/EGLWayland.cpp @@ -0,0 +1,47 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include "Common/GL/GLInterface/EGLWayland.h" +#include "Common/MsgHandler.h" + +GLContextEGLWayland::~GLContextEGLWayland() +{ + if (m_window) + m_wl_egl_window_destroy(m_window); +} + +void GLContextEGLWayland::Update(u32 width, u32 height) +{ + GLContext::Update(width, height); + if (m_window) + m_wl_egl_window_resize(m_window, width, height, 0, 0); +} + +EGLDisplay GLContextEGLWayland::OpenEGLDisplay() +{ + return eglGetDisplay(static_cast(m_wsi.display_connection)); +} + +EGLNativeWindowType GLContextEGLWayland::GetEGLNativeWindow(EGLConfig config) +{ + if (!m_wayland_lib.IsOpen()) + { + std::string name = Common::DynamicLibrary::GetVersionedFilename("wayland-egl", 1); + m_wayland_lib.Open(name.c_str()); + if (!m_wayland_lib.IsOpen()) + { + PanicAlertFmt("Failed to load {}", name); + return reinterpret_cast(m_window); + } +#define LOAD(x) m_wayland_lib.GetSymbol(#x, &m_##x) + LOAD(wl_egl_window_create); + LOAD(wl_egl_window_destroy); + LOAD(wl_egl_window_resize); +#undef LOAD + } + if (m_window) + m_wl_egl_window_destroy(m_window); + m_window = m_wl_egl_window_create(static_cast(m_wsi.render_surface), + m_wsi.render_surface_width, m_wsi.render_surface_height); + return reinterpret_cast(m_window); +} diff --git a/Source/Core/Common/GL/GLInterface/EGLWayland.h b/Source/Core/Common/GL/GLInterface/EGLWayland.h new file mode 100644 index 0000000000..b0ef09ce4d --- /dev/null +++ b/Source/Core/Common/GL/GLInterface/EGLWayland.h @@ -0,0 +1,28 @@ +// Copyright 2023 Dolphin Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#pragma once + +#include +#include "Common/DynamicLibrary.h" +#include "Common/GL/GLInterface/EGL.h" + +struct wl_egl_window; +struct wl_surface; + +class GLContextEGLWayland final : public GLContextEGL +{ +public: + ~GLContextEGLWayland() override; + + void Update(u32 width, u32 height) override; + +protected: + EGLDisplay OpenEGLDisplay() override; + EGLNativeWindowType GetEGLNativeWindow(EGLConfig config) override; + Common::DynamicLibrary m_wayland_lib; + wl_egl_window* (*m_wl_egl_window_create)(wl_surface* surface, int width, int height); + void (*m_wl_egl_window_destroy)(wl_egl_window* window); + void (*m_wl_egl_window_resize)(wl_egl_window* window, int width, int height, int dx, int dy); + wl_egl_window* m_window = nullptr; +}; diff --git a/Source/Core/Common/GL/GLInterface/EGLX11.cpp b/Source/Core/Common/GL/GLInterface/EGLX11.cpp index f430898361..7d86402877 100644 --- a/Source/Core/Common/GL/GLInterface/EGLX11.cpp +++ b/Source/Core/Common/GL/GLInterface/EGLX11.cpp @@ -11,7 +11,7 @@ GLContextEGLX11::~GLContextEGLX11() m_render_window.reset(); } -void GLContextEGLX11::Update() +void GLContextEGLX11::Update(u32, u32) { m_render_window->UpdateDimensions(); m_backbuffer_width = m_render_window->GetWidth(); diff --git a/Source/Core/Common/GL/GLInterface/EGLX11.h b/Source/Core/Common/GL/GLInterface/EGLX11.h index 6f30333341..d7dc2fd3bc 100644 --- a/Source/Core/Common/GL/GLInterface/EGLX11.h +++ b/Source/Core/Common/GL/GLInterface/EGLX11.h @@ -13,7 +13,7 @@ class GLContextEGLX11 final : public GLContextEGL public: ~GLContextEGLX11() override; - void Update() override; + void Update(u32, u32) override; protected: EGLDisplay OpenEGLDisplay() override; diff --git a/Source/Core/Common/GL/GLInterface/WGL.cpp b/Source/Core/Common/GL/GLInterface/WGL.cpp index a85a27a79d..5bdd3bdb87 100644 --- a/Source/Core/Common/GL/GLInterface/WGL.cpp +++ b/Source/Core/Common/GL/GLInterface/WGL.cpp @@ -478,14 +478,3 @@ bool GLContextWGL::ClearCurrent() { return wglMakeCurrent(m_dc, nullptr) == TRUE; } - -// Update window width, size and etc. Called from Render.cpp -void GLContextWGL::Update() -{ - RECT rcWindow; - GetClientRect(m_window_handle, &rcWindow); - - // Get the new window width and height - m_backbuffer_width = rcWindow.right - rcWindow.left; - m_backbuffer_height = rcWindow.bottom - rcWindow.top; -} diff --git a/Source/Core/Common/GL/GLInterface/WGL.h b/Source/Core/Common/GL/GLInterface/WGL.h index 2f5a81e075..7068e14cca 100644 --- a/Source/Core/Common/GL/GLInterface/WGL.h +++ b/Source/Core/Common/GL/GLInterface/WGL.h @@ -19,8 +19,6 @@ public: bool MakeCurrent() override; bool ClearCurrent() override; - void Update() override; - void Swap() override; void SwapInterval(int interval) override; diff --git a/Source/Core/Common/WindowSystemInfo.h b/Source/Core/Common/WindowSystemInfo.h index 9d154776f6..8c6920fe15 100644 --- a/Source/Core/Common/WindowSystemInfo.h +++ b/Source/Core/Common/WindowSystemInfo.h @@ -41,6 +41,11 @@ struct WindowSystemInfo // during video backend startup the surface pointer may change (MoltenVK). void* render_surface = nullptr; + // Width of the render surface + int render_surface_width = 0; + // Height of the render surface + int render_surface_height = 0; + // Scale of the render surface. For hidpi systems, this will be >1. float render_surface_scale = 1.0f; }; diff --git a/Source/Core/DolphinNoGUI/PlatformFBDev.cpp b/Source/Core/DolphinNoGUI/PlatformFBDev.cpp index 249ef20a03..719d959c20 100644 --- a/Source/Core/DolphinNoGUI/PlatformFBDev.cpp +++ b/Source/Core/DolphinNoGUI/PlatformFBDev.cpp @@ -85,6 +85,9 @@ WindowSystemInfo PlatformFBDev::GetWindowSystemInfo() const wsi.display_connection = nullptr; // EGL_DEFAULT_DISPLAY wsi.render_window = nullptr; wsi.render_surface = nullptr; + wsi.render_surface_width = 0; + wsi.render_surface_height = 0; + wsi.render_surface_scale = 1.f; return wsi; } } // namespace diff --git a/Source/Core/DolphinNoGUI/PlatformMacos.mm b/Source/Core/DolphinNoGUI/PlatformMacos.mm index b941858013..3c22b23bae 100644 --- a/Source/Core/DolphinNoGUI/PlatformMacos.mm +++ b/Source/Core/DolphinNoGUI/PlatformMacos.mm @@ -21,6 +21,39 @@ #include #include +@class AppDelegate; + +class PlatformMacOS : public Platform +{ +public: + ~PlatformMacOS() override; + + bool Init() override; + void SetTitle(const std::string& title) override; + void MainLoop() override; + + WindowSystemInfo GetWindowSystemInfo() const override; + + NSWindow* Window() const { return m_window; } + +private: + void ProcessEvents(); + void UpdateWindowPosition(); + void HandleSaveStates(NSUInteger key, NSUInteger flags); + void SetupMenu(); + + NSRect m_window_rect; + NSWindow* m_window; + NSMenu* menuBar; + AppDelegate* m_app_delegate; + + int m_window_x = Config::Get(Config::MAIN_RENDER_WINDOW_XPOS); + int m_window_y = Config::Get(Config::MAIN_RENDER_WINDOW_YPOS); + unsigned int m_window_width = Config::Get(Config::MAIN_RENDER_WINDOW_WIDTH); + unsigned int m_window_height = Config::Get(Config::MAIN_RENDER_WINDOW_HEIGHT); + bool m_window_fullscreen = Config::Get(Config::MAIN_FULLSCREEN); +}; + @interface Application : NSApplication @property Platform* platform; - (void)shutdown; @@ -80,12 +113,13 @@ } @end -@interface AppDelegate : NSObject +@interface AppDelegate : NSObject -@property(readonly) Platform* platform; +@property(readonly) PlatformMacOS* platform; - (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication*)sender; - (id)initWithPlatform:(Platform*)platform; +- (void)windowDidResize:(NSNotification*)notification; @end @implementation AppDelegate @@ -94,7 +128,7 @@ return YES; } -- (id)initWithPlatform:(Platform*)platform +- (id)initWithPlatform:(PlatformMacOS*)platform { self = [super init]; if (self) @@ -103,54 +137,25 @@ } return self; } -@end - -@interface WindowDelegate : NSObject - -- (void)windowDidResize:(NSNotification*)notification; -@end - -@implementation WindowDelegate - (void)windowDidResize:(NSNotification*)notification { + WindowSystemInfo info = _platform->GetWindowSystemInfo(); if (g_presenter) - g_presenter->ResizeSurface(); + g_presenter->ResizeSurface(info.render_surface_width, info.render_surface_height, + info.render_surface_scale); } + +- (void)windowDidChangeScreen:(NSNotification*)notification +{ + if (NSWindow* window = _platform->Window()) + if (CALayer* layer = [[window contentView] layer]) + [layer setContentsScale:[window backingScaleFactor]]; + [self windowDidResize:notification]; +} + @end -namespace -{ -class PlatformMacOS : public Platform -{ -public: - ~PlatformMacOS() override; - - bool Init() override; - void SetTitle(const std::string& title) override; - void MainLoop() override; - - WindowSystemInfo GetWindowSystemInfo() const override; - -private: - void ProcessEvents(); - void UpdateWindowPosition(); - void HandleSaveStates(NSUInteger key, NSUInteger flags); - void SetupMenu(); - - NSRect m_window_rect; - NSWindow* m_window; - NSMenu* menuBar; - AppDelegate* m_app_delegate; - WindowDelegate* m_window_delegate; - - int m_window_x = Config::Get(Config::MAIN_RENDER_WINDOW_XPOS); - int m_window_y = Config::Get(Config::MAIN_RENDER_WINDOW_YPOS); - unsigned int m_window_width = Config::Get(Config::MAIN_RENDER_WINDOW_WIDTH); - unsigned int m_window_height = Config::Get(Config::MAIN_RENDER_WINDOW_HEIGHT); - bool m_window_fullscreen = Config::Get(Config::MAIN_FULLSCREEN); -}; - PlatformMacOS::~PlatformMacOS() { [m_window close]; @@ -176,8 +181,7 @@ bool PlatformMacOS::Init() styleMask:styleMask backing:NSBackingStoreBuffered defer:NO]; - m_window_delegate = [[WindowDelegate alloc] init]; - [m_window setDelegate:m_window_delegate]; + [m_window setDelegate:m_app_delegate]; NSNotificationCenter* c = [NSNotificationCenter defaultCenter]; [c addObserver:NSApp @@ -237,10 +241,15 @@ WindowSystemInfo PlatformMacOS::GetWindowSystemInfo() const { @autoreleasepool { + NSRect frame = [m_window contentRectForFrameRect:[m_window frame]]; + CGFloat scale = [m_window backingScaleFactor]; WindowSystemInfo wsi; wsi.type = WindowSystemType::MacOS; - wsi.render_window = (void*)CFBridgingRetain([m_window contentView]); + wsi.render_window = (__bridge void*)[m_window contentView]; wsi.render_surface = wsi.render_window; + wsi.render_surface_width = frame.size.width * scale; + wsi.render_surface_height = frame.size.height * scale; + wsi.render_surface_scale = scale; return wsi; } } @@ -421,8 +430,6 @@ void PlatformMacOS::SetupMenu() } } -} // namespace - std::unique_ptr Platform::CreateMacOSPlatform() { return std::make_unique(); diff --git a/Source/Core/DolphinNoGUI/PlatformWin32.cpp b/Source/Core/DolphinNoGUI/PlatformWin32.cpp index ff07698115..b7fb456447 100644 --- a/Source/Core/DolphinNoGUI/PlatformWin32.cpp +++ b/Source/Core/DolphinNoGUI/PlatformWin32.cpp @@ -137,6 +137,9 @@ WindowSystemInfo PlatformWin32::GetWindowSystemInfo() const wsi.type = WindowSystemType::Windows; wsi.render_window = reinterpret_cast(m_hwnd); wsi.render_surface = reinterpret_cast(m_hwnd); + wsi.render_surface_width = m_window_width; + wsi.render_surface_height = m_window_height; + wsi.render_surface_scale = 1.f; return wsi; } @@ -192,8 +195,9 @@ LRESULT PlatformWin32::WndProc(const HWND hwnd, const UINT msg, const WPARAM wPa case WM_SIZE: { + UpdateWindowPosition(); if (g_presenter) - g_presenter->ResizeSurface(); + g_presenter->ResizeSurface(m_window_width, m_window_height, 1.f); } break; diff --git a/Source/Core/DolphinNoGUI/PlatformX11.cpp b/Source/Core/DolphinNoGUI/PlatformX11.cpp index 090fadf77e..8151e18516 100644 --- a/Source/Core/DolphinNoGUI/PlatformX11.cpp +++ b/Source/Core/DolphinNoGUI/PlatformX11.cpp @@ -167,6 +167,10 @@ WindowSystemInfo PlatformX11::GetWindowSystemInfo() const wsi.display_connection = static_cast(m_display); wsi.render_window = reinterpret_cast(m_window); wsi.render_surface = reinterpret_cast(m_window); + // TODO: Actually get these (currently both the X11 Vulkan and OGL backends ignore this) + wsi.render_surface_width = 0; + wsi.render_surface_height = 0; + wsi.render_surface_scale = 1.f; return wsi; } @@ -267,7 +271,7 @@ void PlatformX11::ProcessEvents() case ConfigureNotify: { if (g_presenter) - g_presenter->ResizeSurface(); + g_presenter->ResizeSurface(0, 0, 1.f); } break; } diff --git a/Source/Core/DolphinQt/Host.cpp b/Source/Core/DolphinQt/Host.cpp index e2f1b21884..ca3b2c47a0 100644 --- a/Source/Core/DolphinQt/Host.cpp +++ b/Source/Core/DolphinQt/Host.cpp @@ -56,21 +56,6 @@ Host* Host::GetInstance() return s_instance; } -void Host::SetRenderHandle(void* handle) -{ - m_render_to_main = Config::Get(Config::MAIN_RENDER_TO_MAIN); - - if (m_render_handle == handle) - return; - - m_render_handle = handle; - if (g_presenter) - { - g_presenter->ChangeSurface(handle); - g_controller_interface.ChangeWindow(handle); - } -} - void Host::SetMainWindowHandle(void* handle) { m_main_window_handle = handle; @@ -185,10 +170,20 @@ void Host::SetTASInputFocus(const bool focus) m_tas_input_focus = focus; } -void Host::ResizeSurface(int new_width, int new_height) +void Host::SetRenderWindowInfo(void* new_handle, int new_width, int new_height, float new_scale) { + if (new_handle) + { + m_render_to_main = Config::Get(Config::MAIN_RENDER_TO_MAIN); + if (m_render_handle == new_handle) + new_handle = nullptr; + else + m_render_handle = new_handle; + } if (g_presenter) - g_presenter->ResizeSurface(); + g_presenter->ChangeSurface(new_handle, new_width, new_height, new_scale); + if (g_presenter && new_handle) + g_controller_interface.ChangeWindow(new_handle); } std::vector Host_GetPreferredLocales() diff --git a/Source/Core/DolphinQt/Host.h b/Source/Core/DolphinQt/Host.h index 0eacfe646d..43b777dabd 100644 --- a/Source/Core/DolphinQt/Host.h +++ b/Source/Core/DolphinQt/Host.h @@ -28,10 +28,11 @@ public: bool GetTASInputFocus() const; void SetMainWindowHandle(void* handle); - void SetRenderHandle(void* handle); void SetRenderFocus(bool focus); void SetRenderFullFocus(bool focus); void SetRenderFullscreen(bool fullscreen); + // null new_handle => resize + void SetRenderWindowInfo(void* new_handle, int new_width, int new_height, float new_scale); void SetTASInputFocus(bool focus); void ResizeSurface(int new_width, int new_height); diff --git a/Source/Core/DolphinQt/Main.cpp b/Source/Core/DolphinQt/Main.cpp index b223819e7a..e731392ae7 100644 --- a/Source/Core/DolphinQt/Main.cpp +++ b/Source/Core/DolphinQt/Main.cpp @@ -149,14 +149,14 @@ int main(int argc, char* argv[]) #if (QT_VERSION >= QT_VERSION_CHECK(6, 3, 0)) setenv("QT_XCB_NO_XI2", "1", true); #endif - // Dolphin currently doesn't work on Wayland (Only the UI does, games do not launch.) This makes - // XCB the default and forces it on if the platform is specified to be wayland, to prevent this - // from happening. - // For more information: https://bugs.dolphin-emu.org/issues/11807 +#if !HAVE_WAYLAND + // If Dolphin was built without Wayland support, force XCB when the platform is explicitly set to + // Wayland so users get a working backend. const char* current_qt_platform = getenv("QT_QPA_PLATFORM"); const bool replace_qt_platform = current_qt_platform != nullptr && Common::CaseInsensitiveContains(current_qt_platform, "wayland"); setenv("QT_QPA_PLATFORM", "xcb", replace_qt_platform); +#endif #endif QCoreApplication::setOrganizationName(QStringLiteral("Dolphin Emulator")); diff --git a/Source/Core/DolphinQt/MainWindow.cpp b/Source/Core/DolphinQt/MainWindow.cpp index 688ae6ffcd..89a7b1a93a 100644 --- a/Source/Core/DolphinQt/MainWindow.cpp +++ b/Source/Core/DolphinQt/MainWindow.cpp @@ -168,7 +168,7 @@ static WindowSystemType GetWindowSystemType() return WindowSystemType::MacOS; else if (platform_name == QStringLiteral("xcb")) return WindowSystemType::X11; - else if (platform_name == QStringLiteral("wayland")) + else if (platform_name.startsWith(QStringLiteral("wayland"))) return WindowSystemType::Wayland; else if (platform_name == QStringLiteral("haiku")) return WindowSystemType::Haiku; @@ -197,7 +197,10 @@ static WindowSystemInfo GetWindowSystemInfo(QWindow* window) wsi.render_window = window ? reinterpret_cast(window->winId()) : nullptr; wsi.render_surface = wsi.render_window; #endif - wsi.render_surface_scale = window ? static_cast(window->devicePixelRatio()) : 1.0f; + float dpr = window ? static_cast(window->devicePixelRatio()) : 1.f; + wsi.render_surface_width = window ? static_cast(window->width() * dpr) : 0; + wsi.render_surface_height = window ? static_cast(window->height() * dpr) : 0; + wsi.render_surface_scale = dpr; return wsi; } diff --git a/Source/Core/DolphinQt/RenderWidget.cpp b/Source/Core/DolphinQt/RenderWidget.cpp index 9b5026d5fb..7231732d42 100644 --- a/Source/Core/DolphinQt/RenderWidget.cpp +++ b/Source/Core/DolphinQt/RenderWidget.cpp @@ -66,9 +66,7 @@ RenderWidget::RenderWidget(QWidget* parent) : QWidget(parent) // (which results in them not getting called) connect(this, &RenderWidget::StateChanged, Host::GetInstance(), &Host::SetRenderFullscreen, Qt::DirectConnection); - connect(this, &RenderWidget::HandleChanged, this, &RenderWidget::OnHandleChanged, - Qt::DirectConnection); - connect(this, &RenderWidget::SizeChanged, Host::GetInstance(), &Host::ResizeSurface, + connect(this, &RenderWidget::WindowChanged, Host::GetInstance(), &Host::SetRenderWindowInfo, Qt::DirectConnection); connect(this, &RenderWidget::FocusChanged, Host::GetInstance(), &Host::SetRenderFocus, Qt::DirectConnection); @@ -130,20 +128,6 @@ void RenderWidget::dropEvent(QDropEvent* event) State::LoadAs(Core::System::GetInstance(), path.toStdString()); } -void RenderWidget::OnHandleChanged(void* handle) -{ - if (handle) - { -#ifdef _WIN32 - // Remove rounded corners from the render window on Windows 11 - const DWM_WINDOW_CORNER_PREFERENCE corner_preference = DWMWCP_DONOTROUND; - DwmSetWindowAttribute(static_cast(handle), DWMWA_WINDOW_CORNER_PREFERENCE, - &corner_preference, sizeof(corner_preference)); -#endif - } - Host::GetInstance()->SetRenderHandle(handle); -} - void RenderWidget::OnHideCursorChanged() { UpdateCursor(); @@ -212,7 +196,7 @@ void RenderWidget::showFullScreen() const auto dpr = screen->devicePixelRatio(); - emit SizeChanged(width() * dpr, height() * dpr); + emit WindowChanged(nullptr, width() * dpr, height() * dpr, dpr); } // Lock the cursor within the window/widget internal borders, including the aspect ratio if wanted @@ -402,9 +386,6 @@ bool RenderWidget::event(QEvent* event) m_mouse_timer->start(MOUSE_HIDE_DELAY); } break; - case QEvent::WinIdChange: - emit HandleChanged(reinterpret_cast(winId())); - break; case QEvent::Show: // Don't do if "stay on top" changed (or was true) if (Settings::Instance().GetLockCursor() && @@ -469,27 +450,37 @@ bool RenderWidget::event(QEvent* event) // According to https://bugreports.qt.io/browse/QTBUG-95925 the recommended practice for // handling DPI change is responding to paint events + case QEvent::WinIdChange: case QEvent::Paint: case QEvent::Resize: { - SetCursorLocked(m_cursor_locked); + void* handle = nullptr; + if (event->type() == QEvent::WinIdChange) + { + handle = reinterpret_cast(winId()); +#ifdef _WIN32 + // Remove rounded corners from the render window on Windows 11 + const DWM_WINDOW_CORNER_PREFERENCE corner_preference = DWMWCP_DONOTROUND; + DwmSetWindowAttribute(reinterpret_cast(handle), DWMWA_WINDOW_CORNER_PREFERENCE, + &corner_preference, sizeof(corner_preference)); +#endif + } - const QResizeEvent* se = static_cast(event); - QSize new_size = se->size(); + SetCursorLocked(m_cursor_locked); QScreen* screen = window()->windowHandle()->screen(); const float dpr = screen->devicePixelRatio(); - const int width = new_size.width() * dpr; - const int height = new_size.height() * dpr; + const int width = this->width() * dpr; + const int height = this->height() * dpr; - if (m_last_window_width != width || m_last_window_height != height || + if (handle || m_last_window_width != width || m_last_window_height != height || m_last_window_scale != dpr) { m_last_window_width = width; m_last_window_height = height; m_last_window_scale = dpr; - emit SizeChanged(width, height); + emit WindowChanged(handle, width, height, dpr); } break; } diff --git a/Source/Core/DolphinQt/RenderWidget.h b/Source/Core/DolphinQt/RenderWidget.h index d828bf36d1..54c973e71f 100644 --- a/Source/Core/DolphinQt/RenderWidget.h +++ b/Source/Core/DolphinQt/RenderWidget.h @@ -27,14 +27,13 @@ public: signals: void EscapePressed(); void Closed(); - void HandleChanged(void* handle); void StateChanged(bool fullscreen); - void SizeChanged(int new_width, int new_height); + // null new_handle => resize only + void WindowChanged(void* new_handle, int new_width, int new_height, float new_scale); void FocusChanged(bool focus); private: void HandleCursorTimer(); - void OnHandleChanged(void* handle); void OnHideCursorChanged(); void OnNeverHideCursorChanged(); void OnLockCursorChanged(); diff --git a/Source/Core/VideoBackends/D3D/D3DGfx.cpp b/Source/Core/VideoBackends/D3D/D3DGfx.cpp index dc7ee37908..3254832758 100644 --- a/Source/Core/VideoBackends/D3D/D3DGfx.cpp +++ b/Source/Core/VideoBackends/D3D/D3DGfx.cpp @@ -184,22 +184,25 @@ void Gfx::OnConfigChanged(u32 bits) void Gfx::CheckForSwapChainChanges() { - const bool surface_changed = g_presenter->SurfaceChangedTestAndClear(); + const auto change_info = g_presenter->SurfaceChangedTestAndClear(); const bool surface_resized = - g_presenter->SurfaceResizedTestAndClear() || m_swap_chain->CheckForFullscreenChange(); - if (!surface_changed && !surface_resized) + (change_info && !change_info->new_handle) || m_swap_chain->CheckForFullscreenChange(); + if (!change_info && !surface_resized) return; - if (surface_changed) + if (change_info && change_info->new_handle) { - m_swap_chain->ChangeSurface(g_presenter->GetNewSurfaceHandle()); + m_swap_chain->ChangeSurface(change_info->new_handle); } else { m_swap_chain->ResizeSwapChain(); } - g_presenter->SetBackbuffer(m_swap_chain->GetWidth(), m_swap_chain->GetHeight()); + if (change_info) + m_backbuffer_scale = change_info->new_scale; + + g_presenter->SetBackbuffer(GetSurfaceInfo()); } void Gfx::SetFramebuffer(AbstractFramebuffer* framebuffer) diff --git a/Source/Core/VideoBackends/D3D12/D3D12Gfx.cpp b/Source/Core/VideoBackends/D3D12/D3D12Gfx.cpp index cc89b492e6..69e36b102d 100644 --- a/Source/Core/VideoBackends/D3D12/D3D12Gfx.cpp +++ b/Source/Core/VideoBackends/D3D12/D3D12Gfx.cpp @@ -374,24 +374,27 @@ bool Gfx::BindBackbuffer(const ClearColor& clear_color) void Gfx::CheckForSwapChainChanges() { - const bool surface_changed = g_presenter->SurfaceChangedTestAndClear(); + const auto change_info = g_presenter->SurfaceChangedTestAndClear(); const bool surface_resized = - g_presenter->SurfaceResizedTestAndClear() || m_swap_chain->CheckForFullscreenChange(); - if (!surface_changed && !surface_resized) + (change_info && !change_info->new_handle) || m_swap_chain->CheckForFullscreenChange(); + if (!change_info && !surface_resized) return; // The swap chain could be in use from a previous frame. WaitForGPUIdle(); - if (surface_changed) + if (change_info && change_info->new_handle) { - m_swap_chain->ChangeSurface(g_presenter->GetNewSurfaceHandle()); + m_swap_chain->ChangeSurface(change_info->new_handle); } else { m_swap_chain->ResizeSwapChain(); } - g_presenter->SetBackbuffer(m_swap_chain->GetWidth(), m_swap_chain->GetHeight()); + if (change_info) + m_backbuffer_scale = change_info->new_scale; + + g_presenter->SetBackbuffer(GetSurfaceInfo()); } void Gfx::PresentBackbuffer() diff --git a/Source/Core/VideoBackends/Metal/MTLGfx.h b/Source/Core/VideoBackends/Metal/MTLGfx.h index fc9fca0c68..49de5f1c9f 100644 --- a/Source/Core/VideoBackends/Metal/MTLGfx.h +++ b/Source/Core/VideoBackends/Metal/MTLGfx.h @@ -10,6 +10,8 @@ #include "VideoBackends/Metal/MRCHelpers.h" +struct WindowSystemInfo; + namespace Metal { class Framebuffer; @@ -18,7 +20,7 @@ class Texture; class Gfx final : public ::AbstractGfx { public: - Gfx(MRCOwned layer); + Gfx(MRCOwned layer, const WindowSystemInfo& wsi); ~Gfx() override; bool IsHeadless() const override; @@ -84,7 +86,6 @@ private: std::array m_shader_counter = {}; void CheckForSurfaceChange(); - void CheckForSurfaceResize(); - void SetupSurface(); + void SetupSurface(u32 width, u32 height, float scale); }; } // namespace Metal diff --git a/Source/Core/VideoBackends/Metal/MTLGfx.mm b/Source/Core/VideoBackends/Metal/MTLGfx.mm index 4b744fe511..dcbe6cc3d2 100644 --- a/Source/Core/VideoBackends/Metal/MTLGfx.mm +++ b/Source/Core/VideoBackends/Metal/MTLGfx.mm @@ -18,12 +18,13 @@ #include -Metal::Gfx::Gfx(MRCOwned layer) : m_layer(std::move(layer)) +Metal::Gfx::Gfx(MRCOwned layer, const WindowSystemInfo& wsi) + : m_layer(std::move(layer)) { UpdateActiveConfig(); [m_layer setDisplaySyncEnabled:g_ActiveConfig.bVSyncActive]; - SetupSurface(); + SetupSurface(wsi.render_surface_width, wsi.render_surface_height, wsi.render_surface_scale); g_state_tracker->FlushEncoders(); } @@ -453,7 +454,6 @@ bool Metal::Gfx::BindBackbuffer(const ClearColor& clear_color) @autoreleasepool { CheckForSurfaceChange(); - CheckForSurfaceResize(); m_drawable = MRCRetain([m_layer nextDrawable]); m_backbuffer->UpdateBackbufferTexture([m_drawable texture]); SetAndClearFramebuffer(m_backbuffer.get(), clear_color); @@ -488,33 +488,28 @@ void Metal::Gfx::PresentBackbuffer() void Metal::Gfx::CheckForSurfaceChange() { - if (!g_presenter->SurfaceChangedTestAndClear()) - return; - m_layer = MRCRetain(static_cast(g_presenter->GetNewSurfaceHandle())); - SetupSurface(); + if (auto change_info = g_presenter->SurfaceChangedTestAndClear()) + { + if (void* handle = change_info->new_handle) + m_layer = MRCRetain(static_cast(handle)); + SetupSurface(change_info->new_width, change_info->new_height, change_info->new_scale); + } } -void Metal::Gfx::CheckForSurfaceResize() +void Metal::Gfx::SetupSurface(u32 width, u32 height, float scale) { - if (!g_presenter->SurfaceResizedTestAndClear()) - return; - SetupSurface(); -} + AbstractTextureFormat format = Util::ToAbstract([m_layer pixelFormat]); + [m_layer setContentsScale:scale]; + [m_layer setDrawableSize:{static_cast(width), static_cast(height)}]; -void Metal::Gfx::SetupSurface() -{ - auto info = GetSurfaceInfo(); - - [m_layer setDrawableSize:{static_cast(info.width), static_cast(info.height)}]; - - TextureConfig cfg(info.width, info.height, 1, 1, 1, info.format, AbstractTextureFlag_RenderTarget, + TextureConfig cfg(width, height, 1, 1, 1, format, AbstractTextureFlag_RenderTarget, AbstractTextureType::Texture_2DArray); m_bb_texture = std::make_unique(nullptr, cfg); m_backbuffer = std::make_unique( - m_bb_texture.get(), nullptr, std::vector{}, info.width, info.height, 1, 1); + m_bb_texture.get(), nullptr, std::vector{}, width, height, 1, 1); if (g_presenter) - g_presenter->SetBackbuffer(info); + g_presenter->SetBackbuffer({width, height, scale, format}); } SurfaceInfo Metal::Gfx::GetSurfaceInfo() const @@ -522,9 +517,9 @@ SurfaceInfo Metal::Gfx::GetSurfaceInfo() const if (!m_layer) // Headless return {}; - CGSize size = [m_layer bounds].size; + CGSize size = [m_layer drawableSize]; const float scale = [m_layer contentsScale]; - return {static_cast(size.width * scale), static_cast(size.height * scale), scale, + return {static_cast(size.width), static_cast(size.height), scale, Util::ToAbstract([m_layer pixelFormat])}; } diff --git a/Source/Core/VideoBackends/Metal/MTLMain.mm b/Source/Core/VideoBackends/Metal/MTLMain.mm index ff8480edda..9ef0fd90c5 100644 --- a/Source/Core/VideoBackends/Metal/MTLMain.mm +++ b/Source/Core/VideoBackends/Metal/MTLMain.mm @@ -125,9 +125,9 @@ bool Metal::VideoBackend::Initialize(const WindowSystemInfo& wsi) ObjectCache::Initialize(std::move(adapter)); g_state_tracker = std::make_unique(); - return InitializeShared( - std::make_unique(std::move(layer)), std::make_unique(), - std::make_unique(), std::make_unique()); + return InitializeShared(std::make_unique(std::move(layer), wsi), + std::make_unique(), std::make_unique(), + std::make_unique()); } } diff --git a/Source/Core/VideoBackends/OGL/OGLGfx.cpp b/Source/Core/VideoBackends/OGL/OGLGfx.cpp index ff5402ba03..7ffb7e5b89 100644 --- a/Source/Core/VideoBackends/OGL/OGLGfx.cpp +++ b/Source/Core/VideoBackends/OGL/OGLGfx.cpp @@ -410,7 +410,6 @@ void OGLGfx::ClearRegion(const MathUtil::Rectangle& target_rc, bool colorEn bool OGLGfx::BindBackbuffer(const ClearColor& clear_color) { CheckForSurfaceChange(); - CheckForSurfaceResize(); SetAndClearFramebuffer(m_system_framebuffer.get(), clear_color); return true; } @@ -459,29 +458,21 @@ void OGLGfx::WaitForGPUIdle() void OGLGfx::CheckForSurfaceChange() { - if (!g_presenter->SurfaceChangedTestAndClear()) - return; + if (auto change_info = g_presenter->SurfaceChangedTestAndClear()) + { + if (void* handle = change_info->new_handle) + m_main_gl_context->UpdateSurface(handle, change_info->new_width, change_info->new_height); + else + m_main_gl_context->Update(change_info->new_width, change_info->new_height); - m_main_gl_context->UpdateSurface(g_presenter->GetNewSurfaceHandle()); + u32 width = m_main_gl_context->GetBackBufferWidth(); + u32 height = m_main_gl_context->GetBackBufferHeight(); + m_backbuffer_scale = change_info->new_scale; - u32 width = m_main_gl_context->GetBackBufferWidth(); - u32 height = m_main_gl_context->GetBackBufferHeight(); - - // With a surface change, the window likely has new dimensions. - g_presenter->SetBackbuffer(width, height); - m_system_framebuffer->UpdateDimensions(width, height); -} - -void OGLGfx::CheckForSurfaceResize() -{ - if (!g_presenter->SurfaceResizedTestAndClear()) - return; - - m_main_gl_context->Update(); - u32 width = m_main_gl_context->GetBackBufferWidth(); - u32 height = m_main_gl_context->GetBackBufferHeight(); - g_presenter->SetBackbuffer(width, height); - m_system_framebuffer->UpdateDimensions(width, height); + // With a surface change, the window likely has new dimensions. + g_presenter->SetBackbuffer(GetSurfaceInfo()); + m_system_framebuffer->UpdateDimensions(width, height); + } } void OGLGfx::BeginUtilityDrawing() diff --git a/Source/Core/VideoBackends/OGL/OGLGfx.h b/Source/Core/VideoBackends/OGL/OGLGfx.h index a76008fdc9..1fb376af6a 100644 --- a/Source/Core/VideoBackends/OGL/OGLGfx.h +++ b/Source/Core/VideoBackends/OGL/OGLGfx.h @@ -96,7 +96,6 @@ public: private: void CheckForSurfaceChange(); - void CheckForSurfaceResize(); void ApplyRasterizationState(const RasterizationState state); void ApplyDepthState(const DepthState state); diff --git a/Source/Core/VideoBackends/Software/SWGfx.cpp b/Source/Core/VideoBackends/Software/SWGfx.cpp index 7004c50864..f9dca07450 100644 --- a/Source/Core/VideoBackends/Software/SWGfx.cpp +++ b/Source/Core/VideoBackends/Software/SWGfx.cpp @@ -58,12 +58,12 @@ SWGfx::CreateFramebuffer(AbstractTexture* color_attachment, AbstractTexture* dep bool SWGfx::BindBackbuffer(const ClearColor& clear_color) { // Look for framebuffer resizes - if (!g_presenter->SurfaceResizedTestAndClear()) - return true; - - GLContext* context = m_window->GetContext(); - context->Update(); - g_presenter->SetBackbuffer(context->GetBackBufferWidth(), context->GetBackBufferHeight()); + if (auto change_info = g_presenter->SurfaceChangedTestAndClear()) + { + GLContext* context = m_window->GetContext(); + context->Update(change_info->new_width, change_info->new_height); + g_presenter->SetBackbuffer(GetSurfaceInfo()); + } return true; } diff --git a/Source/Core/VideoBackends/Software/SWOGLWindow.cpp b/Source/Core/VideoBackends/Software/SWOGLWindow.cpp index a002e3e519..61cf6cded2 100644 --- a/Source/Core/VideoBackends/Software/SWOGLWindow.cpp +++ b/Source/Core/VideoBackends/Software/SWOGLWindow.cpp @@ -88,7 +88,6 @@ void SWOGLWindow::ShowImage(const AbstractTexture* image, const MathUtil::Rectangle& xfb_region) { const SW::SWTexture* sw_image = static_cast(image); - m_gl_context->Update(); // just updates the render window position and the backbuffer size GLsizei glWidth = (GLsizei)m_gl_context->GetBackBufferWidth(); GLsizei glHeight = (GLsizei)m_gl_context->GetBackBufferHeight(); diff --git a/Source/Core/VideoBackends/Vulkan/VKGfx.cpp b/Source/Core/VideoBackends/Vulkan/VKGfx.cpp index c88e422c98..22e6e5c74c 100644 --- a/Source/Core/VideoBackends/Vulkan/VKGfx.cpp +++ b/Source/Core/VideoBackends/Vulkan/VKGfx.cpp @@ -225,7 +225,6 @@ bool VKGfx::BindBackbuffer(const ClearColor& clear_color) // Handle host window resizes. CheckForSurfaceChange(); - CheckForSurfaceResize(); // Check for exclusive fullscreen request. if (m_swap_chain->GetCurrentFullscreenState() != m_swap_chain->GetNextFullscreenState() && @@ -263,18 +262,18 @@ bool VKGfx::BindBackbuffer(const ClearColor& clear_color) { // The present keeps returning exclusive mode lost unless we re-create the swap chain. INFO_LOG_FMT(VIDEO, "Lost exclusive fullscreen."); - m_swap_chain->RecreateSwapChain(); + m_swap_chain->RecreateSwapChain(0, 0); } else if (res == VK_SUBOPTIMAL_KHR || res == VK_ERROR_OUT_OF_DATE_KHR) { INFO_LOG_FMT(VIDEO, "Resizing swap chain due to suboptimal/out-of-date"); - m_swap_chain->ResizeSwapChain(); + m_swap_chain->ResizeSwapChain(0, 0); } else { ERROR_LOG_FMT(VIDEO, "Unknown present error {:#010X} {}, please report.", std::to_underlying(res), VkResultToString(res)); - m_swap_chain->RecreateSwapChain(); + m_swap_chain->RecreateSwapChain(0, 0); } res = m_swap_chain->AcquireNextImage(); @@ -358,7 +357,8 @@ void VKGfx::ExecuteCommandBuffer(bool submit_off_thread, bool wait_for_completio void VKGfx::CheckForSurfaceChange() { - if (!g_presenter->SurfaceChangedTestAndClear() || !m_swap_chain) + std::optional change_info; + if (!(change_info = g_presenter->SurfaceChangedTestAndClear()) || !m_swap_chain) return; // Submit the current draws up until rendering the XFB. @@ -367,38 +367,25 @@ void VKGfx::CheckForSurfaceChange() // Clear the present failed flag, since we don't want to resize after recreating. g_command_buffer_mgr->CheckLastPresentFail(); - // Recreate the surface. If this fails we're in trouble. - if (!m_swap_chain->RecreateSurface(g_presenter->GetNewSurfaceHandle())) - PanicAlertFmt("Failed to recreate Vulkan surface. Cannot continue."); + if (change_info->new_handle) + { + // Recreate the surface. If this fails we're in trouble. + if (!m_swap_chain->RecreateSurface(change_info->new_handle, // + change_info->new_width, change_info->new_height)) + { + PanicAlertFmt("Failed to recreate Vulkan surface. Cannot continue."); + } + } + else + { + m_swap_chain->RecreateSwapChain(change_info->new_width, change_info->new_height); + } + m_backbuffer_scale = change_info->new_scale; // Handle case where the dimensions are now different. OnSwapChainResized(); } -void VKGfx::CheckForSurfaceResize() -{ - if (!g_presenter->SurfaceResizedTestAndClear()) - return; - - // If we don't have a surface, how can we resize the swap chain? - // CheckForSurfaceChange should handle this case. - if (!m_swap_chain) - { - WARN_LOG_FMT(VIDEO, "Surface resize event received without active surface, ignoring"); - return; - } - - // Wait for the GPU to catch up since we're going to destroy the swap chain. - ExecuteCommandBuffer(false, true); - - // Clear the present failed flag, since we don't want to resize after recreating. - g_command_buffer_mgr->CheckLastPresentFail(); - - // Resize the swap chain. - m_swap_chain->RecreateSwapChain(); - OnSwapChainResized(); -} - void VKGfx::OnConfigChanged(u32 bits) { AbstractGfx::OnConfigChanged(bits); @@ -417,7 +404,7 @@ void VKGfx::OnConfigChanged(u32 bits) if (m_swap_chain && ((bits & CONFIG_CHANGE_BIT_STEREO_MODE) || (bits & CONFIG_CHANGE_BIT_HDR))) { ExecuteCommandBuffer(false, true); - m_swap_chain->RecreateSwapChain(); + m_swap_chain->RecreateSwapChain(0, 0); } // Wipe sampler cache if force texture filtering or anisotropy changes. @@ -430,7 +417,7 @@ void VKGfx::OnConfigChanged(u32 bits) void VKGfx::OnSwapChainResized() { - g_presenter->SetBackbuffer(m_swap_chain->GetWidth(), m_swap_chain->GetHeight()); + g_presenter->SetBackbuffer(GetSurfaceInfo()); } void VKGfx::BindFramebuffer(VKFramebuffer* fb) diff --git a/Source/Core/VideoBackends/Vulkan/VKGfx.h b/Source/Core/VideoBackends/Vulkan/VKGfx.h index e044bf59ca..05e8a671ed 100644 --- a/Source/Core/VideoBackends/Vulkan/VKGfx.h +++ b/Source/Core/VideoBackends/Vulkan/VKGfx.h @@ -89,7 +89,6 @@ public: private: void CheckForSurfaceChange(); - void CheckForSurfaceResize(); void ResetSamplerStates(); diff --git a/Source/Core/VideoBackends/Vulkan/VKSwapChain.cpp b/Source/Core/VideoBackends/Vulkan/VKSwapChain.cpp index 370e22d15c..7e42ec654b 100644 --- a/Source/Core/VideoBackends/Vulkan/VKSwapChain.cpp +++ b/Source/Core/VideoBackends/Vulkan/VKSwapChain.cpp @@ -84,6 +84,29 @@ VkSurfaceKHR SwapChain::CreateVulkanSurface(VkInstance instance, const WindowSys } #endif +#if defined(VK_USE_PLATFORM_WAYLAND_KHR) + if (wsi.type == WindowSystemType::Wayland) + { + VkWaylandSurfaceCreateInfoKHR surface_create_info = { + VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR, // VkStructureType sType + nullptr, // const void* pNext + 0, // VkWaylandSurfaceCreateFlagsKHR flags + static_cast(wsi.display_connection), // struct wl_display* display + static_cast(wsi.render_surface) // struct wl_surface* surface + }; + + VkSurfaceKHR surface; + VkResult res = vkCreateWaylandSurfaceKHR(instance, &surface_create_info, nullptr, &surface); + if (res != VK_SUCCESS) + { + LOG_VULKAN_ERROR(res, "vkCreateWaylandSurfaceKHR failed: "); + return VK_NULL_HANDLE; + } + + return surface; + } +#endif + #if defined(VK_USE_PLATFORM_ANDROID_KHR) if (wsi.type == WindowSystemType::Android) { @@ -132,8 +155,11 @@ std::unique_ptr SwapChain::Create(const WindowSystemInfo& wsi, VkSurf bool vsync) { std::unique_ptr swap_chain = std::make_unique(wsi, surface, vsync); - if (!swap_chain->CreateSwapChain() || !swap_chain->SetupSwapChainImages()) + if (!swap_chain->CreateSwapChain(wsi.render_surface_width, wsi.render_surface_height) || + !swap_chain->SetupSwapChainImages()) + { return nullptr; + } return swap_chain; } @@ -271,7 +297,7 @@ bool SwapChain::SelectPresentMode() return true; } -bool SwapChain::CreateSwapChain() +bool SwapChain::CreateSwapChain(u32 width, u32 height) { // Look up surface properties to determine image count and dimensions VkSurfaceCapabilitiesKHR surface_capabilities; @@ -299,8 +325,8 @@ bool SwapChain::CreateSwapChain() VkExtent2D size = surface_capabilities.currentExtent; if (size.width == UINT32_MAX) { - size.width = std::max(g_presenter->GetBackbufferWidth(), 1); - size.height = std::max(g_presenter->GetBackbufferHeight(), 1); + size.width = width == 0 ? m_width : width; + size.height = height == 0 ? m_height : height; } size.width = std::clamp(size.width, surface_capabilities.minImageExtent.width, surface_capabilities.maxImageExtent.width); @@ -500,10 +526,10 @@ VkResult SwapChain::AcquireNextImage() return res; } -bool SwapChain::ResizeSwapChain() +bool SwapChain::ResizeSwapChain(u32 width, u32 height) { DestroySwapChainImages(); - if (!CreateSwapChain() || !SetupSwapChainImages()) + if (!CreateSwapChain(width, height) || !SetupSwapChainImages()) { PanicAlertFmt("Failed to re-configure swap chain images, this is fatal (for now)"); return false; @@ -512,11 +538,11 @@ bool SwapChain::ResizeSwapChain() return true; } -bool SwapChain::RecreateSwapChain() +bool SwapChain::RecreateSwapChain(u32 width, u32 height) { DestroySwapChainImages(); DestroySwapChain(); - if (!CreateSwapChain() || !SetupSwapChainImages()) + if (!CreateSwapChain(width, height) || !SetupSwapChainImages()) { PanicAlertFmt("Failed to re-configure swap chain images, this is fatal (for now)"); return false; @@ -532,7 +558,7 @@ bool SwapChain::SetVSync(bool enabled) // Recreate the swap chain with the new present mode. m_vsync_enabled = enabled; - return RecreateSwapChain(); + return RecreateSwapChain(m_width, m_height); } bool SwapChain::SetFullscreenState(bool state) @@ -568,7 +594,7 @@ bool SwapChain::SetFullscreenState(bool state) #endif } -bool SwapChain::RecreateSurface(void* native_handle) +bool SwapChain::RecreateSurface(void* native_handle, u32 width, u32 height) { // Destroy the old swap chain, images, and surface. DestroySwapChainImages(); @@ -604,7 +630,7 @@ bool SwapChain::RecreateSurface(void* native_handle) m_next_fullscreen_state = false; // Finally re-create the swap chain - if (!CreateSwapChain() || !SetupSwapChainImages()) + if (!CreateSwapChain(width, height) || !SetupSwapChainImages()) return false; return true; diff --git a/Source/Core/VideoBackends/Vulkan/VKSwapChain.h b/Source/Core/VideoBackends/Vulkan/VKSwapChain.h index 17334dbf97..99d39f8261 100644 --- a/Source/Core/VideoBackends/Vulkan/VKSwapChain.h +++ b/Source/Core/VideoBackends/Vulkan/VKSwapChain.h @@ -54,9 +54,9 @@ public: } VkResult AcquireNextImage(); - bool RecreateSurface(void* native_handle); - bool ResizeSwapChain(); - bool RecreateSwapChain(); + bool RecreateSurface(void* native_handle, u32 width, u32 height); + bool ResizeSwapChain(u32 width, u32 height); + bool RecreateSwapChain(u32 width, u32 height); // Change vsync enabled state. This may fail as it causes a swapchain recreation. bool SetVSync(bool enabled); @@ -76,7 +76,7 @@ private: bool SelectSurfaceFormat(); bool SelectPresentMode(); - bool CreateSwapChain(); + bool CreateSwapChain(u32 width, u32 height); void DestroySwapChain(); bool SetupSwapChainImages(); diff --git a/Source/Core/VideoBackends/Vulkan/VulkanContext.cpp b/Source/Core/VideoBackends/Vulkan/VulkanContext.cpp index d388300b6e..641b54216c 100644 --- a/Source/Core/VideoBackends/Vulkan/VulkanContext.cpp +++ b/Source/Core/VideoBackends/Vulkan/VulkanContext.cpp @@ -362,6 +362,13 @@ bool VulkanContext::SelectInstanceExtensions(std::vector* extension return false; } #endif +#if defined(VK_USE_PLATFORM_WAYLAND_KHR) + if (wstype == WindowSystemType::Wayland && + !AddExtension(VK_KHR_WAYLAND_SURFACE_EXTENSION_NAME, true)) + { + return false; + } +#endif #if defined(VK_USE_PLATFORM_ANDROID_KHR) if (wstype == WindowSystemType::Android && !AddExtension(VK_KHR_ANDROID_SURFACE_EXTENSION_NAME, true)) diff --git a/Source/Core/VideoBackends/Vulkan/VulkanEntryPoints.inl b/Source/Core/VideoBackends/Vulkan/VulkanEntryPoints.inl index d716ce49d2..7d58e112d8 100644 --- a/Source/Core/VideoBackends/Vulkan/VulkanEntryPoints.inl +++ b/Source/Core/VideoBackends/Vulkan/VulkanEntryPoints.inl @@ -49,6 +49,10 @@ VULKAN_INSTANCE_ENTRY_POINT(vkCreateXlibSurfaceKHR, false) VULKAN_INSTANCE_ENTRY_POINT(vkGetPhysicalDeviceXlibPresentationSupportKHR, false) #endif +#if defined(VK_USE_PLATFORM_WAYLAND_KHR) +VULKAN_INSTANCE_ENTRY_POINT(vkCreateWaylandSurfaceKHR, false) +#endif + #if defined(VK_USE_PLATFORM_ANDROID_KHR) VULKAN_INSTANCE_ENTRY_POINT(vkCreateAndroidSurfaceKHR, false) #endif diff --git a/Source/Core/VideoBackends/Vulkan/VulkanLoader.h b/Source/Core/VideoBackends/Vulkan/VulkanLoader.h index 33784d5d2e..af7427b9bd 100644 --- a/Source/Core/VideoBackends/Vulkan/VulkanLoader.h +++ b/Source/Core/VideoBackends/Vulkan/VulkanLoader.h @@ -13,6 +13,10 @@ #define VK_USE_PLATFORM_XLIB_KHR #endif +#if defined(HAVE_WAYLAND) +#define VK_USE_PLATFORM_WAYLAND_KHR +#endif + #if defined(ANDROID) #define VK_USE_PLATFORM_ANDROID_KHR #endif diff --git a/Source/Core/VideoCommon/Present.cpp b/Source/Core/VideoCommon/Present.cpp index 7c62730667..5d35975a5e 100644 --- a/Source/Core/VideoCommon/Present.cpp +++ b/Source/Core/VideoCommon/Present.cpp @@ -583,24 +583,33 @@ void Presenter::ReleaseXFBContentLock() m_xfb_entry->ReleaseContentLock(); } -void Presenter::ChangeSurface(void* new_surface_handle) +void Presenter::ChangeSurface(void* new_surface_handle, u32 width, u32 height, float scale) { std::lock_guard lock(m_swap_mutex); - m_new_surface_handle = new_surface_handle; - m_surface_changed.Set(); + // If you ChangeSurface then ResizeSurface, don't forget about the surface change + // (null new_surface_handle => ResizeSurface) + if (new_surface_handle) + m_new_surface_handle = new_surface_handle; + m_new_surface_width = width; + m_new_surface_height = height; + m_new_surface_scale = scale; + // Actual data is accessed under the mutex, so this can be relaxed + m_surface_changed.store(true, std::memory_order_relaxed); } -void Presenter::ResizeSurface() +Presenter::SurfaceChangedInfo Presenter::SurfaceChangedGetAndClear() { std::lock_guard lock(m_swap_mutex); - m_surface_resized.Set(); -} - -void* Presenter::GetNewSurfaceHandle() -{ - void* handle = m_new_surface_handle; + SurfaceChangedInfo info = {}; + // clang-format off + info.new_handle = m_new_surface_handle; + info.new_width = m_new_surface_width; + info.new_height = m_new_surface_height; + info.new_scale = m_new_surface_scale; + // clang-format on m_new_surface_handle = nullptr; - return handle; + m_surface_changed.store(false, std::memory_order_relaxed); + return info; } u32 Presenter::AutoIntegralScale() const diff --git a/Source/Core/VideoCommon/Present.h b/Source/Core/VideoCommon/Present.h index 91ba870967..abe39c09a9 100644 --- a/Source/Core/VideoCommon/Present.h +++ b/Source/Core/VideoCommon/Present.h @@ -14,6 +14,8 @@ #include #include #include +#include +#include #include class AbstractTexture; @@ -91,11 +93,24 @@ public: VideoCommon::PostProcessing* GetPostProcessor() const { return m_post_processor.get(); } // Final surface changing // This is called when the surface is resized (WX) or the window changes (Android). - void ChangeSurface(void* new_surface_handle); - void ResizeSurface(); - bool SurfaceResizedTestAndClear() { return m_surface_resized.TestAndClear(); } - bool SurfaceChangedTestAndClear() { return m_surface_changed.TestAndClear(); } - void* GetNewSurfaceHandle(); + void ChangeSurface(void* new_surface_handle, u32 width, u32 height, float scale); + void ResizeSurface(u32 width, u32 height, float scale) + { + ChangeSurface(nullptr, width, height, scale); + } + struct SurfaceChangedInfo + { + void* new_handle; ///< New surface handle if changed, else null + int new_width; ///< New surface width + int new_height; ///< New surface height + float new_scale; ///< New surface scale + }; + std::optional SurfaceChangedTestAndClear() + { + if (!m_surface_changed.load(std::memory_order_relaxed)) + return std::nullopt; + return SurfaceChangedGetAndClear(); + } void SetKeyMap(const DolphinKeyMap& key_map); @@ -141,8 +156,12 @@ private: AbstractTextureFormat m_backbuffer_format = AbstractTextureFormat::Undefined; void* m_new_surface_handle = nullptr; - Common::Flag m_surface_changed; - Common::Flag m_surface_resized; + int m_new_surface_width = 0; + int m_new_surface_height = 0; + float m_new_surface_scale = 1.f; + // note: Atomic for anti-tearing / anti-compiler-optimization only, write only under m_swap_mutex + std::atomic_bool m_surface_changed; + SurfaceChangedInfo SurfaceChangedGetAndClear(); // The presentation rectangle. // Width and height correspond to the final output resolution.