From 28ac9aee2b505bd7d13fd444f2e93cb0254eb9a9 Mon Sep 17 00:00:00 2001 From: jebbs Date: Thu, 4 Jun 2026 10:21:47 +0800 Subject: [PATCH] macOS: Fix blurry rendering on Retina displays by setting correct backing scale factor --- src/Ryujinx.Common/Helpers/ObjectiveC.cs | 8 +++++ .../SystemInterop/ForceDpiAware.cs | 28 ++++++++++++++++ src/Ryujinx/UI/Renderer/EmbeddedWindow.cs | 33 +++++++++++++++++-- 3 files changed, 67 insertions(+), 2 deletions(-) diff --git a/src/Ryujinx.Common/Helpers/ObjectiveC.cs b/src/Ryujinx.Common/Helpers/ObjectiveC.cs index 4c2481f6e..40f854df2 100644 --- a/src/Ryujinx.Common/Helpers/ObjectiveC.cs +++ b/src/Ryujinx.Common/Helpers/ObjectiveC.cs @@ -42,6 +42,9 @@ namespace Ryujinx.Common.Helper [return: MarshalAs(UnmanagedType.Bool)] private static partial bool bool_objc_msgSend(nint receiver, Selector selector, nint param); + [LibraryImport(ObjCRuntime, EntryPoint = "objc_msgSend")] + private static partial double double_objc_msgSend(nint receiver, Selector selector); + public readonly struct Object { public readonly nint ObjPtr; @@ -105,6 +108,11 @@ namespace Ryujinx.Common.Helper { return bool_objc_msgSend(ObjPtr, selector, obj.ObjPtr); } + + public double GetDoubleFromMessage(Selector selector) + { + return double_objc_msgSend(ObjPtr, selector); + } } public readonly struct Selector diff --git a/src/Ryujinx.Common/SystemInterop/ForceDpiAware.cs b/src/Ryujinx.Common/SystemInterop/ForceDpiAware.cs index a89dcd1ad..28354a3a6 100644 --- a/src/Ryujinx.Common/SystemInterop/ForceDpiAware.cs +++ b/src/Ryujinx.Common/SystemInterop/ForceDpiAware.cs @@ -1,7 +1,9 @@ +using Ryujinx.Common.Helper; using Ryujinx.Common.Logging; using System; using System.Globalization; using System.Runtime.InteropServices; +using System.Runtime.Versioning; namespace Ryujinx.Common.SystemInterop { @@ -53,6 +55,10 @@ namespace Ryujinx.Common.SystemInterop { userDpiScale = GdiPlusHelper.GetDpiX(nint.Zero); } + else if (OperatingSystem.IsMacOS()) + { + userDpiScale = GetMacOSDpiScale(); + } else if (OperatingSystem.IsLinux()) { string xdgSessionType = Environment.GetEnvironmentVariable("XDG_SESSION_TYPE")?.ToLower(); @@ -93,5 +99,27 @@ namespace Ryujinx.Common.SystemInterop return Math.Min(userDpiScale / StandardDpiScale, MaxScaleFactor); } + + [SupportedOSPlatform("macos")] + private static double GetMacOSDpiScale() + { + try + { + ObjectiveC.Object screenClass = new("NSScreen"); + ObjectiveC.Object mainScreen = screenClass.GetFromMessage("mainScreen"); + if (mainScreen.ObjPtr != nint.Zero) + { + double backingScale = mainScreen.GetDoubleFromMessage("backingScaleFactor"); + // Convert backing scale factor (e.g., 2.0) to equivalent DPI (e.g., 192). + return StandardDpiScale * backingScale; + } + } + catch (Exception e) + { + Logger.Warning?.Print(LogClass.Application, $"Couldn't determine monitor DPI on macOS: {e.Message}"); + } + + return StandardDpiScale; + } } } diff --git a/src/Ryujinx/UI/Renderer/EmbeddedWindow.cs b/src/Ryujinx/UI/Renderer/EmbeddedWindow.cs index 687047672..211569ab8 100644 --- a/src/Ryujinx/UI/Renderer/EmbeddedWindow.cs +++ b/src/Ryujinx/UI/Renderer/EmbeddedWindow.cs @@ -243,9 +243,18 @@ namespace Ryujinx.Ava.UI.Renderer // Make its renderer our metal layer. child.SendMessage("setWantsLayer:", 1); child.SendMessage("setLayer:", metalLayer); - metalLayer.SendMessage("setContentsScale:", Program.DesktopScaleFactor); - // Ensure the scale factor is up to date. + // Query the actual backing scale factor from the screen. + // This ensures the CAMetalLayer drawable is created at the correct + // pixel resolution for Retina displays, preventing blurry rendering. + double backingScaleFactor = GetBackingScaleFactor(); + metalLayer.SendMessage("setContentsScale:", backingScaleFactor); + + // Update DesktopScaleFactor immediately so the rest of the app + // (including the bounds callback) uses the correct value. + Program.DesktopScaleFactor = backingScaleFactor; + + // Ensure the scale factor is up to date on bounds changes. _updateBoundsCallback = rect => { metalLayer.SendMessage("setContentsScale:", Program.DesktopScaleFactor); @@ -258,6 +267,26 @@ namespace Ryujinx.Ava.UI.Renderer return new PlatformHandle(nsView, "NSView"); } + [SupportedOSPlatform("macos")] + private static double GetBackingScaleFactor() + { + try + { + ObjectiveC.Object screenClass = new("NSScreen"); + ObjectiveC.Object mainScreen = screenClass.GetFromMessage("mainScreen"); + if (mainScreen.ObjPtr != nint.Zero) + { + return mainScreen.GetDoubleFromMessage("backingScaleFactor"); + } + } + catch + { + // Fallback to default scale if ObjC call fails. + } + + return 2.0; + } + [SupportedOSPlatform("Linux")] void DestroyLinux() {