Compare commits
22 Commits
Author | SHA1 | Date | |
---|---|---|---|
0e8e735a6d | |||
0003a7c118 | |||
2cdcfe46d8 | |||
fe30c03cac | |||
5813b2e354 | |||
af1906ea04 | |||
68848000f7 | |||
d98da47a0f | |||
306f7e93a0 | |||
8954ff3af2 | |||
d2f3adbf69 | |||
d511c845b7 | |||
21c9ac6240 | |||
81c9052847 | |||
9367e3c35d | |||
52cf141874 | |||
8a352df3c6 | |||
c545c59851 | |||
96ea4e8c8e | |||
b8f48bcf64 | |||
6966211e07 | |||
57524a4c8a |
19
.github/ISSUE_TEMPLATE/missing_shader_instruction.yml
vendored
Normal file
19
.github/ISSUE_TEMPLATE/missing_shader_instruction.yml
vendored
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
name: Missing Shader Instruction
|
||||||
|
description: Shader Instruction is missing in Ryujinx.
|
||||||
|
title: "[GPU]"
|
||||||
|
labels: [gpu, not-implemented]
|
||||||
|
body:
|
||||||
|
- type: textarea
|
||||||
|
id: instruction
|
||||||
|
attributes:
|
||||||
|
label: Shader instruction
|
||||||
|
description: What shader instruction is missing?
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
id: required
|
||||||
|
attributes:
|
||||||
|
label: Required by
|
||||||
|
description: Add links to the [compatibility list page(s)](https://github.com/Ryujinx/Ryujinx-Games-List/issues) of the game(s) that require this instruction.
|
||||||
|
validations:
|
||||||
|
required: true
|
@ -21,7 +21,7 @@
|
|||||||
<PackageVersion Include="LibHac" Version="0.18.0" />
|
<PackageVersion Include="LibHac" Version="0.18.0" />
|
||||||
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
|
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
|
||||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.5.0" />
|
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.5.0" />
|
||||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.6.0" />
|
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.6.1" />
|
||||||
<PackageVersion Include="Microsoft.IO.RecyclableMemoryStream" Version="2.3.2" />
|
<PackageVersion Include="Microsoft.IO.RecyclableMemoryStream" Version="2.3.2" />
|
||||||
<PackageVersion Include="MsgPack.Cli" Version="1.0.1" />
|
<PackageVersion Include="MsgPack.Cli" Version="1.0.1" />
|
||||||
<PackageVersion Include="NUnit" Version="3.13.3" />
|
<PackageVersion Include="NUnit" Version="3.13.3" />
|
||||||
@ -44,7 +44,7 @@
|
|||||||
<PackageVersion Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta11" />
|
<PackageVersion Include="SixLabors.ImageSharp.Drawing" Version="1.0.0-beta11" />
|
||||||
<PackageVersion Include="SPB" Version="0.0.4-build28" />
|
<PackageVersion Include="SPB" Version="0.0.4-build28" />
|
||||||
<PackageVersion Include="System.Drawing.Common" Version="7.0.0" />
|
<PackageVersion Include="System.Drawing.Common" Version="7.0.0" />
|
||||||
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="6.30.1" />
|
<PackageVersion Include="System.IdentityModel.Tokens.Jwt" Version="6.31.0" />
|
||||||
<PackageVersion Include="System.IO.Hashing" Version="7.0.0" />
|
<PackageVersion Include="System.IO.Hashing" Version="7.0.0" />
|
||||||
<PackageVersion Include="System.Management" Version="7.0.1" />
|
<PackageVersion Include="System.Management" Version="7.0.1" />
|
||||||
<PackageVersion Include="UnicornEngine.Unicorn" Version="2.0.2-rc1-fb78016" />
|
<PackageVersion Include="UnicornEngine.Unicorn" Version="2.0.2-rc1-fb78016" />
|
||||||
|
@ -6,10 +6,11 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
using System.Runtime.Versioning;
|
||||||
|
|
||||||
namespace ARMeilleure.Translation.Cache
|
namespace ARMeilleure.Translation.Cache
|
||||||
{
|
{
|
||||||
static class JitCache
|
static partial class JitCache
|
||||||
{
|
{
|
||||||
private const int PageSize = 4 * 1024;
|
private const int PageSize = 4 * 1024;
|
||||||
private const int PageMask = PageSize - 1;
|
private const int PageMask = PageSize - 1;
|
||||||
@ -27,6 +28,10 @@ namespace ARMeilleure.Translation.Cache
|
|||||||
private static readonly object _lock = new object();
|
private static readonly object _lock = new object();
|
||||||
private static bool _initialized;
|
private static bool _initialized;
|
||||||
|
|
||||||
|
[SupportedOSPlatform("windows")]
|
||||||
|
[LibraryImport("kernel32.dll", SetLastError = true)]
|
||||||
|
public static partial IntPtr FlushInstructionCache(IntPtr hProcess, IntPtr lpAddress, UIntPtr dwSize);
|
||||||
|
|
||||||
public static void Initialize(IJitMemoryAllocator allocator)
|
public static void Initialize(IJitMemoryAllocator allocator)
|
||||||
{
|
{
|
||||||
if (_initialized) return;
|
if (_initialized) return;
|
||||||
@ -36,7 +41,11 @@ namespace ARMeilleure.Translation.Cache
|
|||||||
if (_initialized) return;
|
if (_initialized) return;
|
||||||
|
|
||||||
_jitRegion = new ReservedRegion(allocator, CacheSize);
|
_jitRegion = new ReservedRegion(allocator, CacheSize);
|
||||||
_jitCacheInvalidator = new JitCacheInvalidation(allocator);
|
|
||||||
|
if (!OperatingSystem.IsWindows() && !OperatingSystem.IsMacOS())
|
||||||
|
{
|
||||||
|
_jitCacheInvalidator = new JitCacheInvalidation(allocator);
|
||||||
|
}
|
||||||
|
|
||||||
_cacheAllocator = new CacheMemoryAllocator(CacheSize);
|
_cacheAllocator = new CacheMemoryAllocator(CacheSize);
|
||||||
|
|
||||||
@ -77,7 +86,14 @@ namespace ARMeilleure.Translation.Cache
|
|||||||
Marshal.Copy(code, 0, funcPtr, code.Length);
|
Marshal.Copy(code, 0, funcPtr, code.Length);
|
||||||
ReprotectAsExecutable(funcOffset, code.Length);
|
ReprotectAsExecutable(funcOffset, code.Length);
|
||||||
|
|
||||||
_jitCacheInvalidator.Invalidate(funcPtr, (ulong)code.Length);
|
if (OperatingSystem.IsWindows() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
|
||||||
|
{
|
||||||
|
FlushInstructionCache(Process.GetCurrentProcess().Handle, funcPtr, (UIntPtr)code.Length);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_jitCacheInvalidator?.Invalidate(funcPtr, (ulong)code.Length);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Add(funcOffset, code.Length, func.UnwindInfo);
|
Add(funcOffset, code.Length, func.UnwindInfo);
|
||||||
|
@ -47,8 +47,8 @@ namespace ARMeilleure.Translation.Cache
|
|||||||
|
|
||||||
public JitCacheInvalidation(IJitMemoryAllocator allocator)
|
public JitCacheInvalidation(IJitMemoryAllocator allocator)
|
||||||
{
|
{
|
||||||
// On macOS, a different path is used to write to the JIT cache, which does the invalidation.
|
// On macOS and Windows, a different path is used to write to the JIT cache, which does the invalidation.
|
||||||
if (!OperatingSystem.IsMacOS() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
|
if (RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
|
||||||
{
|
{
|
||||||
ulong size = (ulong)_invalidationCode.Length * sizeof(int);
|
ulong size = (ulong)_invalidationCode.Length * sizeof(int);
|
||||||
ulong mask = (ulong)ReservedRegion.DefaultGranularity - 1;
|
ulong mask = (ulong)ReservedRegion.DefaultGranularity - 1;
|
||||||
|
@ -544,6 +544,9 @@
|
|||||||
"SwkbdMinCharacters": "Must be at least {0} characters long",
|
"SwkbdMinCharacters": "Must be at least {0} characters long",
|
||||||
"SwkbdMinRangeCharacters": "Must be {0}-{1} characters long",
|
"SwkbdMinRangeCharacters": "Must be {0}-{1} characters long",
|
||||||
"SoftwareKeyboard": "Software Keyboard",
|
"SoftwareKeyboard": "Software Keyboard",
|
||||||
|
"SoftwareKeyboardModeNumbersOnly": "Must be numbers only",
|
||||||
|
"SoftwareKeyboardModeAlphabet": "Must be alphabets only",
|
||||||
|
"SoftwareKeyboardModeASCII": "Must be ASCII text only",
|
||||||
"DialogControllerAppletMessagePlayerRange": "Application requests {0} player(s) with:\n\nTYPES: {1}\n\nPLAYERS: {2}\n\n{3}Please open Settings and reconfigure Input now or press Close.",
|
"DialogControllerAppletMessagePlayerRange": "Application requests {0} player(s) with:\n\nTYPES: {1}\n\nPLAYERS: {2}\n\n{3}Please open Settings and reconfigure Input now or press Close.",
|
||||||
"DialogControllerAppletMessage": "Application requests exactly {0} player(s) with:\n\nTYPES: {1}\n\nPLAYERS: {2}\n\n{3}Please open Settings and reconfigure Input now or press Close.",
|
"DialogControllerAppletMessage": "Application requests exactly {0} player(s) with:\n\nTYPES: {1}\n\nPLAYERS: {2}\n\n{3}Please open Settings and reconfigure Input now or press Close.",
|
||||||
"DialogControllerAppletDockModeSet": "Docked mode set. Handheld is also invalid.\n\n",
|
"DialogControllerAppletDockModeSet": "Docked mode set. Handheld is also invalid.\n\n",
|
||||||
|
@ -527,6 +527,9 @@
|
|||||||
"SwkbdMinCharacters": "至少应为 {0} 个字长",
|
"SwkbdMinCharacters": "至少应为 {0} 个字长",
|
||||||
"SwkbdMinRangeCharacters": "必须为 {0}-{1} 个字长",
|
"SwkbdMinRangeCharacters": "必须为 {0}-{1} 个字长",
|
||||||
"SoftwareKeyboard": "软件键盘",
|
"SoftwareKeyboard": "软件键盘",
|
||||||
|
"SoftwareKeyboardModeNumbersOnly": "只接受数字",
|
||||||
|
"SoftwareKeyboardModeAlphabet": "只接受英文字母",
|
||||||
|
"SoftwareKeyboardModeASCII": "只接受 ASCII 符号",
|
||||||
"DialogControllerAppletMessagePlayerRange": "游戏需要 {0} 个玩家并满足以下要求:\n\n手柄类型:{1}\n\n玩家类型:{2}\n\n{3}请打开设置窗口,重新配置手柄输入;或者关闭返回。",
|
"DialogControllerAppletMessagePlayerRange": "游戏需要 {0} 个玩家并满足以下要求:\n\n手柄类型:{1}\n\n玩家类型:{2}\n\n{3}请打开设置窗口,重新配置手柄输入;或者关闭返回。",
|
||||||
"DialogControllerAppletMessage": "游戏需要刚好 {0} 个玩家并满足以下要求:\n\n手柄类型:{1}\n\n玩家类型:{2}\n\n{3}请打开设置窗口,重新配置手柄输入;或者关闭返回。",
|
"DialogControllerAppletMessage": "游戏需要刚好 {0} 个玩家并满足以下要求:\n\n手柄类型:{1}\n\n玩家类型:{2}\n\n{3}请打开设置窗口,重新配置手柄输入;或者关闭返回。",
|
||||||
"DialogControllerAppletDockModeSet": "目前处于主机模式,无法使用掌机操作方式",
|
"DialogControllerAppletDockModeSet": "目前处于主机模式,无法使用掌机操作方式",
|
||||||
|
@ -527,6 +527,9 @@
|
|||||||
"SwkbdMinCharacters": "至少應為 {0} 個字長",
|
"SwkbdMinCharacters": "至少應為 {0} 個字長",
|
||||||
"SwkbdMinRangeCharacters": "必須為 {0}-{1} 個字長",
|
"SwkbdMinRangeCharacters": "必須為 {0}-{1} 個字長",
|
||||||
"SoftwareKeyboard": "軟體鍵盤",
|
"SoftwareKeyboard": "軟體鍵盤",
|
||||||
|
"SoftwareKeyboardModeNumbersOnly": "只接受數字",
|
||||||
|
"SoftwareKeyboardModeAlphabet": "只接受英文字母",
|
||||||
|
"SoftwareKeyboardModeASCII": "只接受 ASCII 符號",
|
||||||
"DialogControllerAppletMessagePlayerRange": "本遊戲需要 {0} 個玩家持有:\n\n類型:{1}\n\n玩家:{2}\n\n{3}請打開設定畫面,配置手把,或者關閉本視窗。",
|
"DialogControllerAppletMessagePlayerRange": "本遊戲需要 {0} 個玩家持有:\n\n類型:{1}\n\n玩家:{2}\n\n{3}請打開設定畫面,配置手把,或者關閉本視窗。",
|
||||||
"DialogControllerAppletMessage": "本遊戲需要剛好 {0} 個玩家持有:\n\n類型:{1}\n\n玩家:{2}\n\n{3}請打開設定畫面,配置手把,或者關閉本視窗。",
|
"DialogControllerAppletMessage": "本遊戲需要剛好 {0} 個玩家持有:\n\n類型:{1}\n\n玩家:{2}\n\n{3}請打開設定畫面,配置手把,或者關閉本視窗。",
|
||||||
"DialogControllerAppletDockModeSet": "現在處於主機模式,無法使用掌機操作方式\n\n",
|
"DialogControllerAppletDockModeSet": "現在處於主機模式,無法使用掌機操作方式\n\n",
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
using Avalonia.Input;
|
using Avalonia.Input;
|
||||||
using FluentAvalonia.Core;
|
|
||||||
using Ryujinx.Input;
|
using Ryujinx.Input;
|
||||||
using System;
|
using System;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
@ -30,14 +29,14 @@ namespace Ryujinx.Ava.Input
|
|||||||
_window = window;
|
_window = window;
|
||||||
|
|
||||||
_widget.PointerMoved += Parent_PointerMovedEvent;
|
_widget.PointerMoved += Parent_PointerMovedEvent;
|
||||||
_widget.PointerPressed += Parent_PointerPressEvent;
|
_widget.PointerPressed += Parent_PointerPressedEvent;
|
||||||
_widget.PointerReleased += Parent_PointerReleaseEvent;
|
_widget.PointerReleased += Parent_PointerReleasedEvent;
|
||||||
_widget.PointerWheelChanged += Parent_ScrollEvent;
|
_widget.PointerWheelChanged += Parent_PointerWheelChanged;
|
||||||
|
|
||||||
_window.PointerMoved += Parent_PointerMovedEvent;
|
_window.PointerMoved += Parent_PointerMovedEvent;
|
||||||
_window.PointerPressed += Parent_PointerPressEvent;
|
_window.PointerPressed += Parent_PointerPressedEvent;
|
||||||
_window.PointerReleased += Parent_PointerReleaseEvent;
|
_window.PointerReleased += Parent_PointerReleasedEvent;
|
||||||
_window.PointerWheelChanged += Parent_ScrollEvent;
|
_window.PointerWheelChanged += Parent_PointerWheelChanged;
|
||||||
|
|
||||||
PressedButtons = new bool[(int)MouseButton.Count];
|
PressedButtons = new bool[(int)MouseButton.Count];
|
||||||
|
|
||||||
@ -63,29 +62,25 @@ namespace Ryujinx.Ava.Input
|
|||||||
_size = new Size((int)rect.Width, (int)rect.Height);
|
_size = new Size((int)rect.Width, (int)rect.Height);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Parent_ScrollEvent(object o, PointerWheelEventArgs args)
|
private void Parent_PointerWheelChanged(object o, PointerWheelEventArgs args)
|
||||||
{
|
{
|
||||||
Scroll = new Vector2((float)args.Delta.X, (float)args.Delta.Y);
|
Scroll = new Vector2((float)args.Delta.X, (float)args.Delta.Y);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void Parent_PointerReleaseEvent(object o, PointerReleasedEventArgs args)
|
private void Parent_PointerReleasedEvent(object o, PointerReleasedEventArgs args)
|
||||||
{
|
{
|
||||||
if (args.InitialPressMouseButton != Avalonia.Input.MouseButton.None)
|
uint button = (uint)args.InitialPressMouseButton - 1;
|
||||||
{
|
|
||||||
int button = (int)args.InitialPressMouseButton;
|
|
||||||
|
|
||||||
if (PressedButtons.Count() >= button)
|
if ((uint)PressedButtons.Length > button)
|
||||||
{
|
{
|
||||||
PressedButtons[button] = false;
|
PressedButtons[button] = false;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
private void Parent_PointerPressedEvent(object o, PointerPressedEventArgs args)
|
||||||
private void Parent_PointerPressEvent(object o, PointerPressedEventArgs args)
|
|
||||||
{
|
{
|
||||||
int button = (int)args.GetCurrentPoint(_widget).Properties.PointerUpdateKind;
|
uint button = (uint)args.GetCurrentPoint(_widget).Properties.PointerUpdateKind;
|
||||||
|
|
||||||
if (PressedButtons.Count() >= button)
|
if ((uint)PressedButtons.Length > button)
|
||||||
{
|
{
|
||||||
PressedButtons[button] = true;
|
PressedButtons[button] = true;
|
||||||
}
|
}
|
||||||
@ -100,17 +95,17 @@ namespace Ryujinx.Ava.Input
|
|||||||
|
|
||||||
public void SetMousePressed(MouseButton button)
|
public void SetMousePressed(MouseButton button)
|
||||||
{
|
{
|
||||||
if (PressedButtons.Count() >= (int)button)
|
if ((uint)PressedButtons.Length > (uint)button)
|
||||||
{
|
{
|
||||||
PressedButtons[(int)button] = true;
|
PressedButtons[(uint)button] = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetMouseReleased(MouseButton button)
|
public void SetMouseReleased(MouseButton button)
|
||||||
{
|
{
|
||||||
if (PressedButtons.Count() >= (int)button)
|
if ((uint)PressedButtons.Length > (uint)button)
|
||||||
{
|
{
|
||||||
PressedButtons[(int)button] = false;
|
PressedButtons[(uint)button] = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -121,9 +116,9 @@ namespace Ryujinx.Ava.Input
|
|||||||
|
|
||||||
public bool IsButtonPressed(MouseButton button)
|
public bool IsButtonPressed(MouseButton button)
|
||||||
{
|
{
|
||||||
if (PressedButtons.Count() >= (int)button)
|
if ((uint)PressedButtons.Length > (uint)button)
|
||||||
{
|
{
|
||||||
return PressedButtons[(int)button];
|
return PressedButtons[(uint)button];
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
@ -149,14 +144,14 @@ namespace Ryujinx.Ava.Input
|
|||||||
_isDisposed = true;
|
_isDisposed = true;
|
||||||
|
|
||||||
_widget.PointerMoved -= Parent_PointerMovedEvent;
|
_widget.PointerMoved -= Parent_PointerMovedEvent;
|
||||||
_widget.PointerPressed -= Parent_PointerPressEvent;
|
_widget.PointerPressed -= Parent_PointerPressedEvent;
|
||||||
_widget.PointerReleased -= Parent_PointerReleaseEvent;
|
_widget.PointerReleased -= Parent_PointerReleasedEvent;
|
||||||
_widget.PointerWheelChanged -= Parent_ScrollEvent;
|
_widget.PointerWheelChanged -= Parent_PointerWheelChanged;
|
||||||
|
|
||||||
_window.PointerMoved -= Parent_PointerMovedEvent;
|
_window.PointerMoved -= Parent_PointerMovedEvent;
|
||||||
_window.PointerPressed -= Parent_PointerPressEvent;
|
_window.PointerPressed -= Parent_PointerPressedEvent;
|
||||||
_window.PointerReleased -= Parent_PointerReleaseEvent;
|
_window.PointerReleased -= Parent_PointerReleasedEvent;
|
||||||
_window.PointerWheelChanged -= Parent_ScrollEvent;
|
_window.PointerWheelChanged -= Parent_PointerWheelChanged;
|
||||||
|
|
||||||
_widget = null;
|
_widget = null;
|
||||||
}
|
}
|
||||||
|
@ -740,6 +740,18 @@ namespace Ryujinx.Modules
|
|||||||
{
|
{
|
||||||
var files = Directory.EnumerateFiles(HomeDir); // All files directly in base dir.
|
var files = Directory.EnumerateFiles(HomeDir); // All files directly in base dir.
|
||||||
|
|
||||||
|
// Determine and exclude user files only when the updater is running, not when cleaning old files
|
||||||
|
if (_running)
|
||||||
|
{
|
||||||
|
// Compare the loose files in base directory against the loose files from the incoming update, and store foreign ones in a user list.
|
||||||
|
var oldFiles = Directory.EnumerateFiles(HomeDir, "*", SearchOption.TopDirectoryOnly).Select(Path.GetFileName);
|
||||||
|
var newFiles = Directory.EnumerateFiles(UpdatePublishDir, "*", SearchOption.TopDirectoryOnly).Select(Path.GetFileName);
|
||||||
|
var userFiles = oldFiles.Except(newFiles).Select(filename => Path.Combine(HomeDir, filename));
|
||||||
|
|
||||||
|
// Remove user files from the paths in files.
|
||||||
|
files = files.Except(userFiles);
|
||||||
|
}
|
||||||
|
|
||||||
if (OperatingSystem.IsWindows())
|
if (OperatingSystem.IsWindows())
|
||||||
{
|
{
|
||||||
foreach (string dir in WindowsDependencyDirs)
|
foreach (string dir in WindowsDependencyDirs)
|
||||||
|
@ -9,14 +9,17 @@ using Ryujinx.Ava.Common.Locale;
|
|||||||
using Ryujinx.Ava.UI.Helpers;
|
using Ryujinx.Ava.UI.Helpers;
|
||||||
using Ryujinx.Ava.UI.Windows;
|
using Ryujinx.Ava.UI.Windows;
|
||||||
using Ryujinx.HLE.HOS.Applets;
|
using Ryujinx.HLE.HOS.Applets;
|
||||||
|
using Ryujinx.HLE.HOS.Applets.SoftwareKeyboard;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace Ryujinx.Ava.UI.Controls
|
namespace Ryujinx.Ava.UI.Controls
|
||||||
{
|
{
|
||||||
internal partial class SwkbdAppletDialog : UserControl
|
internal partial class SwkbdAppletDialog : UserControl
|
||||||
{
|
{
|
||||||
private Predicate<int> _checkLength;
|
private Predicate<int> _checkLength = _ => true;
|
||||||
|
private Predicate<string> _checkInput = _ => true;
|
||||||
private int _inputMax;
|
private int _inputMax;
|
||||||
private int _inputMin;
|
private int _inputMin;
|
||||||
private string _placeholder;
|
private string _placeholder;
|
||||||
@ -35,8 +38,6 @@ namespace Ryujinx.Ava.UI.Controls
|
|||||||
Input.Watermark = _placeholder;
|
Input.Watermark = _placeholder;
|
||||||
|
|
||||||
Input.AddHandler(TextInputEvent, Message_TextInput, RoutingStrategies.Tunnel, true);
|
Input.AddHandler(TextInputEvent, Message_TextInput, RoutingStrategies.Tunnel, true);
|
||||||
|
|
||||||
SetInputLengthValidation(0, int.MaxValue); // Disable by default.
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public SwkbdAppletDialog()
|
public SwkbdAppletDialog()
|
||||||
@ -67,6 +68,7 @@ namespace Ryujinx.Ava.UI.Controls
|
|||||||
string input = string.Empty;
|
string input = string.Empty;
|
||||||
|
|
||||||
content.SetInputLengthValidation(args.StringLengthMin, args.StringLengthMax);
|
content.SetInputLengthValidation(args.StringLengthMin, args.StringLengthMax);
|
||||||
|
content.SetInputValidation(args.KeyboardMode);
|
||||||
|
|
||||||
content._host = contentDialog;
|
content._host = contentDialog;
|
||||||
contentDialog.Title = title;
|
contentDialog.Title = title;
|
||||||
@ -91,6 +93,12 @@ namespace Ryujinx.Ava.UI.Controls
|
|||||||
return (result, input);
|
return (result, input);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ApplyValidationInfo(string text)
|
||||||
|
{
|
||||||
|
Error.IsVisible = !string.IsNullOrEmpty(text);
|
||||||
|
Error.Text = text;
|
||||||
|
}
|
||||||
|
|
||||||
public void SetInputLengthValidation(int min, int max)
|
public void SetInputLengthValidation(int min, int max)
|
||||||
{
|
{
|
||||||
_inputMin = Math.Min(min, max);
|
_inputMin = Math.Min(min, max);
|
||||||
@ -99,6 +107,8 @@ namespace Ryujinx.Ava.UI.Controls
|
|||||||
Error.IsVisible = false;
|
Error.IsVisible = false;
|
||||||
Error.FontStyle = FontStyle.Italic;
|
Error.FontStyle = FontStyle.Italic;
|
||||||
|
|
||||||
|
string validationInfoText = "";
|
||||||
|
|
||||||
if (_inputMin <= 0 && _inputMax == int.MaxValue) // Disable.
|
if (_inputMin <= 0 && _inputMax == int.MaxValue) // Disable.
|
||||||
{
|
{
|
||||||
Error.IsVisible = false;
|
Error.IsVisible = false;
|
||||||
@ -107,21 +117,48 @@ namespace Ryujinx.Ava.UI.Controls
|
|||||||
}
|
}
|
||||||
else if (_inputMin > 0 && _inputMax == int.MaxValue)
|
else if (_inputMin > 0 && _inputMax == int.MaxValue)
|
||||||
{
|
{
|
||||||
Error.IsVisible = true;
|
validationInfoText = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.SwkbdMinCharacters, _inputMin);
|
||||||
|
|
||||||
Error.Text = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.SwkbdMinCharacters, _inputMin);
|
|
||||||
|
|
||||||
_checkLength = length => _inputMin <= length;
|
_checkLength = length => _inputMin <= length;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Error.IsVisible = true;
|
validationInfoText = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.SwkbdMinRangeCharacters, _inputMin, _inputMax);
|
||||||
|
|
||||||
Error.Text = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.SwkbdMinRangeCharacters, _inputMin, _inputMax);
|
|
||||||
|
|
||||||
_checkLength = length => _inputMin <= length && length <= _inputMax;
|
_checkLength = length => _inputMin <= length && length <= _inputMax;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ApplyValidationInfo(validationInfoText);
|
||||||
|
Message_TextInput(this, new TextInputEventArgs());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void SetInputValidation(KeyboardMode mode)
|
||||||
|
{
|
||||||
|
string validationInfoText = Error.Text;
|
||||||
|
string localeText;
|
||||||
|
switch (mode)
|
||||||
|
{
|
||||||
|
case KeyboardMode.NumbersOnly:
|
||||||
|
localeText = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.SoftwareKeyboardModeNumbersOnly);
|
||||||
|
validationInfoText = string.IsNullOrEmpty(validationInfoText) ? localeText : string.Join("\n", validationInfoText, localeText);
|
||||||
|
_checkInput = text => text.All(char.IsDigit);
|
||||||
|
break;
|
||||||
|
case KeyboardMode.Alphabet:
|
||||||
|
localeText = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.SoftwareKeyboardModeAlphabet);
|
||||||
|
validationInfoText = string.IsNullOrEmpty(validationInfoText) ? localeText : string.Join("\n", validationInfoText, localeText);
|
||||||
|
_checkInput = text => text.All(char.IsAsciiLetter);
|
||||||
|
break;
|
||||||
|
case KeyboardMode.ASCII:
|
||||||
|
localeText = LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.SoftwareKeyboardModeASCII);
|
||||||
|
validationInfoText = string.IsNullOrEmpty(validationInfoText) ? localeText : string.Join("\n", validationInfoText, localeText);
|
||||||
|
_checkInput = text => text.All(char.IsAscii);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
_checkInput = _ => true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
ApplyValidationInfo(validationInfoText);
|
||||||
Message_TextInput(this, new TextInputEventArgs());
|
Message_TextInput(this, new TextInputEventArgs());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -129,7 +166,7 @@ namespace Ryujinx.Ava.UI.Controls
|
|||||||
{
|
{
|
||||||
if (_host != null)
|
if (_host != null)
|
||||||
{
|
{
|
||||||
_host.IsPrimaryButtonEnabled = _checkLength(Message.Length);
|
_host.IsPrimaryButtonEnabled = _checkLength(Message.Length) && _checkInput(Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -141,7 +178,7 @@ namespace Ryujinx.Ava.UI.Controls
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_host.IsPrimaryButtonEnabled = _checkLength(Message.Length);
|
_host.IsPrimaryButtonEnabled = _checkLength(Message.Length) && _checkInput(Message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,10 +32,10 @@
|
|||||||
<ListBox.ItemsPanel>
|
<ListBox.ItemsPanel>
|
||||||
<ItemsPanelTemplate>
|
<ItemsPanelTemplate>
|
||||||
<flex:FlexPanel
|
<flex:FlexPanel
|
||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Center"
|
||||||
VerticalAlignment="Stretch"
|
VerticalAlignment="Stretch"
|
||||||
AlignContent="FlexStart"
|
AlignContent="FlexStart"
|
||||||
JustifyContent="Center" />
|
JustifyContent="FlexStart" />
|
||||||
</ItemsPanelTemplate>
|
</ItemsPanelTemplate>
|
||||||
</ListBox.ItemsPanel>
|
</ListBox.ItemsPanel>
|
||||||
<ListBox.Styles>
|
<ListBox.Styles>
|
||||||
|
@ -21,6 +21,7 @@ namespace Ryujinx.Ava.UI.Helpers
|
|||||||
if (value is byte[] buffer && targetType == typeof(IImage))
|
if (value is byte[] buffer && targetType == typeof(IImage))
|
||||||
{
|
{
|
||||||
MemoryStream mem = new(buffer);
|
MemoryStream mem = new(buffer);
|
||||||
|
|
||||||
return new Bitmap(mem);
|
return new Bitmap(mem);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -318,7 +318,7 @@ namespace Ryujinx.Ava.UI.Helpers
|
|||||||
|
|
||||||
Window parent = GetMainWindow();
|
Window parent = GetMainWindow();
|
||||||
|
|
||||||
if (parent is { IsActive: true } and MainWindow window && window.ViewModel.IsGameRunning)
|
if (parent != null && parent.IsActive && (parent as MainWindow).ViewModel.IsGameRunning)
|
||||||
{
|
{
|
||||||
contentDialogOverlayWindow = new()
|
contentDialogOverlayWindow = new()
|
||||||
{
|
{
|
||||||
|
@ -257,6 +257,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
|||||||
|
|
||||||
OnPropertyChanged();
|
OnPropertyChanged();
|
||||||
OnPropertyChanged(nameof(EnableNonGameRunningControls));
|
OnPropertyChanged(nameof(EnableNonGameRunningControls));
|
||||||
|
OnPropertyChanged(nameof(IsAppletMenuActive));
|
||||||
OnPropertyChanged(nameof(StatusBarVisible));
|
OnPropertyChanged(nameof(StatusBarVisible));
|
||||||
OnPropertyChanged(nameof(ShowFirmwareStatus));
|
OnPropertyChanged(nameof(ShowFirmwareStatus));
|
||||||
}
|
}
|
||||||
|
@ -9,9 +9,7 @@ using Ryujinx.Ava.UI.ViewModels;
|
|||||||
using Ryujinx.Ava.UI.Windows;
|
using Ryujinx.Ava.UI.Windows;
|
||||||
using Ryujinx.Common;
|
using Ryujinx.Common;
|
||||||
using Ryujinx.Common.Utilities;
|
using Ryujinx.Common.Utilities;
|
||||||
using Ryujinx.HLE.HOS;
|
|
||||||
using Ryujinx.Modules;
|
using Ryujinx.Modules;
|
||||||
using Ryujinx.Ui.App.Common;
|
|
||||||
using Ryujinx.Ui.Common;
|
using Ryujinx.Ui.Common;
|
||||||
using Ryujinx.Ui.Common.Configuration;
|
using Ryujinx.Ui.Common.Configuration;
|
||||||
using Ryujinx.Ui.Common.Helper;
|
using Ryujinx.Ui.Common.Helper;
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
</Design.DataContext>
|
</Design.DataContext>
|
||||||
<DockPanel
|
<DockPanel
|
||||||
Margin="0,0,0,5"
|
Margin="0,0,0,5"
|
||||||
|
Height="35"
|
||||||
HorizontalAlignment="Stretch">
|
HorizontalAlignment="Stretch">
|
||||||
<Button
|
<Button
|
||||||
Width="40"
|
Width="40"
|
||||||
|
@ -519,14 +519,14 @@ namespace Ryujinx.Ava.UI.Windows
|
|||||||
private void ConfirmExit()
|
private void ConfirmExit()
|
||||||
{
|
{
|
||||||
Dispatcher.UIThread.InvokeAsync(async () =>
|
Dispatcher.UIThread.InvokeAsync(async () =>
|
||||||
{
|
{
|
||||||
ViewModel.IsClosing = await ContentDialogHelper.CreateExitDialog();
|
ViewModel.IsClosing = await ContentDialogHelper.CreateExitDialog();
|
||||||
|
|
||||||
if (ViewModel.IsClosing)
|
if (ViewModel.IsClosing)
|
||||||
{
|
{
|
||||||
Close();
|
Close();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public async void LoadApplications()
|
public async void LoadApplications()
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
#nullable enable
|
#nullable enable
|
||||||
|
using Ryujinx.Common.Logging;
|
||||||
using System;
|
using System;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
@ -18,12 +19,14 @@ namespace Ryujinx.Common.Utilities
|
|||||||
public override TEnum Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
public override TEnum Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||||
{
|
{
|
||||||
var enumValue = reader.GetString();
|
var enumValue = reader.GetString();
|
||||||
if (string.IsNullOrEmpty(enumValue))
|
|
||||||
|
if (Enum.TryParse(enumValue, out TEnum value))
|
||||||
{
|
{
|
||||||
return default;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Enum.Parse<TEnum>(enumValue);
|
Logger.Warning?.Print(LogClass.Configuration, $"Failed to parse enum value \"{enumValue}\" for {typeof(TEnum)}, using default \"{default(TEnum)}\"");
|
||||||
|
return default;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Write(Utf8JsonWriter writer, TEnum value, JsonSerializerOptions options)
|
public override void Write(Utf8JsonWriter writer, TEnum value, JsonSerializerOptions options)
|
||||||
@ -31,4 +34,4 @@ namespace Ryujinx.Common.Utilities
|
|||||||
writer.WriteStringValue(value.ToString());
|
writer.WriteStringValue(value.ToString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -34,6 +34,8 @@ namespace Ryujinx.Graphics.GAL
|
|||||||
public readonly bool SupportsCubemapView;
|
public readonly bool SupportsCubemapView;
|
||||||
public readonly bool SupportsNonConstantTextureOffset;
|
public readonly bool SupportsNonConstantTextureOffset;
|
||||||
public readonly bool SupportsShaderBallot;
|
public readonly bool SupportsShaderBallot;
|
||||||
|
public readonly bool SupportsShaderBarrierDivergence;
|
||||||
|
public readonly bool SupportsShaderFloat64;
|
||||||
public readonly bool SupportsTextureShadowLod;
|
public readonly bool SupportsTextureShadowLod;
|
||||||
public readonly bool SupportsViewportIndexVertexTessellation;
|
public readonly bool SupportsViewportIndexVertexTessellation;
|
||||||
public readonly bool SupportsViewportMask;
|
public readonly bool SupportsViewportMask;
|
||||||
@ -81,6 +83,8 @@ namespace Ryujinx.Graphics.GAL
|
|||||||
bool supportsCubemapView,
|
bool supportsCubemapView,
|
||||||
bool supportsNonConstantTextureOffset,
|
bool supportsNonConstantTextureOffset,
|
||||||
bool supportsShaderBallot,
|
bool supportsShaderBallot,
|
||||||
|
bool supportsShaderBarrierDivergence,
|
||||||
|
bool supportsShaderFloat64,
|
||||||
bool supportsTextureShadowLod,
|
bool supportsTextureShadowLod,
|
||||||
bool supportsViewportIndexVertexTessellation,
|
bool supportsViewportIndexVertexTessellation,
|
||||||
bool supportsViewportMask,
|
bool supportsViewportMask,
|
||||||
@ -124,6 +128,8 @@ namespace Ryujinx.Graphics.GAL
|
|||||||
SupportsCubemapView = supportsCubemapView;
|
SupportsCubemapView = supportsCubemapView;
|
||||||
SupportsNonConstantTextureOffset = supportsNonConstantTextureOffset;
|
SupportsNonConstantTextureOffset = supportsNonConstantTextureOffset;
|
||||||
SupportsShaderBallot = supportsShaderBallot;
|
SupportsShaderBallot = supportsShaderBallot;
|
||||||
|
SupportsShaderBarrierDivergence = supportsShaderBarrierDivergence;
|
||||||
|
SupportsShaderFloat64 = supportsShaderFloat64;
|
||||||
SupportsTextureShadowLod = supportsTextureShadowLod;
|
SupportsTextureShadowLod = supportsTextureShadowLod;
|
||||||
SupportsViewportIndexVertexTessellation = supportsViewportIndexVertexTessellation;
|
SupportsViewportIndexVertexTessellation = supportsViewportIndexVertexTessellation;
|
||||||
SupportsViewportMask = supportsViewportMask;
|
SupportsViewportMask = supportsViewportMask;
|
||||||
|
@ -383,6 +383,7 @@ namespace Ryujinx.Graphics.GAL
|
|||||||
case Format.R10G10B10A2Unorm:
|
case Format.R10G10B10A2Unorm:
|
||||||
case Format.R10G10B10A2Uint:
|
case Format.R10G10B10A2Uint:
|
||||||
case Format.R11G11B10Float:
|
case Format.R11G11B10Float:
|
||||||
|
case Format.B8G8R8A8Unorm:
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -80,11 +80,6 @@ namespace Ryujinx.Graphics.Gpu
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public const int GobAlignment = 64;
|
public const int GobAlignment = 64;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Expected byte alignment for storage buffers
|
|
||||||
/// </summary>
|
|
||||||
public const int StorageAlignment = 16;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Number of the uniform buffer reserved by the driver to store the storage buffer base addresses.
|
/// Number of the uniform buffer reserved by the driver to store the storage buffer base addresses.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -151,8 +151,6 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute
|
|||||||
|
|
||||||
ShaderProgramInfo info = cs.Shaders[0].Info;
|
ShaderProgramInfo info = cs.Shaders[0].Info;
|
||||||
|
|
||||||
bool hasUnaligned = _channel.BufferManager.HasUnalignedStorageBuffers;
|
|
||||||
|
|
||||||
for (int index = 0; index < info.SBuffers.Count; index++)
|
for (int index = 0; index < info.SBuffers.Count; index++)
|
||||||
{
|
{
|
||||||
BufferDescriptor sb = info.SBuffers[index];
|
BufferDescriptor sb = info.SBuffers[index];
|
||||||
@ -177,9 +175,17 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute
|
|||||||
_channel.BufferManager.SetComputeStorageBuffer(sb.Slot, sbDescriptor.PackAddress(), size, sb.Flags);
|
_channel.BufferManager.SetComputeStorageBuffer(sb.Slot, sbDescriptor.PackAddress(), size, sb.Flags);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((_channel.BufferManager.HasUnalignedStorageBuffers) != hasUnaligned)
|
if (_channel.BufferManager.HasUnalignedStorageBuffers != computeState.HasUnalignedStorageBuffer)
|
||||||
{
|
{
|
||||||
// Refetch the shader, as assumptions about storage buffer alignment have changed.
|
// Refetch the shader, as assumptions about storage buffer alignment have changed.
|
||||||
|
computeState = new GpuChannelComputeState(
|
||||||
|
qmd.CtaThreadDimension0,
|
||||||
|
qmd.CtaThreadDimension1,
|
||||||
|
qmd.CtaThreadDimension2,
|
||||||
|
localMemorySize,
|
||||||
|
sharedMemorySize,
|
||||||
|
_channel.BufferManager.HasUnalignedStorageBuffers);
|
||||||
|
|
||||||
cs = memoryManager.Physical.ShaderCache.GetComputeShader(_channel, poolState, computeState, shaderGpuVa);
|
cs = memoryManager.Physical.ShaderCache.GetComputeShader(_channel, poolState, computeState, shaderGpuVa);
|
||||||
|
|
||||||
_context.Renderer.Pipeline.SetProgram(cs.HostProgram);
|
_context.Renderer.Pipeline.SetProgram(cs.HostProgram);
|
||||||
@ -187,30 +193,6 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute
|
|||||||
info = cs.Shaders[0].Info;
|
info = cs.Shaders[0].Info;
|
||||||
}
|
}
|
||||||
|
|
||||||
for (int index = 0; index < info.CBuffers.Count; index++)
|
|
||||||
{
|
|
||||||
BufferDescriptor cb = info.CBuffers[index];
|
|
||||||
|
|
||||||
// NVN uses the "hardware" constant buffer for anything that is less than 8,
|
|
||||||
// and those are already bound above.
|
|
||||||
// Anything greater than or equal to 8 uses the emulated constant buffers.
|
|
||||||
// They are emulated using global memory loads.
|
|
||||||
if (cb.Slot < 8)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
ulong cbDescAddress = _channel.BufferManager.GetComputeUniformBufferAddress(0);
|
|
||||||
|
|
||||||
int cbDescOffset = 0x260 + (cb.Slot - 8) * 0x10;
|
|
||||||
|
|
||||||
cbDescAddress += (ulong)cbDescOffset;
|
|
||||||
|
|
||||||
SbDescriptor cbDescriptor = _channel.MemoryManager.Physical.Read<SbDescriptor>(cbDescAddress);
|
|
||||||
|
|
||||||
_channel.BufferManager.SetComputeUniformBuffer(cb.Slot, cbDescriptor.PackAddress(), (uint)cbDescriptor.Size);
|
|
||||||
}
|
|
||||||
|
|
||||||
_channel.BufferManager.SetComputeBufferBindings(cs.Bindings);
|
_channel.BufferManager.SetComputeBufferBindings(cs.Bindings);
|
||||||
|
|
||||||
_channel.TextureManager.SetComputeBindings(cs.Bindings);
|
_channel.TextureManager.SetComputeBindings(cs.Bindings);
|
||||||
|
@ -541,7 +541,8 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
depth,
|
depth,
|
||||||
lhs.FormatInfo.BlockHeight,
|
lhs.FormatInfo.BlockHeight,
|
||||||
lhs.GobBlocksInY,
|
lhs.GobBlocksInY,
|
||||||
lhs.GobBlocksInZ);
|
lhs.GobBlocksInZ,
|
||||||
|
level);
|
||||||
|
|
||||||
return gobBlocksInY == rhs.GobBlocksInY &&
|
return gobBlocksInY == rhs.GobBlocksInY &&
|
||||||
gobBlocksInZ == rhs.GobBlocksInZ;
|
gobBlocksInZ == rhs.GobBlocksInZ;
|
||||||
@ -587,7 +588,8 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
lhsDepth,
|
lhsDepth,
|
||||||
lhs.FormatInfo.BlockHeight,
|
lhs.FormatInfo.BlockHeight,
|
||||||
lhs.GobBlocksInY,
|
lhs.GobBlocksInY,
|
||||||
lhs.GobBlocksInZ);
|
lhs.GobBlocksInZ,
|
||||||
|
lhsLevel);
|
||||||
|
|
||||||
int rhsHeight = Math.Max(1, rhs.Height >> rhsLevel);
|
int rhsHeight = Math.Max(1, rhs.Height >> rhsLevel);
|
||||||
int rhsDepth = Math.Max(1, rhs.GetDepth() >> rhsLevel);
|
int rhsDepth = Math.Max(1, rhs.GetDepth() >> rhsLevel);
|
||||||
@ -597,7 +599,8 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
rhsDepth,
|
rhsDepth,
|
||||||
rhs.FormatInfo.BlockHeight,
|
rhs.FormatInfo.BlockHeight,
|
||||||
rhs.GobBlocksInY,
|
rhs.GobBlocksInY,
|
||||||
rhs.GobBlocksInZ);
|
rhs.GobBlocksInZ,
|
||||||
|
rhsLevel);
|
||||||
|
|
||||||
return lhsGobBlocksInY == rhsGobBlocksInY &&
|
return lhsGobBlocksInY == rhsGobBlocksInY &&
|
||||||
lhsGobBlocksInZ == rhsGobBlocksInZ;
|
lhsGobBlocksInZ == rhsGobBlocksInZ;
|
||||||
|
@ -484,7 +484,7 @@ namespace Ryujinx.Graphics.Gpu.Image
|
|||||||
depthOrLayers = Math.Max(1, depthOrLayers >> minLod);
|
depthOrLayers = Math.Max(1, depthOrLayers >> minLod);
|
||||||
}
|
}
|
||||||
|
|
||||||
(gobBlocksInY, gobBlocksInZ) = SizeCalculator.GetMipGobBlockSizes(height, depth, formatInfo.BlockHeight, gobBlocksInY, gobBlocksInZ);
|
(gobBlocksInY, gobBlocksInZ) = SizeCalculator.GetMipGobBlockSizes(height, depth, formatInfo.BlockHeight, gobBlocksInY, gobBlocksInZ, minLod);
|
||||||
}
|
}
|
||||||
|
|
||||||
levels = (maxLod - minLod) + 1;
|
levels = (maxLod - minLod) + 1;
|
||||||
|
@ -222,7 +222,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
|||||||
/// <param name="gpuVa">Start GPU virtual address of the buffer</param>
|
/// <param name="gpuVa">Start GPU virtual address of the buffer</param>
|
||||||
private void RecordStorageAlignment(BuffersPerStage buffers, int index, ulong gpuVa)
|
private void RecordStorageAlignment(BuffersPerStage buffers, int index, ulong gpuVa)
|
||||||
{
|
{
|
||||||
bool unaligned = (gpuVa & (Constants.StorageAlignment - 1)) != 0;
|
bool unaligned = (gpuVa & ((ulong)_context.Capabilities.StorageBufferOffsetAlignment - 1)) != 0;
|
||||||
|
|
||||||
if (unaligned || HasUnalignedStorageBuffers)
|
if (unaligned || HasUnalignedStorageBuffers)
|
||||||
{
|
{
|
||||||
|
@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
|||||||
private const ushort FileFormatVersionMajor = 1;
|
private const ushort FileFormatVersionMajor = 1;
|
||||||
private const ushort FileFormatVersionMinor = 2;
|
private const ushort FileFormatVersionMinor = 2;
|
||||||
private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor;
|
private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor;
|
||||||
private const uint CodeGenVersion = 5027;
|
private const uint CodeGenVersion = 5044;
|
||||||
|
|
||||||
private const string SharedTocFileName = "shared.toc";
|
private const string SharedTocFileName = "shared.toc";
|
||||||
private const string SharedDataFileName = "shared.data";
|
private const string SharedDataFileName = "shared.data";
|
||||||
|
@ -141,6 +141,10 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||||||
|
|
||||||
public bool QueryHostSupportsShaderBallot() => _context.Capabilities.SupportsShaderBallot;
|
public bool QueryHostSupportsShaderBallot() => _context.Capabilities.SupportsShaderBallot;
|
||||||
|
|
||||||
|
public bool QueryHostSupportsShaderBarrierDivergence() => _context.Capabilities.SupportsShaderBarrierDivergence;
|
||||||
|
|
||||||
|
public bool QueryHostSupportsShaderFloat64() => _context.Capabilities.SupportsShaderFloat64;
|
||||||
|
|
||||||
public bool QueryHostSupportsSnormBufferTextureFormat() => _context.Capabilities.SupportsSnormBufferTextureFormat;
|
public bool QueryHostSupportsSnormBufferTextureFormat() => _context.Capabilities.SupportsSnormBufferTextureFormat;
|
||||||
|
|
||||||
public bool QueryHostSupportsTextureShadowLod() => _context.Capabilities.SupportsTextureShadowLod;
|
public bool QueryHostSupportsTextureShadowLod() => _context.Capabilities.SupportsTextureShadowLod;
|
||||||
|
@ -92,7 +92,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||||||
int imageBinding = stageIndex * imagesPerStage * 2;
|
int imageBinding = stageIndex * imagesPerStage * 2;
|
||||||
|
|
||||||
AddDescriptor(stages, ResourceType.UniformBuffer, UniformSetIndex, uniformBinding, uniformsPerStage);
|
AddDescriptor(stages, ResourceType.UniformBuffer, UniformSetIndex, uniformBinding, uniformsPerStage);
|
||||||
AddArrayDescriptor(stages, ResourceType.StorageBuffer, StorageSetIndex, storageBinding, storagesPerStage);
|
AddDescriptor(stages, ResourceType.StorageBuffer, StorageSetIndex, storageBinding, storagesPerStage);
|
||||||
AddDualDescriptor(stages, ResourceType.TextureAndSampler, ResourceType.BufferTexture, TextureSetIndex, textureBinding, texturesPerStage);
|
AddDualDescriptor(stages, ResourceType.TextureAndSampler, ResourceType.BufferTexture, TextureSetIndex, textureBinding, texturesPerStage);
|
||||||
AddDualDescriptor(stages, ResourceType.Image, ResourceType.BufferImage, ImageSetIndex, imageBinding, imagesPerStage);
|
AddDualDescriptor(stages, ResourceType.Image, ResourceType.BufferImage, ImageSetIndex, imageBinding, imagesPerStage);
|
||||||
|
|
||||||
@ -133,19 +133,6 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
|||||||
AddDescriptor(stages, type2, setIndex, binding + count, count);
|
AddDescriptor(stages, type2, setIndex, binding + count, count);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Adds an array resource to the list of descriptors.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="stages">Shader stages where the resource is used</param>
|
|
||||||
/// <param name="type">Type of the resource</param>
|
|
||||||
/// <param name="setIndex">Descriptor set number where the resource will be bound</param>
|
|
||||||
/// <param name="binding">Binding number where the resource will be bound</param>
|
|
||||||
/// <param name="count">Number of resources bound at the binding location</param>
|
|
||||||
private void AddArrayDescriptor(ResourceStages stages, ResourceType type, int setIndex, int binding, int count)
|
|
||||||
{
|
|
||||||
_resourceDescriptors[setIndex].Add(new ResourceDescriptor(binding, count, type, stages));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds buffer usage information to the list of usages.
|
/// Adds buffer usage information to the list of usages.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -127,6 +127,7 @@ namespace Ryujinx.Graphics.OpenGL
|
|||||||
public Capabilities GetCapabilities()
|
public Capabilities GetCapabilities()
|
||||||
{
|
{
|
||||||
bool intelWindows = HwCapabilities.Vendor == HwCapabilities.GpuVendor.IntelWindows;
|
bool intelWindows = HwCapabilities.Vendor == HwCapabilities.GpuVendor.IntelWindows;
|
||||||
|
bool intelUnix = HwCapabilities.Vendor == HwCapabilities.GpuVendor.IntelUnix;
|
||||||
bool amdWindows = HwCapabilities.Vendor == HwCapabilities.GpuVendor.AmdWindows;
|
bool amdWindows = HwCapabilities.Vendor == HwCapabilities.GpuVendor.AmdWindows;
|
||||||
|
|
||||||
return new Capabilities(
|
return new Capabilities(
|
||||||
@ -158,6 +159,8 @@ namespace Ryujinx.Graphics.OpenGL
|
|||||||
supportsCubemapView: true,
|
supportsCubemapView: true,
|
||||||
supportsNonConstantTextureOffset: HwCapabilities.SupportsNonConstantTextureOffset,
|
supportsNonConstantTextureOffset: HwCapabilities.SupportsNonConstantTextureOffset,
|
||||||
supportsShaderBallot: HwCapabilities.SupportsShaderBallot,
|
supportsShaderBallot: HwCapabilities.SupportsShaderBallot,
|
||||||
|
supportsShaderBarrierDivergence: !(intelWindows || intelUnix),
|
||||||
|
supportsShaderFloat64: true,
|
||||||
supportsTextureShadowLod: HwCapabilities.SupportsTextureShadowLod,
|
supportsTextureShadowLod: HwCapabilities.SupportsTextureShadowLod,
|
||||||
supportsViewportIndexVertexTessellation: HwCapabilities.SupportsShaderViewportLayerArray,
|
supportsViewportIndexVertexTessellation: HwCapabilities.SupportsShaderViewportLayerArray,
|
||||||
supportsViewportMask: HwCapabilities.SupportsViewportArray2,
|
supportsViewportMask: HwCapabilities.SupportsViewportArray2,
|
||||||
|
@ -104,14 +104,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
|||||||
}
|
}
|
||||||
|
|
||||||
DeclareConstantBuffers(context, context.Config.Properties.ConstantBuffers.Values);
|
DeclareConstantBuffers(context, context.Config.Properties.ConstantBuffers.Values);
|
||||||
|
DeclareStorageBuffers(context, context.Config.Properties.StorageBuffers.Values);
|
||||||
var sBufferDescriptors = context.Config.GetStorageBufferDescriptors();
|
|
||||||
if (sBufferDescriptors.Length != 0)
|
|
||||||
{
|
|
||||||
DeclareStorages(context, sBufferDescriptors);
|
|
||||||
|
|
||||||
context.AppendLine();
|
|
||||||
}
|
|
||||||
|
|
||||||
var textureDescriptors = context.Config.GetTextureDescriptors();
|
var textureDescriptors = context.Config.GetTextureDescriptors();
|
||||||
if (textureDescriptors.Length != 0)
|
if (textureDescriptors.Length != 0)
|
||||||
@ -250,11 +243,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
|||||||
AppendHelperFunction(context, "Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/AtomicMinMaxS32Shared.glsl");
|
AppendHelperFunction(context, "Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/AtomicMinMaxS32Shared.glsl");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((info.HelperFunctionsMask & HelperFunctionsMask.AtomicMinMaxS32Storage) != 0)
|
|
||||||
{
|
|
||||||
AppendHelperFunction(context, "Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/AtomicMinMaxS32Storage.glsl");
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((info.HelperFunctionsMask & HelperFunctionsMask.MultiplyHighS32) != 0)
|
if ((info.HelperFunctionsMask & HelperFunctionsMask.MultiplyHighS32) != 0)
|
||||||
{
|
{
|
||||||
AppendHelperFunction(context, "Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/MultiplyHighS32.glsl");
|
AppendHelperFunction(context, "Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/MultiplyHighS32.glsl");
|
||||||
@ -290,11 +278,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
|||||||
AppendHelperFunction(context, "Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/StoreSharedSmallInt.glsl");
|
AppendHelperFunction(context, "Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/StoreSharedSmallInt.glsl");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((info.HelperFunctionsMask & HelperFunctionsMask.StoreStorageSmallInt) != 0)
|
|
||||||
{
|
|
||||||
AppendHelperFunction(context, "Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/StoreStorageSmallInt.glsl");
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((info.HelperFunctionsMask & HelperFunctionsMask.SwizzleAdd) != 0)
|
if ((info.HelperFunctionsMask & HelperFunctionsMask.SwizzleAdd) != 0)
|
||||||
{
|
{
|
||||||
AppendHelperFunction(context, "Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/SwizzleAdd.glsl");
|
AppendHelperFunction(context, "Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/SwizzleAdd.glsl");
|
||||||
@ -356,6 +339,16 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void DeclareConstantBuffers(CodeGenContext context, IEnumerable<BufferDefinition> buffers)
|
private static void DeclareConstantBuffers(CodeGenContext context, IEnumerable<BufferDefinition> buffers)
|
||||||
|
{
|
||||||
|
DeclareBuffers(context, buffers, "uniform");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void DeclareStorageBuffers(CodeGenContext context, IEnumerable<BufferDefinition> buffers)
|
||||||
|
{
|
||||||
|
DeclareBuffers(context, buffers, "buffer");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void DeclareBuffers(CodeGenContext context, IEnumerable<BufferDefinition> buffers, string declType)
|
||||||
{
|
{
|
||||||
foreach (BufferDefinition buffer in buffers)
|
foreach (BufferDefinition buffer in buffers)
|
||||||
{
|
{
|
||||||
@ -365,7 +358,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
|||||||
_ => "std430"
|
_ => "std430"
|
||||||
};
|
};
|
||||||
|
|
||||||
context.AppendLine($"layout (binding = {buffer.Binding}, {layout}) uniform _{buffer.Name}");
|
context.AppendLine($"layout (binding = {buffer.Binding}, {layout}) {declType} _{buffer.Name}");
|
||||||
context.EnterScope();
|
context.EnterScope();
|
||||||
|
|
||||||
foreach (StructureField field in buffer.Type.Fields)
|
foreach (StructureField field in buffer.Type.Fields)
|
||||||
@ -373,9 +366,17 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
|||||||
if (field.Type.HasFlag(AggregateType.Array))
|
if (field.Type.HasFlag(AggregateType.Array))
|
||||||
{
|
{
|
||||||
string typeName = GetVarTypeName(context, field.Type & ~AggregateType.Array);
|
string typeName = GetVarTypeName(context, field.Type & ~AggregateType.Array);
|
||||||
string arraySize = field.ArrayLength.ToString(CultureInfo.InvariantCulture);
|
|
||||||
|
|
||||||
context.AppendLine($"{typeName} {field.Name}[{arraySize}];");
|
if (field.ArrayLength > 0)
|
||||||
|
{
|
||||||
|
string arraySize = field.ArrayLength.ToString(CultureInfo.InvariantCulture);
|
||||||
|
|
||||||
|
context.AppendLine($"{typeName} {field.Name}[{arraySize}];");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
context.AppendLine($"{typeName} {field.Name}[];");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@ -390,22 +391,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void DeclareStorages(CodeGenContext context, BufferDescriptor[] descriptors)
|
|
||||||
{
|
|
||||||
string sbName = OperandManager.GetShaderStagePrefix(context.Config.Stage);
|
|
||||||
|
|
||||||
sbName += "_" + DefaultNames.StorageNamePrefix;
|
|
||||||
|
|
||||||
string blockName = $"{sbName}_{DefaultNames.BlockSuffix}";
|
|
||||||
|
|
||||||
string layout = context.Config.Options.TargetApi == TargetApi.Vulkan ? ", set = 1" : string.Empty;
|
|
||||||
|
|
||||||
context.AppendLine($"layout (binding = {context.Config.FirstStorageBufferBinding}{layout}, std430) buffer {blockName}");
|
|
||||||
context.EnterScope();
|
|
||||||
context.AppendLine("uint " + DefaultNames.DataName + "[];");
|
|
||||||
context.LeaveScope($" {sbName}[{NumberFormatter.FormatInt(descriptors.Max(x => x.Slot) + 1)}];");
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void DeclareSamplers(CodeGenContext context, TextureDescriptor[] descriptors)
|
private static void DeclareSamplers(CodeGenContext context, TextureDescriptor[] descriptors)
|
||||||
{
|
{
|
||||||
int arraySize = 0;
|
int arraySize = 0;
|
||||||
@ -733,7 +718,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
|||||||
|
|
||||||
code = code.Replace("\t", CodeGenContext.Tab);
|
code = code.Replace("\t", CodeGenContext.Tab);
|
||||||
code = code.Replace("$SHARED_MEM$", DefaultNames.SharedMemoryName);
|
code = code.Replace("$SHARED_MEM$", DefaultNames.SharedMemoryName);
|
||||||
code = code.Replace("$STORAGE_MEM$", OperandManager.GetShaderStagePrefix(context.Config.Stage) + "_" + DefaultNames.StorageNamePrefix);
|
|
||||||
|
|
||||||
if (context.Config.GpuAccessor.QueryHostSupportsShaderBallot())
|
if (context.Config.GpuAccessor.QueryHostSupportsShaderBallot())
|
||||||
{
|
{
|
||||||
|
@ -11,12 +11,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
|||||||
public const string IAttributePrefix = "in_attr";
|
public const string IAttributePrefix = "in_attr";
|
||||||
public const string OAttributePrefix = "out_attr";
|
public const string OAttributePrefix = "out_attr";
|
||||||
|
|
||||||
public const string StorageNamePrefix = "s";
|
|
||||||
|
|
||||||
public const string DataName = "data";
|
|
||||||
|
|
||||||
public const string BlockSuffix = "block";
|
|
||||||
|
|
||||||
public const string LocalMemoryName = "local_mem";
|
public const string LocalMemoryName = "local_mem";
|
||||||
public const string SharedMemoryName = "shared_mem";
|
public const string SharedMemoryName = "shared_mem";
|
||||||
|
|
||||||
|
@ -28,18 +28,18 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
|||||||
|
|
||||||
for (int i = 1; i < info.Functions.Count; i++)
|
for (int i = 1; i < info.Functions.Count; i++)
|
||||||
{
|
{
|
||||||
PrintFunction(context, info, info.Functions[i]);
|
PrintFunction(context, info.Functions[i]);
|
||||||
|
|
||||||
context.AppendLine();
|
context.AppendLine();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PrintFunction(context, info, info.Functions[0], MainFunctionName);
|
PrintFunction(context, info.Functions[0], MainFunctionName);
|
||||||
|
|
||||||
return context.GetCode();
|
return context.GetCode();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void PrintFunction(CodeGenContext context, StructuredProgramInfo info, StructuredFunction function, string funcName = null)
|
private static void PrintFunction(CodeGenContext context, StructuredFunction function, string funcName = null)
|
||||||
{
|
{
|
||||||
context.CurrentFunction = function;
|
context.CurrentFunction = function;
|
||||||
|
|
||||||
@ -48,7 +48,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
|||||||
|
|
||||||
Declarations.DeclareLocals(context, function);
|
Declarations.DeclareLocals(context, function);
|
||||||
|
|
||||||
PrintBlock(context, function.MainBlock);
|
PrintBlock(context, function.MainBlock, funcName == MainFunctionName);
|
||||||
|
|
||||||
context.LeaveScope();
|
context.LeaveScope();
|
||||||
}
|
}
|
||||||
@ -72,7 +72,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
|||||||
return $"{Declarations.GetVarTypeName(context, function.ReturnType)} {funcName ?? function.Name}({string.Join(", ", args)})";
|
return $"{Declarations.GetVarTypeName(context, function.ReturnType)} {funcName ?? function.Name}({string.Join(", ", args)})";
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void PrintBlock(CodeGenContext context, AstBlock block)
|
private static void PrintBlock(CodeGenContext context, AstBlock block, bool isMainFunction)
|
||||||
{
|
{
|
||||||
AstBlockVisitor visitor = new AstBlockVisitor(block);
|
AstBlockVisitor visitor = new AstBlockVisitor(block);
|
||||||
|
|
||||||
@ -112,10 +112,32 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
bool supportsBarrierDivergence = context.Config.GpuAccessor.QueryHostSupportsShaderBarrierDivergence();
|
||||||
|
bool mayHaveReturned = false;
|
||||||
|
|
||||||
foreach (IAstNode node in visitor.Visit())
|
foreach (IAstNode node in visitor.Visit())
|
||||||
{
|
{
|
||||||
if (node is AstOperation operation)
|
if (node is AstOperation operation)
|
||||||
{
|
{
|
||||||
|
if (!supportsBarrierDivergence)
|
||||||
|
{
|
||||||
|
if (operation.Inst == IntermediateRepresentation.Instruction.Barrier)
|
||||||
|
{
|
||||||
|
// Barrier on divergent control flow paths may cause the GPU to hang,
|
||||||
|
// so skip emitting the barrier for those cases.
|
||||||
|
if (visitor.Block.Type != AstBlockType.Main || mayHaveReturned || !isMainFunction)
|
||||||
|
{
|
||||||
|
context.Config.GpuAccessor.Log($"Shader has barrier on potentially divergent block, the barrier will be removed.");
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (operation.Inst == IntermediateRepresentation.Instruction.Return)
|
||||||
|
{
|
||||||
|
mayHaveReturned = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
string expr = InstGen.GetExpression(context, operation);
|
string expr = InstGen.GetExpression(context, operation);
|
||||||
|
|
||||||
if (expr != null)
|
if (expr != null)
|
||||||
|
@ -1,21 +0,0 @@
|
|||||||
int Helper_AtomicMaxS32(int index, int offset, int value)
|
|
||||||
{
|
|
||||||
uint oldValue, newValue;
|
|
||||||
do
|
|
||||||
{
|
|
||||||
oldValue = $STORAGE_MEM$[index].data[offset];
|
|
||||||
newValue = uint(max(int(oldValue), value));
|
|
||||||
} while (atomicCompSwap($STORAGE_MEM$[index].data[offset], oldValue, newValue) != oldValue);
|
|
||||||
return int(oldValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
int Helper_AtomicMinS32(int index, int offset, int value)
|
|
||||||
{
|
|
||||||
uint oldValue, newValue;
|
|
||||||
do
|
|
||||||
{
|
|
||||||
oldValue = $STORAGE_MEM$[index].data[offset];
|
|
||||||
newValue = uint(min(int(oldValue), value));
|
|
||||||
} while (atomicCompSwap($STORAGE_MEM$[index].data[offset], oldValue, newValue) != oldValue);
|
|
||||||
return int(oldValue);
|
|
||||||
}
|
|
@ -1,23 +0,0 @@
|
|||||||
void Helper_StoreStorage16(int index, int offset, uint value)
|
|
||||||
{
|
|
||||||
int wordOffset = offset >> 2;
|
|
||||||
int bitOffset = (offset & 3) * 8;
|
|
||||||
uint oldValue, newValue;
|
|
||||||
do
|
|
||||||
{
|
|
||||||
oldValue = $STORAGE_MEM$[index].data[wordOffset];
|
|
||||||
newValue = bitfieldInsert(oldValue, value, bitOffset, 16);
|
|
||||||
} while (atomicCompSwap($STORAGE_MEM$[index].data[wordOffset], oldValue, newValue) != oldValue);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Helper_StoreStorage8(int index, int offset, uint value)
|
|
||||||
{
|
|
||||||
int wordOffset = offset >> 2;
|
|
||||||
int bitOffset = (offset & 3) * 8;
|
|
||||||
uint oldValue, newValue;
|
|
||||||
do
|
|
||||||
{
|
|
||||||
oldValue = $STORAGE_MEM$[index].data[wordOffset];
|
|
||||||
newValue = bitfieldInsert(oldValue, value, bitOffset, 8);
|
|
||||||
} while (atomicCompSwap($STORAGE_MEM$[index].data[wordOffset], oldValue, newValue) != oldValue);
|
|
||||||
}
|
|
@ -68,33 +68,45 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
|||||||
|
|
||||||
string args = string.Empty;
|
string args = string.Empty;
|
||||||
|
|
||||||
for (int argIndex = 0; argIndex < arity; argIndex++)
|
if (atomic && operation.StorageKind == StorageKind.StorageBuffer)
|
||||||
{
|
{
|
||||||
|
args = GenerateLoadOrStore(context, operation, isStore: false);
|
||||||
|
|
||||||
|
AggregateType dstType = operation.Inst == Instruction.AtomicMaxS32 || operation.Inst == Instruction.AtomicMinS32
|
||||||
|
? AggregateType.S32
|
||||||
|
: AggregateType.U32;
|
||||||
|
|
||||||
|
for (int argIndex = operation.SourcesCount - arity + 2; argIndex < operation.SourcesCount; argIndex++)
|
||||||
|
{
|
||||||
|
args += ", " + GetSoureExpr(context, operation.GetSource(argIndex), dstType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (atomic && operation.StorageKind == StorageKind.SharedMemory)
|
||||||
|
{
|
||||||
|
args = LoadShared(context, operation);
|
||||||
|
|
||||||
// For shared memory access, the second argument is unused and should be ignored.
|
// For shared memory access, the second argument is unused and should be ignored.
|
||||||
// It is there to make both storage and shared access have the same number of arguments.
|
// It is there to make both storage and shared access have the same number of arguments.
|
||||||
// For storage, both inputs are consumed when the argument index is 0, so we should skip it here.
|
// For storage, both inputs are consumed when the argument index is 0, so we should skip it here.
|
||||||
if (argIndex == 1 && (atomic || operation.StorageKind == StorageKind.SharedMemory))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (argIndex != 0)
|
for (int argIndex = 2; argIndex < arity; argIndex++)
|
||||||
{
|
{
|
||||||
args += ", ";
|
args += ", ";
|
||||||
}
|
|
||||||
|
|
||||||
if (argIndex == 0 && atomic)
|
AggregateType dstType = GetSrcVarType(inst, argIndex);
|
||||||
|
|
||||||
|
args += GetSoureExpr(context, operation.GetSource(argIndex), dstType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
for (int argIndex = 0; argIndex < arity; argIndex++)
|
||||||
{
|
{
|
||||||
switch (operation.StorageKind)
|
if (argIndex != 0)
|
||||||
{
|
{
|
||||||
case StorageKind.SharedMemory: args += LoadShared(context, operation); break;
|
args += ", ";
|
||||||
case StorageKind.StorageBuffer: args += LoadStorage(context, operation); break;
|
|
||||||
|
|
||||||
default: throw new InvalidOperationException($"Invalid storage kind \"{operation.StorageKind}\".");
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
AggregateType dstType = GetSrcVarType(inst, argIndex);
|
AggregateType dstType = GetSrcVarType(inst, argIndex);
|
||||||
|
|
||||||
args += GetSoureExpr(context, operation.GetSource(argIndex), dstType);
|
args += GetSoureExpr(context, operation.GetSource(argIndex), dstType);
|
||||||
@ -173,9 +185,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
|||||||
case Instruction.LoadShared:
|
case Instruction.LoadShared:
|
||||||
return LoadShared(context, operation);
|
return LoadShared(context, operation);
|
||||||
|
|
||||||
case Instruction.LoadStorage:
|
|
||||||
return LoadStorage(context, operation);
|
|
||||||
|
|
||||||
case Instruction.Lod:
|
case Instruction.Lod:
|
||||||
return Lod(context, operation);
|
return Lod(context, operation);
|
||||||
|
|
||||||
@ -203,15 +212,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
|||||||
case Instruction.StoreShared8:
|
case Instruction.StoreShared8:
|
||||||
return StoreShared8(context, operation);
|
return StoreShared8(context, operation);
|
||||||
|
|
||||||
case Instruction.StoreStorage:
|
|
||||||
return StoreStorage(context, operation);
|
|
||||||
|
|
||||||
case Instruction.StoreStorage16:
|
|
||||||
return StoreStorage16(context, operation);
|
|
||||||
|
|
||||||
case Instruction.StoreStorage8:
|
|
||||||
return StoreStorage8(context, operation);
|
|
||||||
|
|
||||||
case Instruction.TextureSample:
|
case Instruction.TextureSample:
|
||||||
return TextureSample(context, operation);
|
return TextureSample(context, operation);
|
||||||
|
|
||||||
|
@ -85,7 +85,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
|||||||
Add(Instruction.Load, InstType.Special);
|
Add(Instruction.Load, InstType.Special);
|
||||||
Add(Instruction.LoadLocal, InstType.Special);
|
Add(Instruction.LoadLocal, InstType.Special);
|
||||||
Add(Instruction.LoadShared, InstType.Special);
|
Add(Instruction.LoadShared, InstType.Special);
|
||||||
Add(Instruction.LoadStorage, InstType.Special);
|
|
||||||
Add(Instruction.Lod, InstType.Special);
|
Add(Instruction.Lod, InstType.Special);
|
||||||
Add(Instruction.LogarithmB2, InstType.CallUnary, "log2");
|
Add(Instruction.LogarithmB2, InstType.CallUnary, "log2");
|
||||||
Add(Instruction.LogicalAnd, InstType.OpBinaryCom, "&&", 9);
|
Add(Instruction.LogicalAnd, InstType.OpBinaryCom, "&&", 9);
|
||||||
@ -123,9 +122,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
|||||||
Add(Instruction.StoreShared, InstType.Special);
|
Add(Instruction.StoreShared, InstType.Special);
|
||||||
Add(Instruction.StoreShared16, InstType.Special);
|
Add(Instruction.StoreShared16, InstType.Special);
|
||||||
Add(Instruction.StoreShared8, InstType.Special);
|
Add(Instruction.StoreShared8, InstType.Special);
|
||||||
Add(Instruction.StoreStorage, InstType.Special);
|
|
||||||
Add(Instruction.StoreStorage16, InstType.Special);
|
|
||||||
Add(Instruction.StoreStorage8, InstType.Special);
|
|
||||||
Add(Instruction.Subtract, InstType.OpBinary, "-", 2);
|
Add(Instruction.Subtract, InstType.OpBinary, "-", 2);
|
||||||
Add(Instruction.SwizzleAdd, InstType.CallTernary, HelperFunctionNames.SwizzleAdd);
|
Add(Instruction.SwizzleAdd, InstType.CallTernary, HelperFunctionNames.SwizzleAdd);
|
||||||
Add(Instruction.TextureSample, InstType.Special);
|
Add(Instruction.TextureSample, InstType.Special);
|
||||||
|
@ -210,17 +210,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
|||||||
return $"{arrayName}[{offsetExpr}]";
|
return $"{arrayName}[{offsetExpr}]";
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string LoadStorage(CodeGenContext context, AstOperation operation)
|
|
||||||
{
|
|
||||||
IAstNode src1 = operation.GetSource(0);
|
|
||||||
IAstNode src2 = operation.GetSource(1);
|
|
||||||
|
|
||||||
string indexExpr = GetSoureExpr(context, src1, GetSrcVarType(operation.Inst, 0));
|
|
||||||
string offsetExpr = GetSoureExpr(context, src2, GetSrcVarType(operation.Inst, 1));
|
|
||||||
|
|
||||||
return GetStorageBufferAccessor(indexExpr, offsetExpr, context.Config.Stage);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string Lod(CodeGenContext context, AstOperation operation)
|
public static string Lod(CodeGenContext context, AstOperation operation)
|
||||||
{
|
{
|
||||||
AstTextureOperation texOp = (AstTextureOperation)operation;
|
AstTextureOperation texOp = (AstTextureOperation)operation;
|
||||||
@ -326,60 +315,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
|||||||
return $"{HelperFunctionNames.StoreShared8}({offsetExpr}, {src})";
|
return $"{HelperFunctionNames.StoreShared8}({offsetExpr}, {src})";
|
||||||
}
|
}
|
||||||
|
|
||||||
public static string StoreStorage(CodeGenContext context, AstOperation operation)
|
|
||||||
{
|
|
||||||
IAstNode src1 = operation.GetSource(0);
|
|
||||||
IAstNode src2 = operation.GetSource(1);
|
|
||||||
IAstNode src3 = operation.GetSource(2);
|
|
||||||
|
|
||||||
string indexExpr = GetSoureExpr(context, src1, GetSrcVarType(operation.Inst, 0));
|
|
||||||
string offsetExpr = GetSoureExpr(context, src2, GetSrcVarType(operation.Inst, 1));
|
|
||||||
|
|
||||||
AggregateType srcType = OperandManager.GetNodeDestType(context, src3);
|
|
||||||
|
|
||||||
string src = TypeConversion.ReinterpretCast(context, src3, srcType, AggregateType.U32);
|
|
||||||
|
|
||||||
string sb = GetStorageBufferAccessor(indexExpr, offsetExpr, context.Config.Stage);
|
|
||||||
|
|
||||||
return $"{sb} = {src}";
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string StoreStorage16(CodeGenContext context, AstOperation operation)
|
|
||||||
{
|
|
||||||
IAstNode src1 = operation.GetSource(0);
|
|
||||||
IAstNode src2 = operation.GetSource(1);
|
|
||||||
IAstNode src3 = operation.GetSource(2);
|
|
||||||
|
|
||||||
string indexExpr = GetSoureExpr(context, src1, GetSrcVarType(operation.Inst, 0));
|
|
||||||
string offsetExpr = GetSoureExpr(context, src2, GetSrcVarType(operation.Inst, 1));
|
|
||||||
|
|
||||||
AggregateType srcType = OperandManager.GetNodeDestType(context, src3);
|
|
||||||
|
|
||||||
string src = TypeConversion.ReinterpretCast(context, src3, srcType, AggregateType.U32);
|
|
||||||
|
|
||||||
string sb = GetStorageBufferAccessor(indexExpr, offsetExpr, context.Config.Stage);
|
|
||||||
|
|
||||||
return $"{HelperFunctionNames.StoreStorage16}({indexExpr}, {offsetExpr}, {src})";
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string StoreStorage8(CodeGenContext context, AstOperation operation)
|
|
||||||
{
|
|
||||||
IAstNode src1 = operation.GetSource(0);
|
|
||||||
IAstNode src2 = operation.GetSource(1);
|
|
||||||
IAstNode src3 = operation.GetSource(2);
|
|
||||||
|
|
||||||
string indexExpr = GetSoureExpr(context, src1, GetSrcVarType(operation.Inst, 0));
|
|
||||||
string offsetExpr = GetSoureExpr(context, src2, GetSrcVarType(operation.Inst, 1));
|
|
||||||
|
|
||||||
AggregateType srcType = OperandManager.GetNodeDestType(context, src3);
|
|
||||||
|
|
||||||
string src = TypeConversion.ReinterpretCast(context, src3, srcType, AggregateType.U32);
|
|
||||||
|
|
||||||
string sb = GetStorageBufferAccessor(indexExpr, offsetExpr, context.Config.Stage);
|
|
||||||
|
|
||||||
return $"{HelperFunctionNames.StoreStorage8}({indexExpr}, {offsetExpr}, {src})";
|
|
||||||
}
|
|
||||||
|
|
||||||
public static string TextureSample(CodeGenContext context, AstOperation operation)
|
public static string TextureSample(CodeGenContext context, AstOperation operation)
|
||||||
{
|
{
|
||||||
AstTextureOperation texOp = (AstTextureOperation)operation;
|
AstTextureOperation texOp = (AstTextureOperation)operation;
|
||||||
@ -701,25 +636,34 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string GenerateLoadOrStore(CodeGenContext context, AstOperation operation, bool isStore)
|
public static string GenerateLoadOrStore(CodeGenContext context, AstOperation operation, bool isStore)
|
||||||
{
|
{
|
||||||
StorageKind storageKind = operation.StorageKind;
|
StorageKind storageKind = operation.StorageKind;
|
||||||
|
|
||||||
string varName;
|
string varName;
|
||||||
AggregateType varType;
|
AggregateType varType;
|
||||||
int srcIndex = 0;
|
int srcIndex = 0;
|
||||||
int inputsCount = isStore ? operation.SourcesCount - 1 : operation.SourcesCount;
|
bool isStoreOrAtomic = operation.Inst == Instruction.Store || operation.Inst.IsAtomic();
|
||||||
|
int inputsCount = isStoreOrAtomic ? operation.SourcesCount - 1 : operation.SourcesCount;
|
||||||
|
|
||||||
|
if (operation.Inst == Instruction.AtomicCompareAndSwap)
|
||||||
|
{
|
||||||
|
inputsCount--;
|
||||||
|
}
|
||||||
|
|
||||||
switch (storageKind)
|
switch (storageKind)
|
||||||
{
|
{
|
||||||
case StorageKind.ConstantBuffer:
|
case StorageKind.ConstantBuffer:
|
||||||
|
case StorageKind.StorageBuffer:
|
||||||
if (!(operation.GetSource(srcIndex++) is AstOperand bindingIndex) || bindingIndex.Type != OperandType.Constant)
|
if (!(operation.GetSource(srcIndex++) is AstOperand bindingIndex) || bindingIndex.Type != OperandType.Constant)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException($"First input of {operation.Inst} with {storageKind} storage must be a constant operand.");
|
throw new InvalidOperationException($"First input of {operation.Inst} with {storageKind} storage must be a constant operand.");
|
||||||
}
|
}
|
||||||
|
|
||||||
int binding = bindingIndex.Value;
|
int binding = bindingIndex.Value;
|
||||||
BufferDefinition buffer = context.Config.Properties.ConstantBuffers[binding];
|
BufferDefinition buffer = storageKind == StorageKind.ConstantBuffer
|
||||||
|
? context.Config.Properties.ConstantBuffers[binding]
|
||||||
|
: context.Config.Properties.StorageBuffers[binding];
|
||||||
|
|
||||||
if (!(operation.GetSource(srcIndex++) is AstOperand fieldIndex) || fieldIndex.Type != OperandType.Constant)
|
if (!(operation.GetSource(srcIndex++) is AstOperand fieldIndex) || fieldIndex.Type != OperandType.Constant)
|
||||||
{
|
{
|
||||||
@ -825,15 +769,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
|||||||
return varName;
|
return varName;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static string GetStorageBufferAccessor(string slotExpr, string offsetExpr, ShaderStage stage)
|
|
||||||
{
|
|
||||||
string sbName = OperandManager.GetShaderStagePrefix(stage);
|
|
||||||
|
|
||||||
sbName += "_" + DefaultNames.StorageNamePrefix;
|
|
||||||
|
|
||||||
return $"{sbName}[{slotExpr}].{DefaultNames.DataName}[{offsetExpr}]";
|
|
||||||
}
|
|
||||||
|
|
||||||
private static string GetMask(int index)
|
private static string GetMask(int index)
|
||||||
{
|
{
|
||||||
return $".{"rgba".AsSpan(index, 1)}";
|
return $".{"rgba".AsSpan(index, 1)}";
|
||||||
|
@ -118,6 +118,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
|||||||
switch (operation.StorageKind)
|
switch (operation.StorageKind)
|
||||||
{
|
{
|
||||||
case StorageKind.ConstantBuffer:
|
case StorageKind.ConstantBuffer:
|
||||||
|
case StorageKind.StorageBuffer:
|
||||||
if (!(operation.GetSource(0) is AstOperand bindingIndex) || bindingIndex.Type != OperandType.Constant)
|
if (!(operation.GetSource(0) is AstOperand bindingIndex) || bindingIndex.Type != OperandType.Constant)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException($"First input of {operation.Inst} with {operation.StorageKind} storage must be a constant operand.");
|
throw new InvalidOperationException($"First input of {operation.Inst} with {operation.StorageKind} storage must be a constant operand.");
|
||||||
@ -128,7 +129,9 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
|||||||
throw new InvalidOperationException($"Second input of {operation.Inst} with {operation.StorageKind} storage must be a constant operand.");
|
throw new InvalidOperationException($"Second input of {operation.Inst} with {operation.StorageKind} storage must be a constant operand.");
|
||||||
}
|
}
|
||||||
|
|
||||||
BufferDefinition buffer = context.Config.Properties.ConstantBuffers[bindingIndex.Value];
|
BufferDefinition buffer = operation.StorageKind == StorageKind.ConstantBuffer
|
||||||
|
? context.Config.Properties.ConstantBuffers[bindingIndex.Value]
|
||||||
|
: context.Config.Properties.StorageBuffers[bindingIndex.Value];
|
||||||
StructureField field = buffer.Type.Fields[fieldIndex.Value];
|
StructureField field = buffer.Type.Fields[fieldIndex.Value];
|
||||||
|
|
||||||
return field.Type & AggregateType.ElementTypeMask;
|
return field.Type & AggregateType.ElementTypeMask;
|
||||||
|
@ -24,7 +24,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
|||||||
public int InputVertices { get; }
|
public int InputVertices { get; }
|
||||||
|
|
||||||
public Dictionary<int, Instruction> ConstantBuffers { get; } = new Dictionary<int, Instruction>();
|
public Dictionary<int, Instruction> ConstantBuffers { get; } = new Dictionary<int, Instruction>();
|
||||||
public Instruction StorageBuffersArray { get; set; }
|
public Dictionary<int, Instruction> StorageBuffers { get; } = new Dictionary<int, Instruction>();
|
||||||
public Instruction LocalMemory { get; set; }
|
public Instruction LocalMemory { get; set; }
|
||||||
public Instruction SharedMemory { get; set; }
|
public Instruction SharedMemory { get; set; }
|
||||||
public Dictionary<TextureMeta, SamplerType> SamplersTypes { get; } = new Dictionary<TextureMeta, SamplerType>();
|
public Dictionary<TextureMeta, SamplerType> SamplersTypes { get; } = new Dictionary<TextureMeta, SamplerType>();
|
||||||
@ -76,6 +76,9 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
|||||||
|
|
||||||
public SpirvDelegates Delegates { get; }
|
public SpirvDelegates Delegates { get; }
|
||||||
|
|
||||||
|
public bool IsMainFunction { get; private set; }
|
||||||
|
public bool MayHaveReturned { get; set; }
|
||||||
|
|
||||||
public CodeGenContext(
|
public CodeGenContext(
|
||||||
StructuredProgramInfo info,
|
StructuredProgramInfo info,
|
||||||
ShaderConfig config,
|
ShaderConfig config,
|
||||||
@ -108,8 +111,10 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
|||||||
Delegates = new SpirvDelegates(this);
|
Delegates = new SpirvDelegates(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void StartFunction()
|
public void StartFunction(bool isMainFunction)
|
||||||
{
|
{
|
||||||
|
IsMainFunction = isMainFunction;
|
||||||
|
MayHaveReturned = false;
|
||||||
_locals.Clear();
|
_locals.Clear();
|
||||||
_localForArgs.Clear();
|
_localForArgs.Clear();
|
||||||
_funcArgs.Clear();
|
_funcArgs.Clear();
|
||||||
@ -308,7 +313,14 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
|||||||
{
|
{
|
||||||
if ((type & AggregateType.Array) != 0)
|
if ((type & AggregateType.Array) != 0)
|
||||||
{
|
{
|
||||||
return TypeArray(GetType(type & ~AggregateType.Array), Constant(TypeU32(), length));
|
if (length > 0)
|
||||||
|
{
|
||||||
|
return TypeArray(GetType(type & ~AggregateType.Array), Constant(TypeU32(), length));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return TypeRuntimeArray(GetType(type & ~AggregateType.Array));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else if ((type & AggregateType.ElementCountMask) != 0)
|
else if ((type & AggregateType.ElementCountMask) != 0)
|
||||||
{
|
{
|
||||||
|
@ -5,6 +5,7 @@ using Ryujinx.Graphics.Shader.Translation;
|
|||||||
using Spv.Generator;
|
using Spv.Generator;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using static Spv.Specification;
|
using static Spv.Specification;
|
||||||
@ -99,7 +100,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
|||||||
}
|
}
|
||||||
|
|
||||||
DeclareConstantBuffers(context, context.Config.Properties.ConstantBuffers.Values);
|
DeclareConstantBuffers(context, context.Config.Properties.ConstantBuffers.Values);
|
||||||
DeclareStorageBuffers(context, context.Config.GetStorageBufferDescriptors());
|
DeclareStorageBuffers(context, context.Config.Properties.StorageBuffers.Values);
|
||||||
DeclareSamplers(context, context.Config.GetTextureDescriptors());
|
DeclareSamplers(context, context.Config.GetTextureDescriptors());
|
||||||
DeclareImages(context, context.Config.GetImageDescriptors());
|
DeclareImages(context, context.Config.GetImageDescriptors());
|
||||||
DeclareInputsAndOutputs(context, info);
|
DeclareInputsAndOutputs(context, info);
|
||||||
@ -127,6 +128,16 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void DeclareConstantBuffers(CodeGenContext context, IEnumerable<BufferDefinition> buffers)
|
private static void DeclareConstantBuffers(CodeGenContext context, IEnumerable<BufferDefinition> buffers)
|
||||||
|
{
|
||||||
|
DeclareBuffers(context, buffers, isBuffer: false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void DeclareStorageBuffers(CodeGenContext context, IEnumerable<BufferDefinition> buffers)
|
||||||
|
{
|
||||||
|
DeclareBuffers(context, buffers, isBuffer: true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void DeclareBuffers(CodeGenContext context, IEnumerable<BufferDefinition> buffers, bool isBuffer)
|
||||||
{
|
{
|
||||||
HashSet<SpvInstruction> decoratedTypes = new HashSet<SpvInstruction>();
|
HashSet<SpvInstruction> decoratedTypes = new HashSet<SpvInstruction>();
|
||||||
|
|
||||||
@ -155,6 +166,12 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
|||||||
context.Decorate(structFieldTypes[fieldIndex], Decoration.ArrayStride, (LiteralInteger)fieldSize);
|
context.Decorate(structFieldTypes[fieldIndex], Decoration.ArrayStride, (LiteralInteger)fieldSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Zero lengths are assumed to be a "runtime array" (which does not have a explicit length
|
||||||
|
// specified on the shader, and instead assumes the bound buffer length).
|
||||||
|
// It is only valid as the last struct element.
|
||||||
|
|
||||||
|
Debug.Assert(field.ArrayLength > 0 || fieldIndex == buffer.Type.Fields.Length - 1);
|
||||||
|
|
||||||
offset += fieldSize * field.ArrayLength;
|
offset += fieldSize * field.ArrayLength;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -163,56 +180,37 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var ubStructType = context.TypeStruct(false, structFieldTypes);
|
var structType = context.TypeStruct(false, structFieldTypes);
|
||||||
|
|
||||||
if (decoratedTypes.Add(ubStructType))
|
if (decoratedTypes.Add(structType))
|
||||||
{
|
{
|
||||||
context.Decorate(ubStructType, Decoration.Block);
|
context.Decorate(structType, isBuffer ? Decoration.BufferBlock : Decoration.Block);
|
||||||
|
|
||||||
for (int fieldIndex = 0; fieldIndex < structFieldOffsets.Length; fieldIndex++)
|
for (int fieldIndex = 0; fieldIndex < structFieldOffsets.Length; fieldIndex++)
|
||||||
{
|
{
|
||||||
context.MemberDecorate(ubStructType, fieldIndex, Decoration.Offset, (LiteralInteger)structFieldOffsets[fieldIndex]);
|
context.MemberDecorate(structType, fieldIndex, Decoration.Offset, (LiteralInteger)structFieldOffsets[fieldIndex]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var ubPointerType = context.TypePointer(StorageClass.Uniform, ubStructType);
|
var pointerType = context.TypePointer(StorageClass.Uniform, structType);
|
||||||
var ubVariable = context.Variable(ubPointerType, StorageClass.Uniform);
|
var variable = context.Variable(pointerType, StorageClass.Uniform);
|
||||||
|
|
||||||
context.Name(ubVariable, buffer.Name);
|
context.Name(variable, buffer.Name);
|
||||||
context.Decorate(ubVariable, Decoration.DescriptorSet, (LiteralInteger)buffer.Set);
|
context.Decorate(variable, Decoration.DescriptorSet, (LiteralInteger)buffer.Set);
|
||||||
context.Decorate(ubVariable, Decoration.Binding, (LiteralInteger)buffer.Binding);
|
context.Decorate(variable, Decoration.Binding, (LiteralInteger)buffer.Binding);
|
||||||
context.AddGlobalVariable(ubVariable);
|
context.AddGlobalVariable(variable);
|
||||||
context.ConstantBuffers.Add(buffer.Binding, ubVariable);
|
|
||||||
|
if (isBuffer)
|
||||||
|
{
|
||||||
|
context.StorageBuffers.Add(buffer.Binding, variable);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
context.ConstantBuffers.Add(buffer.Binding, variable);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void DeclareStorageBuffers(CodeGenContext context, BufferDescriptor[] descriptors)
|
|
||||||
{
|
|
||||||
if (descriptors.Length == 0)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
int setIndex = context.Config.Options.TargetApi == TargetApi.Vulkan ? 1 : 0;
|
|
||||||
int count = descriptors.Max(x => x.Slot) + 1;
|
|
||||||
|
|
||||||
var sbArrayType = context.TypeRuntimeArray(context.TypeU32());
|
|
||||||
context.Decorate(sbArrayType, Decoration.ArrayStride, (LiteralInteger)4);
|
|
||||||
var sbStructType = context.TypeStruct(true, sbArrayType);
|
|
||||||
context.Decorate(sbStructType, Decoration.BufferBlock);
|
|
||||||
context.MemberDecorate(sbStructType, 0, Decoration.Offset, (LiteralInteger)0);
|
|
||||||
var sbStructArrayType = context.TypeArray(sbStructType, context.Constant(context.TypeU32(), count));
|
|
||||||
var sbPointerType = context.TypePointer(StorageClass.Uniform, sbStructArrayType);
|
|
||||||
var sbVariable = context.Variable(sbPointerType, StorageClass.Uniform);
|
|
||||||
|
|
||||||
context.Name(sbVariable, $"{GetStagePrefix(context.Config.Stage)}_s");
|
|
||||||
context.Decorate(sbVariable, Decoration.DescriptorSet, (LiteralInteger)setIndex);
|
|
||||||
context.Decorate(sbVariable, Decoration.Binding, (LiteralInteger)context.Config.FirstStorageBufferBinding);
|
|
||||||
context.AddGlobalVariable(sbVariable);
|
|
||||||
|
|
||||||
context.StorageBuffersArray = sbVariable;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void DeclareSamplers(CodeGenContext context, TextureDescriptor[] descriptors)
|
private static void DeclareSamplers(CodeGenContext context, TextureDescriptor[] descriptors)
|
||||||
{
|
{
|
||||||
foreach (var descriptor in descriptors)
|
foreach (var descriptor in descriptors)
|
||||||
|
@ -99,7 +99,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
|||||||
Add(Instruction.Load, GenerateLoad);
|
Add(Instruction.Load, GenerateLoad);
|
||||||
Add(Instruction.LoadLocal, GenerateLoadLocal);
|
Add(Instruction.LoadLocal, GenerateLoadLocal);
|
||||||
Add(Instruction.LoadShared, GenerateLoadShared);
|
Add(Instruction.LoadShared, GenerateLoadShared);
|
||||||
Add(Instruction.LoadStorage, GenerateLoadStorage);
|
|
||||||
Add(Instruction.Lod, GenerateLod);
|
Add(Instruction.Lod, GenerateLod);
|
||||||
Add(Instruction.LogarithmB2, GenerateLogarithmB2);
|
Add(Instruction.LogarithmB2, GenerateLogarithmB2);
|
||||||
Add(Instruction.LogicalAnd, GenerateLogicalAnd);
|
Add(Instruction.LogicalAnd, GenerateLogicalAnd);
|
||||||
@ -137,9 +136,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
|||||||
Add(Instruction.StoreShared, GenerateStoreShared);
|
Add(Instruction.StoreShared, GenerateStoreShared);
|
||||||
Add(Instruction.StoreShared16, GenerateStoreShared16);
|
Add(Instruction.StoreShared16, GenerateStoreShared16);
|
||||||
Add(Instruction.StoreShared8, GenerateStoreShared8);
|
Add(Instruction.StoreShared8, GenerateStoreShared8);
|
||||||
Add(Instruction.StoreStorage, GenerateStoreStorage);
|
|
||||||
Add(Instruction.StoreStorage16, GenerateStoreStorage16);
|
|
||||||
Add(Instruction.StoreStorage8, GenerateStoreStorage8);
|
|
||||||
Add(Instruction.Subtract, GenerateSubtract);
|
Add(Instruction.Subtract, GenerateSubtract);
|
||||||
Add(Instruction.SwizzleAdd, GenerateSwizzleAdd);
|
Add(Instruction.SwizzleAdd, GenerateSwizzleAdd);
|
||||||
Add(Instruction.TextureSample, GenerateTextureSample);
|
Add(Instruction.TextureSample, GenerateTextureSample);
|
||||||
@ -246,6 +242,16 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
|||||||
|
|
||||||
private static OperationResult GenerateBarrier(CodeGenContext context, AstOperation operation)
|
private static OperationResult GenerateBarrier(CodeGenContext context, AstOperation operation)
|
||||||
{
|
{
|
||||||
|
// Barrier on divergent control flow paths may cause the GPU to hang,
|
||||||
|
// so skip emitting the barrier for those cases.
|
||||||
|
if (!context.Config.GpuAccessor.QueryHostSupportsShaderBarrierDivergence() &&
|
||||||
|
(context.CurrentBlock.Type != AstBlockType.Main || context.MayHaveReturned || !context.IsMainFunction))
|
||||||
|
{
|
||||||
|
context.Config.GpuAccessor.Log($"Shader has barrier on potentially divergent block, the barrier will be removed.");
|
||||||
|
|
||||||
|
return OperationResult.Invalid;
|
||||||
|
}
|
||||||
|
|
||||||
context.ControlBarrier(
|
context.ControlBarrier(
|
||||||
context.Constant(context.TypeU32(), Scope.Workgroup),
|
context.Constant(context.TypeU32(), Scope.Workgroup),
|
||||||
context.Constant(context.TypeU32(), Scope.Workgroup),
|
context.Constant(context.TypeU32(), Scope.Workgroup),
|
||||||
@ -889,14 +895,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
|||||||
return new OperationResult(AggregateType.U32, value);
|
return new OperationResult(AggregateType.U32, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static OperationResult GenerateLoadStorage(CodeGenContext context, AstOperation operation)
|
|
||||||
{
|
|
||||||
var elemPointer = GetStorageElemPointer(context, operation);
|
|
||||||
var value = context.Load(context.TypeU32(), elemPointer);
|
|
||||||
|
|
||||||
return new OperationResult(AggregateType.U32, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static OperationResult GenerateLod(CodeGenContext context, AstOperation operation)
|
private static OperationResult GenerateLod(CodeGenContext context, AstOperation operation)
|
||||||
{
|
{
|
||||||
AstTextureOperation texOp = (AstTextureOperation)operation;
|
AstTextureOperation texOp = (AstTextureOperation)operation;
|
||||||
@ -1104,6 +1102,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
|||||||
|
|
||||||
private static OperationResult GenerateReturn(CodeGenContext context, AstOperation operation)
|
private static OperationResult GenerateReturn(CodeGenContext context, AstOperation operation)
|
||||||
{
|
{
|
||||||
|
context.MayHaveReturned = true;
|
||||||
|
|
||||||
if (operation.SourcesCount != 0)
|
if (operation.SourcesCount != 0)
|
||||||
{
|
{
|
||||||
context.ReturnValue(context.Get(context.CurrentFunction.ReturnType, operation.GetSource(0)));
|
context.ReturnValue(context.Get(context.CurrentFunction.ReturnType, operation.GetSource(0)));
|
||||||
@ -1307,28 +1307,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
|||||||
return OperationResult.Invalid;
|
return OperationResult.Invalid;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static OperationResult GenerateStoreStorage(CodeGenContext context, AstOperation operation)
|
|
||||||
{
|
|
||||||
var elemPointer = GetStorageElemPointer(context, operation);
|
|
||||||
context.Store(elemPointer, context.Get(AggregateType.U32, operation.GetSource(2)));
|
|
||||||
|
|
||||||
return OperationResult.Invalid;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static OperationResult GenerateStoreStorage16(CodeGenContext context, AstOperation operation)
|
|
||||||
{
|
|
||||||
GenerateStoreStorageSmallInt(context, operation, 16);
|
|
||||||
|
|
||||||
return OperationResult.Invalid;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static OperationResult GenerateStoreStorage8(CodeGenContext context, AstOperation operation)
|
|
||||||
{
|
|
||||||
GenerateStoreStorageSmallInt(context, operation, 8);
|
|
||||||
|
|
||||||
return OperationResult.Invalid;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static OperationResult GenerateSubtract(CodeGenContext context, AstOperation operation)
|
private static OperationResult GenerateSubtract(CodeGenContext context, AstOperation operation)
|
||||||
{
|
{
|
||||||
return GenerateBinary(context, operation, context.Delegates.FSub, context.Delegates.ISub);
|
return GenerateBinary(context, operation, context.Delegates.FSub, context.Delegates.ISub);
|
||||||
@ -1849,13 +1827,13 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
|||||||
AstOperation operation,
|
AstOperation operation,
|
||||||
Func<SpvInstruction, SpvInstruction, SpvInstruction, SpvInstruction, SpvInstruction, SpvInstruction> emitU)
|
Func<SpvInstruction, SpvInstruction, SpvInstruction, SpvInstruction, SpvInstruction, SpvInstruction> emitU)
|
||||||
{
|
{
|
||||||
var value = context.GetU32(operation.GetSource(2));
|
var value = context.GetU32(operation.GetSource(operation.SourcesCount - 1));
|
||||||
|
|
||||||
SpvInstruction elemPointer;
|
SpvInstruction elemPointer;
|
||||||
|
|
||||||
if (operation.StorageKind == StorageKind.StorageBuffer)
|
if (operation.StorageKind == StorageKind.StorageBuffer)
|
||||||
{
|
{
|
||||||
elemPointer = GetStorageElemPointer(context, operation);
|
elemPointer = GetStoragePointer(context, operation, out _);
|
||||||
}
|
}
|
||||||
else if (operation.StorageKind == StorageKind.SharedMemory)
|
else if (operation.StorageKind == StorageKind.SharedMemory)
|
||||||
{
|
{
|
||||||
@ -1875,14 +1853,14 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
|||||||
|
|
||||||
private static OperationResult GenerateAtomicMemoryCas(CodeGenContext context, AstOperation operation)
|
private static OperationResult GenerateAtomicMemoryCas(CodeGenContext context, AstOperation operation)
|
||||||
{
|
{
|
||||||
var value0 = context.GetU32(operation.GetSource(2));
|
var value0 = context.GetU32(operation.GetSource(operation.SourcesCount - 2));
|
||||||
var value1 = context.GetU32(operation.GetSource(3));
|
var value1 = context.GetU32(operation.GetSource(operation.SourcesCount - 1));
|
||||||
|
|
||||||
SpvInstruction elemPointer;
|
SpvInstruction elemPointer;
|
||||||
|
|
||||||
if (operation.StorageKind == StorageKind.StorageBuffer)
|
if (operation.StorageKind == StorageKind.StorageBuffer)
|
||||||
{
|
{
|
||||||
elemPointer = GetStorageElemPointer(context, operation);
|
elemPointer = GetStoragePointer(context, operation, out _);
|
||||||
}
|
}
|
||||||
else if (operation.StorageKind == StorageKind.SharedMemory)
|
else if (operation.StorageKind == StorageKind.SharedMemory)
|
||||||
{
|
{
|
||||||
@ -1901,17 +1879,33 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static OperationResult GenerateLoadOrStore(CodeGenContext context, AstOperation operation, bool isStore)
|
private static OperationResult GenerateLoadOrStore(CodeGenContext context, AstOperation operation, bool isStore)
|
||||||
|
{
|
||||||
|
SpvInstruction pointer = GetStoragePointer(context, operation, out AggregateType varType);
|
||||||
|
|
||||||
|
if (isStore)
|
||||||
|
{
|
||||||
|
context.Store(pointer, context.Get(varType, operation.GetSource(operation.SourcesCount - 1)));
|
||||||
|
return OperationResult.Invalid;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var result = context.Load(context.GetType(varType), pointer);
|
||||||
|
return new OperationResult(varType, result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static SpvInstruction GetStoragePointer(CodeGenContext context, AstOperation operation, out AggregateType varType)
|
||||||
{
|
{
|
||||||
StorageKind storageKind = operation.StorageKind;
|
StorageKind storageKind = operation.StorageKind;
|
||||||
|
|
||||||
StorageClass storageClass;
|
StorageClass storageClass;
|
||||||
SpvInstruction baseObj;
|
SpvInstruction baseObj;
|
||||||
AggregateType varType;
|
|
||||||
int srcIndex = 0;
|
int srcIndex = 0;
|
||||||
|
|
||||||
switch (storageKind)
|
switch (storageKind)
|
||||||
{
|
{
|
||||||
case StorageKind.ConstantBuffer:
|
case StorageKind.ConstantBuffer:
|
||||||
|
case StorageKind.StorageBuffer:
|
||||||
if (!(operation.GetSource(srcIndex++) is AstOperand bindingIndex) || bindingIndex.Type != OperandType.Constant)
|
if (!(operation.GetSource(srcIndex++) is AstOperand bindingIndex) || bindingIndex.Type != OperandType.Constant)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException($"First input of {operation.Inst} with {storageKind} storage must be a constant operand.");
|
throw new InvalidOperationException($"First input of {operation.Inst} with {storageKind} storage must be a constant operand.");
|
||||||
@ -1922,12 +1916,16 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
|||||||
throw new InvalidOperationException($"Second input of {operation.Inst} with {storageKind} storage must be a constant operand.");
|
throw new InvalidOperationException($"Second input of {operation.Inst} with {storageKind} storage must be a constant operand.");
|
||||||
}
|
}
|
||||||
|
|
||||||
BufferDefinition buffer = context.Config.Properties.ConstantBuffers[bindingIndex.Value];
|
BufferDefinition buffer = storageKind == StorageKind.ConstantBuffer
|
||||||
|
? context.Config.Properties.ConstantBuffers[bindingIndex.Value]
|
||||||
|
: context.Config.Properties.StorageBuffers[bindingIndex.Value];
|
||||||
StructureField field = buffer.Type.Fields[fieldIndex.Value];
|
StructureField field = buffer.Type.Fields[fieldIndex.Value];
|
||||||
|
|
||||||
storageClass = StorageClass.Uniform;
|
storageClass = StorageClass.Uniform;
|
||||||
varType = field.Type & AggregateType.ElementTypeMask;
|
varType = field.Type & AggregateType.ElementTypeMask;
|
||||||
baseObj = context.ConstantBuffers[bindingIndex.Value];
|
baseObj = storageKind == StorageKind.ConstantBuffer
|
||||||
|
? context.ConstantBuffers[bindingIndex.Value]
|
||||||
|
: context.StorageBuffers[bindingIndex.Value];
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case StorageKind.Input:
|
case StorageKind.Input:
|
||||||
@ -1993,7 +1991,14 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
|||||||
throw new InvalidOperationException($"Invalid storage kind {storageKind}.");
|
throw new InvalidOperationException($"Invalid storage kind {storageKind}.");
|
||||||
}
|
}
|
||||||
|
|
||||||
int inputsCount = (isStore ? operation.SourcesCount - 1 : operation.SourcesCount) - srcIndex;
|
bool isStoreOrAtomic = operation.Inst == Instruction.Store || operation.Inst.IsAtomic();
|
||||||
|
int inputsCount = (isStoreOrAtomic ? operation.SourcesCount - 1 : operation.SourcesCount) - srcIndex;
|
||||||
|
|
||||||
|
if (operation.Inst == Instruction.AtomicCompareAndSwap)
|
||||||
|
{
|
||||||
|
inputsCount--;
|
||||||
|
}
|
||||||
|
|
||||||
SpvInstruction e0, e1, e2;
|
SpvInstruction e0, e1, e2;
|
||||||
SpvInstruction pointer;
|
SpvInstruction pointer;
|
||||||
|
|
||||||
@ -2030,16 +2035,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isStore)
|
return pointer;
|
||||||
{
|
|
||||||
context.Store(pointer, context.Get(varType, operation.GetSource(srcIndex)));
|
|
||||||
return OperationResult.Invalid;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var result = context.Load(context.GetType(varType), pointer);
|
|
||||||
return new OperationResult(varType, result);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static SpvInstruction GetScalarInput(CodeGenContext context, IoVariable ioVariable)
|
private static SpvInstruction GetScalarInput(CodeGenContext context, IoVariable ioVariable)
|
||||||
@ -2068,25 +2064,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
|||||||
GenerateStoreSmallInt(context, elemPointer, bitOffset, value, bitSize);
|
GenerateStoreSmallInt(context, elemPointer, bitOffset, value, bitSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void GenerateStoreStorageSmallInt(CodeGenContext context, AstOperation operation, int bitSize)
|
|
||||||
{
|
|
||||||
var i0 = context.Get(AggregateType.S32, operation.GetSource(0));
|
|
||||||
var offset = context.Get(AggregateType.U32, operation.GetSource(1));
|
|
||||||
var value = context.Get(AggregateType.U32, operation.GetSource(2));
|
|
||||||
|
|
||||||
var wordOffset = context.ShiftRightLogical(context.TypeU32(), offset, context.Constant(context.TypeU32(), 2));
|
|
||||||
var bitOffset = context.BitwiseAnd(context.TypeU32(), offset, context.Constant(context.TypeU32(), 3));
|
|
||||||
bitOffset = context.ShiftLeftLogical(context.TypeU32(), bitOffset, context.Constant(context.TypeU32(), 3));
|
|
||||||
|
|
||||||
var sbVariable = context.StorageBuffersArray;
|
|
||||||
|
|
||||||
var i1 = context.Constant(context.TypeS32(), 0);
|
|
||||||
|
|
||||||
var elemPointer = context.AccessChain(context.TypePointer(StorageClass.Uniform, context.TypeU32()), sbVariable, i0, i1, wordOffset);
|
|
||||||
|
|
||||||
GenerateStoreSmallInt(context, elemPointer, bitOffset, value, bitSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void GenerateStoreSmallInt(
|
private static void GenerateStoreSmallInt(
|
||||||
CodeGenContext context,
|
CodeGenContext context,
|
||||||
SpvInstruction elemPointer,
|
SpvInstruction elemPointer,
|
||||||
@ -2173,16 +2150,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static SpvInstruction GetStorageElemPointer(CodeGenContext context, AstOperation operation)
|
|
||||||
{
|
|
||||||
var sbVariable = context.StorageBuffersArray;
|
|
||||||
var i0 = context.Get(AggregateType.S32, operation.GetSource(0));
|
|
||||||
var i1 = context.Constant(context.TypeS32(), 0);
|
|
||||||
var i2 = context.Get(AggregateType.S32, operation.GetSource(1));
|
|
||||||
|
|
||||||
return context.AccessChain(context.TypePointer(StorageClass.Uniform, context.TypeU32()), sbVariable, i0, i1, i2);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static OperationResult GenerateUnary(
|
private static OperationResult GenerateUnary(
|
||||||
CodeGenContext context,
|
CodeGenContext context,
|
||||||
AstOperation operation,
|
AstOperation operation,
|
||||||
|
@ -148,7 +148,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
|||||||
|
|
||||||
context.CurrentFunction = function;
|
context.CurrentFunction = function;
|
||||||
context.AddFunction(spvFunc);
|
context.AddFunction(spvFunc);
|
||||||
context.StartFunction();
|
context.StartFunction(isMainFunction: funcIndex == 0);
|
||||||
|
|
||||||
Declarations.DeclareParameters(context, function);
|
Declarations.DeclareParameters(context, function);
|
||||||
|
|
||||||
|
@ -10,7 +10,5 @@ namespace Ryujinx.Graphics.Shader
|
|||||||
public const int NvnBaseVertexByteOffset = 0x640;
|
public const int NvnBaseVertexByteOffset = 0x640;
|
||||||
public const int NvnBaseInstanceByteOffset = 0x644;
|
public const int NvnBaseInstanceByteOffset = 0x644;
|
||||||
public const int NvnDrawIndexByteOffset = 0x648;
|
public const int NvnDrawIndexByteOffset = 0x648;
|
||||||
|
|
||||||
public const int StorageAlignment = 16;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -79,7 +79,7 @@ namespace Ryujinx.Graphics.Shader
|
|||||||
/// <returns>Binding number</returns>
|
/// <returns>Binding number</returns>
|
||||||
int QueryBindingConstantBuffer(int index)
|
int QueryBindingConstantBuffer(int index)
|
||||||
{
|
{
|
||||||
return index;
|
return index + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@ -331,6 +331,24 @@ namespace Ryujinx.Graphics.Shader
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Queries host GPU shader support for barrier instructions on divergent control flow paths.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>True if the GPU supports barriers on divergent control flow paths, false otherwise</returns>
|
||||||
|
bool QueryHostSupportsShaderBarrierDivergence()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Queries host GPU support for 64-bit floating point (double precision) operations on the shader.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>True if the GPU and driver supports double operations, false otherwise</returns>
|
||||||
|
bool QueryHostSupportsShaderFloat64()
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Queries host GPU support for signed normalized buffer texture formats.
|
/// Queries host GPU support for signed normalized buffer texture formats.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -164,6 +164,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
|||||||
|
|
||||||
if (op.Ccc == Ccc.T)
|
if (op.Ccc == Ccc.T)
|
||||||
{
|
{
|
||||||
|
context.PrepareForReturn();
|
||||||
context.Return();
|
context.Return();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@ -175,6 +176,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
|||||||
{
|
{
|
||||||
Operand lblSkip = Label();
|
Operand lblSkip = Label();
|
||||||
context.BranchIfFalse(lblSkip, cond);
|
context.BranchIfFalse(lblSkip, cond);
|
||||||
|
context.PrepareForReturn();
|
||||||
context.Return();
|
context.Return();
|
||||||
context.MarkLabel(lblSkip);
|
context.MarkLabel(lblSkip);
|
||||||
}
|
}
|
||||||
|
@ -336,13 +336,12 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
|||||||
int offset,
|
int offset,
|
||||||
bool extended)
|
bool extended)
|
||||||
{
|
{
|
||||||
bool isSmallInt = size < LsSize.B32;
|
|
||||||
|
|
||||||
int count = GetVectorCount(size);
|
int count = GetVectorCount(size);
|
||||||
|
StorageKind storageKind = GetStorageKind(size);
|
||||||
|
|
||||||
(Operand addrLow, Operand addrHigh) = Get40BitsAddress(context, new Register(ra, RegisterType.Gpr), extended, offset);
|
(_, Operand addrHigh) = Get40BitsAddress(context, new Register(ra, RegisterType.Gpr), extended, offset);
|
||||||
|
|
||||||
Operand bitOffset = GetBitOffset(context, addrLow);
|
Operand srcA = context.Copy(new Operand(new Register(ra, RegisterType.Gpr)));
|
||||||
|
|
||||||
for (int index = 0; index < count; index++)
|
for (int index = 0; index < count; index++)
|
||||||
{
|
{
|
||||||
@ -353,12 +352,7 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
Operand value = context.LoadGlobal(context.IAdd(addrLow, Const(index * 4)), addrHigh);
|
Operand value = context.Load(storageKind, context.IAdd(srcA, Const(offset + index * 4)), addrHigh);
|
||||||
|
|
||||||
if (isSmallInt)
|
|
||||||
{
|
|
||||||
value = ExtractSmallInt(context, size, bitOffset, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
context.Copy(Register(dest), value);
|
context.Copy(Register(dest), value);
|
||||||
}
|
}
|
||||||
@ -445,10 +439,11 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
|||||||
}
|
}
|
||||||
|
|
||||||
int count = GetVectorCount((LsSize)size);
|
int count = GetVectorCount((LsSize)size);
|
||||||
|
StorageKind storageKind = GetStorageKind((LsSize)size);
|
||||||
|
|
||||||
(Operand addrLow, Operand addrHigh) = Get40BitsAddress(context, new Register(ra, RegisterType.Gpr), extended, offset);
|
(_, Operand addrHigh) = Get40BitsAddress(context, new Register(ra, RegisterType.Gpr), extended, offset);
|
||||||
|
|
||||||
Operand bitOffset = GetBitOffset(context, addrLow);
|
Operand srcA = context.Copy(new Operand(new Register(ra, RegisterType.Gpr)));
|
||||||
|
|
||||||
for (int index = 0; index < count; index++)
|
for (int index = 0; index < count; index++)
|
||||||
{
|
{
|
||||||
@ -456,23 +451,24 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
|||||||
|
|
||||||
Operand value = Register(isRz ? rd : rd + index, RegisterType.Gpr);
|
Operand value = Register(isRz ? rd : rd + index, RegisterType.Gpr);
|
||||||
|
|
||||||
Operand addrLowOffset = context.IAdd(addrLow, Const(index * 4));
|
Operand addrLowOffset = context.IAdd(srcA, Const(offset + index * 4));
|
||||||
|
|
||||||
if (size == LsSize2.U8 || size == LsSize2.S8)
|
context.Store(storageKind, addrLowOffset, addrHigh, value);
|
||||||
{
|
|
||||||
context.StoreGlobal8(addrLowOffset, addrHigh, value);
|
|
||||||
}
|
|
||||||
else if (size == LsSize2.U16 || size == LsSize2.S16)
|
|
||||||
{
|
|
||||||
context.StoreGlobal16(addrLowOffset, addrHigh, value);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
context.StoreGlobal(addrLowOffset, addrHigh, value);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static StorageKind GetStorageKind(LsSize size)
|
||||||
|
{
|
||||||
|
return size switch
|
||||||
|
{
|
||||||
|
LsSize.U8 => StorageKind.GlobalMemoryU8,
|
||||||
|
LsSize.S8 => StorageKind.GlobalMemoryS8,
|
||||||
|
LsSize.U16 => StorageKind.GlobalMemoryU16,
|
||||||
|
LsSize.S16 => StorageKind.GlobalMemoryS16,
|
||||||
|
_ => StorageKind.GlobalMemory
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
private static int GetVectorCount(LsSize size)
|
private static int GetVectorCount(LsSize size)
|
||||||
{
|
{
|
||||||
switch (size)
|
switch (size)
|
||||||
|
@ -79,10 +79,8 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
|
|||||||
ImageAtomic,
|
ImageAtomic,
|
||||||
IsNan,
|
IsNan,
|
||||||
Load,
|
Load,
|
||||||
LoadGlobal,
|
|
||||||
LoadLocal,
|
LoadLocal,
|
||||||
LoadShared,
|
LoadShared,
|
||||||
LoadStorage,
|
|
||||||
Lod,
|
Lod,
|
||||||
LogarithmB2,
|
LogarithmB2,
|
||||||
LogicalAnd,
|
LogicalAnd,
|
||||||
@ -117,16 +115,10 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
|
|||||||
Sine,
|
Sine,
|
||||||
SquareRoot,
|
SquareRoot,
|
||||||
Store,
|
Store,
|
||||||
StoreGlobal,
|
|
||||||
StoreGlobal16,
|
|
||||||
StoreGlobal8,
|
|
||||||
StoreLocal,
|
StoreLocal,
|
||||||
StoreShared,
|
StoreShared,
|
||||||
StoreShared16,
|
StoreShared16,
|
||||||
StoreShared8,
|
StoreShared8,
|
||||||
StoreStorage,
|
|
||||||
StoreStorage16,
|
|
||||||
StoreStorage8,
|
|
||||||
Subtract,
|
Subtract,
|
||||||
SwizzleAdd,
|
SwizzleAdd,
|
||||||
TextureSample,
|
TextureSample,
|
||||||
|
@ -255,5 +255,35 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
|
|||||||
|
|
||||||
_sources = new Operand[] { source };
|
_sources = new Operand[] { source };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void TurnDoubleIntoFloat()
|
||||||
|
{
|
||||||
|
if ((Inst & ~Instruction.Mask) == Instruction.FP64)
|
||||||
|
{
|
||||||
|
Inst = (Inst & Instruction.Mask) | Instruction.FP32;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
switch (Inst)
|
||||||
|
{
|
||||||
|
case Instruction.ConvertFP32ToFP64:
|
||||||
|
case Instruction.ConvertFP64ToFP32:
|
||||||
|
Inst = Instruction.Copy;
|
||||||
|
break;
|
||||||
|
case Instruction.ConvertFP64ToS32:
|
||||||
|
Inst = Instruction.ConvertFP32ToS32;
|
||||||
|
break;
|
||||||
|
case Instruction.ConvertFP64ToU32:
|
||||||
|
Inst = Instruction.ConvertFP32ToU32;
|
||||||
|
break;
|
||||||
|
case Instruction.ConvertS32ToFP64:
|
||||||
|
Inst = Instruction.ConvertS32ToFP32;
|
||||||
|
break;
|
||||||
|
case Instruction.ConvertU32ToFP64:
|
||||||
|
Inst = Instruction.ConvertU32ToFP32;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -11,7 +11,12 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
|
|||||||
StorageBuffer,
|
StorageBuffer,
|
||||||
LocalMemory,
|
LocalMemory,
|
||||||
SharedMemory,
|
SharedMemory,
|
||||||
GlobalMemory
|
GlobalMemory,
|
||||||
|
// TODO: Remove those and store type as a field on the Operation class itself.
|
||||||
|
GlobalMemoryS8,
|
||||||
|
GlobalMemoryS16,
|
||||||
|
GlobalMemoryU8,
|
||||||
|
GlobalMemoryU16
|
||||||
}
|
}
|
||||||
|
|
||||||
static class StorageKindExtensions
|
static class StorageKindExtensions
|
||||||
|
@ -11,7 +11,6 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<EmbeddedResource Include="CodeGen\Glsl\HelperFunctions\AtomicMinMaxS32Shared.glsl" />
|
<EmbeddedResource Include="CodeGen\Glsl\HelperFunctions\AtomicMinMaxS32Shared.glsl" />
|
||||||
<EmbeddedResource Include="CodeGen\Glsl\HelperFunctions\AtomicMinMaxS32Storage.glsl" />
|
|
||||||
<EmbeddedResource Include="CodeGen\Glsl\HelperFunctions\MultiplyHighS32.glsl" />
|
<EmbeddedResource Include="CodeGen\Glsl\HelperFunctions\MultiplyHighS32.glsl" />
|
||||||
<EmbeddedResource Include="CodeGen\Glsl\HelperFunctions\MultiplyHighU32.glsl" />
|
<EmbeddedResource Include="CodeGen\Glsl\HelperFunctions\MultiplyHighU32.glsl" />
|
||||||
<EmbeddedResource Include="CodeGen\Glsl\HelperFunctions\Shuffle.glsl" />
|
<EmbeddedResource Include="CodeGen\Glsl\HelperFunctions\Shuffle.glsl" />
|
||||||
@ -19,7 +18,6 @@
|
|||||||
<EmbeddedResource Include="CodeGen\Glsl\HelperFunctions\ShuffleUp.glsl" />
|
<EmbeddedResource Include="CodeGen\Glsl\HelperFunctions\ShuffleUp.glsl" />
|
||||||
<EmbeddedResource Include="CodeGen\Glsl\HelperFunctions\ShuffleXor.glsl" />
|
<EmbeddedResource Include="CodeGen\Glsl\HelperFunctions\ShuffleXor.glsl" />
|
||||||
<EmbeddedResource Include="CodeGen\Glsl\HelperFunctions\StoreSharedSmallInt.glsl" />
|
<EmbeddedResource Include="CodeGen\Glsl\HelperFunctions\StoreSharedSmallInt.glsl" />
|
||||||
<EmbeddedResource Include="CodeGen\Glsl\HelperFunctions\StoreStorageSmallInt.glsl" />
|
|
||||||
<EmbeddedResource Include="CodeGen\Glsl\HelperFunctions\SwizzleAdd.glsl" />
|
<EmbeddedResource Include="CodeGen\Glsl\HelperFunctions\SwizzleAdd.glsl" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
@ -6,7 +6,6 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
|
|||||||
enum HelperFunctionsMask
|
enum HelperFunctionsMask
|
||||||
{
|
{
|
||||||
AtomicMinMaxS32Shared = 1 << 0,
|
AtomicMinMaxS32Shared = 1 << 0,
|
||||||
AtomicMinMaxS32Storage = 1 << 1,
|
|
||||||
MultiplyHighS32 = 1 << 2,
|
MultiplyHighS32 = 1 << 2,
|
||||||
MultiplyHighU32 = 1 << 3,
|
MultiplyHighU32 = 1 << 3,
|
||||||
Shuffle = 1 << 4,
|
Shuffle = 1 << 4,
|
||||||
@ -14,7 +13,6 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
|
|||||||
ShuffleUp = 1 << 6,
|
ShuffleUp = 1 << 6,
|
||||||
ShuffleXor = 1 << 7,
|
ShuffleXor = 1 << 7,
|
||||||
StoreSharedSmallInt = 1 << 8,
|
StoreSharedSmallInt = 1 << 8,
|
||||||
StoreStorageSmallInt = 1 << 9,
|
|
||||||
SwizzleAdd = 1 << 10,
|
SwizzleAdd = 1 << 10,
|
||||||
FSI = 1 << 11
|
FSI = 1 << 11
|
||||||
}
|
}
|
||||||
|
@ -90,10 +90,8 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
|
|||||||
Add(Instruction.ImageAtomic, AggregateType.S32);
|
Add(Instruction.ImageAtomic, AggregateType.S32);
|
||||||
Add(Instruction.IsNan, AggregateType.Bool, AggregateType.Scalar);
|
Add(Instruction.IsNan, AggregateType.Bool, AggregateType.Scalar);
|
||||||
Add(Instruction.Load, AggregateType.FP32);
|
Add(Instruction.Load, AggregateType.FP32);
|
||||||
Add(Instruction.LoadGlobal, AggregateType.U32, AggregateType.S32, AggregateType.S32);
|
|
||||||
Add(Instruction.LoadLocal, AggregateType.U32, AggregateType.S32);
|
Add(Instruction.LoadLocal, AggregateType.U32, AggregateType.S32);
|
||||||
Add(Instruction.LoadShared, AggregateType.U32, AggregateType.S32);
|
Add(Instruction.LoadShared, AggregateType.U32, AggregateType.S32);
|
||||||
Add(Instruction.LoadStorage, AggregateType.U32, AggregateType.S32, AggregateType.S32);
|
|
||||||
Add(Instruction.Lod, AggregateType.FP32);
|
Add(Instruction.Lod, AggregateType.FP32);
|
||||||
Add(Instruction.LogarithmB2, AggregateType.Scalar, AggregateType.Scalar);
|
Add(Instruction.LogarithmB2, AggregateType.Scalar, AggregateType.Scalar);
|
||||||
Add(Instruction.LogicalAnd, AggregateType.Bool, AggregateType.Bool, AggregateType.Bool);
|
Add(Instruction.LogicalAnd, AggregateType.Bool, AggregateType.Bool, AggregateType.Bool);
|
||||||
@ -123,14 +121,10 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
|
|||||||
Add(Instruction.Sine, AggregateType.Scalar, AggregateType.Scalar);
|
Add(Instruction.Sine, AggregateType.Scalar, AggregateType.Scalar);
|
||||||
Add(Instruction.SquareRoot, AggregateType.Scalar, AggregateType.Scalar);
|
Add(Instruction.SquareRoot, AggregateType.Scalar, AggregateType.Scalar);
|
||||||
Add(Instruction.Store, AggregateType.Void);
|
Add(Instruction.Store, AggregateType.Void);
|
||||||
Add(Instruction.StoreGlobal, AggregateType.Void, AggregateType.S32, AggregateType.S32, AggregateType.U32);
|
|
||||||
Add(Instruction.StoreLocal, AggregateType.Void, AggregateType.S32, AggregateType.U32);
|
Add(Instruction.StoreLocal, AggregateType.Void, AggregateType.S32, AggregateType.U32);
|
||||||
Add(Instruction.StoreShared, AggregateType.Void, AggregateType.S32, AggregateType.U32);
|
Add(Instruction.StoreShared, AggregateType.Void, AggregateType.S32, AggregateType.U32);
|
||||||
Add(Instruction.StoreShared16, AggregateType.Void, AggregateType.S32, AggregateType.U32);
|
Add(Instruction.StoreShared16, AggregateType.Void, AggregateType.S32, AggregateType.U32);
|
||||||
Add(Instruction.StoreShared8, AggregateType.Void, AggregateType.S32, AggregateType.U32);
|
Add(Instruction.StoreShared8, AggregateType.Void, AggregateType.S32, AggregateType.U32);
|
||||||
Add(Instruction.StoreStorage, AggregateType.Void, AggregateType.S32, AggregateType.S32, AggregateType.U32);
|
|
||||||
Add(Instruction.StoreStorage16, AggregateType.Void, AggregateType.S32, AggregateType.S32, AggregateType.U32);
|
|
||||||
Add(Instruction.StoreStorage8, AggregateType.Void, AggregateType.S32, AggregateType.S32, AggregateType.U32);
|
|
||||||
Add(Instruction.Subtract, AggregateType.Scalar, AggregateType.Scalar, AggregateType.Scalar);
|
Add(Instruction.Subtract, AggregateType.Scalar, AggregateType.Scalar, AggregateType.Scalar);
|
||||||
Add(Instruction.SwizzleAdd, AggregateType.FP32, AggregateType.FP32, AggregateType.FP32, AggregateType.S32);
|
Add(Instruction.SwizzleAdd, AggregateType.FP32, AggregateType.FP32, AggregateType.FP32, AggregateType.S32);
|
||||||
Add(Instruction.TextureSample, AggregateType.FP32);
|
Add(Instruction.TextureSample, AggregateType.FP32);
|
||||||
@ -166,7 +160,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
|
|||||||
{
|
{
|
||||||
return AggregateType.FP32;
|
return AggregateType.FP32;
|
||||||
}
|
}
|
||||||
else if (inst == Instruction.Call || inst == Instruction.Load || inst == Instruction.Store)
|
else if (inst == Instruction.Call || inst == Instruction.Load || inst == Instruction.Store || inst.IsAtomic())
|
||||||
{
|
{
|
||||||
return AggregateType.S32;
|
return AggregateType.S32;
|
||||||
}
|
}
|
||||||
|
@ -5,17 +5,25 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
|
|||||||
class ShaderProperties
|
class ShaderProperties
|
||||||
{
|
{
|
||||||
private readonly Dictionary<int, BufferDefinition> _constantBuffers;
|
private readonly Dictionary<int, BufferDefinition> _constantBuffers;
|
||||||
|
private readonly Dictionary<int, BufferDefinition> _storageBuffers;
|
||||||
|
|
||||||
public IReadOnlyDictionary<int, BufferDefinition> ConstantBuffers => _constantBuffers;
|
public IReadOnlyDictionary<int, BufferDefinition> ConstantBuffers => _constantBuffers;
|
||||||
|
public IReadOnlyDictionary<int, BufferDefinition> StorageBuffers => _storageBuffers;
|
||||||
|
|
||||||
public ShaderProperties()
|
public ShaderProperties()
|
||||||
{
|
{
|
||||||
_constantBuffers = new Dictionary<int, BufferDefinition>();
|
_constantBuffers = new Dictionary<int, BufferDefinition>();
|
||||||
|
_storageBuffers = new Dictionary<int, BufferDefinition>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddConstantBuffer(int binding, BufferDefinition definition)
|
public void AddConstantBuffer(int binding, BufferDefinition definition)
|
||||||
{
|
{
|
||||||
_constantBuffers[binding] = definition;
|
_constantBuffers[binding] = definition;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void AddStorageBuffer(int binding, BufferDefinition definition)
|
||||||
|
{
|
||||||
|
_storageBuffers[binding] = definition;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -280,10 +280,6 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
|
|||||||
{
|
{
|
||||||
context.Info.HelperFunctionsMask |= HelperFunctionsMask.AtomicMinMaxS32Shared;
|
context.Info.HelperFunctionsMask |= HelperFunctionsMask.AtomicMinMaxS32Shared;
|
||||||
}
|
}
|
||||||
else if (operation.StorageKind == StorageKind.StorageBuffer)
|
|
||||||
{
|
|
||||||
context.Info.HelperFunctionsMask |= HelperFunctionsMask.AtomicMinMaxS32Storage;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case Instruction.MultiplyHighS32:
|
case Instruction.MultiplyHighS32:
|
||||||
context.Info.HelperFunctionsMask |= HelperFunctionsMask.MultiplyHighS32;
|
context.Info.HelperFunctionsMask |= HelperFunctionsMask.MultiplyHighS32;
|
||||||
@ -307,10 +303,6 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
|
|||||||
case Instruction.StoreShared8:
|
case Instruction.StoreShared8:
|
||||||
context.Info.HelperFunctionsMask |= HelperFunctionsMask.StoreSharedSmallInt;
|
context.Info.HelperFunctionsMask |= HelperFunctionsMask.StoreSharedSmallInt;
|
||||||
break;
|
break;
|
||||||
case Instruction.StoreStorage16:
|
|
||||||
case Instruction.StoreStorage8:
|
|
||||||
context.Info.HelperFunctionsMask |= HelperFunctionsMask.StoreStorageSmallInt;
|
|
||||||
break;
|
|
||||||
case Instruction.SwizzleAdd:
|
case Instruction.SwizzleAdd:
|
||||||
context.Info.HelperFunctionsMask |= HelperFunctionsMask.SwizzleAdd;
|
context.Info.HelperFunctionsMask |= HelperFunctionsMask.SwizzleAdd;
|
||||||
break;
|
break;
|
||||||
|
@ -57,6 +57,56 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||||||
return context.Add(Instruction.AtomicXor, storageKind, Local(), a, b, c);
|
return context.Add(Instruction.AtomicXor, storageKind, Local(), a, b, c);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Operand AtomicAdd(this EmitterContext context, StorageKind storageKind, int binding, Operand e0, Operand e1, Operand value)
|
||||||
|
{
|
||||||
|
return context.Add(Instruction.AtomicAdd, storageKind, Local(), Const(binding), e0, e1, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Operand AtomicAnd(this EmitterContext context, StorageKind storageKind, int binding, Operand e0, Operand e1, Operand value)
|
||||||
|
{
|
||||||
|
return context.Add(Instruction.AtomicAnd, storageKind, Local(), Const(binding), e0, e1, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Operand AtomicCompareAndSwap(this EmitterContext context, StorageKind storageKind, int binding, Operand e0, Operand e1, Operand compare, Operand value)
|
||||||
|
{
|
||||||
|
return context.Add(Instruction.AtomicCompareAndSwap, storageKind, Local(), Const(binding), e0, e1, compare, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Operand AtomicMaxS32(this EmitterContext context, StorageKind storageKind, int binding, Operand e0, Operand e1, Operand value)
|
||||||
|
{
|
||||||
|
return context.Add(Instruction.AtomicMaxS32, storageKind, Local(), Const(binding), e0, e1, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Operand AtomicMaxU32(this EmitterContext context, StorageKind storageKind, int binding, Operand e0, Operand e1, Operand value)
|
||||||
|
{
|
||||||
|
return context.Add(Instruction.AtomicMaxU32, storageKind, Local(), Const(binding), e0, e1, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Operand AtomicMinS32(this EmitterContext context, StorageKind storageKind, int binding, Operand e0, Operand e1, Operand value)
|
||||||
|
{
|
||||||
|
return context.Add(Instruction.AtomicMinS32, storageKind, Local(), Const(binding), e0, e1, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Operand AtomicMinU32(this EmitterContext context, StorageKind storageKind, int binding, Operand e0, Operand e1, Operand value)
|
||||||
|
{
|
||||||
|
return context.Add(Instruction.AtomicMinU32, storageKind, Local(), Const(binding), e0, e1, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Operand AtomicOr(this EmitterContext context, StorageKind storageKind, int binding, Operand e0, Operand e1, Operand value)
|
||||||
|
{
|
||||||
|
return context.Add(Instruction.AtomicOr, storageKind, Local(), Const(binding), e0, e1, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Operand AtomicSwap(this EmitterContext context, StorageKind storageKind, int binding, Operand e0, Operand e1, Operand value)
|
||||||
|
{
|
||||||
|
return context.Add(Instruction.AtomicSwap, storageKind, Local(), Const(binding), e0, e1, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Operand AtomicXor(this EmitterContext context, StorageKind storageKind, int binding, Operand e0, Operand e1, Operand value)
|
||||||
|
{
|
||||||
|
return context.Add(Instruction.AtomicXor, storageKind, Local(), Const(binding), e0, e1, value);
|
||||||
|
}
|
||||||
|
|
||||||
public static Operand Ballot(this EmitterContext context, Operand a)
|
public static Operand Ballot(this EmitterContext context, Operand a)
|
||||||
{
|
{
|
||||||
return context.Add(Instruction.Ballot, Local(), a);
|
return context.Add(Instruction.Ballot, Local(), a);
|
||||||
@ -554,6 +604,11 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||||||
return context.Add(fpType | Instruction.IsNan, Local(), a);
|
return context.Add(fpType | Instruction.IsNan, Local(), a);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Operand Load(this EmitterContext context, StorageKind storageKind, Operand e0, Operand e1)
|
||||||
|
{
|
||||||
|
return context.Add(Instruction.Load, storageKind, Local(), e0, e1);
|
||||||
|
}
|
||||||
|
|
||||||
public static Operand Load(this EmitterContext context, StorageKind storageKind, int binding)
|
public static Operand Load(this EmitterContext context, StorageKind storageKind, int binding)
|
||||||
{
|
{
|
||||||
return context.Add(Instruction.Load, storageKind, Local(), Const(binding));
|
return context.Add(Instruction.Load, storageKind, Local(), Const(binding));
|
||||||
@ -606,11 +661,6 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||||||
: context.Load(storageKind, (int)ioVariable, arrayIndex, elemIndex);
|
: context.Load(storageKind, (int)ioVariable, arrayIndex, elemIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Operand LoadGlobal(this EmitterContext context, Operand a, Operand b)
|
|
||||||
{
|
|
||||||
return context.Add(Instruction.LoadGlobal, Local(), a, b);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Operand LoadLocal(this EmitterContext context, Operand a)
|
public static Operand LoadLocal(this EmitterContext context, Operand a)
|
||||||
{
|
{
|
||||||
return context.Add(Instruction.LoadLocal, Local(), a);
|
return context.Add(Instruction.LoadLocal, Local(), a);
|
||||||
@ -655,7 +705,6 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||||||
|
|
||||||
public static void Return(this EmitterContext context)
|
public static void Return(this EmitterContext context)
|
||||||
{
|
{
|
||||||
context.PrepareForReturn();
|
|
||||||
context.Add(Instruction.Return);
|
context.Add(Instruction.Return);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -699,6 +748,16 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||||||
return context.Add(Instruction.ShuffleXor, (Local(), Local()), a, b, c);
|
return context.Add(Instruction.ShuffleXor, (Local(), Local()), a, b, c);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Operand Store(this EmitterContext context, StorageKind storageKind, Operand e0, Operand e1, Operand value)
|
||||||
|
{
|
||||||
|
return context.Add(Instruction.Store, storageKind, null, e0, e1, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Operand Store(this EmitterContext context, StorageKind storageKind, int binding, Operand e0, Operand e1, Operand value)
|
||||||
|
{
|
||||||
|
return context.Add(Instruction.Store, storageKind, null, Const(binding), e0, e1, value);
|
||||||
|
}
|
||||||
|
|
||||||
public static Operand Store(
|
public static Operand Store(
|
||||||
this EmitterContext context,
|
this EmitterContext context,
|
||||||
StorageKind storageKind,
|
StorageKind storageKind,
|
||||||
@ -738,21 +797,6 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||||||
: context.Add(Instruction.Store, storageKind, null, Const((int)ioVariable), arrayIndex, elemIndex, value);
|
: context.Add(Instruction.Store, storageKind, null, Const((int)ioVariable), arrayIndex, elemIndex, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Operand StoreGlobal(this EmitterContext context, Operand a, Operand b, Operand c)
|
|
||||||
{
|
|
||||||
return context.Add(Instruction.StoreGlobal, null, a, b, c);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Operand StoreGlobal16(this EmitterContext context, Operand a, Operand b, Operand c)
|
|
||||||
{
|
|
||||||
return context.Add(Instruction.StoreGlobal16, null, a, b, c);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Operand StoreGlobal8(this EmitterContext context, Operand a, Operand b, Operand c)
|
|
||||||
{
|
|
||||||
return context.Add(Instruction.StoreGlobal8, null, a, b, c);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Operand StoreLocal(this EmitterContext context, Operand a, Operand b)
|
public static Operand StoreLocal(this EmitterContext context, Operand a, Operand b)
|
||||||
{
|
{
|
||||||
return context.Add(Instruction.StoreLocal, null, a, b);
|
return context.Add(Instruction.StoreLocal, null, a, b);
|
||||||
|
@ -1,54 +0,0 @@
|
|||||||
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
|
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.Shader.Translation
|
|
||||||
{
|
|
||||||
static class GlobalMemory
|
|
||||||
{
|
|
||||||
private const int StorageDescsBaseOffset = 0x44; // In words.
|
|
||||||
|
|
||||||
public const int StorageDescSize = 4; // In words.
|
|
||||||
public const int StorageMaxCount = 16;
|
|
||||||
|
|
||||||
public const int StorageDescsSize = StorageDescSize * StorageMaxCount;
|
|
||||||
|
|
||||||
public const int UbeBaseOffset = 0x98; // In words.
|
|
||||||
public const int UbeMaxCount = 9;
|
|
||||||
public const int UbeDescsSize = StorageDescSize * UbeMaxCount;
|
|
||||||
public const int UbeFirstCbuf = 8;
|
|
||||||
|
|
||||||
public const int DriverReservedCb = 0;
|
|
||||||
|
|
||||||
public static bool UsesGlobalMemory(Instruction inst, StorageKind storageKind)
|
|
||||||
{
|
|
||||||
return (inst.IsAtomic() && storageKind == StorageKind.GlobalMemory) ||
|
|
||||||
inst == Instruction.LoadGlobal ||
|
|
||||||
inst == Instruction.StoreGlobal ||
|
|
||||||
inst == Instruction.StoreGlobal16 ||
|
|
||||||
inst == Instruction.StoreGlobal8;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int GetStorageCbOffset(ShaderStage stage, int slot)
|
|
||||||
{
|
|
||||||
return GetStorageBaseCbOffset(stage) + slot * StorageDescSize;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int GetStorageBaseCbOffset(ShaderStage stage)
|
|
||||||
{
|
|
||||||
return stage switch
|
|
||||||
{
|
|
||||||
ShaderStage.Compute => StorageDescsBaseOffset + 2 * StorageDescsSize,
|
|
||||||
ShaderStage.Vertex => StorageDescsBaseOffset,
|
|
||||||
ShaderStage.TessellationControl => StorageDescsBaseOffset + 1 * StorageDescsSize,
|
|
||||||
ShaderStage.TessellationEvaluation => StorageDescsBaseOffset + 2 * StorageDescsSize,
|
|
||||||
ShaderStage.Geometry => StorageDescsBaseOffset + 3 * StorageDescsSize,
|
|
||||||
ShaderStage.Fragment => StorageDescsBaseOffset + 4 * StorageDescsSize,
|
|
||||||
_ => 0
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int GetConstantUbeOffset(int slot)
|
|
||||||
{
|
|
||||||
return UbeBaseOffset + slot * StorageDescSize;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -19,6 +19,14 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||||||
_stage = stage;
|
_stage = stage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int AddFunction(Function function)
|
||||||
|
{
|
||||||
|
int functionId = _functionList.Count;
|
||||||
|
_functionList.Add(function);
|
||||||
|
|
||||||
|
return functionId;
|
||||||
|
}
|
||||||
|
|
||||||
public int GetOrCreateFunctionId(HelperFunctionName functionName)
|
public int GetOrCreateFunctionId(HelperFunctionName functionName)
|
||||||
{
|
{
|
||||||
if (_functionIds.TryGetValue(functionName, out int functionId))
|
if (_functionIds.TryGetValue(functionName, out int functionId))
|
||||||
@ -27,8 +35,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||||||
}
|
}
|
||||||
|
|
||||||
Function function = GenerateFunction(functionName);
|
Function function = GenerateFunction(functionName);
|
||||||
functionId = _functionList.Count;
|
functionId = AddFunction(function);
|
||||||
_functionList.Add(function);
|
|
||||||
_functionIds.Add(functionName, functionId);
|
_functionIds.Add(functionName, functionId);
|
||||||
|
|
||||||
return functionId;
|
return functionId;
|
||||||
@ -38,12 +45,101 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||||||
{
|
{
|
||||||
return functionName switch
|
return functionName switch
|
||||||
{
|
{
|
||||||
|
HelperFunctionName.ConvertDoubleToFloat => GenerateConvertDoubleToFloatFunction(),
|
||||||
|
HelperFunctionName.ConvertFloatToDouble => GenerateConvertFloatToDoubleFunction(),
|
||||||
HelperFunctionName.TexelFetchScale => GenerateTexelFetchScaleFunction(),
|
HelperFunctionName.TexelFetchScale => GenerateTexelFetchScaleFunction(),
|
||||||
HelperFunctionName.TextureSizeUnscale => GenerateTextureSizeUnscaleFunction(),
|
HelperFunctionName.TextureSizeUnscale => GenerateTextureSizeUnscaleFunction(),
|
||||||
_ => throw new ArgumentException($"Invalid function name {functionName}")
|
_ => throw new ArgumentException($"Invalid function name {functionName}")
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Function GenerateConvertDoubleToFloatFunction()
|
||||||
|
{
|
||||||
|
EmitterContext context = new EmitterContext();
|
||||||
|
|
||||||
|
Operand valueLow = Argument(0);
|
||||||
|
Operand valueHigh = Argument(1);
|
||||||
|
|
||||||
|
Operand mantissaLow = context.BitwiseAnd(valueLow, Const(((1 << 22) - 1)));
|
||||||
|
Operand mantissa = context.ShiftRightU32(valueLow, Const(22));
|
||||||
|
|
||||||
|
mantissa = context.BitwiseOr(mantissa, context.ShiftLeft(context.BitwiseAnd(valueHigh, Const(0xfffff)), Const(10)));
|
||||||
|
mantissa = context.BitwiseOr(mantissa, context.ConditionalSelect(mantissaLow, Const(1), Const(0)));
|
||||||
|
|
||||||
|
Operand exp = context.BitwiseAnd(context.ShiftRightU32(valueHigh, Const(20)), Const(0x7ff));
|
||||||
|
Operand sign = context.ShiftRightS32(valueHigh, Const(31));
|
||||||
|
|
||||||
|
Operand resultSign = context.ShiftLeft(sign, Const(31));
|
||||||
|
|
||||||
|
Operand notZero = context.BitwiseOr(mantissa, exp);
|
||||||
|
|
||||||
|
Operand lblNotZero = Label();
|
||||||
|
|
||||||
|
context.BranchIfTrue(lblNotZero, notZero);
|
||||||
|
|
||||||
|
context.Return(resultSign);
|
||||||
|
|
||||||
|
context.MarkLabel(lblNotZero);
|
||||||
|
|
||||||
|
Operand notNaNOrInf = context.ICompareNotEqual(exp, Const(0x7ff));
|
||||||
|
|
||||||
|
mantissa = context.BitwiseOr(mantissa, Const(0x40000000));
|
||||||
|
exp = context.ISubtract(exp, Const(0x381));
|
||||||
|
|
||||||
|
// Note: Overflow cases are not handled here and might produce incorrect results.
|
||||||
|
|
||||||
|
Operand roundBits = context.BitwiseAnd(mantissa, Const(0x7f));
|
||||||
|
Operand roundBitsXor64 = context.BitwiseExclusiveOr(roundBits, Const(0x40));
|
||||||
|
mantissa = context.ShiftRightU32(context.IAdd(mantissa, Const(0x40)), Const(7));
|
||||||
|
mantissa = context.BitwiseAnd(mantissa, context.ConditionalSelect(roundBitsXor64, Const(~0), Const(~1)));
|
||||||
|
|
||||||
|
exp = context.ConditionalSelect(mantissa, exp, Const(0));
|
||||||
|
exp = context.ConditionalSelect(notNaNOrInf, exp, Const(0xff));
|
||||||
|
|
||||||
|
Operand result = context.IAdd(context.IAdd(mantissa, context.ShiftLeft(exp, Const(23))), resultSign);
|
||||||
|
|
||||||
|
context.Return(result);
|
||||||
|
|
||||||
|
return new Function(ControlFlowGraph.Create(context.GetOperations()).Blocks, "ConvertDoubleToFloat", true, 2, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Function GenerateConvertFloatToDoubleFunction()
|
||||||
|
{
|
||||||
|
EmitterContext context = new EmitterContext();
|
||||||
|
|
||||||
|
Operand value = Argument(0);
|
||||||
|
|
||||||
|
Operand mantissa = context.BitwiseAnd(value, Const(0x7fffff));
|
||||||
|
Operand exp = context.BitwiseAnd(context.ShiftRightU32(value, Const(23)), Const(0xff));
|
||||||
|
Operand sign = context.ShiftRightS32(value, Const(31));
|
||||||
|
|
||||||
|
Operand notNaNOrInf = context.ICompareNotEqual(exp, Const(0xff));
|
||||||
|
Operand expNotZero = context.ICompareNotEqual(exp, Const(0));
|
||||||
|
Operand notDenorm = context.BitwiseOr(expNotZero, context.ICompareEqual(mantissa, Const(0)));
|
||||||
|
|
||||||
|
exp = context.IAdd(exp, Const(0x380));
|
||||||
|
|
||||||
|
Operand shiftDist = context.ISubtract(Const(32), context.FindMSBU32(mantissa));
|
||||||
|
Operand normExp = context.ISubtract(context.ISubtract(Const(1), shiftDist), Const(1));
|
||||||
|
Operand normMant = context.ShiftLeft(mantissa, shiftDist);
|
||||||
|
|
||||||
|
exp = context.ConditionalSelect(notNaNOrInf, exp, Const(0x7ff));
|
||||||
|
exp = context.ConditionalSelect(notDenorm, exp, normExp);
|
||||||
|
mantissa = context.ConditionalSelect(expNotZero, mantissa, normMant);
|
||||||
|
|
||||||
|
Operand resultLow = context.ShiftLeft(mantissa, Const(29));
|
||||||
|
Operand resultHigh = context.ShiftRightU32(mantissa, Const(3));
|
||||||
|
|
||||||
|
resultHigh = context.IAdd(resultHigh, context.ShiftLeft(exp, Const(20)));
|
||||||
|
resultHigh = context.IAdd(resultHigh, context.ShiftLeft(sign, Const(31)));
|
||||||
|
|
||||||
|
context.Copy(Argument(1), resultLow);
|
||||||
|
context.Copy(Argument(2), resultHigh);
|
||||||
|
context.Return();
|
||||||
|
|
||||||
|
return new Function(ControlFlowGraph.Create(context.GetOperations()).Blocks, "ConvertFloatToDouble", false, 1, 2);
|
||||||
|
}
|
||||||
|
|
||||||
private Function GenerateTexelFetchScaleFunction()
|
private Function GenerateTexelFetchScaleFunction()
|
||||||
{
|
{
|
||||||
EmitterContext context = new EmitterContext();
|
EmitterContext context = new EmitterContext();
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.Shader.Translation
|
namespace Ryujinx.Graphics.Shader.Translation
|
||||||
{
|
{
|
||||||
enum HelperFunctionName
|
enum HelperFunctionName
|
||||||
{
|
{
|
||||||
|
ConvertDoubleToFloat,
|
||||||
|
ConvertFloatToDouble,
|
||||||
TexelFetchScale,
|
TexelFetchScale,
|
||||||
TextureSizeUnscale
|
TextureSizeUnscale
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,70 @@
|
|||||||
|
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
|
||||||
|
|
||||||
|
namespace Ryujinx.Graphics.Shader.Translation.Optimizations
|
||||||
|
{
|
||||||
|
static class DoubleToFloat
|
||||||
|
{
|
||||||
|
public static void RunPass(HelperFunctionManager hfm, BasicBlock block)
|
||||||
|
{
|
||||||
|
for (LinkedListNode<INode> node = block.Operations.First; node != null; node = node.Next)
|
||||||
|
{
|
||||||
|
if (node.Value is not Operation operation)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
node = InsertSoftFloat64(hfm, node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static LinkedListNode<INode> InsertSoftFloat64(HelperFunctionManager hfm, LinkedListNode<INode> node)
|
||||||
|
{
|
||||||
|
Operation operation = (Operation)node.Value;
|
||||||
|
|
||||||
|
if (operation.Inst == Instruction.PackDouble2x32)
|
||||||
|
{
|
||||||
|
int functionId = hfm.GetOrCreateFunctionId(HelperFunctionName.ConvertDoubleToFloat);
|
||||||
|
|
||||||
|
Operand[] callArgs = new Operand[] { Const(functionId), operation.GetSource(0), operation.GetSource(1) };
|
||||||
|
|
||||||
|
Operand floatValue = operation.Dest;
|
||||||
|
|
||||||
|
operation.Dest = null;
|
||||||
|
|
||||||
|
LinkedListNode<INode> newNode = node.List.AddBefore(node, new Operation(Instruction.Call, 0, floatValue, callArgs));
|
||||||
|
|
||||||
|
Utils.DeleteNode(node, operation);
|
||||||
|
|
||||||
|
return newNode;
|
||||||
|
}
|
||||||
|
else if (operation.Inst == Instruction.UnpackDouble2x32)
|
||||||
|
{
|
||||||
|
int functionId = hfm.GetOrCreateFunctionId(HelperFunctionName.ConvertFloatToDouble);
|
||||||
|
|
||||||
|
// TODO: Allow UnpackDouble2x32 to produce two outputs and get rid of "operation.Index".
|
||||||
|
|
||||||
|
Operand resultLow = operation.Index == 0 ? operation.Dest : Local();
|
||||||
|
Operand resultHigh = operation.Index == 1 ? operation.Dest : Local();
|
||||||
|
|
||||||
|
operation.Dest = null;
|
||||||
|
|
||||||
|
Operand[] callArgs = new Operand[] { Const(functionId), operation.GetSource(0), resultLow, resultHigh };
|
||||||
|
|
||||||
|
LinkedListNode<INode> newNode = node.List.AddBefore(node, new Operation(Instruction.Call, 0, (Operand)null, callArgs));
|
||||||
|
|
||||||
|
Utils.DeleteNode(node, operation);
|
||||||
|
|
||||||
|
return newNode;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
operation.TurnDoubleIntoFloat();
|
||||||
|
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
@ -7,17 +7,19 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
|
|||||||
{
|
{
|
||||||
static class Optimizer
|
static class Optimizer
|
||||||
{
|
{
|
||||||
public static void RunPass(BasicBlock[] blocks, ShaderConfig config)
|
public static void RunPass(HelperFunctionManager hfm, BasicBlock[] blocks, ShaderConfig config)
|
||||||
{
|
{
|
||||||
RunOptimizationPasses(blocks, config);
|
RunOptimizationPasses(blocks, config);
|
||||||
|
|
||||||
int sbUseMask = 0;
|
// TODO: Some of those are not optimizations and shouldn't be here.
|
||||||
int ubeUseMask = 0;
|
|
||||||
|
GlobalToStorage.RunPass(hfm, blocks, config);
|
||||||
|
|
||||||
|
bool hostSupportsShaderFloat64 = config.GpuAccessor.QueryHostSupportsShaderFloat64();
|
||||||
|
|
||||||
// Those passes are looking for specific patterns and only needs to run once.
|
// Those passes are looking for specific patterns and only needs to run once.
|
||||||
for (int blkIndex = 0; blkIndex < blocks.Length; blkIndex++)
|
for (int blkIndex = 0; blkIndex < blocks.Length; blkIndex++)
|
||||||
{
|
{
|
||||||
GlobalToStorage.RunPass(blocks[blkIndex], config, ref sbUseMask, ref ubeUseMask);
|
|
||||||
BindlessToIndexed.RunPass(blocks[blkIndex], config);
|
BindlessToIndexed.RunPass(blocks[blkIndex], config);
|
||||||
BindlessElimination.RunPass(blocks[blkIndex], config);
|
BindlessElimination.RunPass(blocks[blkIndex], config);
|
||||||
|
|
||||||
@ -26,9 +28,13 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
|
|||||||
{
|
{
|
||||||
EliminateMultiplyByFragmentCoordW(blocks[blkIndex]);
|
EliminateMultiplyByFragmentCoordW(blocks[blkIndex]);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
config.SetAccessibleBufferMasks(sbUseMask, ubeUseMask);
|
// If the host does not support double operations, we need to turn them into float operations.
|
||||||
|
if (!hostSupportsShaderFloat64)
|
||||||
|
{
|
||||||
|
DoubleToFloat.RunPass(hfm, blocks[blkIndex]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Run optimizations one last time to remove any code that is now optimizable after above passes.
|
// Run optimizations one last time to remove any code that is now optimizable after above passes.
|
||||||
RunOptimizationPasses(blocks, config);
|
RunOptimizationPasses(blocks, config);
|
||||||
|
@ -13,7 +13,6 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
|
|||||||
switch (operation.Inst)
|
switch (operation.Inst)
|
||||||
{
|
{
|
||||||
case Instruction.Add:
|
case Instruction.Add:
|
||||||
case Instruction.BitwiseExclusiveOr:
|
|
||||||
TryEliminateBinaryOpCommutative(operation, 0);
|
TryEliminateBinaryOpCommutative(operation, 0);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@ -21,6 +20,13 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
|
|||||||
TryEliminateBitwiseAnd(operation);
|
TryEliminateBitwiseAnd(operation);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case Instruction.BitwiseExclusiveOr:
|
||||||
|
if (!TryEliminateXorSwap(operation))
|
||||||
|
{
|
||||||
|
TryEliminateBinaryOpCommutative(operation, 0);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
case Instruction.BitwiseOr:
|
case Instruction.BitwiseOr:
|
||||||
TryEliminateBitwiseOr(operation);
|
TryEliminateBitwiseOr(operation);
|
||||||
break;
|
break;
|
||||||
@ -49,8 +55,9 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
|
|||||||
private static void TryEliminateBitwiseAnd(Operation operation)
|
private static void TryEliminateBitwiseAnd(Operation operation)
|
||||||
{
|
{
|
||||||
// Try to recognize and optimize those 3 patterns (in order):
|
// Try to recognize and optimize those 3 patterns (in order):
|
||||||
// x & 0xFFFFFFFF == x, 0xFFFFFFFF & y == y,
|
// x & 0xFFFFFFFF == x, 0xFFFFFFFF & y == y,
|
||||||
// x & 0x00000000 == 0x00000000, 0x00000000 & y == 0x00000000
|
// x & 0x00000000 == 0x00000000, 0x00000000 & y == 0x00000000
|
||||||
|
|
||||||
Operand x = operation.GetSource(0);
|
Operand x = operation.GetSource(0);
|
||||||
Operand y = operation.GetSource(1);
|
Operand y = operation.GetSource(1);
|
||||||
|
|
||||||
@ -68,11 +75,62 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static bool TryEliminateXorSwap(Operation xCopyOp)
|
||||||
|
{
|
||||||
|
// Try to recognize XOR swap pattern:
|
||||||
|
// x = x ^ y
|
||||||
|
// y = x ^ y
|
||||||
|
// x = x ^ y
|
||||||
|
// Or, in SSA:
|
||||||
|
// x2 = x ^ y
|
||||||
|
// y2 = x2 ^ y
|
||||||
|
// x3 = x2 ^ y2
|
||||||
|
// Transform it into something more sane:
|
||||||
|
// temp = y
|
||||||
|
// y = x
|
||||||
|
// x = temp
|
||||||
|
|
||||||
|
// Note that because XOR is commutative, there are actually
|
||||||
|
// multiple possible combinations of this pattern, for
|
||||||
|
// simplicity this only catches one of them.
|
||||||
|
|
||||||
|
Operand x = xCopyOp.GetSource(0);
|
||||||
|
Operand y = xCopyOp.GetSource(1);
|
||||||
|
|
||||||
|
if (x.AsgOp is not Operation tCopyOp || tCopyOp.Inst != Instruction.BitwiseExclusiveOr ||
|
||||||
|
y.AsgOp is not Operation yCopyOp || yCopyOp.Inst != Instruction.BitwiseExclusiveOr)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tCopyOp == yCopyOp)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (yCopyOp.GetSource(0) != x ||
|
||||||
|
yCopyOp.GetSource(1) != tCopyOp.GetSource(1) ||
|
||||||
|
x.UseOps.Count != 2)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
x = tCopyOp.GetSource(0);
|
||||||
|
y = tCopyOp.GetSource(1);
|
||||||
|
|
||||||
|
tCopyOp.TurnIntoCopy(y); // Temp = Y
|
||||||
|
yCopyOp.TurnIntoCopy(x); // Y = X
|
||||||
|
xCopyOp.TurnIntoCopy(tCopyOp.Dest); // X = Temp
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private static void TryEliminateBitwiseOr(Operation operation)
|
private static void TryEliminateBitwiseOr(Operation operation)
|
||||||
{
|
{
|
||||||
// Try to recognize and optimize those 3 patterns (in order):
|
// Try to recognize and optimize those 3 patterns (in order):
|
||||||
// x | 0x00000000 == x, 0x00000000 | y == y,
|
// x | 0x00000000 == x, 0x00000000 | y == y,
|
||||||
// x | 0xFFFFFFFF == 0xFFFFFFFF, 0xFFFFFFFF | y == 0xFFFFFFFF
|
// x | 0xFFFFFFFF == 0xFFFFFFFF, 0xFFFFFFFF | y == 0xFFFFFFFF
|
||||||
|
|
||||||
Operand x = operation.GetSource(0);
|
Operand x = operation.GetSource(0);
|
||||||
Operand y = operation.GetSource(1);
|
Operand y = operation.GetSource(1);
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
|
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.Shader.Translation.Optimizations
|
namespace Ryujinx.Graphics.Shader.Translation.Optimizations
|
||||||
{
|
{
|
||||||
@ -93,5 +94,17 @@ namespace Ryujinx.Graphics.Shader.Translation.Optimizations
|
|||||||
|
|
||||||
return source;
|
return source;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void DeleteNode(LinkedListNode<INode> node, Operation operation)
|
||||||
|
{
|
||||||
|
node.List.Remove(node);
|
||||||
|
|
||||||
|
for (int srcIndex = 0; srcIndex < operation.SourcesCount; srcIndex++)
|
||||||
|
{
|
||||||
|
operation.SetSource(srcIndex, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
operation.Dest = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -14,6 +14,11 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||||||
private readonly string _stagePrefix;
|
private readonly string _stagePrefix;
|
||||||
|
|
||||||
private readonly int[] _cbSlotToBindingMap;
|
private readonly int[] _cbSlotToBindingMap;
|
||||||
|
private readonly int[] _sbSlotToBindingMap;
|
||||||
|
private uint _sbSlotWritten;
|
||||||
|
|
||||||
|
private readonly Dictionary<int, int> _sbSlots;
|
||||||
|
private readonly Dictionary<int, int> _sbSlotsReverse;
|
||||||
|
|
||||||
private readonly HashSet<int> _usedConstantBufferBindings;
|
private readonly HashSet<int> _usedConstantBufferBindings;
|
||||||
|
|
||||||
@ -26,7 +31,12 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||||||
_stagePrefix = GetShaderStagePrefix(stage);
|
_stagePrefix = GetShaderStagePrefix(stage);
|
||||||
|
|
||||||
_cbSlotToBindingMap = new int[18];
|
_cbSlotToBindingMap = new int[18];
|
||||||
|
_sbSlotToBindingMap = new int[16];
|
||||||
_cbSlotToBindingMap.AsSpan().Fill(-1);
|
_cbSlotToBindingMap.AsSpan().Fill(-1);
|
||||||
|
_sbSlotToBindingMap.AsSpan().Fill(-1);
|
||||||
|
|
||||||
|
_sbSlots = new Dictionary<int, int>();
|
||||||
|
_sbSlotsReverse = new Dictionary<int, int>();
|
||||||
|
|
||||||
_usedConstantBufferBindings = new HashSet<int>();
|
_usedConstantBufferBindings = new HashSet<int>();
|
||||||
|
|
||||||
@ -47,6 +57,52 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||||||
return binding;
|
return binding;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public bool TryGetStorageBufferBinding(int sbCbSlot, int sbCbOffset, bool write, out int binding)
|
||||||
|
{
|
||||||
|
if (!TryGetSbSlot((byte)sbCbSlot, (ushort)sbCbOffset, out int slot))
|
||||||
|
{
|
||||||
|
binding = 0;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
binding = _sbSlotToBindingMap[slot];
|
||||||
|
|
||||||
|
if (binding < 0)
|
||||||
|
{
|
||||||
|
binding = _gpuAccessor.QueryBindingStorageBuffer(slot);
|
||||||
|
_sbSlotToBindingMap[slot] = binding;
|
||||||
|
string slotNumber = slot.ToString(CultureInfo.InvariantCulture);
|
||||||
|
AddNewStorageBuffer(binding, $"{_stagePrefix}_s{slotNumber}");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (write)
|
||||||
|
{
|
||||||
|
_sbSlotWritten |= 1u << slot;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool TryGetSbSlot(byte sbCbSlot, ushort sbCbOffset, out int slot)
|
||||||
|
{
|
||||||
|
int key = PackSbCbInfo(sbCbSlot, sbCbOffset);
|
||||||
|
|
||||||
|
if (!_sbSlots.TryGetValue(key, out slot))
|
||||||
|
{
|
||||||
|
slot = _sbSlots.Count;
|
||||||
|
|
||||||
|
if (slot >= _sbSlotToBindingMap.Length)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_sbSlots.Add(key, slot);
|
||||||
|
_sbSlotsReverse.Add(slot, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
public bool TryGetConstantBufferSlot(int binding, out int slot)
|
public bool TryGetConstantBufferSlot(int binding, out int slot)
|
||||||
{
|
{
|
||||||
for (slot = 0; slot < _cbSlotToBindingMap.Length; slot++)
|
for (slot = 0; slot < _cbSlotToBindingMap.Length; slot++)
|
||||||
@ -90,6 +146,34 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||||||
return descriptors;
|
return descriptors;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public BufferDescriptor[] GetStorageBufferDescriptors()
|
||||||
|
{
|
||||||
|
var descriptors = new BufferDescriptor[_sbSlots.Count];
|
||||||
|
|
||||||
|
int descriptorIndex = 0;
|
||||||
|
|
||||||
|
foreach ((int key, int slot) in _sbSlots)
|
||||||
|
{
|
||||||
|
int binding = _sbSlotToBindingMap[slot];
|
||||||
|
|
||||||
|
if (binding >= 0)
|
||||||
|
{
|
||||||
|
(int sbCbSlot, int sbCbOffset) = UnpackSbCbInfo(key);
|
||||||
|
descriptors[descriptorIndex++] = new BufferDescriptor(binding, slot, sbCbSlot, sbCbOffset)
|
||||||
|
{
|
||||||
|
Flags = (_sbSlotWritten & (1u << slot)) != 0 ? BufferUsageFlags.Write : BufferUsageFlags.None
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (descriptors.Length != descriptorIndex)
|
||||||
|
{
|
||||||
|
Array.Resize(ref descriptors, descriptorIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
return descriptors;
|
||||||
|
}
|
||||||
|
|
||||||
private void AddNewConstantBuffer(int binding, string name)
|
private void AddNewConstantBuffer(int binding, string name)
|
||||||
{
|
{
|
||||||
StructureType type = new StructureType(new[]
|
StructureType type = new StructureType(new[]
|
||||||
@ -100,6 +184,16 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||||||
_properties.AddConstantBuffer(binding, new BufferDefinition(BufferLayout.Std140, 0, binding, name, type));
|
_properties.AddConstantBuffer(binding, new BufferDefinition(BufferLayout.Std140, 0, binding, name, type));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void AddNewStorageBuffer(int binding, string name)
|
||||||
|
{
|
||||||
|
StructureType type = new StructureType(new[]
|
||||||
|
{
|
||||||
|
new StructureField(AggregateType.Array | AggregateType.U32, "data", 0)
|
||||||
|
});
|
||||||
|
|
||||||
|
_properties.AddStorageBuffer(binding, new BufferDefinition(BufferLayout.Std430, 1, binding, name, type));
|
||||||
|
}
|
||||||
|
|
||||||
public static string GetShaderStagePrefix(ShaderStage stage)
|
public static string GetShaderStagePrefix(ShaderStage stage)
|
||||||
{
|
{
|
||||||
uint index = (uint)stage;
|
uint index = (uint)stage;
|
||||||
@ -111,5 +205,15 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||||||
|
|
||||||
return _stagePrefixes[index];
|
return _stagePrefixes[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static int PackSbCbInfo(int sbCbSlot, int sbCbOffset)
|
||||||
|
{
|
||||||
|
return sbCbOffset | ((int)sbCbSlot << 16);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static (int, int) UnpackSbCbInfo(int key)
|
||||||
|
{
|
||||||
|
return ((byte)(key >> 16), (ushort)key);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -2,10 +2,8 @@ using Ryujinx.Graphics.Shader.IntermediateRepresentation;
|
|||||||
using Ryujinx.Graphics.Shader.StructuredIr;
|
using Ryujinx.Graphics.Shader.StructuredIr;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Numerics;
|
|
||||||
|
|
||||||
using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
|
using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
|
||||||
using static Ryujinx.Graphics.Shader.Translation.GlobalMemory;
|
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.Shader.Translation
|
namespace Ryujinx.Graphics.Shader.Translation
|
||||||
{
|
{
|
||||||
@ -23,11 +21,10 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||||||
{
|
{
|
||||||
BasicBlock block = blocks[blkIndex];
|
BasicBlock block = blocks[blkIndex];
|
||||||
|
|
||||||
for (LinkedListNode<INode> node = block.Operations.First; node != null;)
|
for (LinkedListNode<INode> node = block.Operations.First; node != null; node = node.Next)
|
||||||
{
|
{
|
||||||
if (node.Value is not Operation operation)
|
if (node.Value is not Operation operation)
|
||||||
{
|
{
|
||||||
node = node.Next;
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,8 +53,6 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||||||
InsertVectorComponentSelect(node, config);
|
InsertVectorComponentSelect(node, config);
|
||||||
}
|
}
|
||||||
|
|
||||||
LinkedListNode<INode> nextNode = node.Next;
|
|
||||||
|
|
||||||
if (operation is TextureOperation texOp)
|
if (operation is TextureOperation texOp)
|
||||||
{
|
{
|
||||||
node = InsertTexelFetchScale(hfm, node, config);
|
node = InsertTexelFetchScale(hfm, node, config);
|
||||||
@ -74,15 +69,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||||||
node = InsertSnormNormalization(node, config);
|
node = InsertSnormNormalization(node, config);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
nextNode = node.Next;
|
|
||||||
}
|
}
|
||||||
else if (UsesGlobalMemory(operation.Inst, operation.StorageKind))
|
|
||||||
{
|
|
||||||
nextNode = RewriteGlobalAccess(node, config)?.Next ?? nextNode;
|
|
||||||
}
|
|
||||||
|
|
||||||
node = nextNode;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -184,196 +171,6 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||||||
operation.TurnIntoCopy(result);
|
operation.TurnIntoCopy(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static LinkedListNode<INode> RewriteGlobalAccess(LinkedListNode<INode> node, ShaderConfig config)
|
|
||||||
{
|
|
||||||
Operation operation = (Operation)node.Value;
|
|
||||||
|
|
||||||
bool isAtomic = operation.Inst.IsAtomic();
|
|
||||||
bool isStg16Or8 = operation.Inst == Instruction.StoreGlobal16 || operation.Inst == Instruction.StoreGlobal8;
|
|
||||||
bool isWrite = isAtomic || operation.Inst == Instruction.StoreGlobal || isStg16Or8;
|
|
||||||
|
|
||||||
Operation storageOp = null;
|
|
||||||
|
|
||||||
Operand PrependOperation(Instruction inst, params Operand[] sources)
|
|
||||||
{
|
|
||||||
Operand local = Local();
|
|
||||||
|
|
||||||
node.List.AddBefore(node, new Operation(inst, local, sources));
|
|
||||||
|
|
||||||
return local;
|
|
||||||
}
|
|
||||||
|
|
||||||
Operand PrependStorageOperation(Instruction inst, StorageKind storageKind, params Operand[] sources)
|
|
||||||
{
|
|
||||||
Operand local = Local();
|
|
||||||
|
|
||||||
node.List.AddBefore(node, new Operation(inst, storageKind, local, sources));
|
|
||||||
|
|
||||||
return local;
|
|
||||||
}
|
|
||||||
|
|
||||||
Operand PrependExistingOperation(Operation operation)
|
|
||||||
{
|
|
||||||
Operand local = Local();
|
|
||||||
|
|
||||||
operation.Dest = local;
|
|
||||||
node.List.AddBefore(node, operation);
|
|
||||||
|
|
||||||
return local;
|
|
||||||
}
|
|
||||||
|
|
||||||
Operand addrLow = operation.GetSource(0);
|
|
||||||
Operand addrHigh = operation.GetSource(1);
|
|
||||||
|
|
||||||
Operand sbBaseAddrLow = Const(0);
|
|
||||||
Operand sbSlot = Const(0);
|
|
||||||
|
|
||||||
Operand alignMask = Const(-config.GpuAccessor.QueryHostStorageBufferOffsetAlignment());
|
|
||||||
|
|
||||||
Operand BindingRangeCheck(int cbOffset, out Operand baseAddrLow)
|
|
||||||
{
|
|
||||||
baseAddrLow = Cbuf(DriverReservedCb, cbOffset);
|
|
||||||
Operand baseAddrHigh = Cbuf(DriverReservedCb, cbOffset + 1);
|
|
||||||
Operand size = Cbuf(DriverReservedCb, cbOffset + 2);
|
|
||||||
|
|
||||||
Operand offset = PrependOperation(Instruction.Subtract, addrLow, baseAddrLow);
|
|
||||||
Operand borrow = PrependOperation(Instruction.CompareLessU32, addrLow, baseAddrLow);
|
|
||||||
|
|
||||||
Operand inRangeLow = PrependOperation(Instruction.CompareLessU32, offset, size);
|
|
||||||
|
|
||||||
Operand addrHighBorrowed = PrependOperation(Instruction.Add, addrHigh, borrow);
|
|
||||||
|
|
||||||
Operand inRangeHigh = PrependOperation(Instruction.CompareEqual, addrHighBorrowed, baseAddrHigh);
|
|
||||||
|
|
||||||
return PrependOperation(Instruction.BitwiseAnd, inRangeLow, inRangeHigh);
|
|
||||||
}
|
|
||||||
|
|
||||||
int sbUseMask = config.AccessibleStorageBuffersMask;
|
|
||||||
|
|
||||||
while (sbUseMask != 0)
|
|
||||||
{
|
|
||||||
int slot = BitOperations.TrailingZeroCount(sbUseMask);
|
|
||||||
|
|
||||||
sbUseMask &= ~(1 << slot);
|
|
||||||
|
|
||||||
int cbOffset = GetStorageCbOffset(config.Stage, slot);
|
|
||||||
slot = config.GetSbSlot(DriverReservedCb, (ushort)cbOffset);
|
|
||||||
|
|
||||||
config.SetUsedStorageBuffer(slot, isWrite);
|
|
||||||
|
|
||||||
Operand inRange = BindingRangeCheck(cbOffset, out Operand baseAddrLow);
|
|
||||||
|
|
||||||
sbBaseAddrLow = PrependOperation(Instruction.ConditionalSelect, inRange, baseAddrLow, sbBaseAddrLow);
|
|
||||||
sbSlot = PrependOperation(Instruction.ConditionalSelect, inRange, Const(slot), sbSlot);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.AccessibleStorageBuffersMask != 0)
|
|
||||||
{
|
|
||||||
Operand baseAddrTrunc = PrependOperation(Instruction.BitwiseAnd, sbBaseAddrLow, alignMask);
|
|
||||||
Operand byteOffset = PrependOperation(Instruction.Subtract, addrLow, baseAddrTrunc);
|
|
||||||
|
|
||||||
Operand[] sources = new Operand[operation.SourcesCount];
|
|
||||||
|
|
||||||
sources[0] = sbSlot;
|
|
||||||
|
|
||||||
if (isStg16Or8)
|
|
||||||
{
|
|
||||||
sources[1] = byteOffset;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
sources[1] = PrependOperation(Instruction.ShiftRightU32, byteOffset, Const(2));
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int index = 2; index < operation.SourcesCount; index++)
|
|
||||||
{
|
|
||||||
sources[index] = operation.GetSource(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isAtomic)
|
|
||||||
{
|
|
||||||
storageOp = new Operation(operation.Inst, StorageKind.StorageBuffer, operation.Dest, sources);
|
|
||||||
}
|
|
||||||
else if (operation.Inst == Instruction.LoadGlobal)
|
|
||||||
{
|
|
||||||
storageOp = new Operation(Instruction.LoadStorage, operation.Dest, sources);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Instruction storeInst = operation.Inst switch
|
|
||||||
{
|
|
||||||
Instruction.StoreGlobal16 => Instruction.StoreStorage16,
|
|
||||||
Instruction.StoreGlobal8 => Instruction.StoreStorage8,
|
|
||||||
_ => Instruction.StoreStorage
|
|
||||||
};
|
|
||||||
|
|
||||||
storageOp = new Operation(storeInst, null, sources);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else if (operation.Dest != null)
|
|
||||||
{
|
|
||||||
storageOp = new Operation(Instruction.Copy, operation.Dest, Const(0));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (operation.Inst == Instruction.LoadGlobal)
|
|
||||||
{
|
|
||||||
int cbeUseMask = config.AccessibleConstantBuffersMask;
|
|
||||||
|
|
||||||
while (cbeUseMask != 0)
|
|
||||||
{
|
|
||||||
int slot = BitOperations.TrailingZeroCount(cbeUseMask);
|
|
||||||
int cbSlot = UbeFirstCbuf + slot;
|
|
||||||
|
|
||||||
cbeUseMask &= ~(1 << slot);
|
|
||||||
|
|
||||||
Operand previousResult = PrependExistingOperation(storageOp);
|
|
||||||
|
|
||||||
int cbOffset = GetConstantUbeOffset(slot);
|
|
||||||
|
|
||||||
Operand inRange = BindingRangeCheck(cbOffset, out Operand baseAddrLow);
|
|
||||||
|
|
||||||
Operand baseAddrTruncConst = PrependOperation(Instruction.BitwiseAnd, baseAddrLow, alignMask);
|
|
||||||
Operand byteOffsetConst = PrependOperation(Instruction.Subtract, addrLow, baseAddrTruncConst);
|
|
||||||
|
|
||||||
Operand cbIndex = PrependOperation(Instruction.ShiftRightU32, byteOffsetConst, Const(2));
|
|
||||||
Operand vecIndex = PrependOperation(Instruction.ShiftRightU32, cbIndex, Const(2));
|
|
||||||
Operand elemIndex = PrependOperation(Instruction.BitwiseAnd, cbIndex, Const(3));
|
|
||||||
|
|
||||||
Operand[] sourcesCb = new Operand[4];
|
|
||||||
|
|
||||||
sourcesCb[0] = Const(config.ResourceManager.GetConstantBufferBinding(cbSlot));
|
|
||||||
sourcesCb[1] = Const(0);
|
|
||||||
sourcesCb[2] = vecIndex;
|
|
||||||
sourcesCb[3] = elemIndex;
|
|
||||||
|
|
||||||
Operand ldcResult = PrependStorageOperation(Instruction.Load, StorageKind.ConstantBuffer, sourcesCb);
|
|
||||||
|
|
||||||
storageOp = new Operation(Instruction.ConditionalSelect, operation.Dest, inRange, ldcResult, previousResult);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (int index = 0; index < operation.SourcesCount; index++)
|
|
||||||
{
|
|
||||||
operation.SetSource(index, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
LinkedListNode<INode> oldNode = node;
|
|
||||||
LinkedList<INode> oldNodeList = oldNode.List;
|
|
||||||
|
|
||||||
if (storageOp != null)
|
|
||||||
{
|
|
||||||
node = node.List.AddBefore(node, storageOp);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
node = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
oldNodeList.Remove(oldNode);
|
|
||||||
|
|
||||||
return node;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static LinkedListNode<INode> InsertTexelFetchScale(HelperFunctionManager hfm, LinkedListNode<INode> node, ShaderConfig config)
|
private static LinkedListNode<INode> InsertTexelFetchScale(HelperFunctionManager hfm, LinkedListNode<INode> node, ShaderConfig config)
|
||||||
{
|
{
|
||||||
TextureOperation texOp = (TextureOperation)node.Value;
|
TextureOperation texOp = (TextureOperation)node.Value;
|
||||||
|
@ -110,12 +110,6 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||||||
public UInt128 NextInputAttributesComponents { get; private set; }
|
public UInt128 NextInputAttributesComponents { get; private set; }
|
||||||
public UInt128 ThisInputAttributesComponents { get; private set; }
|
public UInt128 ThisInputAttributesComponents { get; private set; }
|
||||||
|
|
||||||
public int AccessibleStorageBuffersMask { get; private set; }
|
|
||||||
public int AccessibleConstantBuffersMask { get; private set; }
|
|
||||||
|
|
||||||
private int _usedStorageBuffers;
|
|
||||||
private int _usedStorageBuffersWrite;
|
|
||||||
|
|
||||||
private readonly record struct TextureInfo(int CbufSlot, int Handle, bool Indexed, TextureFormat Format);
|
private readonly record struct TextureInfo(int CbufSlot, int Handle, bool Indexed, TextureFormat Format);
|
||||||
|
|
||||||
private struct TextureMeta
|
private struct TextureMeta
|
||||||
@ -127,18 +121,9 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||||||
|
|
||||||
private readonly Dictionary<TextureInfo, TextureMeta> _usedTextures;
|
private readonly Dictionary<TextureInfo, TextureMeta> _usedTextures;
|
||||||
private readonly Dictionary<TextureInfo, TextureMeta> _usedImages;
|
private readonly Dictionary<TextureInfo, TextureMeta> _usedImages;
|
||||||
|
|
||||||
private readonly Dictionary<int, int> _sbSlots;
|
|
||||||
private readonly Dictionary<int, int> _sbSlotsReverse;
|
|
||||||
|
|
||||||
private BufferDescriptor[] _cachedStorageBufferDescriptors;
|
|
||||||
private TextureDescriptor[] _cachedTextureDescriptors;
|
private TextureDescriptor[] _cachedTextureDescriptors;
|
||||||
private TextureDescriptor[] _cachedImageDescriptors;
|
private TextureDescriptor[] _cachedImageDescriptors;
|
||||||
|
|
||||||
private int _firstStorageBufferBinding;
|
|
||||||
|
|
||||||
public int FirstStorageBufferBinding => _firstStorageBufferBinding;
|
|
||||||
|
|
||||||
public ShaderConfig(ShaderStage stage, IGpuAccessor gpuAccessor, TranslationOptions options)
|
public ShaderConfig(ShaderStage stage, IGpuAccessor gpuAccessor, TranslationOptions options)
|
||||||
{
|
{
|
||||||
Stage = stage;
|
Stage = stage;
|
||||||
@ -147,18 +132,12 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||||||
|
|
||||||
_transformFeedbackDefinitions = new Dictionary<TransformFeedbackVariable, TransformFeedbackOutput>();
|
_transformFeedbackDefinitions = new Dictionary<TransformFeedbackVariable, TransformFeedbackOutput>();
|
||||||
|
|
||||||
AccessibleStorageBuffersMask = (1 << GlobalMemory.StorageMaxCount) - 1;
|
|
||||||
AccessibleConstantBuffersMask = (1 << GlobalMemory.UbeMaxCount) - 1;
|
|
||||||
|
|
||||||
UsedInputAttributesPerPatch = new HashSet<int>();
|
UsedInputAttributesPerPatch = new HashSet<int>();
|
||||||
UsedOutputAttributesPerPatch = new HashSet<int>();
|
UsedOutputAttributesPerPatch = new HashSet<int>();
|
||||||
|
|
||||||
_usedTextures = new Dictionary<TextureInfo, TextureMeta>();
|
_usedTextures = new Dictionary<TextureInfo, TextureMeta>();
|
||||||
_usedImages = new Dictionary<TextureInfo, TextureMeta>();
|
_usedImages = new Dictionary<TextureInfo, TextureMeta>();
|
||||||
|
|
||||||
_sbSlots = new Dictionary<int, int>();
|
|
||||||
_sbSlotsReverse = new Dictionary<int, int>();
|
|
||||||
|
|
||||||
ResourceManager = new ResourceManager(stage, gpuAccessor, new ShaderProperties());
|
ResourceManager = new ResourceManager(stage, gpuAccessor, new ShaderProperties());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -173,11 +152,6 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||||||
OutputTopology = outputTopology;
|
OutputTopology = outputTopology;
|
||||||
MaxOutputVertices = maxOutputVertices;
|
MaxOutputVertices = maxOutputVertices;
|
||||||
TransformFeedbackEnabled = gpuAccessor.QueryTransformFeedbackEnabled();
|
TransformFeedbackEnabled = gpuAccessor.QueryTransformFeedbackEnabled();
|
||||||
|
|
||||||
if (Stage != ShaderStage.Compute)
|
|
||||||
{
|
|
||||||
AccessibleConstantBuffersMask = 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ShaderConfig(ShaderHeader header, IGpuAccessor gpuAccessor, TranslationOptions options) : this(header.Stage, gpuAccessor, options)
|
public ShaderConfig(ShaderHeader header, IGpuAccessor gpuAccessor, TranslationOptions options) : this(header.Stage, gpuAccessor, options)
|
||||||
@ -433,8 +407,6 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||||||
|
|
||||||
UsedInputAttributes |= other.UsedInputAttributes;
|
UsedInputAttributes |= other.UsedInputAttributes;
|
||||||
UsedOutputAttributes |= other.UsedOutputAttributes;
|
UsedOutputAttributes |= other.UsedOutputAttributes;
|
||||||
_usedStorageBuffers |= other._usedStorageBuffers;
|
|
||||||
_usedStorageBuffersWrite |= other._usedStorageBuffersWrite;
|
|
||||||
|
|
||||||
foreach (var kv in other._usedTextures)
|
foreach (var kv in other._usedTextures)
|
||||||
{
|
{
|
||||||
@ -634,23 +606,6 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||||||
UsedFeatures |= flags;
|
UsedFeatures |= flags;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetAccessibleBufferMasks(int sbMask, int ubeMask)
|
|
||||||
{
|
|
||||||
AccessibleStorageBuffersMask = sbMask;
|
|
||||||
AccessibleConstantBuffersMask = ubeMask;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetUsedStorageBuffer(int slot, bool write)
|
|
||||||
{
|
|
||||||
int mask = 1 << slot;
|
|
||||||
_usedStorageBuffers |= mask;
|
|
||||||
|
|
||||||
if (write)
|
|
||||||
{
|
|
||||||
_usedStorageBuffersWrite |= mask;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void SetUsedTexture(
|
public void SetUsedTexture(
|
||||||
Instruction inst,
|
Instruction inst,
|
||||||
SamplerType type,
|
SamplerType type,
|
||||||
@ -756,76 +711,6 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||||||
return meta;
|
return meta;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BufferDescriptor[] GetStorageBufferDescriptors()
|
|
||||||
{
|
|
||||||
if (_cachedStorageBufferDescriptors != null)
|
|
||||||
{
|
|
||||||
return _cachedStorageBufferDescriptors;
|
|
||||||
}
|
|
||||||
|
|
||||||
return _cachedStorageBufferDescriptors = GetStorageBufferDescriptors(
|
|
||||||
_usedStorageBuffers,
|
|
||||||
_usedStorageBuffersWrite,
|
|
||||||
true,
|
|
||||||
out _firstStorageBufferBinding,
|
|
||||||
GpuAccessor.QueryBindingStorageBuffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
private BufferDescriptor[] GetStorageBufferDescriptors(
|
|
||||||
int usedMask,
|
|
||||||
int writtenMask,
|
|
||||||
bool isArray,
|
|
||||||
out int firstBinding,
|
|
||||||
Func<int, int> getBindingCallback)
|
|
||||||
{
|
|
||||||
firstBinding = 0;
|
|
||||||
bool hasFirstBinding = false;
|
|
||||||
var descriptors = new BufferDescriptor[BitOperations.PopCount((uint)usedMask)];
|
|
||||||
|
|
||||||
int lastSlot = -1;
|
|
||||||
|
|
||||||
for (int i = 0; i < descriptors.Length; i++)
|
|
||||||
{
|
|
||||||
int slot = BitOperations.TrailingZeroCount(usedMask);
|
|
||||||
|
|
||||||
if (isArray)
|
|
||||||
{
|
|
||||||
// The next array entries also consumes bindings, even if they are unused.
|
|
||||||
for (int j = lastSlot + 1; j < slot; j++)
|
|
||||||
{
|
|
||||||
int binding = getBindingCallback(j);
|
|
||||||
|
|
||||||
if (!hasFirstBinding)
|
|
||||||
{
|
|
||||||
firstBinding = binding;
|
|
||||||
hasFirstBinding = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lastSlot = slot;
|
|
||||||
|
|
||||||
(int sbCbSlot, int sbCbOffset) = GetSbCbInfo(slot);
|
|
||||||
|
|
||||||
descriptors[i] = new BufferDescriptor(getBindingCallback(slot), slot, sbCbSlot, sbCbOffset);
|
|
||||||
|
|
||||||
if (!hasFirstBinding)
|
|
||||||
{
|
|
||||||
firstBinding = descriptors[i].Binding;
|
|
||||||
hasFirstBinding = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((writtenMask & (1 << slot)) != 0)
|
|
||||||
{
|
|
||||||
descriptors[i].SetFlag(BufferUsageFlags.Write);
|
|
||||||
}
|
|
||||||
|
|
||||||
usedMask &= ~(1 << slot);
|
|
||||||
}
|
|
||||||
|
|
||||||
return descriptors;
|
|
||||||
}
|
|
||||||
|
|
||||||
public TextureDescriptor[] GetTextureDescriptors()
|
public TextureDescriptor[] GetTextureDescriptors()
|
||||||
{
|
{
|
||||||
return _cachedTextureDescriptors ??= GetTextureOrImageDescriptors(_usedTextures, GpuAccessor.QueryBindingTexture);
|
return _cachedTextureDescriptors ??= GetTextureOrImageDescriptors(_usedTextures, GpuAccessor.QueryBindingTexture);
|
||||||
@ -922,45 +807,11 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||||||
return FindDescriptorIndex(GetImageDescriptors(), texOp);
|
return FindDescriptorIndex(GetImageDescriptors(), texOp);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int GetSbSlot(byte sbCbSlot, ushort sbCbOffset)
|
|
||||||
{
|
|
||||||
int key = PackSbCbInfo(sbCbSlot, sbCbOffset);
|
|
||||||
|
|
||||||
if (!_sbSlots.TryGetValue(key, out int slot))
|
|
||||||
{
|
|
||||||
slot = _sbSlots.Count;
|
|
||||||
_sbSlots.Add(key, slot);
|
|
||||||
_sbSlotsReverse.Add(slot, key);
|
|
||||||
}
|
|
||||||
|
|
||||||
return slot;
|
|
||||||
}
|
|
||||||
|
|
||||||
public (int, int) GetSbCbInfo(int slot)
|
|
||||||
{
|
|
||||||
if (_sbSlotsReverse.TryGetValue(slot, out int key))
|
|
||||||
{
|
|
||||||
return UnpackSbCbInfo(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new ArgumentException($"Invalid slot {slot}.", nameof(slot));
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int PackSbCbInfo(int sbCbSlot, int sbCbOffset)
|
|
||||||
{
|
|
||||||
return sbCbOffset | ((int)sbCbSlot << 16);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static (int, int) UnpackSbCbInfo(int key)
|
|
||||||
{
|
|
||||||
return ((byte)(key >> 16), (ushort)key);
|
|
||||||
}
|
|
||||||
|
|
||||||
public ShaderProgramInfo CreateProgramInfo(ShaderIdentification identification = ShaderIdentification.None)
|
public ShaderProgramInfo CreateProgramInfo(ShaderIdentification identification = ShaderIdentification.None)
|
||||||
{
|
{
|
||||||
return new ShaderProgramInfo(
|
return new ShaderProgramInfo(
|
||||||
ResourceManager.GetConstantBufferDescriptors(),
|
ResourceManager.GetConstantBufferDescriptors(),
|
||||||
GetStorageBufferDescriptors(),
|
ResourceManager.GetStorageBufferDescriptors(),
|
||||||
GetTextureDescriptors(),
|
GetTextureDescriptors(),
|
||||||
GetImageDescriptors(),
|
GetImageDescriptors(),
|
||||||
identification,
|
identification,
|
||||||
|
@ -48,7 +48,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IsResourceWrite(operation.Inst))
|
if (IsResourceWrite(operation.Inst, operation.StorageKind))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -154,7 +154,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||||||
return totalVerticesCount + verticesCount == 3 && writesLayer;
|
return totalVerticesCount + verticesCount == 3 && writesLayer;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool IsResourceWrite(Instruction inst)
|
private static bool IsResourceWrite(Instruction inst, StorageKind storageKind)
|
||||||
{
|
{
|
||||||
switch (inst)
|
switch (inst)
|
||||||
{
|
{
|
||||||
@ -170,13 +170,11 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||||||
case Instruction.AtomicXor:
|
case Instruction.AtomicXor:
|
||||||
case Instruction.ImageAtomic:
|
case Instruction.ImageAtomic:
|
||||||
case Instruction.ImageStore:
|
case Instruction.ImageStore:
|
||||||
case Instruction.StoreGlobal:
|
|
||||||
case Instruction.StoreGlobal16:
|
|
||||||
case Instruction.StoreGlobal8:
|
|
||||||
case Instruction.StoreStorage:
|
|
||||||
case Instruction.StoreStorage16:
|
|
||||||
case Instruction.StoreStorage8:
|
|
||||||
return true;
|
return true;
|
||||||
|
case Instruction.Store:
|
||||||
|
return storageKind == StorageKind.StorageBuffer ||
|
||||||
|
storageKind == StorageKind.SharedMemory ||
|
||||||
|
storageKind == StorageKind.LocalMemory;
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
|
@ -78,7 +78,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
|||||||
|
|
||||||
Ssa.Rename(cfg.Blocks);
|
Ssa.Rename(cfg.Blocks);
|
||||||
|
|
||||||
Optimizer.RunPass(cfg.Blocks, config);
|
Optimizer.RunPass(hfm, cfg.Blocks, config);
|
||||||
Rewriter.RunPass(hfm, cfg.Blocks, config);
|
Rewriter.RunPass(hfm, cfg.Blocks, config);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -143,7 +143,7 @@ namespace Ryujinx.Graphics.Texture
|
|||||||
mipGobBlocksInY >>= 1;
|
mipGobBlocksInY >>= 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
while (d <= (mipGobBlocksInZ >> 1) && mipGobBlocksInZ != 1)
|
if (level > 0 && d <= (mipGobBlocksInZ >> 1) && mipGobBlocksInZ != 1)
|
||||||
{
|
{
|
||||||
mipGobBlocksInZ >>= 1;
|
mipGobBlocksInZ >>= 1;
|
||||||
}
|
}
|
||||||
@ -407,7 +407,7 @@ namespace Ryujinx.Graphics.Texture
|
|||||||
mipGobBlocksInY >>= 1;
|
mipGobBlocksInY >>= 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
while (d <= (mipGobBlocksInZ >> 1) && mipGobBlocksInZ != 1)
|
if (level > 0 && d <= (mipGobBlocksInZ >> 1) && mipGobBlocksInZ != 1)
|
||||||
{
|
{
|
||||||
mipGobBlocksInZ >>= 1;
|
mipGobBlocksInZ >>= 1;
|
||||||
}
|
}
|
||||||
|
@ -36,7 +36,7 @@ namespace Ryujinx.Graphics.Texture
|
|||||||
int gobBlocksInTileX,
|
int gobBlocksInTileX,
|
||||||
int gpuLayerSize = 0)
|
int gpuLayerSize = 0)
|
||||||
{
|
{
|
||||||
bool is3D = depth > 1;
|
bool is3D = depth > 1 || gobBlocksInZ > 1;
|
||||||
|
|
||||||
int layerSize = 0;
|
int layerSize = 0;
|
||||||
|
|
||||||
@ -67,7 +67,7 @@ namespace Ryujinx.Graphics.Texture
|
|||||||
mipGobBlocksInY >>= 1;
|
mipGobBlocksInY >>= 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
while (d <= (mipGobBlocksInZ >> 1) && mipGobBlocksInZ != 1)
|
if (level > 0 && d <= (mipGobBlocksInZ >> 1) && mipGobBlocksInZ != 1)
|
||||||
{
|
{
|
||||||
mipGobBlocksInZ >>= 1;
|
mipGobBlocksInZ >>= 1;
|
||||||
}
|
}
|
||||||
@ -88,6 +88,10 @@ namespace Ryujinx.Graphics.Texture
|
|||||||
|
|
||||||
int robSize = widthInGobs * mipGobBlocksInY * mipGobBlocksInZ * GobSize;
|
int robSize = widthInGobs * mipGobBlocksInY * mipGobBlocksInZ * GobSize;
|
||||||
|
|
||||||
|
mipOffsets[level] = layerSize;
|
||||||
|
sliceSizes[level] = totalBlocksOfGobsInY * robSize;
|
||||||
|
levelSizes[level] = totalBlocksOfGobsInZ * sliceSizes[level];
|
||||||
|
|
||||||
if (is3D)
|
if (is3D)
|
||||||
{
|
{
|
||||||
int gobSize = mipGobBlocksInY * GobSize;
|
int gobSize = mipGobBlocksInY * GobSize;
|
||||||
@ -105,11 +109,22 @@ namespace Ryujinx.Graphics.Texture
|
|||||||
|
|
||||||
allOffsets[z + depthLevelOffset] = baseOffset + zLow * gobSize + zHigh * sliceSize;
|
allOffsets[z + depthLevelOffset] = baseOffset + zLow * gobSize + zHigh * sliceSize;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
mipOffsets[level] = layerSize;
|
int gobRemainderZ = d % mipGobBlocksInZ;
|
||||||
sliceSizes[level] = totalBlocksOfGobsInY * robSize;
|
|
||||||
levelSizes[level] = totalBlocksOfGobsInZ * sliceSizes[level];
|
if (gobRemainderZ != 0 && level == levels - 1)
|
||||||
|
{
|
||||||
|
// The slice only covers up to the end of this slice's depth, rather than the full aligned size.
|
||||||
|
// Avoids size being too large on partial views of 3d textures.
|
||||||
|
|
||||||
|
levelSizes[level] -= gobSize * (mipGobBlocksInZ - gobRemainderZ);
|
||||||
|
|
||||||
|
if (sliceSizes[level] > levelSizes[level])
|
||||||
|
{
|
||||||
|
sliceSizes[level] = levelSizes[level];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
layerSize += levelSizes[level];
|
layerSize += levelSizes[level];
|
||||||
|
|
||||||
@ -267,7 +282,8 @@ namespace Ryujinx.Graphics.Texture
|
|||||||
int depth,
|
int depth,
|
||||||
int blockHeight,
|
int blockHeight,
|
||||||
int gobBlocksInY,
|
int gobBlocksInY,
|
||||||
int gobBlocksInZ)
|
int gobBlocksInZ,
|
||||||
|
int level = int.MaxValue)
|
||||||
{
|
{
|
||||||
height = BitUtils.DivRoundUp(height, blockHeight);
|
height = BitUtils.DivRoundUp(height, blockHeight);
|
||||||
|
|
||||||
@ -276,7 +292,7 @@ namespace Ryujinx.Graphics.Texture
|
|||||||
gobBlocksInY >>= 1;
|
gobBlocksInY >>= 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
while (depth <= (gobBlocksInZ >> 1) && gobBlocksInZ != 1)
|
while (level-- > 0 && depth <= (gobBlocksInZ >> 1) && gobBlocksInZ != 1)
|
||||||
{
|
{
|
||||||
gobBlocksInZ >>= 1;
|
gobBlocksInZ >>= 1;
|
||||||
}
|
}
|
||||||
|
@ -115,8 +115,6 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
holder = Create(gd, size, baseType: baseType, storageHint: storageHint);
|
holder = Create(gd, size, baseType: baseType, storageHint: storageHint);
|
||||||
if (holder == null)
|
if (holder == null)
|
||||||
{
|
{
|
||||||
Logger.Error?.Print(LogClass.Gpu, $"Failed to create buffer with size 0x{size:X} and type \"{baseType}\".");
|
|
||||||
|
|
||||||
return BufferHandle.Null;
|
return BufferHandle.Null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -264,6 +262,8 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
return holder;
|
return holder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Logger.Error?.Print(LogClass.Gpu, $"Failed to create buffer with size 0x{size:X} and type \"{baseType}\".");
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,30 +70,6 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public unsafe void UpdateStorageBuffers(int setIndex, int baseBinding, ReadOnlySpan<DescriptorBufferInfo> bufferInfo)
|
|
||||||
{
|
|
||||||
if (bufferInfo.Length == 0)
|
|
||||||
{
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
fixed (DescriptorBufferInfo* pBufferInfo = bufferInfo)
|
|
||||||
{
|
|
||||||
var writeDescriptorSet = new WriteDescriptorSet
|
|
||||||
{
|
|
||||||
SType = StructureType.WriteDescriptorSet,
|
|
||||||
DstSet = _descriptorSets[setIndex],
|
|
||||||
DstBinding = (uint)(baseBinding & ~(Constants.MaxStorageBuffersPerStage - 1)),
|
|
||||||
DstArrayElement = (uint)(baseBinding & (Constants.MaxStorageBuffersPerStage - 1)),
|
|
||||||
DescriptorType = DescriptorType.StorageBuffer,
|
|
||||||
DescriptorCount = (uint)bufferInfo.Length,
|
|
||||||
PBufferInfo = pBufferInfo
|
|
||||||
};
|
|
||||||
|
|
||||||
_holder.Api.UpdateDescriptorSets(_holder.Device, 1, writeDescriptorSet, 0, null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public unsafe void UpdateImage(int setIndex, int bindingIndex, DescriptorImageInfo imageInfo, DescriptorType type)
|
public unsafe void UpdateImage(int setIndex, int bindingIndex, DescriptorImageInfo imageInfo, DescriptorType type)
|
||||||
{
|
{
|
||||||
if (imageInfo.ImageView.Handle != 0UL)
|
if (imageInfo.ImageView.Handle != 0UL)
|
||||||
|
@ -448,14 +448,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
}
|
}
|
||||||
|
|
||||||
ReadOnlySpan<DescriptorBufferInfo> storageBuffers = _storageBuffers;
|
ReadOnlySpan<DescriptorBufferInfo> storageBuffers = _storageBuffers;
|
||||||
if (program.HasMinimalLayout)
|
dsc.UpdateBuffers(0, binding, storageBuffers.Slice(binding, count), DescriptorType.StorageBuffer);
|
||||||
{
|
|
||||||
dsc.UpdateBuffers(0, binding, storageBuffers.Slice(binding, count), DescriptorType.StorageBuffer);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
dsc.UpdateStorageBuffers(0, binding, storageBuffers.Slice(binding, count));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if (setIndex == PipelineBase.TextureSetIndex)
|
else if (setIndex == PipelineBase.TextureSetIndex)
|
||||||
{
|
{
|
||||||
|
@ -96,8 +96,6 @@ namespace Ryujinx.Graphics.Vulkan.Effects
|
|||||||
{
|
{
|
||||||
var originalInfo = view.Info;
|
var originalInfo = view.Info;
|
||||||
|
|
||||||
var swapRB = originalInfo.Format.IsBgr() && originalInfo.SwizzleR == SwizzleComponent.Red;
|
|
||||||
|
|
||||||
var info = new TextureCreateInfo(
|
var info = new TextureCreateInfo(
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
@ -110,9 +108,9 @@ namespace Ryujinx.Graphics.Vulkan.Effects
|
|||||||
originalInfo.Format,
|
originalInfo.Format,
|
||||||
originalInfo.DepthStencilMode,
|
originalInfo.DepthStencilMode,
|
||||||
originalInfo.Target,
|
originalInfo.Target,
|
||||||
swapRB ? originalInfo.SwizzleB : originalInfo.SwizzleR,
|
originalInfo.SwizzleR,
|
||||||
originalInfo.SwizzleG,
|
originalInfo.SwizzleG,
|
||||||
swapRB ? originalInfo.SwizzleR : originalInfo.SwizzleB,
|
originalInfo.SwizzleB,
|
||||||
originalInfo.SwizzleA);
|
originalInfo.SwizzleA);
|
||||||
_intermediaryTexture?.Dispose();
|
_intermediaryTexture?.Dispose();
|
||||||
_intermediaryTexture = _renderer.CreateTexture(info, view.ScaleFactor) as TextureView;
|
_intermediaryTexture = _renderer.CreateTexture(info, view.ScaleFactor) as TextureView;
|
||||||
@ -155,7 +153,7 @@ namespace Ryujinx.Graphics.Vulkan.Effects
|
|||||||
|
|
||||||
var bufferRanges = new BufferRange(bufferHandle, 0, rangeSize);
|
var bufferRanges = new BufferRange(bufferHandle, 0, rangeSize);
|
||||||
_pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(2, bufferRanges) });
|
_pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(2, bufferRanges) });
|
||||||
_pipeline.SetImage(0, _intermediaryTexture, GAL.Format.R8G8B8A8Unorm);
|
_pipeline.SetImage(0, _intermediaryTexture, FormatTable.ConvertRgba8SrgbToUnorm(view.Info.Format));
|
||||||
_pipeline.DispatchCompute(dispatchX, dispatchY, 1);
|
_pipeline.DispatchCompute(dispatchX, dispatchY, 1);
|
||||||
_pipeline.ComputeBarrier();
|
_pipeline.ComputeBarrier();
|
||||||
|
|
||||||
|
@ -56,28 +56,7 @@ namespace Ryujinx.Graphics.Vulkan.Effects
|
|||||||
if (_texture == null || _texture.Width != view.Width || _texture.Height != view.Height)
|
if (_texture == null || _texture.Width != view.Width || _texture.Height != view.Height)
|
||||||
{
|
{
|
||||||
_texture?.Dispose();
|
_texture?.Dispose();
|
||||||
|
_texture = _renderer.CreateTexture(view.Info, view.ScaleFactor) as TextureView;
|
||||||
var info = view.Info;
|
|
||||||
|
|
||||||
if (view.Info.Format.IsBgr())
|
|
||||||
{
|
|
||||||
info = new TextureCreateInfo(info.Width,
|
|
||||||
info.Height,
|
|
||||||
info.Depth,
|
|
||||||
info.Levels,
|
|
||||||
info.Samples,
|
|
||||||
info.BlockWidth,
|
|
||||||
info.BlockHeight,
|
|
||||||
info.BytesPerPixel,
|
|
||||||
info.Format,
|
|
||||||
info.DepthStencilMode,
|
|
||||||
info.Target,
|
|
||||||
info.SwizzleB,
|
|
||||||
info.SwizzleG,
|
|
||||||
info.SwizzleR,
|
|
||||||
info.SwizzleA);
|
|
||||||
}
|
|
||||||
_texture = _renderer.CreateTexture(info, view.ScaleFactor) as TextureView;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_pipeline.SetCommandBuffer(cbs);
|
_pipeline.SetCommandBuffer(cbs);
|
||||||
@ -96,7 +75,7 @@ namespace Ryujinx.Graphics.Vulkan.Effects
|
|||||||
var dispatchX = BitUtils.DivRoundUp(view.Width, IPostProcessingEffect.LocalGroupSize);
|
var dispatchX = BitUtils.DivRoundUp(view.Width, IPostProcessingEffect.LocalGroupSize);
|
||||||
var dispatchY = BitUtils.DivRoundUp(view.Height, IPostProcessingEffect.LocalGroupSize);
|
var dispatchY = BitUtils.DivRoundUp(view.Height, IPostProcessingEffect.LocalGroupSize);
|
||||||
|
|
||||||
_pipeline.SetImage(0, _texture, GAL.Format.R8G8B8A8Unorm);
|
_pipeline.SetImage(0, _texture, FormatTable.ConvertRgba8SrgbToUnorm(view.Info.Format));
|
||||||
_pipeline.DispatchCompute(dispatchX, dispatchY, 1);
|
_pipeline.DispatchCompute(dispatchX, dispatchY, 1);
|
||||||
|
|
||||||
_renderer.BufferManager.Delete(bufferHandle);
|
_renderer.BufferManager.Delete(bufferHandle);
|
||||||
|
@ -4,7 +4,6 @@ using Ryujinx.Graphics.Shader;
|
|||||||
using Ryujinx.Graphics.Shader.Translation;
|
using Ryujinx.Graphics.Shader.Translation;
|
||||||
using Silk.NET.Vulkan;
|
using Silk.NET.Vulkan;
|
||||||
using System;
|
using System;
|
||||||
using Format = Ryujinx.Graphics.GAL.Format;
|
|
||||||
|
|
||||||
namespace Ryujinx.Graphics.Vulkan.Effects
|
namespace Ryujinx.Graphics.Vulkan.Effects
|
||||||
{
|
{
|
||||||
@ -149,7 +148,7 @@ namespace Ryujinx.Graphics.Vulkan.Effects
|
|||||||
1,
|
1,
|
||||||
1,
|
1,
|
||||||
1,
|
1,
|
||||||
Format.R8G8Unorm,
|
GAL.Format.R8G8Unorm,
|
||||||
DepthStencilMode.Depth,
|
DepthStencilMode.Depth,
|
||||||
Target.Texture2D,
|
Target.Texture2D,
|
||||||
SwizzleComponent.Red,
|
SwizzleComponent.Red,
|
||||||
@ -165,7 +164,7 @@ namespace Ryujinx.Graphics.Vulkan.Effects
|
|||||||
1,
|
1,
|
||||||
1,
|
1,
|
||||||
1,
|
1,
|
||||||
Format.R8Unorm,
|
GAL.Format.R8Unorm,
|
||||||
DepthStencilMode.Depth,
|
DepthStencilMode.Depth,
|
||||||
Target.Texture2D,
|
Target.Texture2D,
|
||||||
SwizzleComponent.Red,
|
SwizzleComponent.Red,
|
||||||
@ -192,30 +191,9 @@ namespace Ryujinx.Graphics.Vulkan.Effects
|
|||||||
_edgeOutputTexture?.Dispose();
|
_edgeOutputTexture?.Dispose();
|
||||||
_blendOutputTexture?.Dispose();
|
_blendOutputTexture?.Dispose();
|
||||||
|
|
||||||
var info = view.Info;
|
_outputTexture = _renderer.CreateTexture(view.Info, view.ScaleFactor) as TextureView;
|
||||||
|
_edgeOutputTexture = _renderer.CreateTexture(view.Info, view.ScaleFactor) as TextureView;
|
||||||
if (view.Info.Format.IsBgr())
|
_blendOutputTexture = _renderer.CreateTexture(view.Info, view.ScaleFactor) as TextureView;
|
||||||
{
|
|
||||||
info = new TextureCreateInfo(info.Width,
|
|
||||||
info.Height,
|
|
||||||
info.Depth,
|
|
||||||
info.Levels,
|
|
||||||
info.Samples,
|
|
||||||
info.BlockWidth,
|
|
||||||
info.BlockHeight,
|
|
||||||
info.BytesPerPixel,
|
|
||||||
info.Format,
|
|
||||||
info.DepthStencilMode,
|
|
||||||
info.Target,
|
|
||||||
info.SwizzleB,
|
|
||||||
info.SwizzleG,
|
|
||||||
info.SwizzleR,
|
|
||||||
info.SwizzleA);
|
|
||||||
}
|
|
||||||
|
|
||||||
_outputTexture = _renderer.CreateTexture(info, view.ScaleFactor) as TextureView;
|
|
||||||
_edgeOutputTexture = _renderer.CreateTexture(info, view.ScaleFactor) as TextureView;
|
|
||||||
_blendOutputTexture = _renderer.CreateTexture(info, view.ScaleFactor) as TextureView;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_pipeline.SetCommandBuffer(cbs);
|
_pipeline.SetCommandBuffer(cbs);
|
||||||
@ -240,7 +218,7 @@ namespace Ryujinx.Graphics.Vulkan.Effects
|
|||||||
_renderer.BufferManager.SetData(bufferHandle, 0, resolutionBuffer);
|
_renderer.BufferManager.SetData(bufferHandle, 0, resolutionBuffer);
|
||||||
var bufferRanges = new BufferRange(bufferHandle, 0, rangeSize);
|
var bufferRanges = new BufferRange(bufferHandle, 0, rangeSize);
|
||||||
_pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(2, bufferRanges) });
|
_pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(2, bufferRanges) });
|
||||||
_pipeline.SetImage(0, _edgeOutputTexture, GAL.Format.R8G8B8A8Unorm);
|
_pipeline.SetImage(0, _edgeOutputTexture, FormatTable.ConvertRgba8SrgbToUnorm(view.Info.Format));
|
||||||
_pipeline.DispatchCompute(dispatchX, dispatchY, 1);
|
_pipeline.DispatchCompute(dispatchX, dispatchY, 1);
|
||||||
_pipeline.ComputeBarrier();
|
_pipeline.ComputeBarrier();
|
||||||
|
|
||||||
@ -250,7 +228,7 @@ namespace Ryujinx.Graphics.Vulkan.Effects
|
|||||||
_pipeline.SetTextureAndSampler(ShaderStage.Compute, 1, _edgeOutputTexture, _samplerLinear);
|
_pipeline.SetTextureAndSampler(ShaderStage.Compute, 1, _edgeOutputTexture, _samplerLinear);
|
||||||
_pipeline.SetTextureAndSampler(ShaderStage.Compute, 3, _areaTexture, _samplerLinear);
|
_pipeline.SetTextureAndSampler(ShaderStage.Compute, 3, _areaTexture, _samplerLinear);
|
||||||
_pipeline.SetTextureAndSampler(ShaderStage.Compute, 4, _searchTexture, _samplerLinear);
|
_pipeline.SetTextureAndSampler(ShaderStage.Compute, 4, _searchTexture, _samplerLinear);
|
||||||
_pipeline.SetImage(0, _blendOutputTexture, GAL.Format.R8G8B8A8Unorm);
|
_pipeline.SetImage(0, _blendOutputTexture, FormatTable.ConvertRgba8SrgbToUnorm(view.Info.Format));
|
||||||
_pipeline.DispatchCompute(dispatchX, dispatchY, 1);
|
_pipeline.DispatchCompute(dispatchX, dispatchY, 1);
|
||||||
_pipeline.ComputeBarrier();
|
_pipeline.ComputeBarrier();
|
||||||
|
|
||||||
@ -259,7 +237,7 @@ namespace Ryujinx.Graphics.Vulkan.Effects
|
|||||||
_pipeline.Specialize(_specConstants);
|
_pipeline.Specialize(_specConstants);
|
||||||
_pipeline.SetTextureAndSampler(ShaderStage.Compute, 3, _blendOutputTexture, _samplerLinear);
|
_pipeline.SetTextureAndSampler(ShaderStage.Compute, 3, _blendOutputTexture, _samplerLinear);
|
||||||
_pipeline.SetTextureAndSampler(ShaderStage.Compute, 1, view, _samplerLinear);
|
_pipeline.SetTextureAndSampler(ShaderStage.Compute, 1, view, _samplerLinear);
|
||||||
_pipeline.SetImage(0, _outputTexture, GAL.Format.R8G8B8A8Unorm);
|
_pipeline.SetImage(0, _outputTexture, FormatTable.ConvertRgba8SrgbToUnorm(view.Info.Format));
|
||||||
_pipeline.DispatchCompute(dispatchX, dispatchY, 1);
|
_pipeline.DispatchCompute(dispatchX, dispatchY, 1);
|
||||||
_pipeline.ComputeBarrier();
|
_pipeline.ComputeBarrier();
|
||||||
|
|
||||||
|
@ -169,6 +169,16 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
return _table[(int)format];
|
return _table[(int)format];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Format ConvertRgba8SrgbToUnorm(Format format)
|
||||||
|
{
|
||||||
|
return format switch
|
||||||
|
{
|
||||||
|
Format.R8G8B8A8Srgb => Format.R8G8B8A8Unorm,
|
||||||
|
Format.B8G8R8A8Srgb => Format.B8G8R8A8Unorm,
|
||||||
|
_ => format
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
public static int GetAttributeFormatSize(VkFormat format)
|
public static int GetAttributeFormatSize(VkFormat format)
|
||||||
{
|
{
|
||||||
switch (format)
|
switch (format)
|
||||||
|
@ -26,6 +26,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
public readonly bool SupportsFragmentShaderInterlock;
|
public readonly bool SupportsFragmentShaderInterlock;
|
||||||
public readonly bool SupportsGeometryShaderPassthrough;
|
public readonly bool SupportsGeometryShaderPassthrough;
|
||||||
public readonly bool SupportsSubgroupSizeControl;
|
public readonly bool SupportsSubgroupSizeControl;
|
||||||
|
public readonly bool SupportsShaderFloat64;
|
||||||
public readonly bool SupportsShaderInt8;
|
public readonly bool SupportsShaderInt8;
|
||||||
public readonly bool SupportsShaderStencilExport;
|
public readonly bool SupportsShaderStencilExport;
|
||||||
public readonly bool SupportsShaderStorageImageMultisample;
|
public readonly bool SupportsShaderStorageImageMultisample;
|
||||||
@ -63,6 +64,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
bool supportsFragmentShaderInterlock,
|
bool supportsFragmentShaderInterlock,
|
||||||
bool supportsGeometryShaderPassthrough,
|
bool supportsGeometryShaderPassthrough,
|
||||||
bool supportsSubgroupSizeControl,
|
bool supportsSubgroupSizeControl,
|
||||||
|
bool supportsShaderFloat64,
|
||||||
bool supportsShaderInt8,
|
bool supportsShaderInt8,
|
||||||
bool supportsShaderStencilExport,
|
bool supportsShaderStencilExport,
|
||||||
bool supportsShaderStorageImageMultisample,
|
bool supportsShaderStorageImageMultisample,
|
||||||
@ -99,6 +101,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
SupportsFragmentShaderInterlock = supportsFragmentShaderInterlock;
|
SupportsFragmentShaderInterlock = supportsFragmentShaderInterlock;
|
||||||
SupportsGeometryShaderPassthrough = supportsGeometryShaderPassthrough;
|
SupportsGeometryShaderPassthrough = supportsGeometryShaderPassthrough;
|
||||||
SupportsSubgroupSizeControl = supportsSubgroupSizeControl;
|
SupportsSubgroupSizeControl = supportsSubgroupSizeControl;
|
||||||
|
SupportsShaderFloat64 = supportsShaderFloat64;
|
||||||
SupportsShaderInt8 = supportsShaderInt8;
|
SupportsShaderInt8 = supportsShaderInt8;
|
||||||
SupportsShaderStencilExport = supportsShaderStencilExport;
|
SupportsShaderStencilExport = supportsShaderStencilExport;
|
||||||
SupportsShaderStorageImageMultisample = supportsShaderStorageImageMultisample;
|
SupportsShaderStorageImageMultisample = supportsShaderStorageImageMultisample;
|
||||||
|
@ -15,6 +15,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
private readonly Device _device;
|
private readonly Device _device;
|
||||||
|
|
||||||
private readonly Auto<DisposableImageView> _imageView;
|
private readonly Auto<DisposableImageView> _imageView;
|
||||||
|
private readonly Auto<DisposableImageView> _imageViewDraw;
|
||||||
private readonly Auto<DisposableImageView> _imageViewIdentity;
|
private readonly Auto<DisposableImageView> _imageViewIdentity;
|
||||||
private readonly Auto<DisposableImageView> _imageView2dArray;
|
private readonly Auto<DisposableImageView> _imageView2dArray;
|
||||||
private Dictionary<GAL.Format, TextureView> _selfManagedViews;
|
private Dictionary<GAL.Format, TextureView> _selfManagedViews;
|
||||||
@ -127,7 +128,8 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
ComponentSwizzle.B,
|
ComponentSwizzle.B,
|
||||||
ComponentSwizzle.A);
|
ComponentSwizzle.A);
|
||||||
|
|
||||||
_imageViewIdentity = CreateImageView(identityComponentMapping, subresourceRangeDepth, type, usage);
|
_imageViewDraw = CreateImageView(identityComponentMapping, subresourceRangeDepth, type, usage);
|
||||||
|
_imageViewIdentity = aspectFlagsDepth == aspectFlags ? _imageViewDraw : CreateImageView(identityComponentMapping, subresourceRange, type, usage);
|
||||||
|
|
||||||
// Framebuffer attachments also require 3D textures to be bound as 2D array.
|
// Framebuffer attachments also require 3D textures to be bound as 2D array.
|
||||||
if (info.Target == Target.Texture3D)
|
if (info.Target == Target.Texture3D)
|
||||||
@ -169,7 +171,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
|
|
||||||
public Auto<DisposableImageView> GetImageViewForAttachment()
|
public Auto<DisposableImageView> GetImageViewForAttachment()
|
||||||
{
|
{
|
||||||
return _imageView2dArray ?? _imageViewIdentity;
|
return _imageView2dArray ?? _imageViewDraw;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void CopyTo(ITexture destination, int firstLayer, int firstLevel)
|
public void CopyTo(ITexture destination, int firstLayer, int firstLevel)
|
||||||
@ -909,6 +911,11 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
_imageViewIdentity.Dispose();
|
_imageViewIdentity.Dispose();
|
||||||
_imageView2dArray?.Dispose();
|
_imageView2dArray?.Dispose();
|
||||||
|
|
||||||
|
if (_imageViewDraw != _imageViewIdentity)
|
||||||
|
{
|
||||||
|
_imageViewDraw.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
Storage.DecrementViewsCount();
|
Storage.DecrementViewsCount();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -306,6 +306,7 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
_physicalDevice.IsDeviceExtensionPresent("VK_EXT_fragment_shader_interlock"),
|
_physicalDevice.IsDeviceExtensionPresent("VK_EXT_fragment_shader_interlock"),
|
||||||
_physicalDevice.IsDeviceExtensionPresent("VK_NV_geometry_shader_passthrough"),
|
_physicalDevice.IsDeviceExtensionPresent("VK_NV_geometry_shader_passthrough"),
|
||||||
supportsSubgroupSizeControl,
|
supportsSubgroupSizeControl,
|
||||||
|
features2.Features.ShaderFloat64,
|
||||||
featuresShaderInt8.ShaderInt8,
|
featuresShaderInt8.ShaderInt8,
|
||||||
_physicalDevice.IsDeviceExtensionPresent("VK_EXT_shader_stencil_export"),
|
_physicalDevice.IsDeviceExtensionPresent("VK_EXT_shader_stencil_export"),
|
||||||
features2.Features.ShaderStorageImageMultisample,
|
features2.Features.ShaderStorageImageMultisample,
|
||||||
@ -594,6 +595,8 @@ namespace Ryujinx.Graphics.Vulkan
|
|||||||
supportsCubemapView: !IsAmdGcn,
|
supportsCubemapView: !IsAmdGcn,
|
||||||
supportsNonConstantTextureOffset: false,
|
supportsNonConstantTextureOffset: false,
|
||||||
supportsShaderBallot: false,
|
supportsShaderBallot: false,
|
||||||
|
supportsShaderBarrierDivergence: Vendor != Vendor.Intel,
|
||||||
|
supportsShaderFloat64: Capabilities.SupportsShaderFloat64,
|
||||||
supportsTextureShadowLod: false,
|
supportsTextureShadowLod: false,
|
||||||
supportsViewportIndexVertexTessellation: featuresVk12.ShaderOutputViewportIndex,
|
supportsViewportIndexVertexTessellation: featuresVk12.ShaderOutputViewportIndex,
|
||||||
supportsViewportMask: Capabilities.SupportsViewportArray2,
|
supportsViewportMask: Capabilities.SupportsViewportArray2,
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Identifies the variant of keyboard displayed on screen.
|
/// Identifies the variant of keyboard displayed on screen.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
enum KeyboardMode : uint
|
public enum KeyboardMode : uint
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A full alpha-numeric keyboard.
|
/// A full alpha-numeric keyboard.
|
||||||
|
@ -209,6 +209,7 @@ namespace Ryujinx.HLE.HOS.Applets
|
|||||||
// Call the configured GUI handler to get user's input.
|
// Call the configured GUI handler to get user's input.
|
||||||
var args = new SoftwareKeyboardUiArgs
|
var args = new SoftwareKeyboardUiArgs
|
||||||
{
|
{
|
||||||
|
KeyboardMode = _keyboardForegroundConfig.Mode,
|
||||||
HeaderText = StripUnicodeControlCodes(_keyboardForegroundConfig.HeaderText),
|
HeaderText = StripUnicodeControlCodes(_keyboardForegroundConfig.HeaderText),
|
||||||
SubtitleText = StripUnicodeControlCodes(_keyboardForegroundConfig.SubtitleText),
|
SubtitleText = StripUnicodeControlCodes(_keyboardForegroundConfig.SubtitleText),
|
||||||
GuideText = StripUnicodeControlCodes(_keyboardForegroundConfig.GuideText),
|
GuideText = StripUnicodeControlCodes(_keyboardForegroundConfig.GuideText),
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
|
using Ryujinx.HLE.HOS.Applets.SoftwareKeyboard;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Applets
|
namespace Ryujinx.HLE.HOS.Applets
|
||||||
{
|
{
|
||||||
public struct SoftwareKeyboardUiArgs
|
public struct SoftwareKeyboardUiArgs
|
||||||
{
|
{
|
||||||
|
public KeyboardMode KeyboardMode;
|
||||||
public string HeaderText;
|
public string HeaderText;
|
||||||
public string SubtitleText;
|
public string SubtitleText;
|
||||||
public string InitialText;
|
public string InitialText;
|
||||||
|
@ -1,49 +1,16 @@
|
|||||||
using System;
|
using Ryujinx.Common.Memory;
|
||||||
|
using System;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrlGpu.Types
|
namespace Ryujinx.HLE.HOS.Services.Nv.NvDrvServices.NvHostCtrlGpu.Types
|
||||||
{
|
{
|
||||||
[StructLayout(LayoutKind.Sequential)]
|
|
||||||
struct ZbcColorArray
|
|
||||||
{
|
|
||||||
private uint element0;
|
|
||||||
private uint element1;
|
|
||||||
private uint element2;
|
|
||||||
private uint element3;
|
|
||||||
|
|
||||||
public uint this[int index]
|
|
||||||
{
|
|
||||||
get
|
|
||||||
{
|
|
||||||
if (index == 0)
|
|
||||||
{
|
|
||||||
return element0;
|
|
||||||
}
|
|
||||||
else if (index == 1)
|
|
||||||
{
|
|
||||||
return element1;
|
|
||||||
}
|
|
||||||
else if (index == 2)
|
|
||||||
{
|
|
||||||
return element2;
|
|
||||||
}
|
|
||||||
else if (index == 2)
|
|
||||||
{
|
|
||||||
return element3;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new IndexOutOfRangeException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[StructLayout(LayoutKind.Sequential)]
|
[StructLayout(LayoutKind.Sequential)]
|
||||||
struct ZbcSetTableArguments
|
struct ZbcSetTableArguments
|
||||||
{
|
{
|
||||||
public ZbcColorArray ColorDs;
|
public Array4<uint> ColorDs;
|
||||||
public ZbcColorArray ColorL2;
|
public Array4<uint> ColorL2;
|
||||||
public uint Depth;
|
public uint Depth;
|
||||||
public uint Format;
|
public uint Format;
|
||||||
public uint Type;
|
public uint Type;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -343,7 +343,14 @@ namespace Ryujinx.Ui.App.Common
|
|||||||
ulong nacpSize = reader.ReadUInt64();
|
ulong nacpSize = reader.ReadUInt64();
|
||||||
|
|
||||||
// Reads and stores game icon as byte array
|
// Reads and stores game icon as byte array
|
||||||
applicationIcon = Read(assetOffset + iconOffset, (int)iconSize);
|
if (iconSize > 0)
|
||||||
|
{
|
||||||
|
applicationIcon = Read(assetOffset + iconOffset, (int)iconSize);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
applicationIcon = _nroIcon;
|
||||||
|
}
|
||||||
|
|
||||||
// Read the NACP data
|
// Read the NACP data
|
||||||
Read(assetOffset + (int)nacpOffset, (int)nacpSize).AsSpan().CopyTo(controlHolder.ByteSpan);
|
Read(assetOffset + (int)nacpOffset, (int)nacpSize).AsSpan().CopyTo(controlHolder.ByteSpan);
|
||||||
@ -666,7 +673,14 @@ namespace Ryujinx.Ui.App.Common
|
|||||||
long iconSize = BitConverter.ToInt64(iconSectionInfo, 8);
|
long iconSize = BitConverter.ToInt64(iconSectionInfo, 8);
|
||||||
|
|
||||||
// Reads and stores game icon as byte array
|
// Reads and stores game icon as byte array
|
||||||
applicationIcon = Read(assetOffset + iconOffset, (int)iconSize);
|
if (iconSize > 0)
|
||||||
|
{
|
||||||
|
applicationIcon = Read(assetOffset + iconOffset, (int)iconSize);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
applicationIcon = _nroIcon;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -565,6 +565,18 @@ namespace Ryujinx.Modules
|
|||||||
{
|
{
|
||||||
var files = Directory.EnumerateFiles(HomeDir); // All files directly in base dir.
|
var files = Directory.EnumerateFiles(HomeDir); // All files directly in base dir.
|
||||||
|
|
||||||
|
// Determine and exclude user files only when the updater is running, not when cleaning old files
|
||||||
|
if (Running)
|
||||||
|
{
|
||||||
|
// Compare the loose files in base directory against the loose files from the incoming update, and store foreign ones in a user list.
|
||||||
|
var oldFiles = Directory.EnumerateFiles(HomeDir, "*", SearchOption.TopDirectoryOnly).Select(Path.GetFileName);
|
||||||
|
var newFiles = Directory.EnumerateFiles(UpdatePublishDir, "*", SearchOption.TopDirectoryOnly).Select(Path.GetFileName);
|
||||||
|
var userFiles = oldFiles.Except(newFiles).Select(filename => Path.Combine(HomeDir, filename));
|
||||||
|
|
||||||
|
// Remove user files from the paths in files.
|
||||||
|
files = files.Except(userFiles);
|
||||||
|
}
|
||||||
|
|
||||||
if (OperatingSystem.IsWindows())
|
if (OperatingSystem.IsWindows())
|
||||||
{
|
{
|
||||||
foreach (string dir in WindowsDependencyDirs)
|
foreach (string dir in WindowsDependencyDirs)
|
||||||
|
@ -106,6 +106,7 @@ namespace Ryujinx.Ui.Applet
|
|||||||
swkbdDialog.OkButton.Label = args.SubmitText;
|
swkbdDialog.OkButton.Label = args.SubmitText;
|
||||||
|
|
||||||
swkbdDialog.SetInputLengthValidation(args.StringLengthMin, args.StringLengthMax);
|
swkbdDialog.SetInputLengthValidation(args.StringLengthMin, args.StringLengthMax);
|
||||||
|
swkbdDialog.SetInputValidation(args.KeyboardMode);
|
||||||
|
|
||||||
if (swkbdDialog.Run() == (int)ResponseType.Ok)
|
if (swkbdDialog.Run() == (int)ResponseType.Ok)
|
||||||
{
|
{
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
using Gtk;
|
using Gtk;
|
||||||
|
using Ryujinx.HLE.HOS.Applets.SoftwareKeyboard;
|
||||||
using System;
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
|
||||||
namespace Ryujinx.Ui.Applet
|
namespace Ryujinx.Ui.Applet
|
||||||
{
|
{
|
||||||
@ -7,8 +9,12 @@ namespace Ryujinx.Ui.Applet
|
|||||||
{
|
{
|
||||||
private int _inputMin;
|
private int _inputMin;
|
||||||
private int _inputMax;
|
private int _inputMax;
|
||||||
|
private KeyboardMode _mode;
|
||||||
|
|
||||||
private Predicate<int> _checkLength;
|
private string _validationInfoText = "";
|
||||||
|
|
||||||
|
private Predicate<int> _checkLength = _ => true;
|
||||||
|
private Predicate<string> _checkInput = _ => true;
|
||||||
|
|
||||||
private readonly Label _validationInfo;
|
private readonly Label _validationInfo;
|
||||||
|
|
||||||
@ -38,8 +44,12 @@ namespace Ryujinx.Ui.Applet
|
|||||||
|
|
||||||
((Box)MessageArea).PackEnd(_validationInfo, true, true, 0);
|
((Box)MessageArea).PackEnd(_validationInfo, true, true, 0);
|
||||||
((Box)MessageArea).PackEnd(InputEntry, true, true, 4);
|
((Box)MessageArea).PackEnd(InputEntry, true, true, 4);
|
||||||
|
}
|
||||||
|
|
||||||
SetInputLengthValidation(0, int.MaxValue); // Disable by default.
|
private void ApplyValidationInfo()
|
||||||
|
{
|
||||||
|
_validationInfo.Visible = !string.IsNullOrEmpty(_validationInfoText);
|
||||||
|
_validationInfo.Markup = _validationInfoText;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetInputLengthValidation(int min, int max)
|
public void SetInputLengthValidation(int min, int max)
|
||||||
@ -53,23 +63,49 @@ namespace Ryujinx.Ui.Applet
|
|||||||
{
|
{
|
||||||
_validationInfo.Visible = false;
|
_validationInfo.Visible = false;
|
||||||
|
|
||||||
_checkLength = (length) => true;
|
_checkLength = _ => true;
|
||||||
}
|
}
|
||||||
else if (_inputMin > 0 && _inputMax == int.MaxValue)
|
else if (_inputMin > 0 && _inputMax == int.MaxValue)
|
||||||
{
|
{
|
||||||
_validationInfo.Visible = true;
|
_validationInfoText = $"<i>Must be at least {_inputMin} characters long.</i> ";
|
||||||
_validationInfo.Markup = $"<i>Must be at least {_inputMin} characters long</i>";
|
|
||||||
|
|
||||||
_checkLength = (length) => _inputMin <= length;
|
_checkLength = length => _inputMin <= length;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
_validationInfo.Visible = true;
|
_validationInfoText = $"<i>Must be {_inputMin}-{_inputMax} characters long.</i> ";
|
||||||
_validationInfo.Markup = $"<i>Must be {_inputMin}-{_inputMax} characters long</i>";
|
|
||||||
|
|
||||||
_checkLength = (length) => _inputMin <= length && length <= _inputMax;
|
_checkLength = length => _inputMin <= length && length <= _inputMax;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ApplyValidationInfo();
|
||||||
|
OnInputChanged(this, EventArgs.Empty);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void SetInputValidation(KeyboardMode mode)
|
||||||
|
{
|
||||||
|
_mode = mode;
|
||||||
|
|
||||||
|
switch (mode)
|
||||||
|
{
|
||||||
|
case KeyboardMode.NumbersOnly:
|
||||||
|
_validationInfoText += "<i>Must be numbers only.</i>";
|
||||||
|
_checkInput = text => text.All(char.IsDigit);
|
||||||
|
break;
|
||||||
|
case KeyboardMode.Alphabet:
|
||||||
|
_validationInfoText += "<i>Must be alphabets only.</i>";
|
||||||
|
_checkInput = text => text.All(char.IsAsciiLetter);
|
||||||
|
break;
|
||||||
|
case KeyboardMode.ASCII:
|
||||||
|
_validationInfoText += "<i>Must be ASCII text only.</i>";
|
||||||
|
_checkInput = text => text.All(char.IsAscii);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
_checkInput = _ => true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
ApplyValidationInfo();
|
||||||
OnInputChanged(this, EventArgs.Empty);
|
OnInputChanged(this, EventArgs.Empty);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,7 +119,7 @@ namespace Ryujinx.Ui.Applet
|
|||||||
|
|
||||||
private void OnInputChanged(object sender, EventArgs e)
|
private void OnInputChanged(object sender, EventArgs e)
|
||||||
{
|
{
|
||||||
OkButton.Sensitive = _checkLength(InputEntry.Text.Length);
|
OkButton.Sensitive = _checkLength(InputEntry.Text.Length) && _checkInput(InputEntry.Text);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
Reference in New Issue
Block a user