From 31aacdd05b872b9f2127b85ccc22085044a40efc Mon Sep 17 00:00:00 2001 From: The Roofer Dev Date: Tue, 16 Jun 2026 17:43:13 -0400 Subject: [PATCH 1/2] Graphics: decode oversized ASTC array textures slice-by-slice Large 2D-array ASTC textures (e.g. from the TOTK 4K texture pack) can decode to more than 2 GB of RGBA8, which overflows the Int32 size computation in QueryDecompressedSize and crashes MemoryOwner.Rent. Add QueryDecompressedSizeLong to detect the overflow and, in that case, decode and upload the texture one layer/level slice at a time instead of allocating a single oversized buffer. Co-Authored-By: Claude Opus 4.8 --- src/Ryujinx.Graphics.Gpu/Image/Texture.cs | 26 +++++++++++++++++++ .../Astc/AstcDecoder.cs | 21 +++++++++++++++ 2 files changed, 47 insertions(+) diff --git a/src/Ryujinx.Graphics.Gpu/Image/Texture.cs b/src/Ryujinx.Graphics.Gpu/Image/Texture.cs index 05a316c45..7008fe6a3 100644 --- a/src/Ryujinx.Graphics.Gpu/Image/Texture.cs +++ b/src/Ryujinx.Graphics.Gpu/Image/Texture.cs @@ -661,6 +661,32 @@ namespace Ryujinx.Graphics.Gpu.Image } } + // Large 4K texture-pack array textures (e.g. a 2048x2048 ASTC 2D array with 121 layers) decode to + // more than 2 GiB of RGBA8, which overflows Int32 and cannot fit in a single array. Decode and + // upload them one slice at a time instead (each slice is only a few MiB), reusing the same + // single-slice path that partial synchronization already uses. + if (Info.FormatInfo.Format.IsAstc && + !_context.Capabilities.SupportsAstcCompression && + _depth == 1 && + AstcDecoder.QueryDecompressedSizeLong(Info.Width, Info.Height, 1, Info.Levels, _layers) > int.MaxValue) + { + for (int layer = 0; layer < _layers; layer++) + { + for (int level = 0; level < Info.Levels; level++) + { + int sliceOffset = _sizeInfo.AllOffsets[layer * Info.Levels + level]; + + MemoryOwner sliceResult = ConvertToHostCompatibleFormat(data[sliceOffset..], level, single: true); + + HostTexture.SetData(sliceResult, layer, level); + } + } + + _hasData = true; + + return; + } + MemoryOwner result = ConvertToHostCompatibleFormat(data); if (ScaleFactor != 1f && AllowScaledSetData()) diff --git a/src/Ryujinx.Graphics.Texture/Astc/AstcDecoder.cs b/src/Ryujinx.Graphics.Texture/Astc/AstcDecoder.cs index 1053f6f18..3cfe36559 100644 --- a/src/Ryujinx.Graphics.Texture/Astc/AstcDecoder.cs +++ b/src/Ryujinx.Graphics.Texture/Astc/AstcDecoder.cs @@ -115,6 +115,27 @@ namespace Ryujinx.Graphics.Texture.Astc return size * 4; } + /// + /// Computes the fully-decoded (RGBA8) size in bytes using 64-bit arithmetic, so callers can detect + /// when the result would overflow (e.g. large 4K texture-pack array textures) and + /// decode slice-by-slice instead of allocating a single oversized buffer. + /// + public static long QueryDecompressedSizeLong(int sizeX, int sizeY, int sizeZ, int levelCount, int layerCount) + { + long size = 0; + + for (int i = 0; i < levelCount; i++) + { + long levelSizeX = Math.Max(1, sizeX >> i); + long levelSizeY = Math.Max(1, sizeY >> i); + long levelSizeZ = Math.Max(1, sizeZ >> i); + + size += levelSizeX * levelSizeY * levelSizeZ * layerCount; + } + + return size * 4; + } + public void ProcessBlock(int index) { Buffer16 inputBlock = MemoryMarshal.Cast(InputBuffer.Span)[index]; From a08051ac4ccd97100e5f2c546cbc0ed01c97c3e3 Mon Sep 17 00:00:00 2001 From: The Roofer Dev Date: Tue, 16 Jun 2026 17:43:15 -0400 Subject: [PATCH 2/2] UI: add a VRR VSync mode using the FifoRelaxed present mode Add a new VSyncMode.Vrr that selects the FifoRelaxed Vulkan present mode, letting the display's variable refresh rate (G-Sync/FreeSync) drive the refresh in fullscreen and smoothing a fluctuating frame rate without tearing. Reachable by cycling the status-bar VSync button; shown in green. Co-Authored-By: Claude Opus 4.8 --- src/Ryujinx.Common/Configuration/VSyncMode.cs | 6 ++++-- src/Ryujinx.Graphics.Vulkan/Window.cs | 9 ++++++++- src/Ryujinx/Assets/Styles/Themes.xaml | 3 +++ src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs | 7 ++++++- 4 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/Ryujinx.Common/Configuration/VSyncMode.cs b/src/Ryujinx.Common/Configuration/VSyncMode.cs index 21505bf2a..b2818eb7a 100644 --- a/src/Ryujinx.Common/Configuration/VSyncMode.cs +++ b/src/Ryujinx.Common/Configuration/VSyncMode.cs @@ -4,7 +4,8 @@ namespace Ryujinx.Common.Configuration { Switch, Unbounded, - Custom + Custom, + Vrr } public static class VSyncModeExtensions @@ -13,8 +14,9 @@ namespace Ryujinx.Common.Configuration vsync switch { VSyncMode.Switch => customEnabled ? VSyncMode.Custom : VSyncMode.Unbounded, - VSyncMode.Unbounded => VSyncMode.Switch, VSyncMode.Custom => VSyncMode.Unbounded, + VSyncMode.Unbounded => VSyncMode.Vrr, + VSyncMode.Vrr => VSyncMode.Switch, _ => VSyncMode.Switch }; } diff --git a/src/Ryujinx.Graphics.Vulkan/Window.cs b/src/Ryujinx.Graphics.Vulkan/Window.cs index eb8d433be..8b2a53fc0 100644 --- a/src/Ryujinx.Graphics.Vulkan/Window.cs +++ b/src/Ryujinx.Graphics.Vulkan/Window.cs @@ -282,7 +282,14 @@ namespace Ryujinx.Graphics.Vulkan private static PresentModeKHR ChooseSwapPresentMode(PresentModeKHR[] availablePresentModes, VSyncMode vSyncMode) { - if (vSyncMode == VSyncMode.Unbounded && availablePresentModes.Contains(PresentModeKHR.ImmediateKhr)) + // VRR: FifoRelaxed lets the display's variable refresh rate (G-Sync/FreeSync) drive the refresh + // in fullscreen, smoothing a fluctuating frame rate without tearing. Immediate (full VSync off) + // was tried first but could hang the GPU under load, so FifoRelaxed is used instead. + if (vSyncMode == VSyncMode.Vrr && availablePresentModes.Contains(PresentModeKHR.FifoRelaxedKhr)) + { + return PresentModeKHR.FifoRelaxedKhr; + } + else if (vSyncMode == VSyncMode.Unbounded && availablePresentModes.Contains(PresentModeKHR.ImmediateKhr)) { return PresentModeKHR.ImmediateKhr; } diff --git a/src/Ryujinx/Assets/Styles/Themes.xaml b/src/Ryujinx/Assets/Styles/Themes.xaml index 53c4dcd59..d073786bd 100644 --- a/src/Ryujinx/Assets/Styles/Themes.xaml +++ b/src/Ryujinx/Assets/Styles/Themes.xaml @@ -18,6 +18,7 @@ #FF2EEAC9 #FFFF4554 #6483F5 + #FF4CD964 #800080 #00B5B8 @@ -38,6 +39,7 @@ #13c3a4 #FFFF4554 #6483F5 + #FF4CD964 #800080 #00B5B8 @@ -58,6 +60,7 @@ #FF2EEAC9 #FFFF4554 #6483F5 + #FF4CD964 #FFA500 #00B5B8 diff --git a/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs b/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs index 51c05a692..c778ec71c 100644 --- a/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs +++ b/src/Ryujinx/UI/ViewModels/MainWindowViewModel.cs @@ -1369,7 +1369,12 @@ namespace Ryujinx.Ava.UI.ViewModels VSyncModeColor = new SolidColorBrush(clr); } - VSyncModeText = args.VSyncMode == "Custom" ? "Custom" : "VSync"; + VSyncModeText = args.VSyncMode switch + { + "Custom" => "Custom", + "Vrr" => "VRR", + _ => "VSync" + }; DockedStatusText = args.DockedMode; AspectRatioStatusText = args.AspectRatio; GameStatusText = args.GameStatus;