mirror of
https://git.ryujinx.app/projects/Kenji-NX.git
synced 2026-10-02 11:11:04 +02:00
Compare commits
52
Commits
c3e20fa1c4
...
dbf78ba55e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dbf78ba55e | ||
|
|
81c9052847 | ||
|
|
9367e3c35d | ||
|
|
52cf141874 | ||
|
|
8a352df3c6 | ||
|
|
c545c59851 | ||
|
|
96ea4e8c8e | ||
|
|
b8f48bcf64 | ||
|
|
6966211e07 | ||
|
|
57524a4c8a | ||
|
|
f4539c49d8 | ||
|
|
12c62fdbc2 | ||
|
|
e3c6be5e29 | ||
|
|
4741a05df9 | ||
|
|
c6676007bf | ||
|
|
92b0b7d753 | ||
|
|
232237bf28 | ||
|
|
c27e453fd3 | ||
|
|
0e037d0213 | ||
|
|
0dca1fbe12 | ||
|
|
35d91a0e58 | ||
|
|
a73a5d7e85 | ||
|
|
832a5e8852 | ||
|
|
96d1f0da2d | ||
|
|
597388ecda | ||
|
|
1cf6d7b7bb | ||
|
|
7bc9d0cdad | ||
|
|
dc0dbc50ab | ||
|
|
994f4dc77d | ||
|
|
c9e297b74c | ||
|
|
dd514a115c | ||
|
|
7e0b4bd538 | ||
|
|
378080eb87 | ||
|
|
e8f5e97fa4 | ||
|
|
f3873620a3 | ||
|
|
986ac9ff83 | ||
|
|
42b9c1e8fe | ||
|
|
3b375525fb | ||
|
|
e6658c133c | ||
|
|
5b42a4d2c4 | ||
|
|
8f0c89ffd6 | ||
|
|
2c9715acf6 | ||
|
|
274af65f69 | ||
|
|
4ca78eded5 | ||
|
|
6cb6b15612 | ||
|
|
2725e40838 | ||
|
|
c2e4c8f98e | ||
|
|
b53e7ffd46 | ||
|
|
ac66643346 | ||
|
|
21e88f17f6 | ||
|
|
5626f2ca1c | ||
|
|
402f05b8ef |
@@ -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
|
||||
@@ -18,6 +18,10 @@ on:
|
||||
- '*.yml'
|
||||
- 'README.md'
|
||||
|
||||
concurrency:
|
||||
group: pr-checks-${{ github.event.number }}
|
||||
cancel-in-progress: true
|
||||
|
||||
env:
|
||||
POWERSHELL_TELEMETRY_OPTOUT: 1
|
||||
DOTNET_CLI_TELEMETRY_OPTOUT: 1
|
||||
@@ -27,7 +31,7 @@ jobs:
|
||||
build:
|
||||
name: ${{ matrix.OS_NAME }} (${{ matrix.configuration }})
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 35
|
||||
timeout-minutes: 45
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macOS-latest, windows-latest]
|
||||
@@ -110,7 +114,7 @@ jobs:
|
||||
build_macos:
|
||||
name: macOS Universal (${{ matrix.configuration }})
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 35
|
||||
timeout-minutes: 45
|
||||
strategy:
|
||||
matrix:
|
||||
configuration: [ Debug, Release ]
|
||||
|
||||
@@ -12,7 +12,7 @@ concurrency: flatpak-release
|
||||
|
||||
jobs:
|
||||
release:
|
||||
timeout-minutes: 35
|
||||
timeout-minutes: ${{ fromJSON(vars.JOB_TIMEOUT) }}
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
env:
|
||||
|
||||
@@ -7,7 +7,7 @@ jobs:
|
||||
pr_comment:
|
||||
if: github.event.workflow_run.event == 'pull_request' && github.event.workflow_run.conclusion == 'success'
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 35
|
||||
timeout-minutes: ${{ fromJSON(vars.JOB_TIMEOUT) }}
|
||||
steps:
|
||||
- uses: actions/github-script@v6
|
||||
with:
|
||||
|
||||
@@ -33,7 +33,7 @@ jobs:
|
||||
shell: bash
|
||||
|
||||
- name: Create tag
|
||||
uses: actions/github-script@v5
|
||||
uses: actions/github-script@v6
|
||||
with:
|
||||
script: |
|
||||
github.rest.git.createRef({
|
||||
@@ -46,7 +46,7 @@ jobs:
|
||||
release:
|
||||
name: Release ${{ matrix.OS_NAME }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
timeout-minutes: 35
|
||||
timeout-minutes: ${{ fromJSON(vars.JOB_TIMEOUT) }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ ubuntu-latest, windows-latest ]
|
||||
@@ -144,7 +144,7 @@ jobs:
|
||||
macos_release:
|
||||
name: Release MacOS universal
|
||||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 35
|
||||
timeout-minutes: ${{ fromJSON(vars.JOB_TIMEOUT) }}
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
|
||||
@@ -3,17 +3,17 @@
|
||||
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageVersion Include="Avalonia" Version="0.10.19" />
|
||||
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="0.10.19" />
|
||||
<PackageVersion Include="Avalonia.Desktop" Version="0.10.19" />
|
||||
<PackageVersion Include="Avalonia.Diagnostics" Version="0.10.19" />
|
||||
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="0.10.19" />
|
||||
<PackageVersion Include="Avalonia" Version="0.10.21" />
|
||||
<PackageVersion Include="Avalonia.Controls.DataGrid" Version="0.10.21" />
|
||||
<PackageVersion Include="Avalonia.Desktop" Version="0.10.21" />
|
||||
<PackageVersion Include="Avalonia.Diagnostics" Version="0.10.21" />
|
||||
<PackageVersion Include="Avalonia.Markup.Xaml.Loader" Version="0.10.21" />
|
||||
<PackageVersion Include="Avalonia.Svg" Version="0.10.18" />
|
||||
<PackageVersion Include="Avalonia.Svg.Skia" Version="0.10.18" />
|
||||
<PackageVersion Include="CommandLineParser" Version="2.9.1" />
|
||||
<PackageVersion Include="Concentus" Version="1.1.7" />
|
||||
<PackageVersion Include="DiscordRichPresence" Version="1.1.3.18" />
|
||||
<PackageVersion Include="DynamicData" Version="7.13.8" />
|
||||
<PackageVersion Include="DynamicData" Version="7.14.2" />
|
||||
<PackageVersion Include="FluentAvaloniaUI" Version="1.4.5" />
|
||||
<PackageVersion Include="GtkSharp.Dependencies" Version="1.1.1" />
|
||||
<PackageVersion Include="GtkSharp.Dependencies.osx" Version="0.0.5" />
|
||||
@@ -21,7 +21,7 @@
|
||||
<PackageVersion Include="LibHac" Version="0.18.0" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.Analyzers" Version="3.3.4" />
|
||||
<PackageVersion Include="Microsoft.CodeAnalysis.CSharp" Version="4.5.0" />
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.5.0" />
|
||||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.6.1" />
|
||||
<PackageVersion Include="Microsoft.IO.RecyclableMemoryStream" Version="2.3.2" />
|
||||
<PackageVersion Include="MsgPack.Cli" Version="1.0.1" />
|
||||
<PackageVersion Include="NUnit" Version="3.13.3" />
|
||||
|
||||
@@ -11,4 +11,10 @@ if [ -f "$SCRIPT_DIR/Ryujinx.Headless.SDL2" ]; then
|
||||
RYUJINX_BIN="Ryujinx.Headless.SDL2"
|
||||
fi
|
||||
|
||||
env DOTNET_EnableAlternateStackCheck=1 "$SCRIPT_DIR/$RYUJINX_BIN" "$@"
|
||||
COMMAND="env DOTNET_EnableAlternateStackCheck=1"
|
||||
|
||||
if command -v gamemoderun > /dev/null 2>&1; then
|
||||
COMMAND="$COMMAND gamemoderun"
|
||||
fi
|
||||
|
||||
$COMMAND "$SCRIPT_DIR/$RYUJINX_BIN" "$@"
|
||||
@@ -25,14 +25,27 @@ error_handler() {
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Wait for Ryujinx to exit
|
||||
# NOTE: in case no fds are open, lsof could be returning with a process still living.
|
||||
# We wait 1s and assume the process stopped after that
|
||||
lsof -p $APP_PID +r 1 &>/dev/null
|
||||
sleep 1
|
||||
|
||||
trap 'error_handler ${LINENO}' ERR
|
||||
|
||||
# Wait for Ryujinx to exit.
|
||||
# If the main process is still acitve, we wait for 1 second and check it again.
|
||||
# After the fifth time checking, this script exits with status 1.
|
||||
|
||||
attempt=0
|
||||
while true; do
|
||||
if lsof -p $APP_PID +r 1 &>/dev/null || ps -p "$APP_PID" &>/dev/null; then
|
||||
if [ "$attempt" -eq 4 ]; then
|
||||
exit 1
|
||||
fi
|
||||
sleep 1
|
||||
else
|
||||
break
|
||||
fi
|
||||
(( attempt++ ))
|
||||
done
|
||||
|
||||
sleep 1
|
||||
|
||||
# Now replace and reopen.
|
||||
rm -rf "$INSTALL_DIRECTORY"
|
||||
mv "$NEW_APP_DIRECTORY" "$INSTALL_DIRECTORY"
|
||||
|
||||
@@ -6,10 +6,11 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Runtime.InteropServices;
|
||||
using System.Runtime.Versioning;
|
||||
|
||||
namespace ARMeilleure.Translation.Cache
|
||||
{
|
||||
static class JitCache
|
||||
static partial class JitCache
|
||||
{
|
||||
private const int PageSize = 4 * 1024;
|
||||
private const int PageMask = PageSize - 1;
|
||||
@@ -27,6 +28,10 @@ namespace ARMeilleure.Translation.Cache
|
||||
private static readonly object _lock = new object();
|
||||
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)
|
||||
{
|
||||
if (_initialized) return;
|
||||
@@ -36,7 +41,11 @@ namespace ARMeilleure.Translation.Cache
|
||||
if (_initialized) return;
|
||||
|
||||
_jitRegion = new ReservedRegion(allocator, CacheSize);
|
||||
_jitCacheInvalidator = new JitCacheInvalidation(allocator);
|
||||
|
||||
if (!OperatingSystem.IsWindows() && !OperatingSystem.IsMacOS())
|
||||
{
|
||||
_jitCacheInvalidator = new JitCacheInvalidation(allocator);
|
||||
}
|
||||
|
||||
_cacheAllocator = new CacheMemoryAllocator(CacheSize);
|
||||
|
||||
@@ -77,7 +86,14 @@ namespace ARMeilleure.Translation.Cache
|
||||
Marshal.Copy(code, 0, funcPtr, 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);
|
||||
|
||||
@@ -47,8 +47,8 @@ namespace ARMeilleure.Translation.Cache
|
||||
|
||||
public JitCacheInvalidation(IJitMemoryAllocator allocator)
|
||||
{
|
||||
// On macOS, a different path is used to write to the JIT cache, which does the invalidation.
|
||||
if (!OperatingSystem.IsMacOS() && RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
|
||||
// On macOS and Windows, a different path is used to write to the JIT cache, which does the invalidation.
|
||||
if (RuntimeInformation.ProcessArchitecture == Architecture.Arm64)
|
||||
{
|
||||
ulong size = (ulong)_invalidationCode.Length * sizeof(int);
|
||||
ulong mask = (ulong)ReservedRegion.DefaultGranularity - 1;
|
||||
|
||||
@@ -45,7 +45,7 @@ namespace Ryujinx.Audio.Backends.SDL2
|
||||
}
|
||||
else
|
||||
{
|
||||
_supportSurroundConfiguration = spec.channels == 6;
|
||||
_supportSurroundConfiguration = spec.channels >= 6;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,6 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||
private AudioRendererRenderingDevice _renderingDevice;
|
||||
private AudioRendererExecutionMode _executionMode;
|
||||
private IWritableEvent _systemEvent;
|
||||
private ManualResetEvent _terminationEvent;
|
||||
private MemoryPoolState _dspMemoryPoolState;
|
||||
private VoiceContext _voiceContext;
|
||||
private MixContext _mixContext;
|
||||
@@ -83,7 +82,6 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||
public AudioRenderSystem(AudioRendererManager manager, IWritableEvent systemEvent)
|
||||
{
|
||||
_manager = manager;
|
||||
_terminationEvent = new ManualResetEvent(false);
|
||||
_dspMemoryPoolState = MemoryPoolState.Create(MemoryPoolState.LocationType.Dsp);
|
||||
_voiceContext = new VoiceContext();
|
||||
_mixContext = new MixContext();
|
||||
@@ -387,11 +385,6 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||
_isActive = false;
|
||||
}
|
||||
|
||||
if (_executionMode == AudioRendererExecutionMode.Auto)
|
||||
{
|
||||
_terminationEvent.WaitOne();
|
||||
}
|
||||
|
||||
Logger.Info?.Print(LogClass.AudioRenderer, $"Stopped renderer id {_sessionId}");
|
||||
}
|
||||
|
||||
@@ -668,8 +661,6 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||
{
|
||||
if (_isActive)
|
||||
{
|
||||
_terminationEvent.Reset();
|
||||
|
||||
if (!_manager.Processor.HasRemainingCommands(_sessionId))
|
||||
{
|
||||
GenerateCommandList(out CommandList commands);
|
||||
@@ -686,10 +677,6 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||
_isDspRunningBehind = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_terminationEvent.Set();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -857,7 +844,6 @@ namespace Ryujinx.Audio.Renderer.Server
|
||||
}
|
||||
|
||||
_manager.Unregister(this);
|
||||
_terminationEvent.Dispose();
|
||||
_workBufferMemoryPin.Dispose();
|
||||
|
||||
if (MemoryManager is IRefCounted rc)
|
||||
|
||||
@@ -92,6 +92,8 @@ namespace Ryujinx.Ava
|
||||
private bool _isActive;
|
||||
private bool _renderingStarted;
|
||||
|
||||
private ManualResetEvent _gpuDoneEvent;
|
||||
|
||||
private IRenderer _renderer;
|
||||
private readonly Thread _renderingThread;
|
||||
private readonly CancellationTokenSource _gpuCancellationTokenSource;
|
||||
@@ -183,6 +185,7 @@ namespace Ryujinx.Ava
|
||||
ConfigurationState.Instance.Multiplayer.LanInterfaceId.Event += UpdateLanInterfaceIdState;
|
||||
|
||||
_gpuCancellationTokenSource = new CancellationTokenSource();
|
||||
_gpuDoneEvent = new ManualResetEvent(false);
|
||||
}
|
||||
|
||||
private void TopLevel_PointerEnterOrMoved(object sender, PointerEventArgs e)
|
||||
@@ -270,7 +273,7 @@ namespace Ryujinx.Ava
|
||||
|
||||
string directory = AppDataManager.Mode switch
|
||||
{
|
||||
AppDataManager.LaunchMode.Portable => Path.Combine(AppDataManager.BaseDirPath, "screenshots"),
|
||||
AppDataManager.LaunchMode.Portable or AppDataManager.LaunchMode.Custom => Path.Combine(AppDataManager.BaseDirPath, "screenshots"),
|
||||
_ => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyPictures), "Ryujinx")
|
||||
};
|
||||
|
||||
@@ -423,10 +426,10 @@ namespace Ryujinx.Ava
|
||||
|
||||
_isActive = false;
|
||||
|
||||
if (_renderingThread.IsAlive)
|
||||
{
|
||||
_renderingThread.Join();
|
||||
}
|
||||
// NOTE: The render loop is allowed to stay alive until the renderer itself is disposed, as it may handle resource dispose.
|
||||
// We only need to wait for all commands submitted during the main gpu loop to be processed.
|
||||
_gpuDoneEvent.WaitOne();
|
||||
_gpuDoneEvent.Dispose();
|
||||
|
||||
DisplaySleep.Restore();
|
||||
|
||||
@@ -917,6 +920,14 @@ namespace Ryujinx.Ava
|
||||
UpdateStatus();
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure all commands in the run loop are fully executed before leaving the loop.
|
||||
if (Device.Gpu.Renderer is ThreadedRenderer threaded)
|
||||
{
|
||||
threaded.FlushThreadedCommands();
|
||||
}
|
||||
|
||||
_gpuDoneEvent.Set();
|
||||
});
|
||||
|
||||
(_rendererHost.EmbeddedWindow as EmbeddedWindowOpenGL)?.MakeCurrent(null);
|
||||
|
||||
@@ -74,6 +74,13 @@
|
||||
"GameListContextMenuExtractDataLogoToolTip": "Extract the Logo section from Application's current config (including updates)",
|
||||
"StatusBarGamesLoaded": "{0}/{1} Games Loaded",
|
||||
"StatusBarSystemVersion": "System Version: {0}",
|
||||
"LinuxVmMaxMapCountDialogTitle": "Low limit for memory mappings detected",
|
||||
"LinuxVmMaxMapCountDialogTextPrimary": "Would you like to increase the value of vm.max_map_count to {0}",
|
||||
"LinuxVmMaxMapCountDialogTextSecondary": "Some games might try to create more memory mappings than currently allowed. Ryujinx will crash as soon as this limit gets exceeded.",
|
||||
"LinuxVmMaxMapCountDialogButtonUntilRestart": "Yes, until the next restart",
|
||||
"LinuxVmMaxMapCountDialogButtonPersistent": "Yes, permanently",
|
||||
"LinuxVmMaxMapCountWarningTextPrimary": "Max amount of memory mappings is lower than recommended.",
|
||||
"LinuxVmMaxMapCountWarningTextSecondary": "The current value of vm.max_map_count ({0}) is lower than {1}. Some games might try to create more memory mappings than currently allowed. Ryujinx will crash as soon as this limit gets exceeded.\n\nYou might want to either manually increase the limit or install pkexec, which allows Ryujinx to assist with that.",
|
||||
"Settings": "Settings",
|
||||
"SettingsTabGeneral": "User Interface",
|
||||
"SettingsTabGeneralGeneral": "General",
|
||||
@@ -216,26 +223,17 @@
|
||||
"ControllerSettingsDPadDown": "Down",
|
||||
"ControllerSettingsDPadLeft": "Left",
|
||||
"ControllerSettingsDPadRight": "Right",
|
||||
"ControllerSettingsStickButton": "Button",
|
||||
"ControllerSettingsStickUp": "Up",
|
||||
"ControllerSettingsStickDown": "Down",
|
||||
"ControllerSettingsStickLeft": "Left",
|
||||
"ControllerSettingsStickRight": "Right",
|
||||
"ControllerSettingsStickStick": "Stick",
|
||||
"ControllerSettingsStickInvertXAxis": "Invert Stick X",
|
||||
"ControllerSettingsStickInvertYAxis": "Invert Stick Y",
|
||||
"ControllerSettingsStickDeadzone": "Deadzone:",
|
||||
"ControllerSettingsLStick": "Left Stick",
|
||||
"ControllerSettingsLStickButton": "Button",
|
||||
"ControllerSettingsLStickUp": "Up",
|
||||
"ControllerSettingsLStickDown": "Down",
|
||||
"ControllerSettingsLStickLeft": "Left",
|
||||
"ControllerSettingsLStickRight": "Right",
|
||||
"ControllerSettingsLStickStick": "Stick",
|
||||
"ControllerSettingsLStickInvertXAxis": "Invert Stick X",
|
||||
"ControllerSettingsLStickInvertYAxis": "Invert Stick Y",
|
||||
"ControllerSettingsLStickDeadzone": "Deadzone:",
|
||||
"ControllerSettingsRStick": "Right Stick",
|
||||
"ControllerSettingsRStickButton": "Button",
|
||||
"ControllerSettingsRStickUp": "Up",
|
||||
"ControllerSettingsRStickDown": "Down",
|
||||
"ControllerSettingsRStickLeft": "Left",
|
||||
"ControllerSettingsRStickRight": "Right",
|
||||
"ControllerSettingsRStickStick": "Stick",
|
||||
"ControllerSettingsRStickInvertXAxis": "Invert Stick X",
|
||||
"ControllerSettingsRStickInvertYAxis": "Invert Stick Y",
|
||||
"ControllerSettingsRStickDeadzone": "Deadzone:",
|
||||
"ControllerSettingsTriggersLeft": "Triggers Left",
|
||||
"ControllerSettingsTriggersRight": "Triggers Right",
|
||||
"ControllerSettingsTriggersButtonsLeft": "Trigger Buttons Left",
|
||||
@@ -291,6 +289,7 @@
|
||||
"ControllerSettingsSaveProfileToolTip": "Save Profile",
|
||||
"MenuBarFileToolsTakeScreenshot": "Take Screenshot",
|
||||
"MenuBarFileToolsHideUi": "Hide UI",
|
||||
"GameListContextMenuRunApplication": "Run Application",
|
||||
"GameListContextMenuToggleFavorite": "Toggle Favorite",
|
||||
"GameListContextMenuToggleFavoriteToolTip": "Toggle Favorite status of Game",
|
||||
"SettingsTabGeneralTheme": "Theme",
|
||||
@@ -629,7 +628,7 @@
|
||||
"Search": "Search",
|
||||
"UserProfilesRecoverLostAccounts": "Recover Lost Accounts",
|
||||
"Recover": "Recover",
|
||||
"UserProfilesRecoverHeading" : "Saves were found for the following accounts",
|
||||
"UserProfilesRecoverHeading": "Saves were found for the following accounts",
|
||||
"UserProfilesRecoverEmptyList": "No profiles to recover",
|
||||
"GraphicsAATooltip": "Applies anti-aliasing to the game render",
|
||||
"GraphicsAALabel": "Anti-Aliasing:",
|
||||
@@ -641,10 +640,12 @@
|
||||
"SmaaMedium": "SMAA Medium",
|
||||
"SmaaHigh": "SMAA High",
|
||||
"SmaaUltra": "SMAA Ultra",
|
||||
"UserEditorTitle" : "Edit User",
|
||||
"UserEditorTitleCreate" : "Create User",
|
||||
"UserEditorTitle": "Edit User",
|
||||
"UserEditorTitleCreate": "Create User",
|
||||
"SettingsTabNetworkInterface": "Network Interface:",
|
||||
"NetworkInterfaceTooltip": "The network interface used for LAN features",
|
||||
"NetworkInterfaceDefault": "Default",
|
||||
"PackagingShaders": "Packaging Shaders"
|
||||
}
|
||||
"PackagingShaders": "Packaging Shaders",
|
||||
"AboutChangelogButton": "View Changelog on GitHub",
|
||||
"AboutChangelogButtonTooltipMessage": "Click to open the changelog for this version in your default browser."
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Input;
|
||||
using FluentAvalonia.Core;
|
||||
using Ryujinx.Input;
|
||||
using System;
|
||||
using System.Numerics;
|
||||
@@ -30,14 +29,14 @@ namespace Ryujinx.Ava.Input
|
||||
_window = window;
|
||||
|
||||
_widget.PointerMoved += Parent_PointerMovedEvent;
|
||||
_widget.PointerPressed += Parent_PointerPressEvent;
|
||||
_widget.PointerReleased += Parent_PointerReleaseEvent;
|
||||
_widget.PointerWheelChanged += Parent_ScrollEvent;
|
||||
_widget.PointerPressed += Parent_PointerPressedEvent;
|
||||
_widget.PointerReleased += Parent_PointerReleasedEvent;
|
||||
_widget.PointerWheelChanged += Parent_PointerWheelChanged;
|
||||
|
||||
_window.PointerMoved += Parent_PointerMovedEvent;
|
||||
_window.PointerPressed += Parent_PointerPressEvent;
|
||||
_window.PointerReleased += Parent_PointerReleaseEvent;
|
||||
_window.PointerWheelChanged += Parent_ScrollEvent;
|
||||
_window.PointerPressed += Parent_PointerPressedEvent;
|
||||
_window.PointerReleased += Parent_PointerReleasedEvent;
|
||||
_window.PointerWheelChanged += Parent_PointerWheelChanged;
|
||||
|
||||
PressedButtons = new bool[(int)MouseButton.Count];
|
||||
|
||||
@@ -63,29 +62,25 @@ namespace Ryujinx.Ava.Input
|
||||
_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);
|
||||
}
|
||||
|
||||
private void Parent_PointerReleaseEvent(object o, PointerReleasedEventArgs args)
|
||||
private void Parent_PointerReleasedEvent(object o, PointerReleasedEventArgs args)
|
||||
{
|
||||
if (args.InitialPressMouseButton != Avalonia.Input.MouseButton.None)
|
||||
{
|
||||
int button = (int)args.InitialPressMouseButton;
|
||||
uint button = (uint)args.InitialPressMouseButton - 1;
|
||||
|
||||
if (PressedButtons.Count() >= button)
|
||||
{
|
||||
PressedButtons[button] = false;
|
||||
}
|
||||
if ((uint)PressedButtons.Length > button)
|
||||
{
|
||||
PressedButtons[button] = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void Parent_PointerPressEvent(object o, PointerPressedEventArgs args)
|
||||
private void Parent_PointerPressedEvent(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;
|
||||
}
|
||||
@@ -100,17 +95,17 @@ namespace Ryujinx.Ava.Input
|
||||
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
if (PressedButtons.Count() >= (int)button)
|
||||
if ((uint)PressedButtons.Length > (uint)button)
|
||||
{
|
||||
return PressedButtons[(int)button];
|
||||
return PressedButtons[(uint)button];
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -149,14 +144,14 @@ namespace Ryujinx.Ava.Input
|
||||
_isDisposed = true;
|
||||
|
||||
_widget.PointerMoved -= Parent_PointerMovedEvent;
|
||||
_widget.PointerPressed -= Parent_PointerPressEvent;
|
||||
_widget.PointerReleased -= Parent_PointerReleaseEvent;
|
||||
_widget.PointerWheelChanged -= Parent_ScrollEvent;
|
||||
_widget.PointerPressed -= Parent_PointerPressedEvent;
|
||||
_widget.PointerReleased -= Parent_PointerReleasedEvent;
|
||||
_widget.PointerWheelChanged -= Parent_PointerWheelChanged;
|
||||
|
||||
_window.PointerMoved -= Parent_PointerMovedEvent;
|
||||
_window.PointerPressed -= Parent_PointerPressEvent;
|
||||
_window.PointerReleased -= Parent_PointerReleaseEvent;
|
||||
_window.PointerWheelChanged -= Parent_ScrollEvent;
|
||||
_window.PointerPressed -= Parent_PointerPressedEvent;
|
||||
_window.PointerReleased -= Parent_PointerReleasedEvent;
|
||||
_window.PointerWheelChanged -= Parent_PointerWheelChanged;
|
||||
|
||||
_widget = null;
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
|
||||
<PropertyGroup Condition="'$(RuntimeIdentifier)' != ''">
|
||||
<PublishSingleFile>true</PublishSingleFile>
|
||||
<TrimmerSingleWarn>false</TrimmerSingleWarn>
|
||||
<PublishTrimmed>true</PublishTrimmed>
|
||||
<TrimMode>partial</TrimMode>
|
||||
</PropertyGroup>
|
||||
@@ -147,4 +148,4 @@
|
||||
<ItemGroup>
|
||||
<AdditionalFiles Include="Assets\Locales\en_US.json" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
</Project>
|
||||
@@ -3,6 +3,9 @@
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale">
|
||||
<MenuItem
|
||||
Click="RunApplication_Click"
|
||||
Header="{locale:Locale GameListContextMenuRunApplication}" />
|
||||
<MenuItem
|
||||
Click="ToggleFavorite_Click"
|
||||
Header="{locale:Locale GameListContextMenuToggleFavorite}"
|
||||
|
||||
@@ -323,5 +323,15 @@ namespace Ryujinx.Ava.UI.Controls
|
||||
await ApplicationHelper.ExtractSection(NcaSectionType.Logo, viewModel.SelectedApplication.Path, viewModel.SelectedApplication.TitleName);
|
||||
}
|
||||
}
|
||||
|
||||
public void RunApplication_Click(object sender, RoutedEventArgs args)
|
||||
{
|
||||
var viewModel = (sender as MenuItem)?.DataContext as MainWindowViewModel;
|
||||
|
||||
if (viewModel?.SelectedApplication != null)
|
||||
{
|
||||
viewModel.LoadApplication(viewModel.SelectedApplication.Path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,6 +21,7 @@ namespace Ryujinx.Ava.UI.Helpers
|
||||
if (value is byte[] buffer && targetType == typeof(IImage))
|
||||
{
|
||||
MemoryStream mem = new(buffer);
|
||||
|
||||
return new Bitmap(mem);
|
||||
}
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@ using Avalonia.Threading;
|
||||
using FluentAvalonia.Core;
|
||||
using FluentAvalonia.UI.Controls;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.UI.Controls;
|
||||
using Ryujinx.Ava.UI.Windows;
|
||||
using Ryujinx.Common.Logging;
|
||||
using System;
|
||||
@@ -19,7 +18,7 @@ namespace Ryujinx.Ava.UI.Helpers
|
||||
{
|
||||
private static bool _isChoiceDialogOpen;
|
||||
|
||||
public async static Task<UserResult> ShowContentDialog(
|
||||
private async static Task<UserResult> ShowContentDialog(
|
||||
string title,
|
||||
object content,
|
||||
string primaryButton,
|
||||
@@ -67,7 +66,7 @@ namespace Ryujinx.Ava.UI.Helpers
|
||||
return result;
|
||||
}
|
||||
|
||||
private async static Task<UserResult> ShowTextDialog(
|
||||
public async static Task<UserResult> ShowTextDialog(
|
||||
string title,
|
||||
string primaryText,
|
||||
string secondaryText,
|
||||
@@ -319,7 +318,7 @@ namespace Ryujinx.Ava.UI.Helpers
|
||||
|
||||
Window parent = GetMainWindow();
|
||||
|
||||
if (parent != null && parent.IsActive && parent is MainWindow window && window.ViewModel.IsGameRunning)
|
||||
if (parent != null && parent.IsActive && (parent as MainWindow).ViewModel.IsGameRunning)
|
||||
{
|
||||
contentDialogOverlayWindow = new()
|
||||
{
|
||||
|
||||
@@ -3,21 +3,20 @@ using Avalonia.Controls;
|
||||
using Avalonia.Controls.Notifications;
|
||||
using Avalonia.Threading;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Common;
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Helpers
|
||||
{
|
||||
public static class NotificationHelper
|
||||
{
|
||||
private const int MaxNotifications = 4;
|
||||
private const int MaxNotifications = 4;
|
||||
private const int NotificationDelayInMs = 5000;
|
||||
|
||||
private static WindowNotificationManager _notificationManager;
|
||||
|
||||
private static readonly ManualResetEvent _templateAppliedEvent = new(false);
|
||||
private static readonly BlockingCollection<Notification> _notifications = new();
|
||||
|
||||
public static void SetNotificationManager(Window host)
|
||||
@@ -29,25 +28,31 @@ namespace Ryujinx.Ava.UI.Helpers
|
||||
Margin = new Thickness(0, 0, 15, 40)
|
||||
};
|
||||
|
||||
var maybeAsyncWorkQueue = new Lazy<AsyncWorkQueue<Notification>>(
|
||||
() => new AsyncWorkQueue<Notification>(notification =>
|
||||
{
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
_notificationManager.Show(notification);
|
||||
});
|
||||
},
|
||||
"UI.NotificationThread",
|
||||
_notifications),
|
||||
LazyThreadSafetyMode.ExecutionAndPublication);
|
||||
|
||||
_notificationManager.TemplateApplied += (sender, args) =>
|
||||
{
|
||||
_templateAppliedEvent.Set();
|
||||
// NOTE: Force creation of the AsyncWorkQueue.
|
||||
_ = maybeAsyncWorkQueue.Value;
|
||||
};
|
||||
|
||||
Task.Run(async () =>
|
||||
host.Closing += (sender, args) =>
|
||||
{
|
||||
_templateAppliedEvent.WaitOne();
|
||||
|
||||
foreach (var notification in _notifications.GetConsumingEnumerable())
|
||||
if (maybeAsyncWorkQueue.IsValueCreated)
|
||||
{
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
_notificationManager.Show(notification);
|
||||
});
|
||||
|
||||
await Task.Delay(NotificationDelayInMs / MaxNotifications);
|
||||
maybeAsyncWorkQueue.Value.Dispose();
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
public static void Show(string title, string text, NotificationType type, bool waitingExit = false, Action onClick = null, Action onClose = null)
|
||||
|
||||
+6
-5
@@ -7,6 +7,7 @@ using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.Input;
|
||||
using Ryujinx.Ava.UI.Helpers;
|
||||
using Ryujinx.Ava.UI.Models;
|
||||
using Ryujinx.Ava.UI.Views.Input;
|
||||
using Ryujinx.Ava.UI.Windows;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Configuration;
|
||||
@@ -30,7 +31,7 @@ using Key = Ryujinx.Common.Configuration.Hid.Key;
|
||||
|
||||
namespace Ryujinx.Ava.UI.ViewModels
|
||||
{
|
||||
public class ControllerSettingsViewModel : BaseModel, IDisposable
|
||||
public class ControllerInputViewModel : BaseModel, IDisposable
|
||||
{
|
||||
private const string Disabled = "disabled";
|
||||
private const string ProControllerResource = "Ryujinx.Ui.Common/Resources/Controller_ProCon.svg";
|
||||
@@ -231,7 +232,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
|
||||
public InputConfig Config { get; set; }
|
||||
|
||||
public ControllerSettingsViewModel(UserControl owner) : this()
|
||||
public ControllerInputViewModel(UserControl owner) : this()
|
||||
{
|
||||
_owner = owner;
|
||||
|
||||
@@ -258,7 +259,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
public ControllerSettingsViewModel()
|
||||
public ControllerInputViewModel()
|
||||
{
|
||||
PlayerIndexes = new ObservableCollection<PlayerModel>();
|
||||
Controllers = new ObservableCollection<ControllerModel>();
|
||||
@@ -328,12 +329,12 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
|
||||
public async void ShowMotionConfig()
|
||||
{
|
||||
await MotionSettingsWindow.Show(this);
|
||||
await MotionInputView.Show(this);
|
||||
}
|
||||
|
||||
public async void ShowRumbleConfig()
|
||||
{
|
||||
await RumbleSettingsWindow.Show(this);
|
||||
await RumbleInputView.Show(this);
|
||||
}
|
||||
|
||||
private void LoadInputDriver()
|
||||
@@ -257,6 +257,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
|
||||
OnPropertyChanged();
|
||||
OnPropertyChanged(nameof(EnableNonGameRunningControls));
|
||||
OnPropertyChanged(nameof(IsAppletMenuActive));
|
||||
OnPropertyChanged(nameof(StatusBarVisible));
|
||||
OnPropertyChanged(nameof(ShowFirmwareStatus));
|
||||
}
|
||||
@@ -1529,6 +1530,8 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
double sessionTimePlayed = DateTime.UtcNow.Subtract(appMetadata.LastPlayed.Value).TotalSeconds;
|
||||
appMetadata.TimePlayed += Math.Round(sessionTimePlayed, MidpointRounding.AwayFromZero);
|
||||
}
|
||||
|
||||
appMetadata.LastPlayed = DateTime.UtcNow;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
namespace Ryujinx.Ava.UI.ViewModels
|
||||
{
|
||||
public class MotionInputViewModel : BaseModel
|
||||
{
|
||||
private int _slot;
|
||||
public int Slot
|
||||
{
|
||||
get => _slot;
|
||||
set
|
||||
{
|
||||
_slot = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private int _altSlot;
|
||||
public int AltSlot
|
||||
{
|
||||
get => _altSlot;
|
||||
set
|
||||
{
|
||||
_altSlot = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private string _dsuServerHost;
|
||||
public string DsuServerHost
|
||||
{
|
||||
get => _dsuServerHost;
|
||||
set
|
||||
{
|
||||
_dsuServerHost = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private int _dsuServerPort;
|
||||
public int DsuServerPort
|
||||
{
|
||||
get => _dsuServerPort;
|
||||
set
|
||||
{
|
||||
_dsuServerPort = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private bool _mirrorInput;
|
||||
public bool MirrorInput
|
||||
{
|
||||
get => _mirrorInput;
|
||||
set
|
||||
{
|
||||
_mirrorInput = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private int _sensitivity;
|
||||
public int Sensitivity
|
||||
{
|
||||
get => _sensitivity;
|
||||
set
|
||||
{
|
||||
_sensitivity = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private double _gryoDeadzone;
|
||||
public double GyroDeadzone
|
||||
{
|
||||
get => _gryoDeadzone;
|
||||
set
|
||||
{
|
||||
_gryoDeadzone = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private bool _enableCemuHookMotion;
|
||||
public bool EnableCemuHookMotion
|
||||
{
|
||||
get => _enableCemuHookMotion;
|
||||
set
|
||||
{
|
||||
_enableCemuHookMotion = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
namespace Ryujinx.Ava.UI.ViewModels
|
||||
{
|
||||
public class RumbleInputViewModel : BaseModel
|
||||
{
|
||||
private float _strongRumble;
|
||||
public float StrongRumble
|
||||
{
|
||||
get => _strongRumble;
|
||||
set
|
||||
{
|
||||
_strongRumble = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
|
||||
private float _weakRumble;
|
||||
public float WeakRumble
|
||||
{
|
||||
get => _weakRumble;
|
||||
set
|
||||
{
|
||||
_weakRumble = value;
|
||||
OnPropertyChanged();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -311,7 +311,7 @@ namespace Ryujinx.Ava.UI.ViewModels
|
||||
{
|
||||
_gpuIds = new List<string>();
|
||||
List<string> names = new();
|
||||
var devices = VulkanRenderer.GetPhysicalDevices(Vk.GetApi());
|
||||
var devices = VulkanRenderer.GetPhysicalDevices();
|
||||
|
||||
if (devices.Length == 0)
|
||||
{
|
||||
|
||||
+302
-300
File diff suppressed because it is too large
Load Diff
+5
-6
@@ -4,7 +4,6 @@ using Avalonia.Input;
|
||||
using Avalonia.Interactivity;
|
||||
using Avalonia.LogicalTree;
|
||||
using Ryujinx.Ava.Common.Locale;
|
||||
using Ryujinx.Ava.UI.Controls;
|
||||
using Ryujinx.Ava.UI.Helpers;
|
||||
using Ryujinx.Ava.UI.Models;
|
||||
using Ryujinx.Ava.UI.ViewModels;
|
||||
@@ -13,18 +12,18 @@ using Ryujinx.Input;
|
||||
using Ryujinx.Input.Assigner;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Windows
|
||||
namespace Ryujinx.Ava.UI.Views.Input
|
||||
{
|
||||
public partial class ControllerSettingsWindow : UserControl
|
||||
public partial class ControllerInputView : UserControl
|
||||
{
|
||||
private bool _dialogOpen;
|
||||
|
||||
private ButtonKeyAssigner _currentAssigner;
|
||||
internal ControllerSettingsViewModel ViewModel { get; set; }
|
||||
internal ControllerInputViewModel ViewModel { get; set; }
|
||||
|
||||
public ControllerSettingsWindow()
|
||||
public ControllerInputView()
|
||||
{
|
||||
DataContext = ViewModel = new ControllerSettingsViewModel(this);
|
||||
DataContext = ViewModel = new ControllerInputViewModel(this);
|
||||
|
||||
InitializeComponent();
|
||||
|
||||
+66
-38
@@ -1,12 +1,15 @@
|
||||
<UserControl
|
||||
<UserControl
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:ui="clr-namespace:FluentAvalonia.UI.Controls;assembly=FluentAvalonia"
|
||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
|
||||
mc:Ignorable="d"
|
||||
x:Class="Ryujinx.Ava.UI.Windows.MotionSettingsWindow"
|
||||
x:Class="Ryujinx.Ava.UI.Views.Input.MotionInputView"
|
||||
x:CompileBindings="True"
|
||||
x:DataType="viewModels:MotionInputViewModel"
|
||||
Focusable="True">
|
||||
<Grid Margin="10">
|
||||
<Grid.RowDefinitions>
|
||||
@@ -14,7 +17,9 @@
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
<StackPanel Orientation="Vertical">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
|
||||
<StackPanel
|
||||
Orientation="Horizontal"
|
||||
HorizontalAlignment="Center">
|
||||
<TextBlock
|
||||
Margin="0"
|
||||
HorizontalAlignment="Center"
|
||||
@@ -28,11 +33,14 @@
|
||||
Maximum="100"
|
||||
Minimum="0"
|
||||
Value="{Binding Sensitivity, Mode=TwoWay}" />
|
||||
<TextBlock HorizontalAlignment="Center"
|
||||
Margin="5, 0"
|
||||
Text="{Binding Sensitivity, StringFormat=\{0:0\}%}" />
|
||||
<TextBlock
|
||||
HorizontalAlignment="Center"
|
||||
Margin="5, 0"
|
||||
Text="{Binding Sensitivity, StringFormat=\{0:0\}%}" />
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
|
||||
<StackPanel
|
||||
Orientation="Horizontal"
|
||||
HorizontalAlignment="Center">
|
||||
<TextBlock
|
||||
Margin="0"
|
||||
HorizontalAlignment="Center"
|
||||
@@ -51,17 +59,25 @@
|
||||
Margin="5, 0"
|
||||
Text="{Binding GyroDeadzone, StringFormat=\{0:0.00\}}" />
|
||||
</StackPanel>
|
||||
<Separator Height="1" Margin="0,5" />
|
||||
<CheckBox Margin="5" IsChecked="{Binding EnableCemuHookMotion}">
|
||||
<TextBlock Margin="0,3,0,0" VerticalAlignment="Center"
|
||||
Text="{locale:Locale ControllerSettingsMotionUseCemuhookCompatibleMotion}" />
|
||||
<Separator
|
||||
Height="1"
|
||||
Margin="0,5" />
|
||||
<CheckBox
|
||||
Margin="5"
|
||||
IsChecked="{Binding EnableCemuHookMotion}">
|
||||
<TextBlock
|
||||
Margin="0,3,0,0"
|
||||
VerticalAlignment="Center"
|
||||
Text="{locale:Locale ControllerSettingsMotionUseCemuhookCompatibleMotion}" />
|
||||
</CheckBox>
|
||||
</StackPanel>
|
||||
<Border Grid.Row="1"
|
||||
Padding="20,5"
|
||||
BorderBrush="{DynamicResource ThemeControlBorderColor}"
|
||||
BorderThickness="1"
|
||||
HorizontalAlignment="Stretch">
|
||||
<Border
|
||||
Grid.Row="1"
|
||||
Padding="20,5"
|
||||
BorderBrush="{DynamicResource ThemeControlBorderColor}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="5"
|
||||
HorizontalAlignment="Stretch">
|
||||
<Grid VerticalAlignment="Top">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
@@ -109,30 +125,42 @@
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock Margin="0,10,0,0" VerticalAlignment="Center"
|
||||
Text="{locale:Locale ControllerSettingsMotionControllerSlot}" />
|
||||
<ui:NumberBox Grid.Row="0" Grid.Column="1"
|
||||
Name="CemuHookSlotUpDown"
|
||||
SmallChange="1"
|
||||
LargeChange="1"
|
||||
Maximum="4"
|
||||
Minimum="0"
|
||||
Value="{Binding Slot}" />
|
||||
<TextBlock Margin="0,10,0,0" Grid.Row="1" Grid.Column="0" VerticalAlignment="Center"
|
||||
Text="{locale:Locale ControllerSettingsMotionRightJoyConSlot}" />
|
||||
<ui:NumberBox Grid.Row="1" Grid.Column="1"
|
||||
Name="CemuHookRightJoyConSlotUpDown"
|
||||
SmallChange="1"
|
||||
LargeChange="1"
|
||||
Maximum="4"
|
||||
Minimum="0"
|
||||
Value="{Binding AltSlot}" />
|
||||
<TextBlock
|
||||
Margin="0,10,0,0"
|
||||
VerticalAlignment="Center"
|
||||
Text="{locale:Locale ControllerSettingsMotionControllerSlot}" />
|
||||
<ui:NumberBox
|
||||
Grid.Row="0"
|
||||
Grid.Column="1"
|
||||
Name="CemuHookSlotUpDown"
|
||||
SmallChange="1"
|
||||
LargeChange="1"
|
||||
Maximum="4"
|
||||
Minimum="0"
|
||||
Value="{Binding Slot}" />
|
||||
<TextBlock
|
||||
Margin="0,10,0,0"
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
Text="{locale:Locale ControllerSettingsMotionRightJoyConSlot}" />
|
||||
<ui:NumberBox
|
||||
Grid.Row="1"
|
||||
Grid.Column="1"
|
||||
Name="CemuHookRightJoyConSlotUpDown"
|
||||
SmallChange="1"
|
||||
LargeChange="1"
|
||||
Maximum="4"
|
||||
Minimum="0"
|
||||
Value="{Binding AltSlot}" />
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
<CheckBox HorizontalAlignment="Center"
|
||||
IsChecked="{Binding MirrorInput, Mode=TwoWay}">
|
||||
<TextBlock HorizontalAlignment="Center"
|
||||
Text="{locale:Locale ControllerSettingsMotionMirrorInput}" />
|
||||
<CheckBox
|
||||
HorizontalAlignment="Center"
|
||||
IsChecked="{Binding MirrorInput, Mode=TwoWay}">
|
||||
<TextBlock
|
||||
HorizontalAlignment="Center"
|
||||
Text="{locale:Locale ControllerSettingsMotionMirrorInput}" />
|
||||
</CheckBox>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
+20
-23
@@ -6,44 +6,42 @@ using Ryujinx.Ava.UI.ViewModels;
|
||||
using Ryujinx.Common.Configuration.Hid.Controller;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Windows
|
||||
namespace Ryujinx.Ava.UI.Views.Input
|
||||
{
|
||||
public partial class MotionSettingsWindow : UserControl
|
||||
public partial class MotionInputView : UserControl
|
||||
{
|
||||
private readonly InputConfiguration<GamepadInputId, StickInputId> _viewmodel;
|
||||
private MotionInputViewModel _viewModel;
|
||||
|
||||
public MotionSettingsWindow()
|
||||
public MotionInputView()
|
||||
{
|
||||
InitializeComponent();
|
||||
DataContext = _viewmodel;
|
||||
}
|
||||
|
||||
public MotionSettingsWindow(ControllerSettingsViewModel viewmodel)
|
||||
public MotionInputView(ControllerInputViewModel viewModel)
|
||||
{
|
||||
var config = viewmodel.Configuration as InputConfiguration<GamepadInputId, StickInputId>;
|
||||
var config = viewModel.Configuration as InputConfiguration<GamepadInputId, StickInputId>;
|
||||
|
||||
_viewmodel = new InputConfiguration<GamepadInputId, StickInputId>()
|
||||
_viewModel = new MotionInputViewModel
|
||||
{
|
||||
Slot = config.Slot,
|
||||
AltSlot = config.AltSlot,
|
||||
DsuServerHost = config.DsuServerHost,
|
||||
DsuServerPort = config.DsuServerPort,
|
||||
MirrorInput = config.MirrorInput,
|
||||
EnableMotion = config.EnableMotion,
|
||||
Sensitivity = config.Sensitivity,
|
||||
GyroDeadzone = config.GyroDeadzone,
|
||||
EnableCemuHookMotion = config.EnableCemuHookMotion
|
||||
};
|
||||
|
||||
InitializeComponent();
|
||||
DataContext = _viewmodel;
|
||||
DataContext = _viewModel;
|
||||
}
|
||||
|
||||
public static async Task Show(ControllerSettingsViewModel viewmodel)
|
||||
public static async Task Show(ControllerInputViewModel viewModel)
|
||||
{
|
||||
MotionSettingsWindow content = new MotionSettingsWindow(viewmodel);
|
||||
MotionInputView content = new(viewModel);
|
||||
|
||||
ContentDialog contentDialog = new ContentDialog
|
||||
ContentDialog contentDialog = new()
|
||||
{
|
||||
Title = LocaleManager.Instance[LocaleKeys.ControllerMotionTitle],
|
||||
PrimaryButtonText = LocaleManager.Instance[LocaleKeys.ControllerSettingsSave],
|
||||
@@ -53,16 +51,15 @@ namespace Ryujinx.Ava.UI.Windows
|
||||
};
|
||||
contentDialog.PrimaryButtonClick += (sender, args) =>
|
||||
{
|
||||
var config = viewmodel.Configuration as InputConfiguration<GamepadInputId, StickInputId>;
|
||||
config.Slot = content._viewmodel.Slot;
|
||||
config.EnableMotion = content._viewmodel.EnableMotion;
|
||||
config.Sensitivity = content._viewmodel.Sensitivity;
|
||||
config.GyroDeadzone = content._viewmodel.GyroDeadzone;
|
||||
config.AltSlot = content._viewmodel.AltSlot;
|
||||
config.DsuServerHost = content._viewmodel.DsuServerHost;
|
||||
config.DsuServerPort = content._viewmodel.DsuServerPort;
|
||||
config.EnableCemuHookMotion = content._viewmodel.EnableCemuHookMotion;
|
||||
config.MirrorInput = content._viewmodel.MirrorInput;
|
||||
var config = viewModel.Configuration as InputConfiguration<GamepadInputId, StickInputId>;
|
||||
config.Slot = content._viewModel.Slot;
|
||||
config.Sensitivity = content._viewModel.Sensitivity;
|
||||
config.GyroDeadzone = content._viewModel.GyroDeadzone;
|
||||
config.AltSlot = content._viewModel.AltSlot;
|
||||
config.DsuServerHost = content._viewModel.DsuServerHost;
|
||||
config.DsuServerPort = content._viewModel.DsuServerPort;
|
||||
config.EnableCemuHookMotion = content._viewModel.EnableCemuHookMotion;
|
||||
config.MirrorInput = content._viewModel.MirrorInput;
|
||||
};
|
||||
|
||||
await contentDialog.ShowAsync();
|
||||
+5
-2
@@ -1,11 +1,14 @@
|
||||
<UserControl
|
||||
<UserControl
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
|
||||
mc:Ignorable="d"
|
||||
x:Class="Ryujinx.Ava.UI.Windows.RumbleSettingsWindow"
|
||||
x:Class="Ryujinx.Ava.UI.Views.Input.RumbleInputView"
|
||||
x:DataType="viewModels:RumbleInputViewModel"
|
||||
x:CompileBindings="True"
|
||||
Focusable="True">
|
||||
<Grid Margin="10">
|
||||
<Grid.RowDefinitions>
|
||||
+19
-18
@@ -6,36 +6,37 @@ using Ryujinx.Ava.UI.ViewModels;
|
||||
using Ryujinx.Common.Configuration.Hid.Controller;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Ryujinx.Ava.UI.Windows
|
||||
namespace Ryujinx.Ava.UI.Views.Input
|
||||
{
|
||||
public partial class RumbleSettingsWindow : UserControl
|
||||
public partial class RumbleInputView : UserControl
|
||||
{
|
||||
private readonly InputConfiguration<GamepadInputId, StickInputId> _viewmodel;
|
||||
private RumbleInputViewModel _viewModel;
|
||||
|
||||
public RumbleSettingsWindow()
|
||||
public RumbleInputView()
|
||||
{
|
||||
InitializeComponent();
|
||||
DataContext = _viewmodel;
|
||||
}
|
||||
|
||||
public RumbleSettingsWindow(ControllerSettingsViewModel viewmodel)
|
||||
public RumbleInputView(ControllerInputViewModel viewModel)
|
||||
{
|
||||
var config = viewmodel.Configuration as InputConfiguration<GamepadInputId, StickInputId>;
|
||||
var config = viewModel.Configuration as InputConfiguration<GamepadInputId, StickInputId>;
|
||||
|
||||
_viewmodel = new InputConfiguration<GamepadInputId, StickInputId>()
|
||||
_viewModel = new RumbleInputViewModel
|
||||
{
|
||||
StrongRumble = config.StrongRumble, WeakRumble = config.WeakRumble
|
||||
StrongRumble = config.StrongRumble,
|
||||
WeakRumble = config.WeakRumble
|
||||
};
|
||||
|
||||
InitializeComponent();
|
||||
DataContext = _viewmodel;
|
||||
|
||||
DataContext = _viewModel;
|
||||
}
|
||||
|
||||
public static async Task Show(ControllerSettingsViewModel viewmodel)
|
||||
public static async Task Show(ControllerInputViewModel viewModel)
|
||||
{
|
||||
RumbleSettingsWindow content = new RumbleSettingsWindow(viewmodel);
|
||||
RumbleInputView content = new(viewModel);
|
||||
|
||||
ContentDialog contentDialog = new ContentDialog
|
||||
ContentDialog contentDialog = new()
|
||||
{
|
||||
Title = LocaleManager.Instance[LocaleKeys.ControllerRumbleTitle],
|
||||
PrimaryButtonText = LocaleManager.Instance[LocaleKeys.ControllerSettingsSave],
|
||||
@@ -43,14 +44,14 @@ namespace Ryujinx.Ava.UI.Windows
|
||||
CloseButtonText = LocaleManager.Instance[LocaleKeys.ControllerSettingsClose],
|
||||
Content = content,
|
||||
};
|
||||
|
||||
|
||||
contentDialog.PrimaryButtonClick += (sender, args) =>
|
||||
{
|
||||
var config = viewmodel.Configuration as InputConfiguration<GamepadInputId, StickInputId>;
|
||||
config.StrongRumble = content._viewmodel.StrongRumble;
|
||||
config.WeakRumble = content._viewmodel.WeakRumble;
|
||||
var config = viewModel.Configuration as InputConfiguration<GamepadInputId, StickInputId>;
|
||||
config.StrongRumble = content._viewModel.StrongRumble;
|
||||
config.WeakRumble = content._viewModel.WeakRumble;
|
||||
};
|
||||
|
||||
|
||||
await contentDialog.ShowAsync();
|
||||
}
|
||||
}
|
||||
@@ -9,9 +9,7 @@ using Ryujinx.Ava.UI.ViewModels;
|
||||
using Ryujinx.Ava.UI.Windows;
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Common.Utilities;
|
||||
using Ryujinx.HLE.HOS;
|
||||
using Ryujinx.Modules;
|
||||
using Ryujinx.Ui.App.Common;
|
||||
using Ryujinx.Ui.Common;
|
||||
using Ryujinx.Ui.Common.Configuration;
|
||||
using Ryujinx.Ui.Common.Helper;
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
</Design.DataContext>
|
||||
<DockPanel
|
||||
Margin="0,0,0,5"
|
||||
Height="35"
|
||||
HorizontalAlignment="Stretch">
|
||||
<Button
|
||||
Width="40"
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<UserControl
|
||||
<UserControl
|
||||
x:Class="Ryujinx.Ava.UI.Views.Settings.SettingsInputView"
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:locale="clr-namespace:Ryujinx.Ava.Common.Locale"
|
||||
xmlns:window="clr-namespace:Ryujinx.Ava.UI.Windows"
|
||||
xmlns:views="clr-namespace:Ryujinx.Ava.UI.Views.Input"
|
||||
xmlns:viewModels="clr-namespace:Ryujinx.Ava.UI.ViewModels"
|
||||
mc:Ignorable="d"
|
||||
x:CompileBindings="True"
|
||||
@@ -13,34 +13,56 @@
|
||||
<Design.DataContext>
|
||||
<viewModels:SettingsViewModel />
|
||||
</Design.DataContext>
|
||||
<ScrollViewer
|
||||
<ScrollViewer
|
||||
Name="InputPage"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
HorizontalScrollBarVisibility="Disabled"
|
||||
VerticalScrollBarVisibility="Auto">
|
||||
<Border Classes="settings">
|
||||
<StackPanel Margin="4" Orientation="Vertical">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<CheckBox Margin="5,0"
|
||||
ToolTip.Tip="{locale:Locale DockModeToggleTooltip}"
|
||||
IsChecked="{Binding EnableDockedMode}">
|
||||
<TextBlock VerticalAlignment="Center"
|
||||
Text="{locale:Locale SettingsTabInputEnableDockedMode}" />
|
||||
</CheckBox>
|
||||
<CheckBox Margin="5,0"
|
||||
ToolTip.Tip="{locale:Locale DirectKeyboardTooltip}"
|
||||
IsChecked="{Binding EnableKeyboard}">
|
||||
<TextBlock Text="{locale:Locale SettingsTabInputDirectKeyboardAccess}" />
|
||||
</CheckBox>
|
||||
<CheckBox Margin="5,0"
|
||||
ToolTip.Tip="{locale:Locale DirectMouseTooltip}"
|
||||
IsChecked="{Binding EnableMouse}">
|
||||
<TextBlock Text="{locale:Locale SettingsTabInputDirectMouseAccess}" />
|
||||
</CheckBox>
|
||||
</StackPanel>
|
||||
<window:ControllerSettingsWindow Name="ControllerSettings" Margin="0" MinHeight="600" />
|
||||
</StackPanel>
|
||||
<Panel
|
||||
Margin="10">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto"/>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<views:ControllerInputView
|
||||
Grid.Row="0"
|
||||
Name="ControllerSettings" />
|
||||
<StackPanel
|
||||
Orientation="Vertical"
|
||||
Grid.Row="2">
|
||||
<Separator
|
||||
Margin="0 10"
|
||||
Height="1" />
|
||||
<StackPanel
|
||||
Orientation="Horizontal"
|
||||
Spacing="10">
|
||||
<CheckBox
|
||||
ToolTip.Tip="{locale:Locale DockModeToggleTooltip}"
|
||||
MinWidth="0"
|
||||
IsChecked="{Binding EnableDockedMode}">
|
||||
<TextBlock
|
||||
Text="{locale:Locale SettingsTabInputEnableDockedMode}" />
|
||||
</CheckBox>
|
||||
<CheckBox
|
||||
ToolTip.Tip="{locale:Locale DirectKeyboardTooltip}"
|
||||
IsChecked="{Binding EnableKeyboard}">
|
||||
<TextBlock
|
||||
Text="{locale:Locale SettingsTabInputDirectKeyboardAccess}" />
|
||||
</CheckBox>
|
||||
<CheckBox
|
||||
ToolTip.Tip="{locale:Locale DirectMouseTooltip}"
|
||||
IsChecked="{Binding EnableMouse}">
|
||||
<TextBlock
|
||||
Text="{locale:Locale SettingsTabInputDirectMouseAccess}" />
|
||||
</CheckBox>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Panel>
|
||||
</Border>
|
||||
</ScrollViewer>
|
||||
</UserControl>
|
||||
@@ -58,11 +58,20 @@
|
||||
JustifyContent="SpaceAround"
|
||||
RowSpacing="2">
|
||||
<TextBlock
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="28"
|
||||
FontWeight="Bold"
|
||||
Text="Ryujinx"
|
||||
TextAlignment="Left" />
|
||||
<TextBlock Text="(REE-YOU-JINX)" TextAlignment="Left" />
|
||||
TextAlignment="Center"
|
||||
Width="100" />
|
||||
<TextBlock
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
FontSize="11"
|
||||
Text="(REE-YOU-JINX)"
|
||||
TextAlignment="Center"
|
||||
Width="100" />
|
||||
</flex:FlexPanel>
|
||||
</Grid>
|
||||
<TextBlock
|
||||
@@ -72,6 +81,18 @@
|
||||
LineHeight="12"
|
||||
Text="{Binding Version}"
|
||||
TextAlignment="Center" />
|
||||
<Button
|
||||
Padding="5"
|
||||
HorizontalAlignment="Center"
|
||||
Background="Transparent"
|
||||
Click="Button_OnClick"
|
||||
Tag="https://github.com/Ryujinx/Ryujinx/wiki/Changelog#ryujinx-changelog">
|
||||
<TextBlock
|
||||
FontSize="10"
|
||||
Text="{locale:Locale AboutChangelogButton}"
|
||||
TextAlignment="Center"
|
||||
ToolTip.Tip="{locale:Locale AboutChangelogButtonTooltipMessage}" />
|
||||
</Button>
|
||||
</StackPanel>
|
||||
<StackPanel
|
||||
Grid.Row="2"
|
||||
|
||||
@@ -23,6 +23,7 @@ using Ryujinx.Ui.Common.Helper;
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.IO;
|
||||
using System.Runtime.Versioning;
|
||||
using System.Threading.Tasks;
|
||||
using InputManager = Ryujinx.Input.HLE.InputManager;
|
||||
|
||||
@@ -258,7 +259,64 @@ namespace Ryujinx.Ava.UI.Windows
|
||||
ApplicationHelper.Initialize(VirtualFileSystem, AccountManager, LibHacHorizonManager.RyujinxClient, this);
|
||||
}
|
||||
|
||||
protected void CheckLaunchState()
|
||||
[SupportedOSPlatform("linux")]
|
||||
private static async void ShowVmMaxMapCountWarning()
|
||||
{
|
||||
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LinuxVmMaxMapCountWarningTextSecondary,
|
||||
LinuxHelper.VmMaxMapCount, LinuxHelper.RecommendedVmMaxMapCount);
|
||||
|
||||
await ContentDialogHelper.CreateWarningDialog(
|
||||
LocaleManager.Instance[LocaleKeys.LinuxVmMaxMapCountWarningTextPrimary],
|
||||
LocaleManager.Instance[LocaleKeys.LinuxVmMaxMapCountWarningTextSecondary]
|
||||
);
|
||||
}
|
||||
|
||||
[SupportedOSPlatform("linux")]
|
||||
private static async void ShowVmMaxMapCountDialog()
|
||||
{
|
||||
LocaleManager.Instance.UpdateAndGetDynamicValue(LocaleKeys.LinuxVmMaxMapCountDialogTextPrimary,
|
||||
LinuxHelper.RecommendedVmMaxMapCount);
|
||||
|
||||
UserResult response = await ContentDialogHelper.ShowTextDialog(
|
||||
$"Ryujinx - {LocaleManager.Instance[LocaleKeys.LinuxVmMaxMapCountDialogTitle]}",
|
||||
LocaleManager.Instance[LocaleKeys.LinuxVmMaxMapCountDialogTextPrimary],
|
||||
LocaleManager.Instance[LocaleKeys.LinuxVmMaxMapCountDialogTextSecondary],
|
||||
LocaleManager.Instance[LocaleKeys.LinuxVmMaxMapCountDialogButtonUntilRestart],
|
||||
LocaleManager.Instance[LocaleKeys.LinuxVmMaxMapCountDialogButtonPersistent],
|
||||
LocaleManager.Instance[LocaleKeys.InputDialogNo],
|
||||
(int)Symbol.Help
|
||||
);
|
||||
|
||||
int rc;
|
||||
|
||||
switch (response)
|
||||
{
|
||||
case UserResult.Ok:
|
||||
rc = LinuxHelper.RunPkExec($"echo {LinuxHelper.RecommendedVmMaxMapCount} > {LinuxHelper.VmMaxMapCountPath}");
|
||||
if (rc == 0)
|
||||
{
|
||||
Logger.Info?.Print(LogClass.Application, $"vm.max_map_count set to {LinuxHelper.VmMaxMapCount} until the next restart.");
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Application, $"Unable to change vm.max_map_count. Process exited with code: {rc}");
|
||||
}
|
||||
break;
|
||||
case UserResult.No:
|
||||
rc = LinuxHelper.RunPkExec($"echo \"vm.max_map_count = {LinuxHelper.RecommendedVmMaxMapCount}\" > {LinuxHelper.SysCtlConfigPath} && sysctl -p {LinuxHelper.SysCtlConfigPath}");
|
||||
if (rc == 0)
|
||||
{
|
||||
Logger.Info?.Print(LogClass.Application, $"vm.max_map_count set to {LinuxHelper.VmMaxMapCount}. Written to config: {LinuxHelper.SysCtlConfigPath}");
|
||||
}
|
||||
else
|
||||
{
|
||||
Logger.Error?.Print(LogClass.Application, $"Unable to write new value for vm.max_map_count to config. Process exited with code: {rc}");
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void CheckLaunchState()
|
||||
{
|
||||
if (ShowKeyErrorOnLoad)
|
||||
{
|
||||
@@ -268,6 +326,20 @@ namespace Ryujinx.Ava.UI.Windows
|
||||
UserErrorDialog.ShowUserErrorDialog(UserError.NoKeys, this));
|
||||
}
|
||||
|
||||
if (OperatingSystem.IsLinux() && LinuxHelper.VmMaxMapCount < LinuxHelper.RecommendedVmMaxMapCount)
|
||||
{
|
||||
Logger.Warning?.Print(LogClass.Application, $"The value of vm.max_map_count is lower than {LinuxHelper.RecommendedVmMaxMapCount}. ({LinuxHelper.VmMaxMapCount})");
|
||||
|
||||
if (LinuxHelper.PkExecPath is not null)
|
||||
{
|
||||
Dispatcher.UIThread.Post(ShowVmMaxMapCountDialog);
|
||||
}
|
||||
else
|
||||
{
|
||||
Dispatcher.UIThread.Post(ShowVmMaxMapCountWarning);
|
||||
}
|
||||
}
|
||||
|
||||
if (_deferLoad)
|
||||
{
|
||||
_deferLoad = false;
|
||||
@@ -447,14 +519,14 @@ namespace Ryujinx.Ava.UI.Windows
|
||||
private void ConfirmExit()
|
||||
{
|
||||
Dispatcher.UIThread.InvokeAsync(async () =>
|
||||
{
|
||||
ViewModel.IsClosing = await ContentDialogHelper.CreateExitDialog();
|
||||
{
|
||||
ViewModel.IsClosing = await ContentDialogHelper.CreateExitDialog();
|
||||
|
||||
if (ViewModel.IsClosing)
|
||||
{
|
||||
Close();
|
||||
}
|
||||
});
|
||||
if (ViewModel.IsClosing)
|
||||
{
|
||||
Close();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public async void LoadApplications()
|
||||
|
||||
@@ -22,9 +22,11 @@ namespace Ryujinx.Common
|
||||
_cts = new CancellationTokenSource();
|
||||
_queue = collection;
|
||||
_workerAction = callback;
|
||||
_workerThread = new Thread(DoWork) { Name = name };
|
||||
|
||||
_workerThread.IsBackground = true;
|
||||
_workerThread = new Thread(DoWork)
|
||||
{
|
||||
Name = name,
|
||||
IsBackground = true
|
||||
};
|
||||
_workerThread.Start();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System.Text;
|
||||
using System.Diagnostics;
|
||||
using System.Text;
|
||||
|
||||
namespace Ryujinx.Common.Logging
|
||||
namespace Ryujinx.Common.Logging.Formatters
|
||||
{
|
||||
internal class DefaultLogFormatter : ILogFormatter
|
||||
{
|
||||
@@ -27,6 +28,14 @@ namespace Ryujinx.Common.Logging
|
||||
|
||||
if (args.Data is not null)
|
||||
{
|
||||
if (args.Data is StackTrace trace)
|
||||
{
|
||||
sb.Append('\n');
|
||||
sb.Append(trace);
|
||||
|
||||
return sb.ToString();
|
||||
}
|
||||
|
||||
sb.Append(' ');
|
||||
DynamicObjectFormatter.Format(sb, args.Data);
|
||||
}
|
||||
@@ -39,4 +48,4 @@ namespace Ryujinx.Common.Logging
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,9 +3,9 @@ using System;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
|
||||
namespace Ryujinx.Common.Logging
|
||||
namespace Ryujinx.Common.Logging.Formatters
|
||||
{
|
||||
internal class DynamicObjectFormatter
|
||||
internal static class DynamicObjectFormatter
|
||||
{
|
||||
private static readonly ObjectPool<StringBuilder> StringBuilderPool = SharedPools.Default<StringBuilder>();
|
||||
|
||||
@@ -17,7 +17,7 @@ namespace Ryujinx.Common.Logging
|
||||
}
|
||||
|
||||
StringBuilder sb = StringBuilderPool.Allocate();
|
||||
|
||||
|
||||
try
|
||||
{
|
||||
Format(sb, dynamicObject);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
namespace Ryujinx.Common.Logging
|
||||
namespace Ryujinx.Common.Logging.Formatters
|
||||
{
|
||||
interface ILogFormatter
|
||||
{
|
||||
string Format(LogEventArgs args);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using Ryujinx.Common.Logging.Formatters;
|
||||
using System;
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace Ryujinx.Common.Logging
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using Ryujinx.Common.Logging.Targets;
|
||||
using Ryujinx.Common.SystemInterop;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -55,6 +56,16 @@ namespace Ryujinx.Common.Logging
|
||||
}
|
||||
}
|
||||
|
||||
[StackTraceHidden]
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void PrintStack(LogClass logClass, string message, [CallerMemberName] string caller = "")
|
||||
{
|
||||
if (m_EnabledClasses[(int)logClass])
|
||||
{
|
||||
Updated?.Invoke(null, new LogEventArgs(Level, m_Time.Elapsed, Thread.CurrentThread.Name, FormatMessage(logClass, caller, message), new StackTrace(true)));
|
||||
}
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void PrintStub(LogClass logClass, string message = "", [CallerMemberName] string caller = "")
|
||||
{
|
||||
@@ -122,7 +133,7 @@ namespace Ryujinx.Common.Logging
|
||||
AsyncLogTargetOverflowAction.Discard));
|
||||
|
||||
Notice = new Log(LogLevel.Notice);
|
||||
|
||||
|
||||
// Enable important log levels before configuration is loaded
|
||||
Error = new Log(LogLevel.Error);
|
||||
Warning = new Log(LogLevel.Warning);
|
||||
@@ -221,4 +232,4 @@ namespace Ryujinx.Common.Logging
|
||||
m_EnabledClasses[(int)logClass] = enabled;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.Common.Logging
|
||||
namespace Ryujinx.Common.Logging.Targets
|
||||
{
|
||||
public enum AsyncLogTargetOverflowAction
|
||||
{
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
using System;
|
||||
using Ryujinx.Common.Logging.Formatters;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Common.Logging
|
||||
namespace Ryujinx.Common.Logging.Targets
|
||||
{
|
||||
public class ConsoleLogTarget : ILogTarget
|
||||
{
|
||||
@@ -38,4 +39,4 @@ namespace Ryujinx.Common.Logging
|
||||
Console.ResetColor();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,9 @@
|
||||
using System;
|
||||
using Ryujinx.Common.Logging.Formatters;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
|
||||
namespace Ryujinx.Common.Logging
|
||||
namespace Ryujinx.Common.Logging.Targets
|
||||
{
|
||||
public class FileLogTarget : ILogTarget
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Common.Logging
|
||||
namespace Ryujinx.Common.Logging.Targets
|
||||
{
|
||||
public interface ILogTarget : IDisposable
|
||||
{
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
using Ryujinx.Common.Utilities;
|
||||
using System.IO;
|
||||
|
||||
namespace Ryujinx.Common.Logging
|
||||
namespace Ryujinx.Common.Logging.Targets
|
||||
{
|
||||
public class JsonLogTarget : ILogTarget
|
||||
{
|
||||
|
||||
@@ -39,6 +39,7 @@ namespace Ryujinx.Graphics.GAL
|
||||
public readonly bool SupportsViewportMask;
|
||||
public readonly bool SupportsViewportSwizzle;
|
||||
public readonly bool SupportsIndirectParameters;
|
||||
public readonly bool SupportsDepthClipControl;
|
||||
|
||||
public readonly uint MaximumUniformBuffersPerStage;
|
||||
public readonly uint MaximumStorageBuffersPerStage;
|
||||
@@ -85,6 +86,7 @@ namespace Ryujinx.Graphics.GAL
|
||||
bool supportsViewportMask,
|
||||
bool supportsViewportSwizzle,
|
||||
bool supportsIndirectParameters,
|
||||
bool supportsDepthClipControl,
|
||||
uint maximumUniformBuffersPerStage,
|
||||
uint maximumStorageBuffersPerStage,
|
||||
uint maximumTexturesPerStage,
|
||||
@@ -127,6 +129,7 @@ namespace Ryujinx.Graphics.GAL
|
||||
SupportsViewportMask = supportsViewportMask;
|
||||
SupportsViewportSwizzle = supportsViewportSwizzle;
|
||||
SupportsIndirectParameters = supportsIndirectParameters;
|
||||
SupportsDepthClipControl = supportsDepthClipControl;
|
||||
MaximumUniformBuffersPerStage = maximumUniformBuffersPerStage;
|
||||
MaximumStorageBuffersPerStage = maximumStorageBuffersPerStage;
|
||||
MaximumTexturesPerStage = maximumTexturesPerStage;
|
||||
|
||||
@@ -383,6 +383,7 @@ namespace Ryujinx.Graphics.GAL
|
||||
case Format.R10G10B10A2Unorm:
|
||||
case Format.R10G10B10A2Uint:
|
||||
case Format.R11G11B10Float:
|
||||
case Format.B8G8R8A8Unorm:
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -108,6 +108,7 @@ namespace Ryujinx.Graphics.GAL
|
||||
bool TryHostConditionalRendering(ICounterEvent value, ICounterEvent compare, bool isEqual);
|
||||
void EndHostConditionalRendering();
|
||||
|
||||
void UpdatePageTableGpuAddress(ulong address);
|
||||
void UpdateRenderScale(ReadOnlySpan<float> scales, int totalCount, int fragmentCount);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Ryujinx.Common.Configuration;
|
||||
using System;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.Graphics.GAL
|
||||
{
|
||||
@@ -35,6 +36,7 @@ namespace Ryujinx.Graphics.GAL
|
||||
void DeleteBuffer(BufferHandle buffer);
|
||||
|
||||
PinnedSpan<byte> GetBufferData(BufferHandle buffer, int offset, int size);
|
||||
ulong GetBufferGpuAddress(BufferHandle buffer);
|
||||
|
||||
Capabilities GetCapabilities();
|
||||
ulong GetCurrentSync();
|
||||
@@ -52,7 +54,7 @@ namespace Ryujinx.Graphics.GAL
|
||||
|
||||
void ResetCounter(CounterType type);
|
||||
|
||||
void RunLoop(Action gpuLoop)
|
||||
void RunLoop(ThreadStart gpuLoop)
|
||||
{
|
||||
gpuLoop();
|
||||
}
|
||||
|
||||
@@ -57,6 +57,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
||||
|
||||
Register<BufferDisposeCommand>(CommandType.BufferDispose);
|
||||
Register<BufferGetDataCommand>(CommandType.BufferGetData);
|
||||
Register<BufferGetGpuAddressCommand>(CommandType.BufferGetGpuAddress);
|
||||
Register<BufferSetDataCommand>(CommandType.BufferSetData);
|
||||
|
||||
Register<CounterEventDisposeCommand>(CommandType.CounterEventDispose);
|
||||
@@ -138,6 +139,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
||||
Register<TextureBarrierTiledCommand>(CommandType.TextureBarrierTiled);
|
||||
Register<TryHostConditionalRenderingCommand>(CommandType.TryHostConditionalRendering);
|
||||
Register<TryHostConditionalRenderingFlushCommand>(CommandType.TryHostConditionalRenderingFlush);
|
||||
Register<UpdatePageTableGpuAddressCommand>(CommandType.UpdatePageTableGpuAddress);
|
||||
Register<UpdateRenderScaleCommand>(CommandType.UpdateRenderScale);
|
||||
|
||||
return maxCommandSize;
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
|
||||
BufferDispose,
|
||||
BufferGetData,
|
||||
BufferGetGpuAddress,
|
||||
BufferSetData,
|
||||
|
||||
CounterEventDispose,
|
||||
@@ -100,6 +101,7 @@
|
||||
TextureBarrierTiled,
|
||||
TryHostConditionalRendering,
|
||||
TryHostConditionalRenderingFlush,
|
||||
UpdatePageTableGpuAddress,
|
||||
UpdateRenderScale
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
using Ryujinx.Graphics.GAL.Multithreading.Model;
|
||||
|
||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Buffer
|
||||
{
|
||||
struct BufferGetGpuAddressCommand : IGALCommand, IGALCommand<BufferGetGpuAddressCommand>
|
||||
{
|
||||
public CommandType CommandType => CommandType.BufferGetGpuAddress;
|
||||
private BufferHandle _buffer;
|
||||
private TableRef<ResultBox<ulong>> _result;
|
||||
|
||||
public void Set(BufferHandle buffer, TableRef<ResultBox<ulong>> result)
|
||||
{
|
||||
_buffer = buffer;
|
||||
_result = result;
|
||||
}
|
||||
|
||||
public static void Run(ref BufferGetGpuAddressCommand command, ThreadedRenderer threaded, IRenderer renderer)
|
||||
{
|
||||
ulong result = renderer.GetBufferGpuAddress(threaded.Buffers.MapBuffer(command._buffer));
|
||||
|
||||
command._result.Get(threaded).Result = result;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
namespace Ryujinx.Graphics.GAL.Multithreading.Commands
|
||||
{
|
||||
struct UpdatePageTableGpuAddressCommand : IGALCommand, IGALCommand<UpdatePageTableGpuAddressCommand>
|
||||
{
|
||||
public CommandType CommandType => CommandType.UpdatePageTableGpuAddress;
|
||||
private ulong _address;
|
||||
|
||||
public void Set(ulong address)
|
||||
{
|
||||
_address = address;
|
||||
}
|
||||
|
||||
public static void Run(ref UpdatePageTableGpuAddressCommand command, ThreadedRenderer threaded, IRenderer renderer)
|
||||
{
|
||||
renderer.Pipeline.UpdatePageTableGpuAddress(command._address);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -371,6 +371,12 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
||||
return false;
|
||||
}
|
||||
|
||||
public void UpdatePageTableGpuAddress(ulong address)
|
||||
{
|
||||
_renderer.New<UpdatePageTableGpuAddressCommand>().Set(address);
|
||||
_renderer.QueueCommand();
|
||||
}
|
||||
|
||||
public void UpdateRenderScale(ReadOnlySpan<float> scales, int totalCount, int fragmentCount)
|
||||
{
|
||||
_renderer.New<UpdateRenderScaleCommand>().Set(_renderer.CopySpan(scales.Slice(0, totalCount)), totalCount, fragmentCount);
|
||||
|
||||
@@ -30,7 +30,6 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
||||
private IRenderer _baseRenderer;
|
||||
private Thread _gpuThread;
|
||||
private Thread _backendThread;
|
||||
private bool _disposed;
|
||||
private bool _running;
|
||||
|
||||
private AutoResetEvent _frameComplete = new AutoResetEvent(true);
|
||||
@@ -98,19 +97,17 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
||||
_refQueue = new object[MaxRefsPerCommand * QueueCount];
|
||||
}
|
||||
|
||||
public void RunLoop(Action gpuLoop)
|
||||
public void RunLoop(ThreadStart gpuLoop)
|
||||
{
|
||||
_running = true;
|
||||
|
||||
_backendThread = Thread.CurrentThread;
|
||||
|
||||
_gpuThread = new Thread(() => {
|
||||
gpuLoop();
|
||||
_running = false;
|
||||
_galWorkAvailable.Set();
|
||||
});
|
||||
_gpuThread = new Thread(gpuLoop)
|
||||
{
|
||||
Name = "GPU.MainThread"
|
||||
};
|
||||
|
||||
_gpuThread.Name = "GPU.MainThread";
|
||||
_gpuThread.Start();
|
||||
|
||||
RenderLoop();
|
||||
@@ -120,7 +117,7 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
||||
{
|
||||
// Power through the render queue until the Gpu thread work is done.
|
||||
|
||||
while (_running && !_disposed)
|
||||
while (_running)
|
||||
{
|
||||
_galWorkAvailable.Wait();
|
||||
_galWorkAvailable.Reset();
|
||||
@@ -364,6 +361,22 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
||||
}
|
||||
}
|
||||
|
||||
public ulong GetBufferGpuAddress(BufferHandle buffer)
|
||||
{
|
||||
if (IsGpuThread())
|
||||
{
|
||||
ResultBox<ulong> box = new ResultBox<ulong>();
|
||||
New<BufferGetGpuAddressCommand>().Set(buffer, Ref(box));
|
||||
InvokeCommand();
|
||||
|
||||
return box.Result;
|
||||
}
|
||||
else
|
||||
{
|
||||
return _baseRenderer.GetBufferGpuAddress(Buffers.MapBufferBlocking(buffer));
|
||||
}
|
||||
}
|
||||
|
||||
public Capabilities GetCapabilities()
|
||||
{
|
||||
ResultBox<Capabilities> box = new ResultBox<Capabilities>();
|
||||
@@ -488,12 +501,23 @@ namespace Ryujinx.Graphics.GAL.Multithreading
|
||||
return _baseRenderer.PrepareHostMapping(address, size);
|
||||
}
|
||||
|
||||
public void FlushThreadedCommands()
|
||||
{
|
||||
SpinWait wait = new();
|
||||
|
||||
while (Volatile.Read(ref _commandCount) > 0)
|
||||
{
|
||||
wait.SpinOnce();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
// Dispose must happen from the render thread, after all commands have completed.
|
||||
|
||||
// Stop the GPU thread.
|
||||
_disposed = true;
|
||||
_running = false;
|
||||
_galWorkAvailable.Set();
|
||||
|
||||
if (_gpuThread != null && _gpuThread.IsAlive)
|
||||
{
|
||||
|
||||
@@ -63,6 +63,8 @@ namespace Ryujinx.Graphics.GAL
|
||||
public bool PrimitiveRestartEnable;
|
||||
public uint PatchControlPoints;
|
||||
|
||||
public DepthMode DepthMode;
|
||||
|
||||
public void SetVertexAttribs(ReadOnlySpan<VertexAttribDescriptor> vertexAttribs)
|
||||
{
|
||||
VertexAttribCount = vertexAttribs.Length;
|
||||
|
||||
@@ -0,0 +1,179 @@
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace Ryujinx.Graphics.GAL
|
||||
{
|
||||
public enum ResourceType : byte
|
||||
{
|
||||
UniformBuffer,
|
||||
StorageBuffer,
|
||||
Texture,
|
||||
Sampler,
|
||||
TextureAndSampler,
|
||||
Image,
|
||||
BufferTexture,
|
||||
BufferImage
|
||||
}
|
||||
|
||||
public enum ResourceAccess : byte
|
||||
{
|
||||
None = 0,
|
||||
Read = 1,
|
||||
Write = 2,
|
||||
ReadWrite = Read | Write
|
||||
}
|
||||
|
||||
[Flags]
|
||||
public enum ResourceStages : byte
|
||||
{
|
||||
None = 0,
|
||||
Compute = 1 << 0,
|
||||
Vertex = 1 << 1,
|
||||
TessellationControl = 1 << 2,
|
||||
TessellationEvaluation = 1 << 3,
|
||||
Geometry = 1 << 4,
|
||||
Fragment = 1 << 5
|
||||
}
|
||||
|
||||
public readonly struct ResourceDescriptor : IEquatable<ResourceDescriptor>
|
||||
{
|
||||
public int Binding { get; }
|
||||
public int Count { get; }
|
||||
public ResourceType Type { get; }
|
||||
public ResourceStages Stages { get; }
|
||||
|
||||
public ResourceDescriptor(int binding, int count, ResourceType type, ResourceStages stages)
|
||||
{
|
||||
Binding = binding;
|
||||
Count = count;
|
||||
Type = type;
|
||||
Stages = stages;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(Binding, Count, Type, Stages);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is ResourceDescriptor other && Equals(other);
|
||||
}
|
||||
|
||||
public bool Equals(ResourceDescriptor other)
|
||||
{
|
||||
return Binding == other.Binding && Count == other.Count && Type == other.Type && Stages == other.Stages;
|
||||
}
|
||||
}
|
||||
|
||||
public readonly struct ResourceUsage : IEquatable<ResourceUsage>
|
||||
{
|
||||
public int Binding { get; }
|
||||
public ResourceType Type { get; }
|
||||
public ResourceStages Stages { get; }
|
||||
public ResourceAccess Access { get; }
|
||||
|
||||
public ResourceUsage(int binding, ResourceType type, ResourceStages stages, ResourceAccess access)
|
||||
{
|
||||
Binding = binding;
|
||||
Type = type;
|
||||
Stages = stages;
|
||||
Access = access;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
return HashCode.Combine(Binding, Type, Stages, Access);
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is ResourceUsage other && Equals(other);
|
||||
}
|
||||
|
||||
public bool Equals(ResourceUsage other)
|
||||
{
|
||||
return Binding == other.Binding && Type == other.Type && Stages == other.Stages && Access == other.Access;
|
||||
}
|
||||
}
|
||||
|
||||
public readonly struct ResourceDescriptorCollection
|
||||
{
|
||||
public ReadOnlyCollection<ResourceDescriptor> Descriptors { get; }
|
||||
|
||||
public ResourceDescriptorCollection(ReadOnlyCollection<ResourceDescriptor> descriptors)
|
||||
{
|
||||
Descriptors = descriptors;
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
HashCode hasher = new HashCode();
|
||||
|
||||
if (Descriptors != null)
|
||||
{
|
||||
foreach (var descriptor in Descriptors)
|
||||
{
|
||||
hasher.Add(descriptor);
|
||||
}
|
||||
}
|
||||
|
||||
return hasher.ToHashCode();
|
||||
}
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
return obj is ResourceDescriptorCollection other && Equals(other);
|
||||
}
|
||||
|
||||
public bool Equals(ResourceDescriptorCollection other)
|
||||
{
|
||||
if ((Descriptors == null) != (other.Descriptors == null))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Descriptors != null)
|
||||
{
|
||||
if (Descriptors.Count != other.Descriptors.Count)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
for (int index = 0; index < Descriptors.Count; index++)
|
||||
{
|
||||
if (!Descriptors[index].Equals(other.Descriptors[index]))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public readonly struct ResourceUsageCollection
|
||||
{
|
||||
public ReadOnlyCollection<ResourceUsage> Usages { get; }
|
||||
|
||||
public ResourceUsageCollection(ReadOnlyCollection<ResourceUsage> usages)
|
||||
{
|
||||
Usages = usages;
|
||||
}
|
||||
}
|
||||
|
||||
public readonly struct ResourceLayout
|
||||
{
|
||||
public ReadOnlyCollection<ResourceDescriptorCollection> Sets { get; }
|
||||
public ReadOnlyCollection<ResourceUsageCollection> SetUsages { get; }
|
||||
|
||||
public ResourceLayout(
|
||||
ReadOnlyCollection<ResourceDescriptorCollection> sets,
|
||||
ReadOnlyCollection<ResourceUsageCollection> setUsages)
|
||||
{
|
||||
Sets = sets;
|
||||
SetUsages = setUsages;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.Graphics.GAL
|
||||
{
|
||||
public readonly struct ShaderBindings
|
||||
{
|
||||
public IReadOnlyCollection<int> UniformBufferBindings { get; }
|
||||
public IReadOnlyCollection<int> StorageBufferBindings { get; }
|
||||
public IReadOnlyCollection<int> TextureBindings { get; }
|
||||
public IReadOnlyCollection<int> ImageBindings { get; }
|
||||
|
||||
public ShaderBindings(
|
||||
IReadOnlyCollection<int> uniformBufferBindings,
|
||||
IReadOnlyCollection<int> storageBufferBindings,
|
||||
IReadOnlyCollection<int> textureBindings,
|
||||
IReadOnlyCollection<int> imageBindings)
|
||||
{
|
||||
UniformBufferBindings = uniformBufferBindings;
|
||||
StorageBufferBindings = storageBufferBindings;
|
||||
TextureBindings = textureBindings;
|
||||
ImageBindings = imageBindings;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,19 +3,22 @@ namespace Ryujinx.Graphics.GAL
|
||||
public struct ShaderInfo
|
||||
{
|
||||
public int FragmentOutputMap { get; }
|
||||
public ResourceLayout ResourceLayout { get; }
|
||||
public ProgramPipelineState? State { get; }
|
||||
public bool FromCache { get; set; }
|
||||
|
||||
public ShaderInfo(int fragmentOutputMap, ProgramPipelineState state, bool fromCache = false)
|
||||
public ShaderInfo(int fragmentOutputMap, ResourceLayout resourceLayout, ProgramPipelineState state, bool fromCache = false)
|
||||
{
|
||||
FragmentOutputMap = fragmentOutputMap;
|
||||
ResourceLayout = resourceLayout;
|
||||
State = state;
|
||||
FromCache = fromCache;
|
||||
}
|
||||
|
||||
public ShaderInfo(int fragmentOutputMap, bool fromCache = false)
|
||||
public ShaderInfo(int fragmentOutputMap, ResourceLayout resourceLayout, bool fromCache = false)
|
||||
{
|
||||
FragmentOutputMap = fragmentOutputMap;
|
||||
ResourceLayout = resourceLayout;
|
||||
State = null;
|
||||
FromCache = fromCache;
|
||||
}
|
||||
|
||||
@@ -7,24 +7,22 @@ namespace Ryujinx.Graphics.GAL
|
||||
{
|
||||
public string Code { get; }
|
||||
public byte[] BinaryCode { get; }
|
||||
public ShaderBindings Bindings { get; }
|
||||
public ShaderStage Stage { get; }
|
||||
public TargetLanguage Language { get; }
|
||||
|
||||
public ShaderSource(string code, byte[] binaryCode, ShaderBindings bindings, ShaderStage stage, TargetLanguage language)
|
||||
public ShaderSource(string code, byte[] binaryCode, ShaderStage stage, TargetLanguage language)
|
||||
{
|
||||
Code = code;
|
||||
BinaryCode = binaryCode;
|
||||
Bindings = bindings;
|
||||
Stage = stage;
|
||||
Language = language;
|
||||
}
|
||||
|
||||
public ShaderSource(string code, ShaderBindings bindings, ShaderStage stage, TargetLanguage language) : this(code, null, bindings, stage, language)
|
||||
public ShaderSource(string code, ShaderStage stage, TargetLanguage language) : this(code, null, stage, language)
|
||||
{
|
||||
}
|
||||
|
||||
public ShaderSource(byte[] binaryCode, ShaderBindings bindings, ShaderStage stage, TargetLanguage language) : this(null, binaryCode, bindings, stage, language)
|
||||
public ShaderSource(byte[] binaryCode, ShaderStage stage, TargetLanguage language) : this(null, binaryCode, stage, language)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,6 +44,20 @@ namespace Ryujinx.Graphics.GAL
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdatePageTableBasePointer(ulong address)
|
||||
{
|
||||
uint addrLow = (uint)address;
|
||||
uint addrHigh = (uint)(address >> 32);
|
||||
|
||||
if (Data.PageTableBasePointer.X != addrLow || Data.PageTableBasePointer.Y != addrHigh)
|
||||
{
|
||||
Data.PageTableBasePointer.X = addrLow;
|
||||
Data.PageTableBasePointer.Y = addrHigh;
|
||||
|
||||
MarkDirty(SupportBuffer.PageTableBasePointerOffset, sizeof(ulong));
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateFragmentRenderScaleCount(int count)
|
||||
{
|
||||
if (Data.FragmentRenderScaleCount.X != count)
|
||||
|
||||
@@ -212,14 +212,22 @@ namespace Ryujinx.Graphics.Gpu.Engine.Compute
|
||||
}
|
||||
|
||||
_channel.BufferManager.SetComputeBufferBindings(cs.Bindings);
|
||||
|
||||
_channel.TextureManager.SetComputeBindings(cs.Bindings);
|
||||
|
||||
if (info.UsesGlobalMemory)
|
||||
{
|
||||
_channel.BufferManager.SynchronizeComputeStorageBuffers(info.UsesGlobalMemoryWrite);
|
||||
}
|
||||
|
||||
// Should never return false for mismatching spec state, since the shader was fetched above.
|
||||
_channel.TextureManager.CommitComputeBindings(cs.SpecializationState);
|
||||
|
||||
_channel.BufferManager.CommitComputeBindings();
|
||||
|
||||
if (info.UsesGlobalMemory)
|
||||
{
|
||||
_channel.BufferManager.UpdatePageTable();
|
||||
}
|
||||
|
||||
_context.Renderer.Pipeline.DispatchCompute(qmd.CtaRasterWidth, qmd.CtaRasterHeight, qmd.CtaRasterDepth);
|
||||
|
||||
_3dEngine.ForceShaderUpdate();
|
||||
|
||||
@@ -37,6 +37,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
|
||||
private ProgramPipelineState _pipeline;
|
||||
|
||||
private uint _globalMemoryUseMask;
|
||||
private uint _globalMemoryWriteMask;
|
||||
private bool _vsUsesDrawParameters;
|
||||
private bool _vtgWritesRtLayer;
|
||||
private byte _vsClipDistancesWritten;
|
||||
@@ -320,9 +322,15 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
/// </summary>
|
||||
private void CommitBindings()
|
||||
{
|
||||
UpdateStorageBuffers();
|
||||
|
||||
bool unalignedChanged = _currentSpecState.SetHasUnalignedStorageBuffer(_channel.BufferManager.HasUnalignedStorageBuffers);
|
||||
bool usesGlobalMemory = _globalMemoryUseMask != 0;
|
||||
|
||||
UpdateStorageBuffers();
|
||||
|
||||
if (usesGlobalMemory)
|
||||
{
|
||||
_channel.BufferManager.SynchronizeGraphicsStorageBuffers(_globalMemoryUseMask, _globalMemoryWriteMask);
|
||||
}
|
||||
|
||||
if (!_channel.TextureManager.CommitGraphicsBindings(_shaderSpecState) || unalignedChanged)
|
||||
{
|
||||
@@ -331,6 +339,11 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
}
|
||||
|
||||
_channel.BufferManager.CommitGraphicsBindings();
|
||||
|
||||
if (usesGlobalMemory)
|
||||
{
|
||||
_channel.BufferManager.UpdatePageTable();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -771,7 +784,11 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
/// </summary>
|
||||
private void UpdateDepthMode()
|
||||
{
|
||||
_context.Renderer.Pipeline.SetDepthMode(GetDepthMode());
|
||||
DepthMode mode = GetDepthMode();
|
||||
|
||||
_pipeline.DepthMode = mode;
|
||||
|
||||
_context.Renderer.Pipeline.SetDepthMode(mode);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -1370,16 +1387,32 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
|
||||
|
||||
UpdateShaderBindings(gs.Bindings);
|
||||
|
||||
_globalMemoryUseMask = 0;
|
||||
_globalMemoryWriteMask = 0;
|
||||
|
||||
for (int stageIndex = 0; stageIndex < Constants.ShaderStages; stageIndex++)
|
||||
{
|
||||
ShaderProgramInfo info = gs.Shaders[stageIndex + 1]?.Info;
|
||||
|
||||
if (info?.UsesRtLayer == true)
|
||||
{
|
||||
_vtgWritesRtLayer = true;
|
||||
}
|
||||
|
||||
_currentProgramInfo[stageIndex] = info;
|
||||
|
||||
if (info != null)
|
||||
{
|
||||
if (info.UsesGlobalMemory)
|
||||
{
|
||||
_globalMemoryUseMask |= 1u << stageIndex;
|
||||
}
|
||||
|
||||
if (info.UsesGlobalMemoryWrite)
|
||||
{
|
||||
_globalMemoryWriteMask |= 1u << stageIndex;
|
||||
}
|
||||
|
||||
if (info?.UsesRtLayer == true)
|
||||
{
|
||||
_vtgWritesRtLayer = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_context.Renderer.Pipeline.SetProgram(gs.HostProgram);
|
||||
|
||||
@@ -390,7 +390,6 @@ namespace Ryujinx.Graphics.Gpu
|
||||
/// </summary>
|
||||
public void Dispose()
|
||||
{
|
||||
Renderer.Dispose();
|
||||
GPFifo.Dispose();
|
||||
HostInitalized.Dispose();
|
||||
|
||||
@@ -403,6 +402,8 @@ namespace Ryujinx.Graphics.Gpu
|
||||
PhysicalMemoryRegistry.Clear();
|
||||
|
||||
RunDeferredActions();
|
||||
|
||||
Renderer.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -66,6 +66,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
private bool _syncActionRegistered;
|
||||
|
||||
private int _referenceCount = 1;
|
||||
private ulong _hostGpuAddress;
|
||||
|
||||
private ulong _dirtyStart = ulong.MaxValue;
|
||||
private ulong _dirtyEnd = ulong.MaxValue;
|
||||
@@ -170,6 +171,16 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
return new BufferRange(Handle, offset, (int)size);
|
||||
}
|
||||
|
||||
public ulong GetHostGpuAddress(ulong address)
|
||||
{
|
||||
if (_hostGpuAddress == 0)
|
||||
{
|
||||
_hostGpuAddress = _context.Renderer.GetBufferGpuAddress(Handle);
|
||||
}
|
||||
|
||||
return _hostGpuAddress + (address - Address);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a given range overlaps with the buffer.
|
||||
/// </summary>
|
||||
|
||||
@@ -27,7 +27,6 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// Must lock for any access from other threads.
|
||||
/// </remarks>
|
||||
private readonly RangeList<Buffer> _buffers;
|
||||
|
||||
private Buffer[] _bufferOverlaps;
|
||||
|
||||
private readonly Dictionary<ulong, BufferCacheEntry> _dirtyCache;
|
||||
@@ -395,6 +394,11 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
return GetBuffer(address, size, write).GetRange(address, size);
|
||||
}
|
||||
|
||||
public ulong GetBufferHostGpuAddress(ulong address, ulong size, bool write = false)
|
||||
{
|
||||
return GetBuffer(address, size, write).GetHostGpuAddress(address);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets a buffer for a given memory range.
|
||||
/// A buffer overlapping with the specified range is assumed to already exist on the cache.
|
||||
@@ -431,13 +435,19 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// </summary>
|
||||
/// <param name="address">Start address of the memory range</param>
|
||||
/// <param name="size">Size in bytes of the memory range</param>
|
||||
public void SynchronizeBufferRange(ulong address, ulong size)
|
||||
/// <param name="write">Whether the buffer will be written to by this use</param>
|
||||
public void SynchronizeBufferRange(ulong address, ulong size, bool write = false)
|
||||
{
|
||||
if (size != 0)
|
||||
{
|
||||
Buffer buffer = _buffers.FindFirstOverlap(address, size);
|
||||
|
||||
buffer.SynchronizeMemory(address, size);
|
||||
|
||||
if (write)
|
||||
{
|
||||
buffer.SignalModified(address, size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using Ryujinx.Common;
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Gpu.Engine.Types;
|
||||
using Ryujinx.Graphics.Gpu.Image;
|
||||
using Ryujinx.Graphics.Gpu.Shader;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
@@ -107,6 +108,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
private bool _transformFeedbackBuffersDirty;
|
||||
|
||||
private bool _rebind;
|
||||
private bool _rebindPageTable;
|
||||
|
||||
private BufferPageTable _bufferPageTable;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new instance of the buffer manager.
|
||||
@@ -137,6 +141,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
_bufferTextures = new List<BufferTextureBinding>();
|
||||
|
||||
_ranges = new BufferAssignment[Constants.TotalGpUniformBuffers * Constants.ShaderStages];
|
||||
|
||||
_bufferPageTable = new BufferPageTable(context);
|
||||
}
|
||||
|
||||
|
||||
@@ -438,7 +444,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
CommitBufferTextureBindings();
|
||||
|
||||
// Force rebind after doing compute work.
|
||||
Rebind();
|
||||
Rebind(rebindPageTable: false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -747,8 +753,90 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
/// Force all bound textures and images to be rebound the next time CommitBindings is called.
|
||||
/// </summary>
|
||||
public void Rebind()
|
||||
{
|
||||
Rebind(rebindPageTable: true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Force all bound textures and images to be rebound the next time CommitBindings is called.
|
||||
/// </summary>
|
||||
/// <param name="rebindPageTable">Indicates that the page table needs to also be rebound</param>
|
||||
public void Rebind(bool rebindPageTable)
|
||||
{
|
||||
_rebind = true;
|
||||
|
||||
if (rebindPageTable)
|
||||
{
|
||||
_rebindPageTable = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void SynchronizeComputeStorageBuffers(bool write)
|
||||
{
|
||||
MemoryManager memoryManager = _channel.MemoryManager;
|
||||
|
||||
var bufferCache = memoryManager.Physical.BufferCache;
|
||||
|
||||
for (int index = 0; index < 16; index++)
|
||||
{
|
||||
ulong sbDescAddress = GetComputeUniformBufferAddress(0);
|
||||
|
||||
int sbDescOffset = 0x310 + index * 0x10;
|
||||
|
||||
sbDescAddress += (ulong)sbDescOffset;
|
||||
|
||||
SbDescriptor sbDescriptor = _channel.MemoryManager.Physical.Read<SbDescriptor>(sbDescAddress);
|
||||
|
||||
ulong address = bufferCache.TranslateAndCreateBuffer(memoryManager, sbDescriptor.PackAddress(), (ulong)sbDescriptor.Size);
|
||||
|
||||
if (address != 0)
|
||||
{
|
||||
bufferCache.SynchronizeBufferRange(address, (ulong)sbDescriptor.Size, write);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void SynchronizeGraphicsStorageBuffers(uint globalMemoryUseMask, uint globalMemoryWriteMask)
|
||||
{
|
||||
MemoryManager memoryManager = _channel.MemoryManager;
|
||||
|
||||
var bufferCache = memoryManager.Physical.BufferCache;
|
||||
|
||||
for (int stage = 0; stage < Constants.ShaderStages; stage++)
|
||||
{
|
||||
if ((globalMemoryUseMask & (1u << stage)) == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
bool write = (globalMemoryWriteMask & (1u << stage)) != 0;
|
||||
|
||||
for (int index = 0; index < 16; index++)
|
||||
{
|
||||
ulong sbDescAddress = GetGraphicsUniformBufferAddress(stage, 0);
|
||||
|
||||
int sbDescOffset = 0x110 + stage * 0x100 + index * 0x10;
|
||||
|
||||
sbDescAddress += (ulong)sbDescOffset;
|
||||
|
||||
SbDescriptor sbDescriptor = memoryManager.Physical.Read<SbDescriptor>(sbDescAddress);
|
||||
|
||||
ulong address = bufferCache.TranslateAndCreateBuffer(memoryManager, sbDescriptor.PackAddress(), (ulong)sbDescriptor.Size);
|
||||
|
||||
if (address != 0)
|
||||
{
|
||||
bufferCache.SynchronizeBufferRange(address, (ulong)sbDescriptor.Size, write);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdatePageTable()
|
||||
{
|
||||
MemoryManager memoryManager = _channel.MemoryManager;
|
||||
|
||||
_bufferPageTable.Update(memoryManager, _rebindPageTable);
|
||||
_rebindPageTable = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,201 @@
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using System.Collections.Generic;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Memory
|
||||
{
|
||||
class BufferPageTable
|
||||
{
|
||||
private const int PageBits = MemoryManager.PtPageBits;
|
||||
private const ulong PageSize = MemoryManager.PageSize;
|
||||
private const ulong PageMask = MemoryManager.PageMask;
|
||||
|
||||
private const int AsBits = 40;
|
||||
private const ulong AsSize = 1UL << AsBits;
|
||||
private const int AsPtBits = AsBits - PageBits;
|
||||
private const int AsPtLevels = 2;
|
||||
private const int AsPtLevelBits = AsPtBits / AsPtLevels;
|
||||
|
||||
private const int PtLevel0Shift = PageBits;
|
||||
private const int PtLevel1Shift = PtLevel0Shift + AsPtLevelBits;
|
||||
private const ulong PtLevelMask = (1UL << AsPtLevelBits) - 1;
|
||||
|
||||
private readonly GpuContext _context;
|
||||
|
||||
private struct BufferMapping
|
||||
{
|
||||
public readonly ulong CpuAddress;
|
||||
public readonly ulong GpuAddress;
|
||||
public readonly ulong Size;
|
||||
|
||||
public BufferMapping(ulong cpuAddress, ulong gpuAddress, ulong size)
|
||||
{
|
||||
CpuAddress = cpuAddress;
|
||||
GpuAddress = gpuAddress;
|
||||
Size = size;
|
||||
}
|
||||
}
|
||||
|
||||
private BufferMapping[] _mappings;
|
||||
private BufferHandle _bufferMap;
|
||||
private ulong _bufferMapHostGpuAddress;
|
||||
private int _bufferMapSize;
|
||||
|
||||
private readonly Dictionary<int, int> _blockIdMap;
|
||||
private readonly ulong[] _blockBitmap;
|
||||
|
||||
private readonly int[] _idMap;
|
||||
private bool _idMapDataDirty;
|
||||
|
||||
public BufferPageTable(GpuContext context)
|
||||
{
|
||||
_context = context;
|
||||
|
||||
_blockIdMap = new Dictionary<int, int>();
|
||||
_blockBitmap = new ulong[((1 << AsPtLevelBits) + 63) / 64];
|
||||
|
||||
_idMap = new int[1 << AsPtLevelBits];
|
||||
}
|
||||
|
||||
public void Update(MemoryManager memoryManager, bool forceUpdate)
|
||||
{
|
||||
BufferCache bufferCache = memoryManager.Physical.BufferCache;
|
||||
|
||||
if (memoryManager.MappingsModified || forceUpdate)
|
||||
{
|
||||
Mapping[] mappings = memoryManager.GetMappings();
|
||||
|
||||
BufferMapping[] bufferMappings = new BufferMapping[mappings.Length];
|
||||
|
||||
for (int i = 0; i < mappings.Length; i++)
|
||||
{
|
||||
Mapping mapping = mappings[i];
|
||||
ulong cpuAddress = bufferCache.TranslateAndCreateBuffer(memoryManager, mapping.Address, mapping.Size);
|
||||
|
||||
bufferMappings[i] = new BufferMapping(cpuAddress, mapping.Address, mapping.Size);
|
||||
}
|
||||
|
||||
_mappings = bufferMappings;
|
||||
|
||||
for (int i = 0; i < bufferMappings.Length; i++)
|
||||
{
|
||||
BufferMapping mapping = bufferMappings[i];
|
||||
|
||||
ulong hostAddress = 0;
|
||||
|
||||
if (mapping.CpuAddress != 0)
|
||||
{
|
||||
hostAddress = bufferCache.GetBufferHostGpuAddress(mapping.CpuAddress, mapping.Size);
|
||||
}
|
||||
|
||||
Map(hostAddress, mapping.GpuAddress, mapping.Size);
|
||||
}
|
||||
|
||||
if (_idMapDataDirty)
|
||||
{
|
||||
BufferHandle bufferMap = EnsureBufferMap(_idMap.Length * sizeof(int));
|
||||
_context.Renderer.SetBufferData(bufferMap, 0, MemoryMarshal.Cast<int, byte>(_idMap));
|
||||
|
||||
_idMapDataDirty = false;
|
||||
}
|
||||
|
||||
_context.Renderer.Pipeline.UpdatePageTableGpuAddress(_bufferMapHostGpuAddress);
|
||||
}
|
||||
}
|
||||
|
||||
private void Map(ulong hostAddress, ulong guestAddress, ulong size)
|
||||
{
|
||||
ulong endGuestAddress = guestAddress + size;
|
||||
ulong blockSize = PageSize << AsPtLevelBits;
|
||||
|
||||
while (guestAddress < endGuestAddress)
|
||||
{
|
||||
ulong nextGuestAddress = (guestAddress + blockSize) & ~(blockSize - 1);
|
||||
|
||||
ulong chunckSize = Math.Min(nextGuestAddress - guestAddress, endGuestAddress - guestAddress);
|
||||
|
||||
int pages = (int)(chunckSize / PageSize);
|
||||
|
||||
int blockRegionOffset = sizeof(uint) << AsPtLevelBits;
|
||||
int blockOffset = GetBlockId(guestAddress) * (sizeof(ulong) << AsPtLevelBits);
|
||||
int blockInnerOffset = (int)((guestAddress >> PtLevel0Shift) & PtLevelMask) * sizeof(ulong);
|
||||
int baseOffset = blockRegionOffset + blockOffset + blockInnerOffset;
|
||||
|
||||
ulong[] data = new ulong[pages];
|
||||
|
||||
for (int page = 0; page < pages; page++)
|
||||
{
|
||||
data[page] = hostAddress;
|
||||
|
||||
if (hostAddress != 0)
|
||||
{
|
||||
hostAddress += PageSize;
|
||||
}
|
||||
}
|
||||
|
||||
BufferHandle bufferMap = EnsureBufferMap(blockRegionOffset + blockOffset + (sizeof(ulong) << AsPtLevelBits));
|
||||
_context.Renderer.SetBufferData(bufferMap, baseOffset, MemoryMarshal.Cast<ulong, byte>(data));
|
||||
|
||||
guestAddress += chunckSize;
|
||||
}
|
||||
}
|
||||
|
||||
private BufferHandle EnsureBufferMap(int requiredSize)
|
||||
{
|
||||
if (requiredSize > _bufferMapSize)
|
||||
{
|
||||
BufferHandle newBuffer = _context.Renderer.CreateBuffer(requiredSize);
|
||||
|
||||
if (_bufferMap != BufferHandle.Null)
|
||||
{
|
||||
_context.Renderer.Pipeline.CopyBuffer(_bufferMap, newBuffer, 0, 0, _bufferMapSize);
|
||||
_context.Renderer.DeleteBuffer(_bufferMap);
|
||||
}
|
||||
|
||||
_bufferMap = newBuffer;
|
||||
_bufferMapHostGpuAddress = _context.Renderer.GetBufferGpuAddress(_bufferMap);
|
||||
_bufferMapSize = requiredSize;
|
||||
}
|
||||
|
||||
return _bufferMap;
|
||||
}
|
||||
|
||||
private int GetBlockId(ulong address)
|
||||
{
|
||||
int blockIndex = (int)((address >> PtLevel1Shift) & PtLevelMask);
|
||||
|
||||
if (!_blockIdMap.TryGetValue(blockIndex, out int mappedIndex))
|
||||
{
|
||||
mappedIndex = AllocateNewBlock(_blockBitmap);
|
||||
|
||||
_idMap[blockIndex] = mappedIndex << AsPtLevelBits;
|
||||
_idMapDataDirty = true;
|
||||
|
||||
_blockIdMap.Add(blockIndex, mappedIndex);
|
||||
}
|
||||
|
||||
return mappedIndex;
|
||||
}
|
||||
|
||||
private static int AllocateNewBlock(ulong[] bitmap)
|
||||
{
|
||||
for (int index = 0; index < bitmap.Length; index++)
|
||||
{
|
||||
ref ulong v = ref bitmap[index];
|
||||
|
||||
if (v == ulong.MaxValue)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
int firstFreeBit = BitOperations.TrailingZeroCount(~v);
|
||||
v |= 1UL << firstFreeBit;
|
||||
return index * 64 + firstFreeBit;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException("No free space left on the texture or sampler table.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
using Ryujinx.Memory.Range;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Memory
|
||||
{
|
||||
struct Mapping : IRange
|
||||
{
|
||||
public ulong Address { get; }
|
||||
public ulong Size { get; }
|
||||
public ulong EndAddress => Address + Size;
|
||||
|
||||
public bool OverlapsWith(ulong address, ulong size)
|
||||
{
|
||||
return Address < address + size && address < EndAddress;
|
||||
}
|
||||
|
||||
public Mapping(ulong address, ulong size)
|
||||
{
|
||||
Address = address;
|
||||
Size = size;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -32,6 +32,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
|
||||
private readonly ulong[][] _pageTable;
|
||||
|
||||
private readonly RangeList<Mapping> _mappings;
|
||||
internal bool MappingsModified { get; private set; }
|
||||
|
||||
public event EventHandler<UnmapEventArgs> MemoryUnmapped;
|
||||
|
||||
/// <summary>
|
||||
@@ -53,6 +56,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
Physical = physicalMemory;
|
||||
CounterCache = new CounterCache();
|
||||
_pageTable = new ulong[PtLvl0Size][];
|
||||
_mappings = new RangeList<Mapping>();
|
||||
MemoryUnmapped += Physical.TextureCache.MemoryUnmappedHandler;
|
||||
MemoryUnmapped += Physical.BufferCache.MemoryUnmappedHandler;
|
||||
MemoryUnmapped += CounterCache.MemoryUnmappedHandler;
|
||||
@@ -395,6 +399,11 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
{
|
||||
lock (_pageTable)
|
||||
{
|
||||
if (kind == PteKind.Pitch)
|
||||
{
|
||||
AddMapping(va, size);
|
||||
}
|
||||
|
||||
UnmapEventArgs e = new(va, size);
|
||||
MemoryUnmapped?.Invoke(this, e);
|
||||
|
||||
@@ -416,6 +425,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
{
|
||||
lock (_pageTable)
|
||||
{
|
||||
RemoveMapping(va, size);
|
||||
|
||||
// Event handlers are not expected to be thread safe.
|
||||
UnmapEventArgs e = new(va, size);
|
||||
MemoryUnmapped?.Invoke(this, e);
|
||||
@@ -758,5 +769,79 @@ namespace Ryujinx.Graphics.Gpu.Memory
|
||||
{
|
||||
return pte & 0xffffffffffffffUL;
|
||||
}
|
||||
|
||||
private void AddMapping(ulong va, ulong size)
|
||||
{
|
||||
lock (_mappings)
|
||||
{
|
||||
ulong startAddress = va;
|
||||
ulong endAddress = va + size;
|
||||
|
||||
Mapping[] overlaps = Array.Empty<Mapping>();
|
||||
|
||||
int overlapsCount = _mappings.FindOverlapsNonOverlapping(va, size, ref overlaps);
|
||||
for (int i = 0; i < overlapsCount; i++)
|
||||
{
|
||||
Mapping overlap = overlaps[i];
|
||||
|
||||
if (overlap.Address < startAddress)
|
||||
{
|
||||
startAddress = overlap.Address;
|
||||
}
|
||||
|
||||
if (overlap.EndAddress > endAddress)
|
||||
{
|
||||
endAddress = overlap.EndAddress;
|
||||
}
|
||||
|
||||
_mappings.Remove(overlap);
|
||||
}
|
||||
|
||||
_mappings.Add(new Mapping(startAddress, endAddress - startAddress));
|
||||
MappingsModified = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveMapping(ulong va, ulong size)
|
||||
{
|
||||
lock (_mappings)
|
||||
{
|
||||
ulong endAddress = va + size;
|
||||
|
||||
Mapping[] overlaps = Array.Empty<Mapping>();
|
||||
|
||||
int overlapsCount = _mappings.FindOverlapsNonOverlapping(va, size, ref overlaps);
|
||||
for (int i = 0; i < overlapsCount; i++)
|
||||
{
|
||||
Mapping overlap = overlaps[i];
|
||||
|
||||
_mappings.Remove(overlap);
|
||||
|
||||
if (overlap.Address < va)
|
||||
{
|
||||
_mappings.Add(new Mapping(overlap.Address, va - overlap.Address));
|
||||
}
|
||||
|
||||
if (overlap.EndAddress > endAddress)
|
||||
{
|
||||
_mappings.Add(new Mapping(endAddress, overlap.EndAddress - endAddress));
|
||||
}
|
||||
}
|
||||
|
||||
if (overlapsCount != 0)
|
||||
{
|
||||
MappingsModified = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal Mapping[] GetMappings()
|
||||
{
|
||||
lock (_mappings)
|
||||
{
|
||||
MappingsModified = false;
|
||||
return _mappings.ToArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,7 +22,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
private const ushort FileFormatVersionMajor = 1;
|
||||
private const ushort FileFormatVersionMinor = 2;
|
||||
private const uint FileFormatVersionPacked = ((uint)FileFormatVersionMajor << 16) | FileFormatVersionMinor;
|
||||
private const uint CodeGenVersion = 5027;
|
||||
private const uint CodeGenVersion = 4735;
|
||||
|
||||
private const string SharedTocFileName = "shared.toc";
|
||||
private const string SharedDataFileName = "shared.data";
|
||||
@@ -140,6 +140,16 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
/// </summary>
|
||||
public ShaderStage Stage;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if the shader uses instructions that access global memory, such as LDG, STG and ATOM.
|
||||
/// </summary>
|
||||
public bool UsesGlobalMemory;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if the shader uses instructions that modify global memory, such as STG and ATOM.
|
||||
/// </summary>
|
||||
public bool UsesGlobalMemoryWrite;
|
||||
|
||||
/// <summary>
|
||||
/// Indicates if the shader accesses the Instance ID built-in variable.
|
||||
/// </summary>
|
||||
@@ -368,12 +378,7 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
|
||||
if (hostCode != null)
|
||||
{
|
||||
bool hasFragmentShader = shaders.Length > 5 && shaders[5] != null;
|
||||
int fragmentOutputMap = hasFragmentShader ? shaders[5].Info.FragmentOutputMap : -1;
|
||||
|
||||
ShaderInfo shaderInfo = specState.PipelineState.HasValue
|
||||
? new ShaderInfo(fragmentOutputMap, specState.PipelineState.Value, fromCache: true)
|
||||
: new ShaderInfo(fragmentOutputMap, fromCache: true);
|
||||
ShaderInfo shaderInfo = ShaderInfoBuilder.BuildForCache(context, shaders, specState.PipelineState);
|
||||
|
||||
IProgram hostProgram;
|
||||
|
||||
@@ -385,6 +390,8 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
}
|
||||
else
|
||||
{
|
||||
bool hasFragmentShader = shaders.Length > 5 && shaders[5] != null;
|
||||
|
||||
hostProgram = context.Renderer.LoadProgramBinary(hostCode, hasFragmentShader, shaderInfo);
|
||||
}
|
||||
|
||||
@@ -777,6 +784,8 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
ShaderIdentification.None,
|
||||
0,
|
||||
dataInfo.Stage,
|
||||
dataInfo.UsesGlobalMemory,
|
||||
dataInfo.UsesGlobalMemoryWrite,
|
||||
dataInfo.UsesInstanceId,
|
||||
dataInfo.UsesDrawParameters,
|
||||
dataInfo.UsesRtLayer,
|
||||
@@ -803,6 +812,8 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
dataInfo.TexturesCount = (ushort)info.Textures.Count;
|
||||
dataInfo.ImagesCount = (ushort)info.Images.Count;
|
||||
dataInfo.Stage = info.Stage;
|
||||
dataInfo.UsesGlobalMemory = info.UsesGlobalMemory;
|
||||
dataInfo.UsesGlobalMemoryWrite = info.UsesGlobalMemoryWrite;
|
||||
dataInfo.UsesInstanceId = info.UsesInstanceId;
|
||||
dataInfo.UsesDrawParameters = info.UsesDrawParameters;
|
||||
dataInfo.UsesRtLayer = info.UsesRtLayer;
|
||||
|
||||
@@ -491,23 +491,16 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
{
|
||||
ShaderSource[] shaderSources = new ShaderSource[compilation.TranslatedStages.Length];
|
||||
|
||||
int fragmentOutputMap = -1;
|
||||
ShaderInfoBuilder shaderInfoBuilder = new ShaderInfoBuilder(_context);
|
||||
|
||||
for (int index = 0; index < compilation.TranslatedStages.Length; index++)
|
||||
{
|
||||
ShaderProgram shader = compilation.TranslatedStages[index];
|
||||
shaderSources[index] = CreateShaderSource(shader);
|
||||
|
||||
if (shader.Info.Stage == ShaderStage.Fragment)
|
||||
{
|
||||
fragmentOutputMap = shader.Info.FragmentOutputMap;
|
||||
}
|
||||
shaderInfoBuilder.AddStageInfo(shader.Info);
|
||||
}
|
||||
|
||||
ShaderInfo shaderInfo = compilation.SpecializationState.PipelineState.HasValue
|
||||
? new ShaderInfo(fragmentOutputMap, compilation.SpecializationState.PipelineState.Value, fromCache: true)
|
||||
: new ShaderInfo(fragmentOutputMap, fromCache: true);
|
||||
|
||||
ShaderInfo shaderInfo = shaderInfoBuilder.Build(compilation.SpecializationState.PipelineState, fromCache: true);
|
||||
IProgram hostProgram = _context.Renderer.CreateProgram(shaderSources, shaderInfo);
|
||||
CachedShaderProgram program = new CachedShaderProgram(hostProgram, compilation.SpecializationState, compilation.Shaders);
|
||||
|
||||
|
||||
@@ -42,25 +42,10 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
int binaryCodeLength = reader.ReadInt32();
|
||||
byte[] binaryCode = reader.ReadBytes(binaryCodeLength);
|
||||
|
||||
output.Add(new ShaderSource(binaryCode, GetBindings(stages, stage), stage, TargetLanguage.Spirv));
|
||||
output.Add(new ShaderSource(binaryCode, stage, TargetLanguage.Spirv));
|
||||
}
|
||||
|
||||
return output.ToArray();
|
||||
}
|
||||
|
||||
private static ShaderBindings GetBindings(CachedShaderStage[] stages, ShaderStage stage)
|
||||
{
|
||||
for (int i = 0; i < stages.Length; i++)
|
||||
{
|
||||
CachedShaderStage currentStage = stages[i];
|
||||
|
||||
if (currentStage?.Info != null && currentStage.Info.Stage == stage)
|
||||
{
|
||||
return ShaderCache.GetBindings(currentStage.Info);
|
||||
}
|
||||
}
|
||||
|
||||
return new ShaderBindings(Array.Empty<int>(), Array.Empty<int>(), Array.Empty<int>(), Array.Empty<int>());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ using Ryujinx.Graphics.Gpu.Engine.Threed;
|
||||
using Ryujinx.Graphics.Gpu.Image;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
using Ryujinx.Graphics.Shader.Translation;
|
||||
using System;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Shader
|
||||
{
|
||||
@@ -93,16 +94,16 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
Logger.Error?.Print(LogClass.Gpu, $"{resourceName} index {index} exceeds per stage limit of {maxPerStage}.");
|
||||
}
|
||||
|
||||
return GetStageIndex() * (int)maxPerStage + index;
|
||||
return GetStageIndex(_stageIndex) * (int)maxPerStage + index;
|
||||
}
|
||||
|
||||
private int GetStageIndex()
|
||||
public static int GetStageIndex(int stageIndex)
|
||||
{
|
||||
// This is just a simple remapping to ensure that most frequently used shader stages
|
||||
// have the lowest binding numbers.
|
||||
// This is useful because if we need to run on a system with a low limit on the bindings,
|
||||
// then we can still get most games working as the most common shaders will have low binding numbers.
|
||||
return _stageIndex switch
|
||||
return stageIndex switch
|
||||
{
|
||||
4 => 1, // Fragment
|
||||
3 => 2, // Geometry
|
||||
@@ -148,6 +149,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
|
||||
public bool QueryHostSupportsViewportMask() => _context.Capabilities.SupportsViewportMask;
|
||||
|
||||
public bool QueryHostSupportsDepthClipControl() => _context.Capabilities.SupportsDepthClipControl;
|
||||
|
||||
/// <summary>
|
||||
/// Converts a packed Maxwell texture format to the shader translator texture format.
|
||||
/// </summary>
|
||||
|
||||
@@ -31,6 +31,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
public ResourceCounts()
|
||||
{
|
||||
UniformBuffersCount = 1; // The first binding is reserved for the support buffer.
|
||||
StorageBuffersCount = 1; // The first binding is reserved for the buffer mappings table for GPU address translation.
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -219,12 +219,11 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
GpuAccessor gpuAccessor = new GpuAccessor(_context, channel, gpuAccessorState);
|
||||
|
||||
TranslatorContext translatorContext = DecodeComputeShader(gpuAccessor, _context.Capabilities.Api, gpuVa);
|
||||
|
||||
TranslatedShader translatedShader = TranslateShader(_dumper, channel, translatorContext, cachedGuestCode);
|
||||
|
||||
ShaderSource[] shaderSourcesArray = new ShaderSource[] { CreateShaderSource(translatedShader.Program) };
|
||||
|
||||
IProgram hostProgram = _context.Renderer.CreateProgram(shaderSourcesArray, new ShaderInfo(-1));
|
||||
ShaderInfo info = ShaderInfoBuilder.BuildForCompute(_context, translatedShader.Program.Info);
|
||||
IProgram hostProgram = _context.Renderer.CreateProgram(shaderSourcesArray, info);
|
||||
|
||||
cpShader = new CachedShaderProgram(hostProgram, specState, translatedShader.Shader);
|
||||
|
||||
@@ -363,6 +362,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
|
||||
TranslatorContext previousStage = null;
|
||||
|
||||
ShaderInfoBuilder infoBuilder = new ShaderInfoBuilder(_context);
|
||||
|
||||
for (int stageIndex = 0; stageIndex < Constants.ShaderStages; stageIndex++)
|
||||
{
|
||||
TranslatorContext currentStage = translatorContexts[stageIndex + 1];
|
||||
@@ -398,6 +399,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
if (program != null)
|
||||
{
|
||||
shaderSources.Add(CreateShaderSource(program));
|
||||
infoBuilder.AddStageInfo(program.Info);
|
||||
}
|
||||
|
||||
previousStage = currentStage;
|
||||
@@ -414,8 +416,9 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
|
||||
ShaderSource[] shaderSourcesArray = shaderSources.ToArray();
|
||||
|
||||
int fragmentOutputMap = shaders[5]?.Info.FragmentOutputMap ?? -1;
|
||||
IProgram hostProgram = _context.Renderer.CreateProgram(shaderSourcesArray, new ShaderInfo(fragmentOutputMap, pipeline));
|
||||
ShaderInfo info = infoBuilder.Build(pipeline);
|
||||
|
||||
IProgram hostProgram = _context.Renderer.CreateProgram(shaderSourcesArray, info);
|
||||
|
||||
gpShaders = new CachedShaderProgram(hostProgram, specState, shaders);
|
||||
|
||||
@@ -466,7 +469,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
/// <returns>Shader source</returns>
|
||||
public static ShaderSource CreateShaderSource(ShaderProgram program)
|
||||
{
|
||||
return new ShaderSource(program.Code, program.BinaryCode, GetBindings(program.Info), program.Info.Stage, program.Language);
|
||||
return new ShaderSource(program.Code, program.BinaryCode, program.Info.Stage, program.Language);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -717,25 +720,6 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets information about the bindings used by a shader program.
|
||||
/// </summary>
|
||||
/// <param name="info">Shader program information to get the information from</param>
|
||||
/// <returns>Shader bindings</returns>
|
||||
public static ShaderBindings GetBindings(ShaderProgramInfo info)
|
||||
{
|
||||
var uniformBufferBindings = info.CBuffers.Select(x => x.Binding).ToArray();
|
||||
var storageBufferBindings = info.SBuffers.Select(x => x.Binding).ToArray();
|
||||
var textureBindings = info.Textures.Select(x => x.Binding).ToArray();
|
||||
var imageBindings = info.Images.Select(x => x.Binding).ToArray();
|
||||
|
||||
return new ShaderBindings(
|
||||
uniformBufferBindings,
|
||||
storageBufferBindings,
|
||||
textureBindings,
|
||||
imageBindings);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates shader translation options with the requested graphics API and flags.
|
||||
/// The shader language is choosen based on the current configuration and graphics API.
|
||||
|
||||
@@ -0,0 +1,260 @@
|
||||
using Ryujinx.Graphics.GAL;
|
||||
using Ryujinx.Graphics.Shader;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.Graphics.Gpu.Shader
|
||||
{
|
||||
/// <summary>
|
||||
/// Shader info structure builder.
|
||||
/// </summary>
|
||||
class ShaderInfoBuilder
|
||||
{
|
||||
private const int TotalSets = 4;
|
||||
|
||||
private const int UniformSetIndex = 0;
|
||||
private const int StorageSetIndex = 1;
|
||||
private const int TextureSetIndex = 2;
|
||||
private const int ImageSetIndex = 3;
|
||||
|
||||
private const ResourceStages SupportBufferStags =
|
||||
ResourceStages.Compute |
|
||||
ResourceStages.Vertex |
|
||||
ResourceStages.Fragment;
|
||||
|
||||
private readonly GpuContext _context;
|
||||
|
||||
private int _fragmentOutputMap;
|
||||
|
||||
private readonly List<ResourceDescriptor>[] _resourceDescriptors;
|
||||
private readonly List<ResourceUsage>[] _resourceUsages;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new shader info builder.
|
||||
/// </summary>
|
||||
/// <param name="context">GPU context that owns the shaders that will be added to the builder</param>
|
||||
public ShaderInfoBuilder(GpuContext context)
|
||||
{
|
||||
_context = context;
|
||||
|
||||
_fragmentOutputMap = -1;
|
||||
|
||||
_resourceDescriptors = new List<ResourceDescriptor>[TotalSets];
|
||||
_resourceUsages = new List<ResourceUsage>[TotalSets];
|
||||
|
||||
for (int index = 0; index < TotalSets; index++)
|
||||
{
|
||||
_resourceDescriptors[index] = new();
|
||||
_resourceUsages[index] = new();
|
||||
}
|
||||
|
||||
AddDescriptor(SupportBufferStags, ResourceType.UniformBuffer, UniformSetIndex, 0, 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds information from a given shader stage.
|
||||
/// </summary>
|
||||
/// <param name="info">Shader stage information</param>
|
||||
public void AddStageInfo(ShaderProgramInfo info)
|
||||
{
|
||||
if (info.Stage == ShaderStage.Fragment)
|
||||
{
|
||||
_fragmentOutputMap = info.FragmentOutputMap;
|
||||
}
|
||||
|
||||
int stageIndex = GpuAccessorBase.GetStageIndex(info.Stage switch
|
||||
{
|
||||
ShaderStage.TessellationControl => 1,
|
||||
ShaderStage.TessellationEvaluation => 2,
|
||||
ShaderStage.Geometry => 3,
|
||||
ShaderStage.Fragment => 4,
|
||||
_ => 0
|
||||
});
|
||||
|
||||
ResourceStages stages = info.Stage switch
|
||||
{
|
||||
ShaderStage.Compute => ResourceStages.Compute,
|
||||
ShaderStage.Vertex => ResourceStages.Vertex,
|
||||
ShaderStage.TessellationControl => ResourceStages.TessellationControl,
|
||||
ShaderStage.TessellationEvaluation => ResourceStages.TessellationEvaluation,
|
||||
ShaderStage.Geometry => ResourceStages.Geometry,
|
||||
ShaderStage.Fragment => ResourceStages.Fragment,
|
||||
_ => ResourceStages.None
|
||||
};
|
||||
|
||||
int uniformsPerStage = (int)_context.Capabilities.MaximumUniformBuffersPerStage;
|
||||
int storagesPerStage = (int)_context.Capabilities.MaximumStorageBuffersPerStage;
|
||||
int texturesPerStage = (int)_context.Capabilities.MaximumTexturesPerStage;
|
||||
int imagesPerStage = (int)_context.Capabilities.MaximumImagesPerStage;
|
||||
|
||||
int uniformBinding = 1 + stageIndex * uniformsPerStage;
|
||||
int storageBinding = stageIndex * storagesPerStage;
|
||||
int textureBinding = stageIndex * texturesPerStage * 2;
|
||||
int imageBinding = stageIndex * imagesPerStage * 2;
|
||||
|
||||
AddDescriptor(stages, ResourceType.UniformBuffer, UniformSetIndex, uniformBinding, uniformsPerStage);
|
||||
AddArrayDescriptor(stages, ResourceType.StorageBuffer, StorageSetIndex, storageBinding, storagesPerStage);
|
||||
AddDualDescriptor(stages, ResourceType.TextureAndSampler, ResourceType.BufferTexture, TextureSetIndex, textureBinding, texturesPerStage);
|
||||
AddDualDescriptor(stages, ResourceType.Image, ResourceType.BufferImage, ImageSetIndex, imageBinding, imagesPerStage);
|
||||
|
||||
AddUsage(info.CBuffers, stages, UniformSetIndex, isStorage: false);
|
||||
AddUsage(info.SBuffers, stages, StorageSetIndex, isStorage: true);
|
||||
AddUsage(info.Textures, stages, TextureSetIndex, isImage: false);
|
||||
AddUsage(info.Images, stages, ImageSetIndex, isImage: true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds a resource descriptor 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 AddDescriptor(ResourceStages stages, ResourceType type, int setIndex, int binding, int count)
|
||||
{
|
||||
for (int index = 0; index < count; index++)
|
||||
{
|
||||
_resourceDescriptors[setIndex].Add(new ResourceDescriptor(binding + index, 1, type, stages));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds two interleaved groups of resources to the list of descriptors.
|
||||
/// </summary>
|
||||
/// <param name="stages">Shader stages where the resource is used</param>
|
||||
/// <param name="type">Type of the first interleaved resource</param>
|
||||
/// <param name="type2">Type of the second interleaved 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 AddDualDescriptor(ResourceStages stages, ResourceType type, ResourceType type2, int setIndex, int binding, int count)
|
||||
{
|
||||
AddDescriptor(stages, type, setIndex, binding, 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>
|
||||
/// Adds buffer usage information to the list of usages.
|
||||
/// </summary>
|
||||
/// <param name="buffers">Buffers to be added</param>
|
||||
/// <param name="stages">Stages where the buffers are used</param>
|
||||
/// <param name="setIndex">Descriptor set index where the buffers will be bound</param>
|
||||
/// <param name="isStorage">True for storage buffers, false for uniform buffers</param>
|
||||
private void AddUsage(IEnumerable<BufferDescriptor> buffers, ResourceStages stages, int setIndex, bool isStorage)
|
||||
{
|
||||
foreach (BufferDescriptor buffer in buffers)
|
||||
{
|
||||
_resourceUsages[setIndex].Add(new ResourceUsage(
|
||||
buffer.Binding,
|
||||
isStorage ? ResourceType.StorageBuffer : ResourceType.UniformBuffer,
|
||||
stages,
|
||||
buffer.Flags.HasFlag(BufferUsageFlags.Write) ? ResourceAccess.ReadWrite : ResourceAccess.Read));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Adds texture usage information to the list of usages.
|
||||
/// </summary>
|
||||
/// <param name="textures">Textures to be added</param>
|
||||
/// <param name="stages">Stages where the textures are used</param>
|
||||
/// <param name="setIndex">Descriptor set index where the textures will be bound</param>
|
||||
/// <param name="isImage">True for images, false for textures</param>
|
||||
private void AddUsage(IEnumerable<TextureDescriptor> textures, ResourceStages stages, int setIndex, bool isImage)
|
||||
{
|
||||
foreach (TextureDescriptor texture in textures)
|
||||
{
|
||||
bool isBuffer = (texture.Type & SamplerType.Mask) == SamplerType.TextureBuffer;
|
||||
|
||||
ResourceType type = isBuffer
|
||||
? (isImage ? ResourceType.BufferImage : ResourceType.BufferTexture)
|
||||
: (isImage ? ResourceType.Image : ResourceType.TextureAndSampler);
|
||||
|
||||
_resourceUsages[setIndex].Add(new ResourceUsage(
|
||||
texture.Binding,
|
||||
type,
|
||||
stages,
|
||||
texture.Flags.HasFlag(TextureUsageFlags.ImageStore) ? ResourceAccess.ReadWrite : ResourceAccess.Read));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new shader information structure from the added information.
|
||||
/// </summary>
|
||||
/// <param name="pipeline">Optional pipeline state for background shader compilation</param>
|
||||
/// <param name="fromCache">Indicates if the shader comes from a disk cache</param>
|
||||
/// <returns>Shader information</returns>
|
||||
public ShaderInfo Build(ProgramPipelineState? pipeline, bool fromCache = false)
|
||||
{
|
||||
var descriptors = new ResourceDescriptorCollection[TotalSets];
|
||||
var usages = new ResourceUsageCollection[TotalSets];
|
||||
|
||||
for (int index = 0; index < TotalSets; index++)
|
||||
{
|
||||
descriptors[index] = new ResourceDescriptorCollection(_resourceDescriptors[index].ToArray().AsReadOnly());
|
||||
usages[index] = new ResourceUsageCollection(_resourceUsages[index].ToArray().AsReadOnly());
|
||||
}
|
||||
|
||||
ResourceLayout resourceLayout = new ResourceLayout(descriptors.AsReadOnly(), usages.AsReadOnly());
|
||||
|
||||
if (pipeline.HasValue)
|
||||
{
|
||||
return new ShaderInfo(_fragmentOutputMap, resourceLayout, pipeline.Value, fromCache);
|
||||
}
|
||||
else
|
||||
{
|
||||
return new ShaderInfo(_fragmentOutputMap, resourceLayout, fromCache);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds shader information for shaders from the disk cache.
|
||||
/// </summary>
|
||||
/// <param name="context">GPU context that owns the shaders</param>
|
||||
/// <param name="programs">Shaders from the disk cache</param>
|
||||
/// <param name="pipeline">Optional pipeline for background compilation</param>
|
||||
/// <returns>Shader information</returns>
|
||||
public static ShaderInfo BuildForCache(GpuContext context, IEnumerable<CachedShaderStage> programs, ProgramPipelineState? pipeline)
|
||||
{
|
||||
ShaderInfoBuilder builder = new ShaderInfoBuilder(context);
|
||||
|
||||
foreach (CachedShaderStage program in programs)
|
||||
{
|
||||
if (program?.Info != null)
|
||||
{
|
||||
builder.AddStageInfo(program.Info);
|
||||
}
|
||||
}
|
||||
|
||||
return builder.Build(pipeline, fromCache: true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Builds shader information for a compute shader.
|
||||
/// </summary>
|
||||
/// <param name="context">GPU context that owns the shader</param>
|
||||
/// <param name="info">Compute shader information</param>
|
||||
/// <param name="fromCache">True if the compute shader comes from a disk cache, false otherwise</param>
|
||||
/// <returns>Shader information</returns>
|
||||
public static ShaderInfo BuildForCompute(GpuContext context, ShaderProgramInfo info, bool fromCache = false)
|
||||
{
|
||||
ShaderInfoBuilder builder = new ShaderInfoBuilder(context);
|
||||
|
||||
builder.AddStageInfo(info);
|
||||
|
||||
return builder.Build(null, fromCache);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -736,6 +736,19 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
return MatchesTexture(specializationState, descriptor);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Populates pipeline state that doesn't exist in older caches with default values
|
||||
/// based on specialization state.
|
||||
/// </summary>
|
||||
/// <param name="pipelineState">Pipeline state to prepare</param>
|
||||
private void PreparePipelineState(ref ProgramPipelineState pipelineState)
|
||||
{
|
||||
if (!_compute)
|
||||
{
|
||||
pipelineState.DepthMode = GraphicsState.DepthMode ? DepthMode.MinusOneToOne : DepthMode.ZeroToOne;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads shader specialization state that has been serialized.
|
||||
/// </summary>
|
||||
@@ -776,6 +789,8 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
{
|
||||
ProgramPipelineState pipelineState = default;
|
||||
dataReader.ReadWithMagicAndSize(ref pipelineState, PgpsMagic);
|
||||
|
||||
specState.PreparePipelineState(ref pipelineState);
|
||||
specState.PipelineState = pipelineState;
|
||||
}
|
||||
|
||||
|
||||
@@ -94,6 +94,14 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
}
|
||||
}
|
||||
|
||||
public static ulong GetGpuAddress(BufferHandle handle)
|
||||
{
|
||||
GL.BindBuffer(BufferTarget.CopyWriteBuffer, handle.ToInt32());
|
||||
GL.NV.MakeBufferResident((NvShaderBufferLoad)BufferTarget.CopyWriteBuffer, (NvShaderBufferLoad)All.ReadWrite);
|
||||
GL.NV.GetBufferParameter(BufferTargetArb.CopyWriteBuffer, NvShaderBufferLoad.BufferGpuAddressNv, out ulong gpuAddress);
|
||||
return gpuAddress;
|
||||
}
|
||||
|
||||
public static void Resize(BufferHandle handle, int size)
|
||||
{
|
||||
GL.BindBuffer(BufferTarget.CopyWriteBuffer, handle.ToInt32());
|
||||
|
||||
@@ -124,6 +124,11 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
return Buffer.GetData(this, buffer, offset, size);
|
||||
}
|
||||
|
||||
public ulong GetBufferGpuAddress(BufferHandle buffer)
|
||||
{
|
||||
return Buffer.GetGpuAddress(buffer);
|
||||
}
|
||||
|
||||
public Capabilities GetCapabilities()
|
||||
{
|
||||
bool intelWindows = HwCapabilities.Vendor == HwCapabilities.GpuVendor.IntelWindows;
|
||||
@@ -163,6 +168,7 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
supportsViewportMask: HwCapabilities.SupportsViewportArray2,
|
||||
supportsViewportSwizzle: HwCapabilities.SupportsViewportSwizzle,
|
||||
supportsIndirectParameters: HwCapabilities.SupportsIndirectParameters,
|
||||
supportsDepthClipControl: true,
|
||||
maximumUniformBuffersPerStage: 13, // TODO: Avoid hardcoding those limits here and get from driver?
|
||||
maximumStorageBuffersPerStage: 16,
|
||||
maximumTexturesPerStage: 32,
|
||||
|
||||
@@ -1548,6 +1548,11 @@ namespace Ryujinx.Graphics.OpenGL
|
||||
return (_boundDrawFramebuffer, _boundReadFramebuffer);
|
||||
}
|
||||
|
||||
public void UpdatePageTableGpuAddress(ulong address)
|
||||
{
|
||||
_supportBuffer.UpdatePageTableBasePointer(address);
|
||||
}
|
||||
|
||||
public void UpdateRenderScale(ReadOnlySpan<float> scales, int totalCount, int fragmentCount)
|
||||
{
|
||||
bool changed = false;
|
||||
|
||||
@@ -3,6 +3,7 @@ using Ryujinx.Graphics.Shader.StructuredIr;
|
||||
using Ryujinx.Graphics.Shader.Translation;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
|
||||
@@ -59,6 +60,22 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
||||
context.AppendLine("#extension GL_NV_geometry_shader_passthrough : enable");
|
||||
}
|
||||
|
||||
if ((info.HelperFunctionsMask & HelperFunctionsMask.GlobalMemory) != 0)
|
||||
{
|
||||
context.AppendLine("#extension GL_EXT_shader_16bit_storage : enable");
|
||||
context.AppendLine("#extension GL_EXT_shader_8bit_storage : enable");
|
||||
|
||||
if (context.Config.Options.TargetApi == TargetApi.Vulkan)
|
||||
{
|
||||
context.AppendLine("#extension GL_EXT_buffer_reference : enable");
|
||||
context.AppendLine("#extension GL_EXT_buffer_reference_uvec2 : enable");
|
||||
}
|
||||
else
|
||||
{
|
||||
context.AppendLine("#extension GL_NV_shader_buffer_load : enable");
|
||||
}
|
||||
}
|
||||
|
||||
if (context.Config.GpuAccessor.QueryHostSupportsViewportMask())
|
||||
{
|
||||
context.AppendLine("#extension GL_NV_viewport_array2 : enable");
|
||||
@@ -102,13 +119,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
||||
context.AppendLine();
|
||||
}
|
||||
|
||||
var cBufferDescriptors = context.Config.GetConstantBufferDescriptors();
|
||||
if (cBufferDescriptors.Length != 0)
|
||||
{
|
||||
DeclareUniforms(context, cBufferDescriptors);
|
||||
|
||||
context.AppendLine();
|
||||
}
|
||||
DeclareConstantBuffers(context, context.Config.Properties.ConstantBuffers.Values);
|
||||
|
||||
var sBufferDescriptors = context.Config.GetStorageBufferDescriptors();
|
||||
if (sBufferDescriptors.Length != 0)
|
||||
@@ -244,39 +255,10 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
||||
context.AppendLine();
|
||||
}
|
||||
|
||||
bool isFragment = context.Config.Stage == ShaderStage.Fragment;
|
||||
|
||||
if (isFragment || context.Config.Stage == ShaderStage.Compute || context.Config.Stage == ShaderStage.Vertex)
|
||||
if (context.Config.Stage == ShaderStage.Fragment && context.Config.GpuAccessor.QueryEarlyZForce())
|
||||
{
|
||||
if (isFragment && context.Config.GpuAccessor.QueryEarlyZForce())
|
||||
{
|
||||
context.AppendLine("layout(early_fragment_tests) in;");
|
||||
context.AppendLine();
|
||||
}
|
||||
|
||||
if ((context.Config.UsedFeatures & (FeatureFlags.FragCoordXY | FeatureFlags.IntegerSampling)) != 0)
|
||||
{
|
||||
string stage = OperandManager.GetShaderStagePrefix(context.Config.Stage);
|
||||
|
||||
int scaleElements = context.Config.GetTextureDescriptors().Length + context.Config.GetImageDescriptors().Length;
|
||||
|
||||
if (isFragment)
|
||||
{
|
||||
scaleElements++; // Also includes render target scale, for gl_FragCoord.
|
||||
}
|
||||
|
||||
DeclareSupportUniformBlock(context, context.Config.Stage, scaleElements);
|
||||
|
||||
if (context.Config.UsedFeatures.HasFlag(FeatureFlags.IntegerSampling) && scaleElements != 0)
|
||||
{
|
||||
AppendHelperFunction(context, $"Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/TexelFetchScale_{stage}.glsl");
|
||||
context.AppendLine();
|
||||
}
|
||||
}
|
||||
else if (isFragment || context.Config.Stage == ShaderStage.Vertex)
|
||||
{
|
||||
DeclareSupportUniformBlock(context, context.Config.Stage, 0);
|
||||
}
|
||||
context.AppendLine("layout(early_fragment_tests) in;");
|
||||
context.AppendLine();
|
||||
}
|
||||
|
||||
if ((info.HelperFunctionsMask & HelperFunctionsMask.AtomicMinMaxS32Shared) != 0)
|
||||
@@ -289,6 +271,13 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
||||
AppendHelperFunction(context, "Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/AtomicMinMaxS32Storage.glsl");
|
||||
}
|
||||
|
||||
if ((info.HelperFunctionsMask & HelperFunctionsMask.GlobalMemory) != 0)
|
||||
{
|
||||
AppendHelperFunction(context, context.Config.Options.TargetApi == TargetApi.Vulkan
|
||||
? "Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/GlobalMemoryVk.glsl"
|
||||
: "Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/GlobalMemory.glsl");
|
||||
}
|
||||
|
||||
if ((info.HelperFunctionsMask & HelperFunctionsMask.MultiplyHighS32) != 0)
|
||||
{
|
||||
AppendHelperFunction(context, "Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/MultiplyHighS32.glsl");
|
||||
@@ -389,36 +378,38 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
||||
};
|
||||
}
|
||||
|
||||
private static void DeclareUniforms(CodeGenContext context, BufferDescriptor[] descriptors)
|
||||
private static void DeclareConstantBuffers(CodeGenContext context, IEnumerable<BufferDefinition> buffers)
|
||||
{
|
||||
string ubSize = "[" + NumberFormatter.FormatInt(Constants.ConstantBufferSize / 16) + "]";
|
||||
|
||||
if (context.Config.UsedFeatures.HasFlag(FeatureFlags.CbIndexing))
|
||||
foreach (BufferDefinition buffer in buffers)
|
||||
{
|
||||
string ubName = OperandManager.GetShaderStagePrefix(context.Config.Stage);
|
||||
|
||||
ubName += "_" + DefaultNames.UniformNamePrefix;
|
||||
|
||||
string blockName = $"{ubName}_{DefaultNames.BlockSuffix}";
|
||||
|
||||
context.AppendLine($"layout (binding = {context.Config.FirstConstantBufferBinding}, std140) uniform {blockName}");
|
||||
context.EnterScope();
|
||||
context.AppendLine("vec4 " + DefaultNames.DataName + ubSize + ";");
|
||||
context.LeaveScope($" {ubName}[{NumberFormatter.FormatInt(descriptors.Max(x => x.Slot) + 1)}];");
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var descriptor in descriptors)
|
||||
string layout = buffer.Layout switch
|
||||
{
|
||||
string ubName = OperandManager.GetShaderStagePrefix(context.Config.Stage);
|
||||
BufferLayout.Std140 => "std140",
|
||||
_ => "std430"
|
||||
};
|
||||
|
||||
ubName += "_" + DefaultNames.UniformNamePrefix + descriptor.Slot;
|
||||
context.AppendLine($"layout (binding = {buffer.Binding}, {layout}) uniform _{buffer.Name}");
|
||||
context.EnterScope();
|
||||
|
||||
context.AppendLine($"layout (binding = {descriptor.Binding}, std140) uniform {ubName}");
|
||||
context.EnterScope();
|
||||
context.AppendLine("vec4 " + OperandManager.GetUbName(context.Config.Stage, descriptor.Slot, false) + ubSize + ";");
|
||||
context.LeaveScope(";");
|
||||
foreach (StructureField field in buffer.Type.Fields)
|
||||
{
|
||||
if (field.Type.HasFlag(AggregateType.Array))
|
||||
{
|
||||
string typeName = GetVarTypeName(context, field.Type & ~AggregateType.Array);
|
||||
string arraySize = field.ArrayLength.ToString(CultureInfo.InvariantCulture);
|
||||
|
||||
context.AppendLine($"{typeName} {field.Name}[{arraySize}];");
|
||||
}
|
||||
else
|
||||
{
|
||||
string typeName = GetVarTypeName(context, field.Type);
|
||||
|
||||
context.AppendLine($"{typeName} {field.Name};");
|
||||
}
|
||||
}
|
||||
|
||||
context.LeaveScope($" {buffer.Name};");
|
||||
context.AppendLine();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -759,39 +750,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
||||
context.AppendLine($"layout (location = {location}) patch out vec4 {name};");
|
||||
}
|
||||
|
||||
private static void DeclareSupportUniformBlock(CodeGenContext context, ShaderStage stage, int scaleElements)
|
||||
{
|
||||
bool needsSupportBlock = stage == ShaderStage.Fragment ||
|
||||
(context.Config.LastInVertexPipeline && context.Config.GpuAccessor.QueryViewportTransformDisable());
|
||||
|
||||
if (!needsSupportBlock && scaleElements == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
context.AppendLine($"layout (binding = 0, std140) uniform {DefaultNames.SupportBlockName}");
|
||||
context.EnterScope();
|
||||
|
||||
switch (stage)
|
||||
{
|
||||
case ShaderStage.Fragment:
|
||||
case ShaderStage.Vertex:
|
||||
context.AppendLine($"uint {DefaultNames.SupportBlockAlphaTestName};");
|
||||
context.AppendLine($"bool {DefaultNames.SupportBlockIsBgraName}[{SupportBuffer.FragmentIsBgraCount}];");
|
||||
context.AppendLine($"vec4 {DefaultNames.SupportBlockViewportInverse};");
|
||||
context.AppendLine($"int {DefaultNames.SupportBlockFragmentScaleCount};");
|
||||
break;
|
||||
case ShaderStage.Compute:
|
||||
context.AppendLine($"uint s_reserved[{SupportBuffer.ComputeRenderScaleOffset / SupportBuffer.FieldSize}];");
|
||||
break;
|
||||
}
|
||||
|
||||
context.AppendLine($"float {DefaultNames.SupportBlockRenderScaleName}[{SupportBuffer.RenderScaleMaxCount}];");
|
||||
|
||||
context.LeaveScope(";");
|
||||
context.AppendLine();
|
||||
}
|
||||
|
||||
private static void AppendHelperFunction(CodeGenContext context, string filename)
|
||||
{
|
||||
string code = EmbeddedResources.ReadAllText(filename);
|
||||
|
||||
@@ -15,18 +15,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
||||
|
||||
public const string DataName = "data";
|
||||
|
||||
public const string SupportBlockName = "support_block";
|
||||
public const string SupportBlockAlphaTestName = "s_alpha_test";
|
||||
public const string SupportBlockIsBgraName = "s_is_bgra";
|
||||
public const string SupportBlockViewportInverse = "s_viewport_inverse";
|
||||
public const string SupportBlockFragmentScaleCount = "s_frag_scale_count";
|
||||
public const string SupportBlockRenderScaleName = "s_render_scale";
|
||||
|
||||
public const string BlockSuffix = "block";
|
||||
|
||||
public const string UniformNamePrefix = "c";
|
||||
public const string UniformNameSuffix = "data";
|
||||
|
||||
public const string LocalMemoryName = "local_mem";
|
||||
public const string SharedMemoryName = "shared_mem";
|
||||
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
struct PTType
|
||||
{
|
||||
uint blockIndices[1 << 14];
|
||||
uvec2 pointers[1 << 28];
|
||||
};
|
||||
|
||||
uvec2 Helper_TranslateAddress(uvec2 address)
|
||||
{
|
||||
PTType* br = (PTType*)packPtr(s_page_table.xy);
|
||||
|
||||
uint l0 = (address.x >> 12) & 0x3fff;
|
||||
uint l1 = ((address.x >> 26) & 0x3f) | ((address.y << 6) & 0x3fc0);
|
||||
|
||||
uvec2 hostAddress = br->pointers[br->blockIndices[l1] + l0];
|
||||
|
||||
hostAddress.x += (address.x & 0xfff);
|
||||
|
||||
return hostAddress;
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
layout (buffer_reference, std430, buffer_reference_align = 8) buffer buffer_regions_block
|
||||
{
|
||||
uint blockIndices[1 << 14];
|
||||
uvec2 pointers[];
|
||||
};
|
||||
|
||||
layout (buffer_reference, std430, buffer_reference_align = 1) buffer uint8_t_ptr
|
||||
{
|
||||
uint8_t value;
|
||||
};
|
||||
|
||||
layout (buffer_reference, std430, buffer_reference_align = 2) buffer uint16_t_ptr
|
||||
{
|
||||
uint16_t value;
|
||||
};
|
||||
|
||||
layout (buffer_reference, std430, buffer_reference_align = 4) buffer uint_ptr
|
||||
{
|
||||
uint value;
|
||||
};
|
||||
|
||||
uvec2 Helper_TranslateAddress(uvec2 address)
|
||||
{
|
||||
buffer_regions_block br = buffer_regions_block(s_page_table.xy);
|
||||
|
||||
uint l0 = (address.x >> 12) & 0x3fff;
|
||||
uint l1 = ((address.x >> 26) & 0x3f) | ((address.y << 6) & 0x3fc0);
|
||||
|
||||
uvec2 hostAddress = br.pointers[br.blockIndices[l1] + l0];
|
||||
|
||||
hostAddress.x += (address.x & 0xfff);
|
||||
|
||||
return hostAddress;
|
||||
}
|
||||
@@ -18,5 +18,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
||||
public static string StoreShared8 = "Helper_StoreShared8";
|
||||
public static string StoreStorage16 = "Helper_StoreStorage16";
|
||||
public static string StoreStorage8 = "Helper_StoreStorage8";
|
||||
|
||||
public static string TranslateAddress = "Helper_TranslateAddress";
|
||||
}
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
ivec2 Helper_TexelFetchScale(ivec2 inputVec, int samplerIndex)
|
||||
{
|
||||
float scale = s_render_scale[samplerIndex];
|
||||
if (scale == 1.0)
|
||||
{
|
||||
return inputVec;
|
||||
}
|
||||
return ivec2(vec2(inputVec) * scale);
|
||||
}
|
||||
|
||||
int Helper_TextureSizeUnscale(int size, int samplerIndex)
|
||||
{
|
||||
float scale = s_render_scale[samplerIndex];
|
||||
if (scale == 1.0)
|
||||
{
|
||||
return size;
|
||||
}
|
||||
return int(float(size) / scale);
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
ivec2 Helper_TexelFetchScale(ivec2 inputVec, int samplerIndex)
|
||||
{
|
||||
float scale = s_render_scale[1 + samplerIndex];
|
||||
if (scale == 1.0)
|
||||
{
|
||||
return inputVec;
|
||||
}
|
||||
if (scale < 0.0) // If less than 0, try interpolate between texels by using the screen position.
|
||||
{
|
||||
return ivec2(vec2(inputVec) * (-scale) + mod(gl_FragCoord.xy, 0.0 - scale));
|
||||
}
|
||||
else
|
||||
{
|
||||
return ivec2(vec2(inputVec) * scale);
|
||||
}
|
||||
}
|
||||
|
||||
int Helper_TextureSizeUnscale(int size, int samplerIndex)
|
||||
{
|
||||
float scale = abs(s_render_scale[1 + samplerIndex]);
|
||||
if (scale == 1.0)
|
||||
{
|
||||
return size;
|
||||
}
|
||||
return int(float(size) / scale);
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
ivec2 Helper_TexelFetchScale(ivec2 inputVec, int samplerIndex)
|
||||
{
|
||||
float scale = abs(s_render_scale[1 + samplerIndex + s_frag_scale_count]);
|
||||
if (scale == 1.0)
|
||||
{
|
||||
return inputVec;
|
||||
}
|
||||
|
||||
return ivec2(vec2(inputVec) * scale);
|
||||
}
|
||||
|
||||
int Helper_TextureSizeUnscale(int size, int samplerIndex)
|
||||
{
|
||||
float scale = abs(s_render_scale[1 + samplerIndex + s_frag_scale_count]);
|
||||
if (scale == 1.0)
|
||||
{
|
||||
return size;
|
||||
}
|
||||
return int(float(size) / scale);
|
||||
}
|
||||
@@ -87,6 +87,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
||||
{
|
||||
switch (operation.StorageKind)
|
||||
{
|
||||
case StorageKind.GlobalMemory: args += LoadGlobal(context, operation); break;
|
||||
case StorageKind.SharedMemory: args += LoadShared(context, operation); break;
|
||||
case StorageKind.StorageBuffer: args += LoadStorage(context, operation); break;
|
||||
|
||||
@@ -167,8 +168,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
||||
case Instruction.Load:
|
||||
return Load(context, operation);
|
||||
|
||||
case Instruction.LoadConstant:
|
||||
return LoadConstant(context, operation);
|
||||
case Instruction.LoadGlobal:
|
||||
return LoadGlobal(context, operation);
|
||||
|
||||
case Instruction.LoadLocal:
|
||||
return LoadLocal(context, operation);
|
||||
@@ -194,6 +195,15 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
||||
case Instruction.Store:
|
||||
return Store(context, operation);
|
||||
|
||||
case Instruction.StoreGlobal:
|
||||
return StoreGlobal(context, operation);
|
||||
|
||||
case Instruction.StoreGlobal16:
|
||||
return StoreGlobal16(context, operation);
|
||||
|
||||
case Instruction.StoreGlobal8:
|
||||
return StoreGlobal8(context, operation);
|
||||
|
||||
case Instruction.StoreLocal:
|
||||
return StoreLocal(context, operation);
|
||||
|
||||
|
||||
@@ -83,7 +83,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
||||
Add(Instruction.ImageAtomic, InstType.Special);
|
||||
Add(Instruction.IsNan, InstType.CallUnary, "isnan");
|
||||
Add(Instruction.Load, InstType.Special);
|
||||
Add(Instruction.LoadConstant, InstType.Special);
|
||||
Add(Instruction.LoadGlobal, InstType.Special);
|
||||
Add(Instruction.LoadLocal, InstType.Special);
|
||||
Add(Instruction.LoadShared, InstType.Special);
|
||||
Add(Instruction.LoadStorage, InstType.Special);
|
||||
@@ -102,6 +102,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
||||
Add(Instruction.MemoryBarrier, InstType.CallNullary, "memoryBarrier");
|
||||
Add(Instruction.Minimum, InstType.CallBinary, "min");
|
||||
Add(Instruction.MinimumU32, InstType.CallBinary, "min");
|
||||
Add(Instruction.Modulo, InstType.CallBinary, "mod");
|
||||
Add(Instruction.Multiply, InstType.OpBinaryCom, "*", 1);
|
||||
Add(Instruction.MultiplyHighS32, InstType.CallBinary, HelperFunctionNames.MultiplyHighS32);
|
||||
Add(Instruction.MultiplyHighU32, InstType.CallBinary, HelperFunctionNames.MultiplyHighU32);
|
||||
@@ -119,6 +120,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
||||
Add(Instruction.Sine, InstType.CallUnary, "sin");
|
||||
Add(Instruction.SquareRoot, InstType.CallUnary, "sqrt");
|
||||
Add(Instruction.Store, InstType.Special);
|
||||
Add(Instruction.StoreGlobal, InstType.Special);
|
||||
Add(Instruction.StoreLocal, InstType.Special);
|
||||
Add(Instruction.StoreShared, InstType.Special);
|
||||
Add(Instruction.StoreShared16, InstType.Special);
|
||||
|
||||
@@ -97,30 +97,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
||||
texCallBuilder.Append(str);
|
||||
}
|
||||
|
||||
string ApplyScaling(string vector)
|
||||
{
|
||||
if (context.Config.Stage.SupportsRenderScale() &&
|
||||
texOp.Inst == Instruction.ImageLoad &&
|
||||
!isBindless &&
|
||||
!isIndexed)
|
||||
{
|
||||
// Image scales start after texture ones.
|
||||
int scaleIndex = context.Config.GetTextureDescriptors().Length + context.Config.FindImageDescriptorIndex(texOp);
|
||||
|
||||
if (pCount == 3 && isArray)
|
||||
{
|
||||
// The array index is not scaled, just x and y.
|
||||
vector = $"ivec3(Helper_TexelFetchScale(({vector}).xy, {scaleIndex}), ({vector}).z)";
|
||||
}
|
||||
else if (pCount == 2 && !isArray)
|
||||
{
|
||||
vector = $"Helper_TexelFetchScale({vector}, {scaleIndex})";
|
||||
}
|
||||
}
|
||||
|
||||
return vector;
|
||||
}
|
||||
|
||||
if (pCount > 1)
|
||||
{
|
||||
string[] elems = new string[pCount];
|
||||
@@ -130,7 +106,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
||||
elems[index] = Src(AggregateType.S32);
|
||||
}
|
||||
|
||||
Append(ApplyScaling($"ivec{pCount}({string.Join(", ", elems)})"));
|
||||
Append($"ivec{pCount}({string.Join(", ", elems)})");
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -215,26 +191,21 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
||||
return GenerateLoadOrStore(context, operation, isStore: false);
|
||||
}
|
||||
|
||||
public static string LoadConstant(CodeGenContext context, AstOperation operation)
|
||||
public static string LoadGlobal(CodeGenContext context, AstOperation operation)
|
||||
{
|
||||
IAstNode src1 = operation.GetSource(0);
|
||||
IAstNode src2 = operation.GetSource(1);
|
||||
|
||||
string offsetExpr = GetSoureExpr(context, src2, GetSrcVarType(operation.Inst, 1));
|
||||
offsetExpr = Enclose(offsetExpr, src2, Instruction.ShiftRightS32, isLhs: true);
|
||||
string addressLowExpr = GetSoureExpr(context, src1, GetSrcVarType(operation.Inst, 0));
|
||||
string addressHighExpr = GetSoureExpr(context, src2, GetSrcVarType(operation.Inst, 1));
|
||||
|
||||
var config = context.Config;
|
||||
bool indexElement = !config.GpuAccessor.QueryHostHasVectorIndexingBug();
|
||||
|
||||
if (src1 is AstOperand operand && operand.Type == OperandType.Constant)
|
||||
if (context.Config.Options.TargetApi == TargetApi.Vulkan)
|
||||
{
|
||||
bool cbIndexable = config.UsedFeatures.HasFlag(Translation.FeatureFlags.CbIndexing);
|
||||
return OperandManager.GetConstantBufferName(operand.Value, offsetExpr, config.Stage, cbIndexable, indexElement);
|
||||
return $"uint_ptr({HelperFunctionNames.TranslateAddress}(uvec2({addressLowExpr}, {addressHighExpr}))).value";
|
||||
}
|
||||
else
|
||||
{
|
||||
string slotExpr = GetSoureExpr(context, src1, GetSrcVarType(operation.Inst, 0));
|
||||
return OperandManager.GetConstantBufferName(slotExpr, offsetExpr, config.Stage, indexElement);
|
||||
return $"*(uint*)packPtr({HelperFunctionNames.TranslateAddress}(uvec2({addressLowExpr}, {addressHighExpr})))";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -321,6 +292,44 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
||||
return GenerateLoadOrStore(context, operation, isStore: true);
|
||||
}
|
||||
|
||||
public static string StoreGlobal(CodeGenContext context, AstOperation operation)
|
||||
{
|
||||
return StoreGlobal(context, operation, "uint");
|
||||
}
|
||||
|
||||
public static string StoreGlobal16(CodeGenContext context, AstOperation operation)
|
||||
{
|
||||
return StoreGlobal(context, operation, "uint16_t");
|
||||
}
|
||||
|
||||
public static string StoreGlobal8(CodeGenContext context, AstOperation operation)
|
||||
{
|
||||
return StoreGlobal(context, operation, "uint8_t");
|
||||
}
|
||||
|
||||
private static string StoreGlobal(CodeGenContext context, AstOperation operation, string type)
|
||||
{
|
||||
IAstNode src1 = operation.GetSource(0);
|
||||
IAstNode src2 = operation.GetSource(1);
|
||||
IAstNode src3 = operation.GetSource(2);
|
||||
|
||||
string addressLowExpr = GetSoureExpr(context, src1, GetSrcVarType(operation.Inst, 0));
|
||||
string addressHighExpr = GetSoureExpr(context, src2, GetSrcVarType(operation.Inst, 1));
|
||||
|
||||
AggregateType srcType = OperandManager.GetNodeDestType(context, src3);
|
||||
|
||||
string src = TypeConversion.ReinterpretCast(context, src3, srcType, AggregateType.U32);
|
||||
|
||||
if (context.Config.Options.TargetApi == TargetApi.Vulkan)
|
||||
{
|
||||
return $"{type}_ptr({HelperFunctionNames.TranslateAddress}(uvec2({addressLowExpr}, {addressHighExpr}))).value = {src}";
|
||||
}
|
||||
else
|
||||
{
|
||||
return $"*({type}*)packPtr({HelperFunctionNames.TranslateAddress}(uvec2({addressLowExpr}, {addressHighExpr}))) = {src}";
|
||||
}
|
||||
}
|
||||
|
||||
public static string StoreLocal(CodeGenContext context, AstOperation operation)
|
||||
{
|
||||
return StoreLocalOrShared(context, operation, DefaultNames.LocalMemoryName);
|
||||
@@ -607,53 +616,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
||||
}
|
||||
}
|
||||
|
||||
string ApplyScaling(string vector)
|
||||
{
|
||||
if (intCoords)
|
||||
{
|
||||
if (context.Config.Stage.SupportsRenderScale() &&
|
||||
!isBindless &&
|
||||
!isIndexed)
|
||||
{
|
||||
int index = context.Config.FindTextureDescriptorIndex(texOp);
|
||||
|
||||
if (pCount == 3 && isArray)
|
||||
{
|
||||
// The array index is not scaled, just x and y.
|
||||
vector = "ivec3(Helper_TexelFetchScale((" + vector + ").xy, " + index + "), (" + vector + ").z)";
|
||||
}
|
||||
else if (pCount == 2 && !isArray)
|
||||
{
|
||||
vector = "Helper_TexelFetchScale(" + vector + ", " + index + ")";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return vector;
|
||||
}
|
||||
|
||||
string ApplyBias(string vector)
|
||||
{
|
||||
int gatherBiasPrecision = context.Config.GpuAccessor.QueryHostGatherBiasPrecision();
|
||||
if (isGather && gatherBiasPrecision != 0)
|
||||
{
|
||||
// GPU requires texture gather to be slightly offset to match NVIDIA behaviour when point is exactly between two texels.
|
||||
// Offset by the gather precision divided by 2 to correct for rounding.
|
||||
|
||||
if (pCount == 1)
|
||||
{
|
||||
vector = $"{vector} + (1.0 / (float(textureSize({samplerName}, 0)) * float({1 << (gatherBiasPrecision + 1)})))";
|
||||
}
|
||||
else
|
||||
{
|
||||
vector = $"{vector} + (1.0 / (vec{pCount}(textureSize({samplerName}, 0).{"xyz".Substring(0, pCount)}) * float({1 << (gatherBiasPrecision + 1)})))";
|
||||
}
|
||||
}
|
||||
|
||||
return vector;
|
||||
}
|
||||
|
||||
Append(ApplyBias(ApplyScaling(AssemblePVector(pCount))));
|
||||
Append(AssemblePVector(pCount));
|
||||
|
||||
string AssembleDerivativesVector(int count)
|
||||
{
|
||||
@@ -773,7 +736,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
||||
}
|
||||
else
|
||||
{
|
||||
(TextureDescriptor descriptor, int descriptorIndex) = context.Config.FindTextureDescriptor(texOp);
|
||||
TextureDescriptor descriptor = context.Config.FindTextureDescriptor(texOp);
|
||||
bool hasLod = !descriptor.Type.HasFlag(SamplerType.Multisample) && descriptor.Type != SamplerType.TextureBuffer;
|
||||
string texCall;
|
||||
|
||||
@@ -790,14 +753,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
||||
texCall = $"textureSize({samplerName}){GetMask(texOp.Index)}";
|
||||
}
|
||||
|
||||
if (context.Config.Stage.SupportsRenderScale() &&
|
||||
(texOp.Index < 2 || (texOp.Type & SamplerType.Mask) == SamplerType.Texture3D) &&
|
||||
!isBindless &&
|
||||
!isIndexed)
|
||||
{
|
||||
texCall = $"Helper_TextureSizeUnscale({texCall}, {descriptorIndex})";
|
||||
}
|
||||
|
||||
return texCall;
|
||||
}
|
||||
}
|
||||
@@ -809,9 +764,29 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
||||
string varName;
|
||||
AggregateType varType;
|
||||
int srcIndex = 0;
|
||||
int inputsCount = isStore ? operation.SourcesCount - 1 : operation.SourcesCount;
|
||||
|
||||
switch (storageKind)
|
||||
{
|
||||
case StorageKind.ConstantBuffer:
|
||||
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.");
|
||||
}
|
||||
|
||||
int binding = bindingIndex.Value;
|
||||
BufferDefinition buffer = context.Config.Properties.ConstantBuffers[binding];
|
||||
|
||||
if (!(operation.GetSource(srcIndex++) is AstOperand fieldIndex) || fieldIndex.Type != OperandType.Constant)
|
||||
{
|
||||
throw new InvalidOperationException($"Second input of {operation.Inst} with {storageKind} storage must be a constant operand.");
|
||||
}
|
||||
|
||||
StructureField field = buffer.Type.Fields[fieldIndex.Value];
|
||||
varName = $"{buffer.Name}.{field.Name}";
|
||||
varType = field.Type;
|
||||
break;
|
||||
|
||||
case StorageKind.Input:
|
||||
case StorageKind.InputPerPatch:
|
||||
case StorageKind.Output:
|
||||
@@ -864,40 +839,39 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
||||
varName = $"gl_out[{expr}].{varName}";
|
||||
}
|
||||
}
|
||||
|
||||
int firstSrcIndex = srcIndex;
|
||||
int inputsCount = isStore ? operation.SourcesCount - 1 : operation.SourcesCount;
|
||||
|
||||
for (; srcIndex < inputsCount; srcIndex++)
|
||||
{
|
||||
IAstNode src = operation.GetSource(srcIndex);
|
||||
|
||||
if ((varType & AggregateType.ElementCountMask) != 0 &&
|
||||
srcIndex == inputsCount - 1 &&
|
||||
src is AstOperand elementIndex &&
|
||||
elementIndex.Type == OperandType.Constant)
|
||||
{
|
||||
varName += "." + "xyzw"[elementIndex.Value & 3];
|
||||
}
|
||||
else if (srcIndex == firstSrcIndex && context.Config.Stage == ShaderStage.TessellationControl && storageKind == StorageKind.Output)
|
||||
{
|
||||
// GLSL requires that for tessellation control shader outputs,
|
||||
// that the index expression must be *exactly* "gl_InvocationID",
|
||||
// otherwise the compilation fails.
|
||||
// TODO: Get rid of this and use expression propagation to make sure we generate the correct code from IR.
|
||||
varName += "[gl_InvocationID]";
|
||||
}
|
||||
else
|
||||
{
|
||||
varName += $"[{GetSoureExpr(context, src, AggregateType.S32)}]";
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new InvalidOperationException($"Invalid storage kind {storageKind}.");
|
||||
}
|
||||
|
||||
int firstSrcIndex = srcIndex;
|
||||
|
||||
for (; srcIndex < inputsCount; srcIndex++)
|
||||
{
|
||||
IAstNode src = operation.GetSource(srcIndex);
|
||||
|
||||
if ((varType & AggregateType.ElementCountMask) != 0 &&
|
||||
srcIndex == inputsCount - 1 &&
|
||||
src is AstOperand elementIndex &&
|
||||
elementIndex.Type == OperandType.Constant)
|
||||
{
|
||||
varName += "." + "xyzw"[elementIndex.Value & 3];
|
||||
}
|
||||
else if (srcIndex == firstSrcIndex && context.Config.Stage == ShaderStage.TessellationControl && storageKind == StorageKind.Output)
|
||||
{
|
||||
// GLSL requires that for tessellation control shader outputs,
|
||||
// that the index expression must be *exactly* "gl_InvocationID",
|
||||
// otherwise the compilation fails.
|
||||
// TODO: Get rid of this and use expression propagation to make sure we generate the correct code from IR.
|
||||
varName += "[gl_InvocationID]";
|
||||
}
|
||||
else
|
||||
{
|
||||
varName += $"[{GetSoureExpr(context, src, AggregateType.S32)}]";
|
||||
}
|
||||
}
|
||||
|
||||
if (isStore)
|
||||
{
|
||||
varType &= AggregateType.ElementTypeMask;
|
||||
|
||||
@@ -27,7 +27,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
||||
IoVariable.FragmentCoord => ("gl_FragCoord", AggregateType.Vector4 | AggregateType.FP32),
|
||||
IoVariable.FragmentOutputColor => GetFragmentOutputColorVariableName(config, location),
|
||||
IoVariable.FragmentOutputDepth => ("gl_FragDepth", AggregateType.FP32),
|
||||
IoVariable.FragmentOutputIsBgra => (DefaultNames.SupportBlockIsBgraName, AggregateType.Array | AggregateType.Bool),
|
||||
IoVariable.FrontColorDiffuse => ("gl_FrontColor", AggregateType.Vector4 | AggregateType.FP32), // Deprecated.
|
||||
IoVariable.FrontColorSpecular => ("gl_FrontSecondaryColor", AggregateType.Vector4 | AggregateType.FP32), // Deprecated.
|
||||
IoVariable.FrontFacing => ("gl_FrontFacing", AggregateType.Bool),
|
||||
@@ -46,8 +45,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl.Instructions
|
||||
IoVariable.SubgroupLaneId => GetSubgroupInvocationIdVariableName(config),
|
||||
IoVariable.SubgroupLeMask => GetSubgroupMaskVariableName(config, "Le"),
|
||||
IoVariable.SubgroupLtMask => GetSubgroupMaskVariableName(config, "Lt"),
|
||||
IoVariable.SupportBlockRenderScale => (DefaultNames.SupportBlockRenderScaleName, AggregateType.Array | AggregateType.FP32),
|
||||
IoVariable.SupportBlockViewInverse => (DefaultNames.SupportBlockViewportInverse, AggregateType.Vector2 | AggregateType.FP32),
|
||||
IoVariable.TessellationCoord => ("gl_TessCoord", AggregateType.Vector3 | AggregateType.FP32),
|
||||
IoVariable.TessellationLevelInner => ("gl_TessLevelInner", AggregateType.Array | AggregateType.FP32),
|
||||
IoVariable.TessellationLevelOuter => ("gl_TessLevelOuter", AggregateType.Array | AggregateType.FP32),
|
||||
|
||||
@@ -36,63 +36,12 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
||||
{
|
||||
OperandType.Argument => GetArgumentName(operand.Value),
|
||||
OperandType.Constant => NumberFormatter.FormatInt(operand.Value),
|
||||
OperandType.ConstantBuffer => GetConstantBufferName(operand, context.Config),
|
||||
OperandType.LocalVariable => _locals[operand],
|
||||
OperandType.Undefined => DefaultNames.UndefinedName,
|
||||
_ => throw new ArgumentException($"Invalid operand type \"{operand.Type}\".")
|
||||
};
|
||||
}
|
||||
|
||||
private static string GetConstantBufferName(AstOperand operand, ShaderConfig config)
|
||||
{
|
||||
return GetConstantBufferName(operand.CbufSlot, operand.CbufOffset, config.Stage, config.UsedFeatures.HasFlag(FeatureFlags.CbIndexing));
|
||||
}
|
||||
|
||||
public static string GetConstantBufferName(int slot, int offset, ShaderStage stage, bool cbIndexable)
|
||||
{
|
||||
return $"{GetUbName(stage, slot, cbIndexable)}[{offset >> 2}].{GetSwizzleMask(offset & 3)}";
|
||||
}
|
||||
|
||||
private static string GetVec4Indexed(string vectorName, string indexExpr, bool indexElement)
|
||||
{
|
||||
if (indexElement)
|
||||
{
|
||||
return $"{vectorName}[{indexExpr}]";
|
||||
}
|
||||
|
||||
string result = $"{vectorName}.x";
|
||||
for (int i = 1; i < 4; i++)
|
||||
{
|
||||
result = $"(({indexExpr}) == {i}) ? ({vectorName}.{GetSwizzleMask(i)}) : ({result})";
|
||||
}
|
||||
return $"({result})";
|
||||
}
|
||||
|
||||
public static string GetConstantBufferName(int slot, string offsetExpr, ShaderStage stage, bool cbIndexable, bool indexElement)
|
||||
{
|
||||
return GetVec4Indexed(GetUbName(stage, slot, cbIndexable) + $"[{offsetExpr} >> 2]", offsetExpr + " & 3", indexElement);
|
||||
}
|
||||
|
||||
public static string GetConstantBufferName(string slotExpr, string offsetExpr, ShaderStage stage, bool indexElement)
|
||||
{
|
||||
return GetVec4Indexed(GetUbName(stage, slotExpr) + $"[{offsetExpr} >> 2]", offsetExpr + " & 3", indexElement);
|
||||
}
|
||||
|
||||
public static string GetUbName(ShaderStage stage, int slot, bool cbIndexable)
|
||||
{
|
||||
if (cbIndexable)
|
||||
{
|
||||
return GetUbName(stage, NumberFormatter.FormatInt(slot, AggregateType.S32));
|
||||
}
|
||||
|
||||
return $"{GetShaderStagePrefix(stage)}_{DefaultNames.UniformNamePrefix}{slot}_{DefaultNames.UniformNameSuffix}";
|
||||
}
|
||||
|
||||
private static string GetUbName(ShaderStage stage, string slotExpr)
|
||||
{
|
||||
return $"{GetShaderStagePrefix(stage)}_{DefaultNames.UniformNamePrefix}[{slotExpr}].{DefaultNames.DataName}";
|
||||
}
|
||||
|
||||
public static string GetSamplerName(ShaderStage stage, AstTextureOperation texOp, string indexExpr)
|
||||
{
|
||||
return GetSamplerName(stage, texOp.CbufSlot, texOp.Handle, texOp.Type.HasFlag(SamplerType.Indexed), indexExpr);
|
||||
@@ -168,6 +117,22 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Glsl
|
||||
{
|
||||
switch (operation.StorageKind)
|
||||
{
|
||||
case StorageKind.ConstantBuffer:
|
||||
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.");
|
||||
}
|
||||
|
||||
if (!(operation.GetSource(1) is AstOperand fieldIndex) || fieldIndex.Type != OperandType.Constant)
|
||||
{
|
||||
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];
|
||||
StructureField field = buffer.Type.Fields[fieldIndex.Value];
|
||||
|
||||
return field.Type & AggregateType.ElementTypeMask;
|
||||
|
||||
case StorageKind.Input:
|
||||
case StorageKind.InputPerPatch:
|
||||
case StorageKind.Output:
|
||||
|
||||
@@ -23,9 +23,8 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||
|
||||
public int InputVertices { get; }
|
||||
|
||||
public Dictionary<int, Instruction> UniformBuffers { get; } = new Dictionary<int, Instruction>();
|
||||
public Instruction SupportBuffer { get; set; }
|
||||
public Instruction UniformBuffersArray { get; set; }
|
||||
public Dictionary<int, Instruction> ConstantBuffers { get; } = new Dictionary<int, Instruction>();
|
||||
public Instruction PageTablePointerType { get; set; }
|
||||
public Instruction StorageBuffersArray { get; set; }
|
||||
public Instruction LocalMemory { get; set; }
|
||||
public Instruction SharedMemory { get; set; }
|
||||
@@ -38,6 +37,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||
public Dictionary<IoDefinition, Instruction> OutputsPerPatch { get; } = new Dictionary<IoDefinition, Instruction>();
|
||||
|
||||
public Instruction CoordTemp { get; set; }
|
||||
public StructuredFunction CurrentFunction { get; set; }
|
||||
private readonly Dictionary<AstOperand, Instruction> _locals = new Dictionary<AstOperand, Instruction>();
|
||||
private readonly Dictionary<int, Instruction[]> _localForArgs = new Dictionary<int, Instruction[]>();
|
||||
private readonly Dictionary<int, Instruction> _funcArgs = new Dictionary<int, Instruction>();
|
||||
@@ -217,7 +217,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||
{
|
||||
IrOperandType.Argument => GetArgument(type, operand),
|
||||
IrOperandType.Constant => GetConstant(type, operand),
|
||||
IrOperandType.ConstantBuffer => GetConstantBuffer(type, operand),
|
||||
IrOperandType.LocalVariable => GetLocal(type, operand),
|
||||
IrOperandType.Undefined => GetUndefined(type),
|
||||
_ => throw new ArgumentException($"Invalid operand type \"{operand.Type}\".")
|
||||
@@ -274,31 +273,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||
};
|
||||
}
|
||||
|
||||
public Instruction GetConstantBuffer(AggregateType type, AstOperand operand)
|
||||
{
|
||||
var i1 = Constant(TypeS32(), 0);
|
||||
var i2 = Constant(TypeS32(), operand.CbufOffset >> 2);
|
||||
var i3 = Constant(TypeU32(), operand.CbufOffset & 3);
|
||||
|
||||
Instruction elemPointer;
|
||||
|
||||
if (UniformBuffersArray != null)
|
||||
{
|
||||
var ubVariable = UniformBuffersArray;
|
||||
var i0 = Constant(TypeS32(), operand.CbufSlot);
|
||||
|
||||
elemPointer = AccessChain(TypePointer(StorageClass.Uniform, TypeFP32()), ubVariable, i0, i1, i2, i3);
|
||||
}
|
||||
else
|
||||
{
|
||||
var ubVariable = UniformBuffers[operand.CbufSlot];
|
||||
|
||||
elemPointer = AccessChain(TypePointer(StorageClass.Uniform, TypeFP32()), ubVariable, i1, i2, i3);
|
||||
}
|
||||
|
||||
return BitcastIfNeeded(type, AggregateType.FP32, Load(TypeFP32(), elemPointer));
|
||||
}
|
||||
|
||||
public Instruction GetLocalPointer(AstOperand local)
|
||||
{
|
||||
return _locals[local];
|
||||
|
||||
@@ -98,8 +98,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||
DeclareLocalMemory(context, localMemorySize);
|
||||
}
|
||||
|
||||
DeclareSupportBuffer(context);
|
||||
DeclareUniformBuffers(context, context.Config.GetConstantBufferDescriptors());
|
||||
DeclareConstantBuffers(context, context.Config.Properties.ConstantBuffers.Values);
|
||||
DeclareStorageBuffers(context, context.Config.GetStorageBufferDescriptors());
|
||||
DeclareSamplers(context, context.Config.GetTextureDescriptors());
|
||||
DeclareImages(context, context.Config.GetImageDescriptors());
|
||||
@@ -127,85 +126,78 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||
return variable;
|
||||
}
|
||||
|
||||
private static void DeclareSupportBuffer(CodeGenContext context)
|
||||
private static void DeclareConstantBuffers(CodeGenContext context, IEnumerable<BufferDefinition> buffers)
|
||||
{
|
||||
if (!context.Config.Stage.SupportsRenderScale() && !(context.Config.LastInVertexPipeline && context.Config.GpuAccessor.QueryViewportTransformDisable()))
|
||||
HashSet<SpvInstruction> decoratedTypes = new HashSet<SpvInstruction>();
|
||||
|
||||
foreach (BufferDefinition buffer in buffers)
|
||||
{
|
||||
return;
|
||||
}
|
||||
int alignment = buffer.Layout == BufferLayout.Std140 ? 16 : 4;
|
||||
int alignmentMask = alignment - 1;
|
||||
int offset = 0;
|
||||
|
||||
var isBgraArrayType = context.TypeArray(context.TypeU32(), context.Constant(context.TypeU32(), SupportBuffer.FragmentIsBgraCount));
|
||||
var viewportInverseVectorType = context.TypeVector(context.TypeFP32(), 4);
|
||||
var renderScaleArrayType = context.TypeArray(context.TypeFP32(), context.Constant(context.TypeU32(), SupportBuffer.RenderScaleMaxCount));
|
||||
SpvInstruction[] structFieldTypes = new SpvInstruction[buffer.Type.Fields.Length];
|
||||
int[] structFieldOffsets = new int[buffer.Type.Fields.Length];
|
||||
|
||||
context.Decorate(isBgraArrayType, Decoration.ArrayStride, (LiteralInteger)SupportBuffer.FieldSize);
|
||||
context.Decorate(renderScaleArrayType, Decoration.ArrayStride, (LiteralInteger)SupportBuffer.FieldSize);
|
||||
for (int fieldIndex = 0; fieldIndex < buffer.Type.Fields.Length; fieldIndex++)
|
||||
{
|
||||
StructureField field = buffer.Type.Fields[fieldIndex];
|
||||
int fieldSize = (field.Type.GetSizeInBytes() + alignmentMask) & ~alignmentMask;
|
||||
|
||||
var supportBufferStructType = context.TypeStruct(false, context.TypeU32(), isBgraArrayType, viewportInverseVectorType, context.TypeS32(), renderScaleArrayType);
|
||||
structFieldTypes[fieldIndex] = context.GetType(field.Type, field.ArrayLength);
|
||||
structFieldOffsets[fieldIndex] = offset;
|
||||
|
||||
context.MemberDecorate(supportBufferStructType, 0, Decoration.Offset, (LiteralInteger)SupportBuffer.FragmentAlphaTestOffset);
|
||||
context.MemberDecorate(supportBufferStructType, 1, Decoration.Offset, (LiteralInteger)SupportBuffer.FragmentIsBgraOffset);
|
||||
context.MemberDecorate(supportBufferStructType, 2, Decoration.Offset, (LiteralInteger)SupportBuffer.ViewportInverseOffset);
|
||||
context.MemberDecorate(supportBufferStructType, 3, Decoration.Offset, (LiteralInteger)SupportBuffer.FragmentRenderScaleCountOffset);
|
||||
context.MemberDecorate(supportBufferStructType, 4, Decoration.Offset, (LiteralInteger)SupportBuffer.GraphicsRenderScaleOffset);
|
||||
context.Decorate(supportBufferStructType, Decoration.Block);
|
||||
if (field.Type.HasFlag(AggregateType.Array))
|
||||
{
|
||||
// We can't decorate the type more than once.
|
||||
if (decoratedTypes.Add(structFieldTypes[fieldIndex]))
|
||||
{
|
||||
context.Decorate(structFieldTypes[fieldIndex], Decoration.ArrayStride, (LiteralInteger)fieldSize);
|
||||
}
|
||||
|
||||
var supportBufferPointerType = context.TypePointer(StorageClass.Uniform, supportBufferStructType);
|
||||
var supportBufferVariable = context.Variable(supportBufferPointerType, StorageClass.Uniform);
|
||||
offset += fieldSize * field.ArrayLength;
|
||||
}
|
||||
else
|
||||
{
|
||||
offset += fieldSize;
|
||||
}
|
||||
}
|
||||
|
||||
context.Decorate(supportBufferVariable, Decoration.DescriptorSet, (LiteralInteger)0);
|
||||
context.Decorate(supportBufferVariable, Decoration.Binding, (LiteralInteger)0);
|
||||
var ubStructType = context.TypeStruct(false, structFieldTypes);
|
||||
|
||||
context.AddGlobalVariable(supportBufferVariable);
|
||||
if (decoratedTypes.Add(ubStructType))
|
||||
{
|
||||
context.Decorate(ubStructType, Decoration.Block);
|
||||
|
||||
context.SupportBuffer = supportBufferVariable;
|
||||
}
|
||||
for (int fieldIndex = 0; fieldIndex < structFieldOffsets.Length; fieldIndex++)
|
||||
{
|
||||
context.MemberDecorate(ubStructType, fieldIndex, Decoration.Offset, (LiteralInteger)structFieldOffsets[fieldIndex]);
|
||||
}
|
||||
}
|
||||
|
||||
private static void DeclareUniformBuffers(CodeGenContext context, BufferDescriptor[] descriptors)
|
||||
{
|
||||
if (descriptors.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
uint ubSize = Constants.ConstantBufferSize / 16;
|
||||
|
||||
var ubArrayType = context.TypeArray(context.TypeVector(context.TypeFP32(), 4), context.Constant(context.TypeU32(), ubSize), true);
|
||||
context.Decorate(ubArrayType, Decoration.ArrayStride, (LiteralInteger)16);
|
||||
var ubStructType = context.TypeStruct(true, ubArrayType);
|
||||
context.Decorate(ubStructType, Decoration.Block);
|
||||
context.MemberDecorate(ubStructType, 0, Decoration.Offset, (LiteralInteger)0);
|
||||
|
||||
if (context.Config.UsedFeatures.HasFlag(FeatureFlags.CbIndexing))
|
||||
{
|
||||
int count = descriptors.Max(x => x.Slot) + 1;
|
||||
|
||||
var ubStructArrayType = context.TypeArray(ubStructType, context.Constant(context.TypeU32(), count));
|
||||
var ubPointerType = context.TypePointer(StorageClass.Uniform, ubStructArrayType);
|
||||
var ubPointerType = context.TypePointer(StorageClass.Uniform, ubStructType);
|
||||
var ubVariable = context.Variable(ubPointerType, StorageClass.Uniform);
|
||||
|
||||
context.Name(ubVariable, $"{GetStagePrefix(context.Config.Stage)}_u");
|
||||
context.Decorate(ubVariable, Decoration.DescriptorSet, (LiteralInteger)0);
|
||||
context.Decorate(ubVariable, Decoration.Binding, (LiteralInteger)context.Config.FirstConstantBufferBinding);
|
||||
context.Name(ubVariable, buffer.Name);
|
||||
context.Decorate(ubVariable, Decoration.DescriptorSet, (LiteralInteger)buffer.Set);
|
||||
context.Decorate(ubVariable, Decoration.Binding, (LiteralInteger)buffer.Binding);
|
||||
context.AddGlobalVariable(ubVariable);
|
||||
|
||||
context.UniformBuffersArray = ubVariable;
|
||||
context.ConstantBuffers.Add(buffer.Binding, ubVariable);
|
||||
}
|
||||
else
|
||||
{
|
||||
var ubPointerType = context.TypePointer(StorageClass.Uniform, ubStructType);
|
||||
|
||||
foreach (var descriptor in descriptors)
|
||||
{
|
||||
var ubVariable = context.Variable(ubPointerType, StorageClass.Uniform);
|
||||
var blockArrayType = context.TypeArray(context.TypeU32(), context.Constant(context.TypeU32(), 1 << 14));
|
||||
var pointerArrayType = context.TypeRuntimeArray(context.TypeVector(context.TypeU32(), 2));
|
||||
|
||||
context.Name(ubVariable, $"{GetStagePrefix(context.Config.Stage)}_c{descriptor.Slot}");
|
||||
context.Decorate(ubVariable, Decoration.DescriptorSet, (LiteralInteger)0);
|
||||
context.Decorate(ubVariable, Decoration.Binding, (LiteralInteger)descriptor.Binding);
|
||||
context.AddGlobalVariable(ubVariable);
|
||||
context.UniformBuffers.Add(descriptor.Slot, ubVariable);
|
||||
}
|
||||
}
|
||||
context.Decorate(blockArrayType, Decoration.ArrayStride, (LiteralInteger)4);
|
||||
context.Decorate(pointerArrayType, Decoration.ArrayStride, (LiteralInteger)8);
|
||||
|
||||
var ptStructType = context.TypeStruct(false, blockArrayType, pointerArrayType);
|
||||
|
||||
context.MemberDecorate(ptStructType, 0, Decoration.Offset, (LiteralInteger)0);
|
||||
context.MemberDecorate(ptStructType, 1, Decoration.Offset, (LiteralInteger)((1 << 14) * sizeof(uint)));
|
||||
context.Decorate(ptStructType, Decoration.Block);
|
||||
|
||||
context.PageTablePointerType = context.TypePointer(StorageClass.PhysicalStorageBuffer, ptStructType);
|
||||
}
|
||||
|
||||
private static void DeclareStorageBuffers(CodeGenContext context, BufferDescriptor[] descriptors)
|
||||
@@ -394,25 +386,11 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||
{
|
||||
foreach (var ioDefinition in info.IoDefinitions)
|
||||
{
|
||||
var ioVariable = ioDefinition.IoVariable;
|
||||
|
||||
// Those are actually from constant buffer, rather than being actual inputs or outputs,
|
||||
// so we must ignore them here as they are declared as part of the support buffer.
|
||||
// TODO: Delete this after we represent this properly on the IR (as a constant buffer rather than "input").
|
||||
if (ioVariable == IoVariable.FragmentOutputIsBgra ||
|
||||
ioVariable == IoVariable.SupportBlockRenderScale ||
|
||||
ioVariable == IoVariable.SupportBlockViewInverse)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
bool isOutput = ioDefinition.StorageKind.IsOutput();
|
||||
bool isPerPatch = ioDefinition.StorageKind.IsPerPatch();
|
||||
|
||||
PixelImap iq = PixelImap.Unused;
|
||||
|
||||
if (context.Config.Stage == ShaderStage.Fragment)
|
||||
{
|
||||
var ioVariable = ioDefinition.IoVariable;
|
||||
if (ioVariable == IoVariable.UserDefined)
|
||||
{
|
||||
iq = context.Config.ImapTypes[ioDefinition.Location].GetFirstUsedType();
|
||||
@@ -429,6 +407,9 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||
}
|
||||
}
|
||||
|
||||
bool isOutput = ioDefinition.StorageKind.IsOutput();
|
||||
bool isPerPatch = ioDefinition.StorageKind.IsPerPatch();
|
||||
|
||||
DeclareInputOrOutput(context, ioDefinition, isOutput, isPerPatch, iq);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ using Ryujinx.Graphics.Shader.Translation;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using static Spv.Specification;
|
||||
|
||||
@@ -98,7 +97,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||
Add(Instruction.ImageStore, GenerateImageStore);
|
||||
Add(Instruction.IsNan, GenerateIsNan);
|
||||
Add(Instruction.Load, GenerateLoad);
|
||||
Add(Instruction.LoadConstant, GenerateLoadConstant);
|
||||
Add(Instruction.LoadGlobal, GenerateLoadGlobal);
|
||||
Add(Instruction.LoadLocal, GenerateLoadLocal);
|
||||
Add(Instruction.LoadShared, GenerateLoadShared);
|
||||
Add(Instruction.LoadStorage, GenerateLoadStorage);
|
||||
@@ -115,6 +114,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||
Add(Instruction.MemoryBarrier, GenerateMemoryBarrier);
|
||||
Add(Instruction.Minimum, GenerateMinimum);
|
||||
Add(Instruction.MinimumU32, GenerateMinimumU32);
|
||||
Add(Instruction.Modulo, GenerateModulo);
|
||||
Add(Instruction.Multiply, GenerateMultiply);
|
||||
Add(Instruction.MultiplyHighS32, GenerateMultiplyHighS32);
|
||||
Add(Instruction.MultiplyHighU32, GenerateMultiplyHighU32);
|
||||
@@ -134,6 +134,9 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||
Add(Instruction.Sine, GenerateSine);
|
||||
Add(Instruction.SquareRoot, GenerateSquareRoot);
|
||||
Add(Instruction.Store, GenerateStore);
|
||||
Add(Instruction.StoreGlobal, GenerateStoreGlobal);
|
||||
// Add(Instruction.StoreGlobal16, GenerateStoreGlobal16);
|
||||
// Add(Instruction.StoreGlobal8, GenerateStoreGlobal8);
|
||||
Add(Instruction.StoreLocal, GenerateStoreLocal);
|
||||
Add(Instruction.StoreShared, GenerateStoreShared);
|
||||
Add(Instruction.StoreShared16, GenerateStoreShared16);
|
||||
@@ -313,10 +316,11 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||
|
||||
for (int i = 0; i < args.Length; i++)
|
||||
{
|
||||
var operand = (AstOperand)operation.GetSource(i + 1);
|
||||
var operand = operation.GetSource(i + 1);
|
||||
|
||||
if (i >= function.InArguments.Length)
|
||||
{
|
||||
args[i] = context.GetLocalPointer(operand);
|
||||
args[i] = context.GetLocalPointer((AstOperand)operand);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -744,8 +748,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||
pCoords = Src(AggregateType.S32);
|
||||
}
|
||||
|
||||
pCoords = ScalingHelpers.ApplyScaling(context, texOp, pCoords, intCoords: true, isBindless, isIndexed, isArray, pCount);
|
||||
|
||||
(var imageType, var imageVariable) = context.Images[new TextureMeta(texOp.CbufSlot, texOp.Handle, texOp.Format)];
|
||||
|
||||
var image = context.Load(imageType, imageVariable);
|
||||
@@ -867,66 +869,12 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||
return GenerateLoadOrStore(context, operation, isStore: false);
|
||||
}
|
||||
|
||||
private static OperationResult GenerateLoadConstant(CodeGenContext context, AstOperation operation)
|
||||
private static OperationResult GenerateLoadGlobal(CodeGenContext context, AstOperation operation)
|
||||
{
|
||||
var src1 = operation.GetSource(0);
|
||||
var src2 = context.Get(AggregateType.S32, operation.GetSource(1));
|
||||
var elemPointer = GetGlobalElemPointer(context, operation, context.TypeU32());
|
||||
var value = context.Load(context.TypeU32(), elemPointer, MemoryAccessMask.Aligned, 4);
|
||||
|
||||
var i1 = context.Constant(context.TypeS32(), 0);
|
||||
var i2 = context.ShiftRightArithmetic(context.TypeS32(), src2, context.Constant(context.TypeS32(), 2));
|
||||
var i3 = context.BitwiseAnd(context.TypeS32(), src2, context.Constant(context.TypeS32(), 3));
|
||||
|
||||
SpvInstruction value = null;
|
||||
|
||||
if (context.Config.GpuAccessor.QueryHostHasVectorIndexingBug())
|
||||
{
|
||||
// Test for each component individually.
|
||||
for (int i = 0; i < 4; i++)
|
||||
{
|
||||
var component = context.Constant(context.TypeS32(), i);
|
||||
|
||||
SpvInstruction elemPointer;
|
||||
if (context.UniformBuffersArray != null)
|
||||
{
|
||||
var ubVariable = context.UniformBuffersArray;
|
||||
var i0 = context.Get(AggregateType.S32, src1);
|
||||
|
||||
elemPointer = context.AccessChain(context.TypePointer(StorageClass.Uniform, context.TypeFP32()), ubVariable, i0, i1, i2, component);
|
||||
}
|
||||
else
|
||||
{
|
||||
var ubVariable = context.UniformBuffers[((AstOperand)src1).Value];
|
||||
|
||||
elemPointer = context.AccessChain(context.TypePointer(StorageClass.Uniform, context.TypeFP32()), ubVariable, i1, i2, component);
|
||||
}
|
||||
|
||||
SpvInstruction newValue = context.Load(context.TypeFP32(), elemPointer);
|
||||
|
||||
value = value != null ? context.Select(context.TypeFP32(), context.IEqual(context.TypeBool(), i3, component), newValue, value) : newValue;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
SpvInstruction elemPointer;
|
||||
|
||||
if (context.UniformBuffersArray != null)
|
||||
{
|
||||
var ubVariable = context.UniformBuffersArray;
|
||||
var i0 = context.Get(AggregateType.S32, src1);
|
||||
|
||||
elemPointer = context.AccessChain(context.TypePointer(StorageClass.Uniform, context.TypeFP32()), ubVariable, i0, i1, i2, i3);
|
||||
}
|
||||
else
|
||||
{
|
||||
var ubVariable = context.UniformBuffers[((AstOperand)src1).Value];
|
||||
|
||||
elemPointer = context.AccessChain(context.TypePointer(StorageClass.Uniform, context.TypeFP32()), ubVariable, i1, i2, i3);
|
||||
}
|
||||
|
||||
value = context.Load(context.TypeFP32(), elemPointer);
|
||||
}
|
||||
|
||||
return new OperationResult(AggregateType.FP32, value);
|
||||
return new OperationResult(AggregateType.U32, value);
|
||||
}
|
||||
|
||||
private static OperationResult GenerateLoadLocal(CodeGenContext context, AstOperation operation)
|
||||
@@ -1102,6 +1050,11 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||
return GenerateBinaryU32(context, operation, context.Delegates.GlslUMin);
|
||||
}
|
||||
|
||||
private static OperationResult GenerateModulo(CodeGenContext context, AstOperation operation)
|
||||
{
|
||||
return GenerateBinary(context, operation, context.Delegates.FMod, null);
|
||||
}
|
||||
|
||||
private static OperationResult GenerateMultiply(CodeGenContext context, AstOperation operation)
|
||||
{
|
||||
return GenerateBinary(context, operation, context.Delegates.FMul, context.Delegates.IMul);
|
||||
@@ -1163,7 +1116,15 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||
|
||||
private static OperationResult GenerateReturn(CodeGenContext context, AstOperation operation)
|
||||
{
|
||||
context.Return();
|
||||
if (operation.SourcesCount != 0)
|
||||
{
|
||||
context.ReturnValue(context.Get(context.CurrentFunction.ReturnType, operation.GetSource(0)));
|
||||
}
|
||||
else
|
||||
{
|
||||
context.Return();
|
||||
}
|
||||
|
||||
return OperationResult.Invalid;
|
||||
}
|
||||
|
||||
@@ -1319,6 +1280,30 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||
return GenerateLoadOrStore(context, operation, isStore: true);
|
||||
}
|
||||
|
||||
private static OperationResult GenerateStoreGlobal(CodeGenContext context, AstOperation operation)
|
||||
{
|
||||
var elemPointer = GetGlobalElemPointer(context, operation, context.TypeU32());
|
||||
context.Store(elemPointer, context.Get(AggregateType.U32, operation.GetSource(2)), MemoryAccessMask.Aligned, 4);
|
||||
|
||||
return OperationResult.Invalid;
|
||||
}
|
||||
|
||||
private static OperationResult GenerateStoreGlobal16(CodeGenContext context, AstOperation operation)
|
||||
{
|
||||
var elemPointer = GetGlobalElemPointer(context, operation, context.TypeInt(16, 0));
|
||||
context.Store(elemPointer, context.Get(AggregateType.U32, operation.GetSource(2)), MemoryAccessMask.Aligned, 2);
|
||||
|
||||
return OperationResult.Invalid;
|
||||
}
|
||||
|
||||
private static OperationResult GenerateStoreGlobal8(CodeGenContext context, AstOperation operation)
|
||||
{
|
||||
var elemPointer = GetGlobalElemPointer(context, operation, context.TypeInt(8, 0));
|
||||
context.Store(elemPointer, context.Get(AggregateType.U32, operation.GetSource(2)), MemoryAccessMask.Aligned, 1);
|
||||
|
||||
return OperationResult.Invalid;
|
||||
}
|
||||
|
||||
private static OperationResult GenerateStoreLocal(CodeGenContext context, AstOperation operation)
|
||||
{
|
||||
return GenerateStoreLocalOrShared(context, operation, StorageClass.Private, context.LocalMemory);
|
||||
@@ -1501,35 +1486,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||
}
|
||||
}
|
||||
|
||||
SpvInstruction ApplyBias(SpvInstruction vector, SpvInstruction image)
|
||||
{
|
||||
int gatherBiasPrecision = context.Config.GpuAccessor.QueryHostGatherBiasPrecision();
|
||||
if (isGather && gatherBiasPrecision != 0)
|
||||
{
|
||||
// GPU requires texture gather to be slightly offset to match NVIDIA behaviour when point is exactly between two texels.
|
||||
// Offset by the gather precision divided by 2 to correct for rounding.
|
||||
var sizeType = pCount == 1 ? context.TypeS32() : context.TypeVector(context.TypeS32(), pCount);
|
||||
var pVectorType = pCount == 1 ? context.TypeFP32() : context.TypeVector(context.TypeFP32(), pCount);
|
||||
|
||||
var bias = context.Constant(context.TypeFP32(), (float)(1 << (gatherBiasPrecision + 1)));
|
||||
var biasVector = context.CompositeConstruct(pVectorType, Enumerable.Repeat(bias, pCount).ToArray());
|
||||
|
||||
var one = context.Constant(context.TypeFP32(), 1f);
|
||||
var oneVector = context.CompositeConstruct(pVectorType, Enumerable.Repeat(one, pCount).ToArray());
|
||||
|
||||
var divisor = context.FMul(
|
||||
pVectorType,
|
||||
context.ConvertSToF(pVectorType, context.ImageQuerySize(sizeType, image)),
|
||||
biasVector);
|
||||
|
||||
vector = context.FAdd(pVectorType, vector, context.FDiv(pVectorType, oneVector, divisor));
|
||||
}
|
||||
|
||||
return vector;
|
||||
}
|
||||
|
||||
SpvInstruction pCoords = AssemblePVector(pCount);
|
||||
pCoords = ScalingHelpers.ApplyScaling(context, texOp, pCoords, intCoords, isBindless, isIndexed, isArray, pCount);
|
||||
|
||||
SpvInstruction AssembleDerivativesVector(int count)
|
||||
{
|
||||
@@ -1700,8 +1657,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||
image = context.Image(imageType, image);
|
||||
}
|
||||
|
||||
pCoords = ApplyBias(pCoords, image);
|
||||
|
||||
var operands = operandsList.ToArray();
|
||||
|
||||
SpvInstruction result;
|
||||
@@ -1817,11 +1772,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||
result = context.CompositeExtract(context.TypeS32(), result, (SpvLiteralInteger)texOp.Index);
|
||||
}
|
||||
|
||||
if (texOp.Index < 2 || (type & SamplerType.Mask) == SamplerType.Texture3D)
|
||||
{
|
||||
result = ScalingHelpers.ApplyUnscaling(context, texOp.WithType(type), result, isBindless, isIndexed);
|
||||
}
|
||||
|
||||
return new OperationResult(AggregateType.S32, result);
|
||||
}
|
||||
}
|
||||
@@ -1939,7 +1889,11 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||
|
||||
SpvInstruction elemPointer;
|
||||
|
||||
if (operation.StorageKind == StorageKind.StorageBuffer)
|
||||
if (operation.StorageKind == StorageKind.GlobalMemory)
|
||||
{
|
||||
elemPointer = GetGlobalElemPointer(context, operation, context.TypeU32());
|
||||
}
|
||||
else if (operation.StorageKind == StorageKind.StorageBuffer)
|
||||
{
|
||||
elemPointer = GetStorageElemPointer(context, operation);
|
||||
}
|
||||
@@ -1966,7 +1920,11 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||
|
||||
SpvInstruction elemPointer;
|
||||
|
||||
if (operation.StorageKind == StorageKind.StorageBuffer)
|
||||
if (operation.StorageKind == StorageKind.GlobalMemory)
|
||||
{
|
||||
elemPointer = GetGlobalElemPointer(context, operation, context.TypeU32());
|
||||
}
|
||||
else if (operation.StorageKind == StorageKind.StorageBuffer)
|
||||
{
|
||||
elemPointer = GetStorageElemPointer(context, operation);
|
||||
}
|
||||
@@ -1990,12 +1948,32 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||
{
|
||||
StorageKind storageKind = operation.StorageKind;
|
||||
|
||||
SpvInstruction pointer;
|
||||
StorageClass storageClass;
|
||||
SpvInstruction baseObj;
|
||||
AggregateType varType;
|
||||
int srcIndex = 0;
|
||||
|
||||
switch (storageKind)
|
||||
{
|
||||
case StorageKind.ConstantBuffer:
|
||||
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.");
|
||||
}
|
||||
|
||||
if (!(operation.GetSource(srcIndex) is AstOperand fieldIndex) || fieldIndex.Type != OperandType.Constant)
|
||||
{
|
||||
throw new InvalidOperationException($"Second input of {operation.Inst} with {storageKind} storage must be a constant operand.");
|
||||
}
|
||||
|
||||
BufferDefinition buffer = context.Config.Properties.ConstantBuffers[bindingIndex.Value];
|
||||
StructureField field = buffer.Type.Fields[fieldIndex.Value];
|
||||
|
||||
storageClass = StorageClass.Uniform;
|
||||
varType = field.Type & AggregateType.ElementTypeMask;
|
||||
baseObj = context.ConstantBuffers[bindingIndex.Value];
|
||||
break;
|
||||
|
||||
case StorageKind.Input:
|
||||
case StorageKind.InputPerPatch:
|
||||
case StorageKind.Output:
|
||||
@@ -2038,33 +2016,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||
{
|
||||
varType = context.Config.GetFragmentOutputColorType(location);
|
||||
}
|
||||
else if (ioVariable == IoVariable.FragmentOutputIsBgra)
|
||||
{
|
||||
var pointerType = context.TypePointer(StorageClass.Uniform, context.TypeU32());
|
||||
var elemIndex = context.Get(AggregateType.S32, operation.GetSource(srcIndex++));
|
||||
pointer = context.AccessChain(pointerType, context.SupportBuffer, context.Constant(context.TypeU32(), 1), elemIndex);
|
||||
varType = AggregateType.U32;
|
||||
|
||||
break;
|
||||
}
|
||||
else if (ioVariable == IoVariable.SupportBlockRenderScale)
|
||||
{
|
||||
var pointerType = context.TypePointer(StorageClass.Uniform, context.TypeFP32());
|
||||
var elemIndex = context.Get(AggregateType.S32, operation.GetSource(srcIndex++));
|
||||
pointer = context.AccessChain(pointerType, context.SupportBuffer, context.Constant(context.TypeU32(), 4), elemIndex);
|
||||
varType = AggregateType.FP32;
|
||||
|
||||
break;
|
||||
}
|
||||
else if (ioVariable == IoVariable.SupportBlockViewInverse)
|
||||
{
|
||||
var pointerType = context.TypePointer(StorageClass.Uniform, context.TypeFP32());
|
||||
var elemIndex = context.Get(AggregateType.S32, operation.GetSource(srcIndex++));
|
||||
pointer = context.AccessChain(pointerType, context.SupportBuffer, context.Constant(context.TypeU32(), 2), elemIndex);
|
||||
varType = AggregateType.FP32;
|
||||
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
(_, varType) = IoMap.GetSpirvBuiltIn(ioVariable);
|
||||
@@ -2072,55 +2023,57 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||
|
||||
varType &= AggregateType.ElementTypeMask;
|
||||
|
||||
int inputsCount = (isStore ? operation.SourcesCount - 1 : operation.SourcesCount) - srcIndex;
|
||||
var storageClass = isOutput ? StorageClass.Output : StorageClass.Input;
|
||||
storageClass = isOutput ? StorageClass.Output : StorageClass.Input;
|
||||
|
||||
var ioDefinition = new IoDefinition(storageKind, ioVariable, location, component);
|
||||
var dict = isPerPatch
|
||||
? (isOutput ? context.OutputsPerPatch : context.InputsPerPatch)
|
||||
: (isOutput ? context.Outputs : context.Inputs);
|
||||
|
||||
SpvInstruction baseObj = dict[ioDefinition];
|
||||
SpvInstruction e0, e1, e2;
|
||||
|
||||
switch (inputsCount)
|
||||
{
|
||||
case 0:
|
||||
pointer = baseObj;
|
||||
break;
|
||||
case 1:
|
||||
e0 = context.Get(AggregateType.S32, operation.GetSource(srcIndex++));
|
||||
pointer = context.AccessChain(context.TypePointer(storageClass, context.GetType(varType)), baseObj, e0);
|
||||
break;
|
||||
case 2:
|
||||
e0 = context.Get(AggregateType.S32, operation.GetSource(srcIndex++));
|
||||
e1 = context.Get(AggregateType.S32, operation.GetSource(srcIndex++));
|
||||
pointer = context.AccessChain(context.TypePointer(storageClass, context.GetType(varType)), baseObj, e0, e1);
|
||||
break;
|
||||
case 3:
|
||||
e0 = context.Get(AggregateType.S32, operation.GetSource(srcIndex++));
|
||||
e1 = context.Get(AggregateType.S32, operation.GetSource(srcIndex++));
|
||||
e2 = context.Get(AggregateType.S32, operation.GetSource(srcIndex++));
|
||||
pointer = context.AccessChain(context.TypePointer(storageClass, context.GetType(varType)), baseObj, e0, e1, e2);
|
||||
break;
|
||||
default:
|
||||
var indexes = new SpvInstruction[inputsCount];
|
||||
int index = 0;
|
||||
|
||||
for (; index < inputsCount; srcIndex++, index++)
|
||||
{
|
||||
indexes[index] = context.Get(AggregateType.S32, operation.GetSource(srcIndex));
|
||||
}
|
||||
|
||||
pointer = context.AccessChain(context.TypePointer(storageClass, context.GetType(varType)), baseObj, indexes);
|
||||
break;
|
||||
}
|
||||
baseObj = dict[ioDefinition];
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new InvalidOperationException($"Invalid storage kind {storageKind}.");
|
||||
}
|
||||
|
||||
int inputsCount = (isStore ? operation.SourcesCount - 1 : operation.SourcesCount) - srcIndex;
|
||||
SpvInstruction e0, e1, e2;
|
||||
SpvInstruction pointer;
|
||||
|
||||
switch (inputsCount)
|
||||
{
|
||||
case 0:
|
||||
pointer = baseObj;
|
||||
break;
|
||||
case 1:
|
||||
e0 = context.Get(AggregateType.S32, operation.GetSource(srcIndex++));
|
||||
pointer = context.AccessChain(context.TypePointer(storageClass, context.GetType(varType)), baseObj, e0);
|
||||
break;
|
||||
case 2:
|
||||
e0 = context.Get(AggregateType.S32, operation.GetSource(srcIndex++));
|
||||
e1 = context.Get(AggregateType.S32, operation.GetSource(srcIndex++));
|
||||
pointer = context.AccessChain(context.TypePointer(storageClass, context.GetType(varType)), baseObj, e0, e1);
|
||||
break;
|
||||
case 3:
|
||||
e0 = context.Get(AggregateType.S32, operation.GetSource(srcIndex++));
|
||||
e1 = context.Get(AggregateType.S32, operation.GetSource(srcIndex++));
|
||||
e2 = context.Get(AggregateType.S32, operation.GetSource(srcIndex++));
|
||||
pointer = context.AccessChain(context.TypePointer(storageClass, context.GetType(varType)), baseObj, e0, e1, e2);
|
||||
break;
|
||||
default:
|
||||
var indexes = new SpvInstruction[inputsCount];
|
||||
int index = 0;
|
||||
|
||||
for (; index < inputsCount; srcIndex++, index++)
|
||||
{
|
||||
indexes[index] = context.Get(AggregateType.S32, operation.GetSource(srcIndex));
|
||||
}
|
||||
|
||||
pointer = context.AccessChain(context.TypePointer(storageClass, context.GetType(varType)), baseObj, indexes);
|
||||
break;
|
||||
}
|
||||
|
||||
if (isStore)
|
||||
{
|
||||
context.Store(pointer, context.Get(varType, operation.GetSource(srcIndex)));
|
||||
@@ -2206,6 +2159,60 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||
context.AddLabel(loopEnd);
|
||||
}
|
||||
|
||||
private static SpvInstruction GetGlobalElemPointer(CodeGenContext context, AstOperation operation, SpvInstruction elemType)
|
||||
{
|
||||
var vec4UintType = context.TypeVector(context.TypeU32(), 4);
|
||||
var ptBasePointer = context.AccessChain(context.TypePointer(StorageClass.Uniform, vec4UintType), context.ConstantBuffers[0], context.Constant(context.TypeU32(), 5));
|
||||
ptBasePointer = context.Load(vec4UintType, ptBasePointer);
|
||||
ptBasePointer = context.VectorShuffle(context.TypeVector(context.TypeU32(), 2), ptBasePointer, ptBasePointer, 0, 1);
|
||||
ptBasePointer = context.Bitcast(context.PageTablePointerType, ptBasePointer);
|
||||
|
||||
var addrLow = context.Get(AggregateType.U32, operation.GetSource(0));
|
||||
var addrHigh = context.Get(AggregateType.U32, operation.GetSource(1));
|
||||
|
||||
// uint l0 = (addrLow >> 12) & 0x3fff;
|
||||
// uint l1 = ((addrLow >> 26) & 0x3f) | ((addrHigh << 6) & 0x3fc0);
|
||||
var l0 = ShiftRightAndMask(context, addrLow, 12, 0x3fff);
|
||||
var l1 = context.BitwiseOr(context.TypeU32(),
|
||||
ShiftRightAndMask(context, addrLow, 26, 0x3f),
|
||||
ShiftLeftAndMask(context, addrHigh, 6, 0x3fc0));
|
||||
|
||||
var blockIndexPointerType = context.TypePointer(StorageClass.PhysicalStorageBuffer, context.TypeU32());
|
||||
var blockIndex = context.AccessChain(blockIndexPointerType, ptBasePointer, context.Constant(context.TypeS32(), 0), l1);
|
||||
blockIndex = context.Load(context.TypeU32(), blockIndex, MemoryAccessMask.Aligned, 4);
|
||||
|
||||
var offset = context.IAdd(context.TypeU32(), blockIndex, l0);
|
||||
|
||||
var vec2UintType = context.TypeVector(context.TypeU32(), 2);
|
||||
var vec2UintPointerType = context.TypePointer(StorageClass.PhysicalStorageBuffer, vec2UintType);
|
||||
var hostPointer = context.AccessChain(vec2UintPointerType, ptBasePointer, context.Constant(context.TypeS32(), 1), offset);
|
||||
hostPointer = context.Load(vec2UintType, hostPointer, MemoryAccessMask.Aligned, 8);
|
||||
|
||||
var pageOffset = context.BitwiseAnd(context.TypeU32(), addrLow, context.Constant(context.TypeU32(), 0xfff));
|
||||
|
||||
var hostPointerLow = context.IAdd(context.TypeU32(), context.CompositeExtract(context.TypeU32(), hostPointer, 0), pageOffset);
|
||||
var hostPointerHigh = context.CompositeExtract(context.TypeU32(), hostPointer, 1);
|
||||
|
||||
hostPointer = context.CompositeConstruct(vec2UintType, hostPointerLow, hostPointerHigh);
|
||||
|
||||
var elemStructType = context.TypeStruct(false, elemType);
|
||||
var elemStructPointerType = context.TypePointer(StorageClass.PhysicalStorageBuffer, elemStructType);
|
||||
var elemPointerType = context.TypePointer(StorageClass.PhysicalStorageBuffer, elemType);
|
||||
return context.AccessChain(elemPointerType, context.Bitcast(elemStructPointerType, hostPointer), context.Constant(context.TypeS32(), 0));
|
||||
}
|
||||
|
||||
private static SpvInstruction ShiftLeftAndMask(CodeGenContext context, SpvInstruction value, int shift, int mask)
|
||||
{
|
||||
value = context.ShiftLeftLogical(context.TypeU32(), value, context.Constant(context.TypeS32(), shift));
|
||||
return context.BitwiseAnd(context.TypeU32(), value, context.Constant(context.TypeU32(), mask));
|
||||
}
|
||||
|
||||
private static SpvInstruction ShiftRightAndMask(CodeGenContext context, SpvInstruction value, int shift, int mask)
|
||||
{
|
||||
value = context.ShiftRightLogical(context.TypeU32(), value, context.Constant(context.TypeS32(), shift));
|
||||
return context.BitwiseAnd(context.TypeU32(), value, context.Constant(context.TypeU32(), mask));
|
||||
}
|
||||
|
||||
private static OperationResult GetZeroOperationResult(
|
||||
CodeGenContext context,
|
||||
AstTextureOperation texOp,
|
||||
@@ -2336,7 +2343,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||
{
|
||||
var result = emitF(context.TypeFP64(), context.GetFP64(src1), context.GetFP64(src2));
|
||||
|
||||
if (!context.Config.GpuAccessor.QueryHostReducedPrecision())
|
||||
if (!context.Config.GpuAccessor.QueryHostReducedPrecision() || operation.ForcePrecise)
|
||||
{
|
||||
context.Decorate(result, Decoration.NoContraction);
|
||||
}
|
||||
@@ -2347,7 +2354,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||
{
|
||||
var result = emitF(context.TypeFP32(), context.GetFP32(src1), context.GetFP32(src2));
|
||||
|
||||
if (!context.Config.GpuAccessor.QueryHostReducedPrecision())
|
||||
if (!context.Config.GpuAccessor.QueryHostReducedPrecision() || operation.ForcePrecise)
|
||||
{
|
||||
context.Decorate(result, Decoration.NoContraction);
|
||||
}
|
||||
@@ -2407,7 +2414,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||
{
|
||||
var result = emitF(context.TypeFP64(), context.GetFP64(src1), context.GetFP64(src2), context.GetFP64(src3));
|
||||
|
||||
if (!context.Config.GpuAccessor.QueryHostReducedPrecision())
|
||||
if (!context.Config.GpuAccessor.QueryHostReducedPrecision() || operation.ForcePrecise)
|
||||
{
|
||||
context.Decorate(result, Decoration.NoContraction);
|
||||
}
|
||||
@@ -2418,7 +2425,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||
{
|
||||
var result = emitF(context.TypeFP32(), context.GetFP32(src1), context.GetFP32(src2), context.GetFP32(src3));
|
||||
|
||||
if (!context.Config.GpuAccessor.QueryHostReducedPrecision())
|
||||
if (!context.Config.GpuAccessor.QueryHostReducedPrecision() || operation.ForcePrecise)
|
||||
{
|
||||
context.Decorate(result, Decoration.NoContraction);
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user