Compare commits
41 Commits
Author | SHA1 | Date | |
---|---|---|---|
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 |
8
.github/workflows/build.yml
vendored
8
.github/workflows/build.yml
vendored
@ -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 ]
|
||||
|
2
.github/workflows/flatpak.yml
vendored
2
.github/workflows/flatpak.yml
vendored
@ -12,7 +12,7 @@ concurrency: flatpak-release
|
||||
|
||||
jobs:
|
||||
release:
|
||||
timeout-minutes: 35
|
||||
timeout-minutes: ${{ fromJSON(vars.JOB_TIMEOUT) }}
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
env:
|
||||
|
2
.github/workflows/nightly_pr_comment.yml
vendored
2
.github/workflows/nightly_pr_comment.yml
vendored
@ -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:
|
||||
|
6
.github/workflows/release.yml
vendored
6
.github/workflows/release.yml
vendored
@ -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.0" />
|
||||
<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"
|
||||
|
@ -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."
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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 is { IsActive: true } and MainWindow window && window.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)
|
||||
|
@ -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()
|
@ -1529,6 +1529,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;
|
||||
});
|
||||
}
|
||||
|
||||
|
93
src/Ryujinx.Ava/UI/ViewModels/MotionInputViewModel.cs
Normal file
93
src/Ryujinx.Ava/UI/ViewModels/MotionInputViewModel.cs
Normal file
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
27
src/Ryujinx.Ava/UI/ViewModels/RumbleInputViewModel.cs
Normal file
27
src/Ryujinx.Ava/UI/ViewModels/RumbleInputViewModel.cs
Normal file
@ -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)
|
||||
{
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -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();
|
||||
|
@ -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>
|
@ -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();
|
@ -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>
|
@ -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();
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -1,5 +1,6 @@
|
||||
using Ryujinx.Common.Configuration;
|
||||
using System;
|
||||
using System.Threading;
|
||||
|
||||
namespace Ryujinx.Graphics.GAL
|
||||
{
|
||||
@ -52,7 +53,7 @@ namespace Ryujinx.Graphics.GAL
|
||||
|
||||
void ResetCounter(CounterType type);
|
||||
|
||||
void RunLoop(Action gpuLoop)
|
||||
void RunLoop(ThreadStart gpuLoop)
|
||||
{
|
||||
gpuLoop();
|
||||
}
|
||||
|
@ -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();
|
||||
@ -488,12 +485,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;
|
||||
|
179
src/Ryujinx.Graphics.GAL/ResourceLayout.cs
Normal file
179
src/Ryujinx.Graphics.GAL/ResourceLayout.cs
Normal file
@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
@ -771,7 +771,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>
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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 = 4646;
|
||||
private const uint CodeGenVersion = 5027;
|
||||
|
||||
private const string SharedTocFileName = "shared.toc";
|
||||
private const string SharedDataFileName = "shared.data";
|
||||
@ -368,12 +368,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 +380,8 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache
|
||||
}
|
||||
else
|
||||
{
|
||||
bool hasFragmentShader = shaders.Length > 5 && shaders[5] != null;
|
||||
|
||||
hostProgram = context.Renderer.LoadProgramBinary(hostCode, hasFragmentShader, shaderInfo);
|
||||
}
|
||||
|
||||
|
@ -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>());
|
||||
}
|
||||
}
|
||||
}
|
@ -17,8 +17,6 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
private readonly ResourceCounts _resourceCounts;
|
||||
private readonly int _stageIndex;
|
||||
|
||||
private readonly int[] _constantBufferBindings;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new GPU accessor.
|
||||
/// </summary>
|
||||
@ -28,12 +26,6 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
_context = context;
|
||||
_resourceCounts = resourceCounts;
|
||||
_stageIndex = stageIndex;
|
||||
|
||||
if (context.Capabilities.Api != TargetApi.Vulkan)
|
||||
{
|
||||
_constantBufferBindings = new int[Constants.TotalGpUniformBuffers];
|
||||
_constantBufferBindings.AsSpan().Fill(-1);
|
||||
}
|
||||
}
|
||||
|
||||
public int QueryBindingConstantBuffer(int index)
|
||||
@ -45,15 +37,7 @@ namespace Ryujinx.Graphics.Gpu.Shader
|
||||
}
|
||||
else
|
||||
{
|
||||
int binding = _constantBufferBindings[index];
|
||||
|
||||
if (binding < 0)
|
||||
{
|
||||
binding = _resourceCounts.UniformBuffersCount++;
|
||||
_constantBufferBindings[index] = binding;
|
||||
}
|
||||
|
||||
return binding;
|
||||
return _resourceCounts.UniformBuffersCount++;
|
||||
}
|
||||
}
|
||||
|
||||
@ -110,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
|
||||
@ -165,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>
|
||||
|
@ -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.
|
||||
|
260
src/Ryujinx.Graphics.Gpu/Shader/ShaderInfoBuilder.cs
Normal file
260
src/Ryujinx.Graphics.Gpu/Shader/ShaderInfoBuilder.cs
Normal file
@ -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;
|
||||
}
|
||||
|
||||
|
@ -163,6 +163,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,
|
||||
|
@ -239,33 +239,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.
|
||||
}
|
||||
|
||||
if (context.Config.UsedFeatures.HasFlag(FeatureFlags.IntegerSampling) && scaleElements != 0)
|
||||
{
|
||||
AppendHelperFunction(context, $"Ryujinx.Graphics.Shader/CodeGen/Glsl/HelperFunctions/TexelFetchScale_{stage}.glsl");
|
||||
context.AppendLine();
|
||||
}
|
||||
}
|
||||
context.AppendLine("layout(early_fragment_tests) in;");
|
||||
context.AppendLine();
|
||||
}
|
||||
|
||||
if ((info.HelperFunctionsMask & HelperFunctionsMask.AtomicMinMaxS32Shared) != 0)
|
||||
|
@ -1,19 +0,0 @@
|
||||
ivec2 Helper_TexelFetchScale(ivec2 inputVec, int samplerIndex)
|
||||
{
|
||||
float scale = support_buffer.s_render_scale[1 + samplerIndex];
|
||||
if (scale == 1.0)
|
||||
{
|
||||
return inputVec;
|
||||
}
|
||||
return ivec2(vec2(inputVec) * scale);
|
||||
}
|
||||
|
||||
int Helper_TextureSizeUnscale(int size, int samplerIndex)
|
||||
{
|
||||
float scale = support_buffer.s_render_scale[1 + 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 = support_buffer.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(support_buffer.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(support_buffer.s_render_scale[1 + samplerIndex + support_buffer.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(support_buffer.s_render_scale[1 + samplerIndex + support_buffer.s_frag_scale_count]);
|
||||
if (scale == 1.0)
|
||||
{
|
||||
return size;
|
||||
}
|
||||
return int(float(size) / scale);
|
||||
}
|
@ -101,6 +101,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);
|
||||
|
@ -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
|
||||
{
|
||||
@ -584,53 +560,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)
|
||||
{
|
||||
@ -750,7 +680,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;
|
||||
|
||||
@ -767,14 +697,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;
|
||||
}
|
||||
}
|
||||
|
@ -36,6 +36,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>();
|
||||
|
@ -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;
|
||||
|
||||
@ -114,6 +113,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);
|
||||
@ -744,8 +744,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);
|
||||
@ -1040,6 +1038,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);
|
||||
@ -1101,7 +1104,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;
|
||||
}
|
||||
|
||||
@ -1439,35 +1450,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)
|
||||
{
|
||||
@ -1638,8 +1621,6 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||
image = context.Image(imageType, image);
|
||||
}
|
||||
|
||||
pCoords = ApplyBias(pCoords, image);
|
||||
|
||||
var operands = operandsList.ToArray();
|
||||
|
||||
SpvInstruction result;
|
||||
@ -1755,11 +1736,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);
|
||||
}
|
||||
}
|
||||
@ -2269,7 +2245,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);
|
||||
}
|
||||
@ -2280,7 +2256,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);
|
||||
}
|
||||
@ -2340,7 +2316,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);
|
||||
}
|
||||
@ -2351,7 +2327,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);
|
||||
}
|
||||
|
@ -1,227 +0,0 @@
|
||||
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
|
||||
using Ryujinx.Graphics.Shader.StructuredIr;
|
||||
using Ryujinx.Graphics.Shader.Translation;
|
||||
using static Spv.Specification;
|
||||
|
||||
namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||
{
|
||||
using SpvInstruction = Spv.Generator.Instruction;
|
||||
|
||||
static class ScalingHelpers
|
||||
{
|
||||
public static SpvInstruction ApplyScaling(
|
||||
CodeGenContext context,
|
||||
AstTextureOperation texOp,
|
||||
SpvInstruction vector,
|
||||
bool intCoords,
|
||||
bool isBindless,
|
||||
bool isIndexed,
|
||||
bool isArray,
|
||||
int pCount)
|
||||
{
|
||||
if (intCoords)
|
||||
{
|
||||
if (context.Config.Stage.SupportsRenderScale() &&
|
||||
!isBindless &&
|
||||
!isIndexed)
|
||||
{
|
||||
int index = texOp.Inst == Instruction.ImageLoad
|
||||
? context.Config.GetTextureDescriptors().Length + context.Config.FindImageDescriptorIndex(texOp)
|
||||
: context.Config.FindTextureDescriptorIndex(texOp);
|
||||
|
||||
if (pCount == 3 && isArray)
|
||||
{
|
||||
return ApplyScaling2DArray(context, vector, index);
|
||||
}
|
||||
else if (pCount == 2 && !isArray)
|
||||
{
|
||||
return ApplyScaling2D(context, vector, index);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return vector;
|
||||
}
|
||||
|
||||
private static SpvInstruction ApplyScaling2DArray(CodeGenContext context, SpvInstruction vector, int index)
|
||||
{
|
||||
// The array index is not scaled, just x and y.
|
||||
var vectorXY = context.VectorShuffle(context.TypeVector(context.TypeS32(), 2), vector, vector, 0, 1);
|
||||
var vectorZ = context.CompositeExtract(context.TypeS32(), vector, 2);
|
||||
var vectorXYScaled = ApplyScaling2D(context, vectorXY, index);
|
||||
var vectorScaled = context.CompositeConstruct(context.TypeVector(context.TypeS32(), 3), vectorXYScaled, vectorZ);
|
||||
|
||||
return vectorScaled;
|
||||
}
|
||||
|
||||
private static SpvInstruction ApplyScaling2D(CodeGenContext context, SpvInstruction vector, int index)
|
||||
{
|
||||
var pointerType = context.TypePointer(StorageClass.Uniform, context.TypeFP32());
|
||||
var fieldIndex = context.Constant(context.TypeU32(), 4);
|
||||
var scaleIndex = context.Constant(context.TypeU32(), index);
|
||||
|
||||
if (context.Config.Stage == ShaderStage.Vertex)
|
||||
{
|
||||
var scaleCountPointerType = context.TypePointer(StorageClass.Uniform, context.TypeS32());
|
||||
var scaleCountElemPointer = context.AccessChain(scaleCountPointerType, context.ConstantBuffers[0], context.Constant(context.TypeU32(), 3));
|
||||
var scaleCount = context.Load(context.TypeS32(), scaleCountElemPointer);
|
||||
|
||||
scaleIndex = context.IAdd(context.TypeU32(), scaleIndex, scaleCount);
|
||||
}
|
||||
|
||||
scaleIndex = context.IAdd(context.TypeU32(), scaleIndex, context.Constant(context.TypeU32(), 1));
|
||||
|
||||
var scaleElemPointer = context.AccessChain(pointerType, context.ConstantBuffers[0], fieldIndex, scaleIndex);
|
||||
var scale = context.Load(context.TypeFP32(), scaleElemPointer);
|
||||
|
||||
var ivector2Type = context.TypeVector(context.TypeS32(), 2);
|
||||
var localVector = context.CoordTemp;
|
||||
|
||||
var passthrough = context.FOrdEqual(context.TypeBool(), scale, context.Constant(context.TypeFP32(), 1f));
|
||||
|
||||
var mergeLabel = context.Label();
|
||||
|
||||
if (context.Config.Stage == ShaderStage.Fragment)
|
||||
{
|
||||
var scaledInterpolatedLabel = context.Label();
|
||||
var scaledNoInterpolationLabel = context.Label();
|
||||
|
||||
var needsInterpolation = context.FOrdLessThan(context.TypeBool(), scale, context.Constant(context.TypeFP32(), 0f));
|
||||
|
||||
context.SelectionMerge(mergeLabel, SelectionControlMask.MaskNone);
|
||||
context.BranchConditional(needsInterpolation, scaledInterpolatedLabel, scaledNoInterpolationLabel);
|
||||
|
||||
// scale < 0.0
|
||||
context.AddLabel(scaledInterpolatedLabel);
|
||||
|
||||
ApplyScalingInterpolated(context, localVector, vector, scale);
|
||||
context.Branch(mergeLabel);
|
||||
|
||||
// scale >= 0.0
|
||||
context.AddLabel(scaledNoInterpolationLabel);
|
||||
|
||||
ApplyScalingNoInterpolation(context, localVector, vector, scale);
|
||||
context.Branch(mergeLabel);
|
||||
|
||||
context.AddLabel(mergeLabel);
|
||||
|
||||
var passthroughLabel = context.Label();
|
||||
var finalMergeLabel = context.Label();
|
||||
|
||||
context.SelectionMerge(finalMergeLabel, SelectionControlMask.MaskNone);
|
||||
context.BranchConditional(passthrough, passthroughLabel, finalMergeLabel);
|
||||
|
||||
context.AddLabel(passthroughLabel);
|
||||
|
||||
context.Store(localVector, vector);
|
||||
context.Branch(finalMergeLabel);
|
||||
|
||||
context.AddLabel(finalMergeLabel);
|
||||
|
||||
return context.Load(ivector2Type, localVector);
|
||||
}
|
||||
else
|
||||
{
|
||||
var passthroughLabel = context.Label();
|
||||
var scaledLabel = context.Label();
|
||||
|
||||
context.SelectionMerge(mergeLabel, SelectionControlMask.MaskNone);
|
||||
context.BranchConditional(passthrough, passthroughLabel, scaledLabel);
|
||||
|
||||
// scale == 1.0
|
||||
context.AddLabel(passthroughLabel);
|
||||
|
||||
context.Store(localVector, vector);
|
||||
context.Branch(mergeLabel);
|
||||
|
||||
// scale != 1.0
|
||||
context.AddLabel(scaledLabel);
|
||||
|
||||
ApplyScalingNoInterpolation(context, localVector, vector, scale);
|
||||
context.Branch(mergeLabel);
|
||||
|
||||
context.AddLabel(mergeLabel);
|
||||
|
||||
return context.Load(ivector2Type, localVector);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ApplyScalingInterpolated(CodeGenContext context, SpvInstruction output, SpvInstruction vector, SpvInstruction scale)
|
||||
{
|
||||
var vector2Type = context.TypeVector(context.TypeFP32(), 2);
|
||||
|
||||
var scaleNegated = context.FNegate(context.TypeFP32(), scale);
|
||||
var scaleVector = context.CompositeConstruct(vector2Type, scaleNegated, scaleNegated);
|
||||
|
||||
var vectorFloat = context.ConvertSToF(vector2Type, vector);
|
||||
var vectorScaled = context.VectorTimesScalar(vector2Type, vectorFloat, scaleNegated);
|
||||
|
||||
var fragCoordPointer = context.Inputs[new IoDefinition(StorageKind.Input, IoVariable.FragmentCoord)];
|
||||
var fragCoord = context.Load(context.TypeVector(context.TypeFP32(), 4), fragCoordPointer);
|
||||
var fragCoordXY = context.VectorShuffle(vector2Type, fragCoord, fragCoord, 0, 1);
|
||||
|
||||
var scaleMod = context.FMod(vector2Type, fragCoordXY, scaleVector);
|
||||
var vectorInterpolated = context.FAdd(vector2Type, vectorScaled, scaleMod);
|
||||
|
||||
context.Store(output, context.ConvertFToS(context.TypeVector(context.TypeS32(), 2), vectorInterpolated));
|
||||
}
|
||||
|
||||
private static void ApplyScalingNoInterpolation(CodeGenContext context, SpvInstruction output, SpvInstruction vector, SpvInstruction scale)
|
||||
{
|
||||
if (context.Config.Stage == ShaderStage.Vertex)
|
||||
{
|
||||
scale = context.GlslFAbs(context.TypeFP32(), scale);
|
||||
}
|
||||
|
||||
var vector2Type = context.TypeVector(context.TypeFP32(), 2);
|
||||
|
||||
var vectorFloat = context.ConvertSToF(vector2Type, vector);
|
||||
var vectorScaled = context.VectorTimesScalar(vector2Type, vectorFloat, scale);
|
||||
|
||||
context.Store(output, context.ConvertFToS(context.TypeVector(context.TypeS32(), 2), vectorScaled));
|
||||
}
|
||||
|
||||
public static SpvInstruction ApplyUnscaling(
|
||||
CodeGenContext context,
|
||||
AstTextureOperation texOp,
|
||||
SpvInstruction size,
|
||||
bool isBindless,
|
||||
bool isIndexed)
|
||||
{
|
||||
if (context.Config.Stage.SupportsRenderScale() &&
|
||||
!isBindless &&
|
||||
!isIndexed)
|
||||
{
|
||||
int index = context.Config.FindTextureDescriptorIndex(texOp);
|
||||
|
||||
var pointerType = context.TypePointer(StorageClass.Uniform, context.TypeFP32());
|
||||
var fieldIndex = context.Constant(context.TypeU32(), 4);
|
||||
var scaleIndex = context.Constant(context.TypeU32(), index);
|
||||
|
||||
if (context.Config.Stage == ShaderStage.Vertex)
|
||||
{
|
||||
var scaleCountPointerType = context.TypePointer(StorageClass.Uniform, context.TypeS32());
|
||||
var scaleCountElemPointer = context.AccessChain(scaleCountPointerType, context.ConstantBuffers[0], context.Constant(context.TypeU32(), 3));
|
||||
var scaleCount = context.Load(context.TypeS32(), scaleCountElemPointer);
|
||||
|
||||
scaleIndex = context.IAdd(context.TypeU32(), scaleIndex, scaleCount);
|
||||
}
|
||||
|
||||
scaleIndex = context.IAdd(context.TypeU32(), scaleIndex, context.Constant(context.TypeU32(), 1));
|
||||
|
||||
var scaleElemPointer = context.AccessChain(pointerType, context.ConstantBuffers[0], fieldIndex, scaleIndex);
|
||||
var scale = context.GlslFAbs(context.TypeFP32(), context.Load(context.TypeFP32(), scaleElemPointer));
|
||||
|
||||
var passthrough = context.FOrdEqual(context.TypeBool(), scale, context.Constant(context.TypeFP32(), 1f));
|
||||
|
||||
var sizeFloat = context.ConvertSToF(context.TypeFP32(), size);
|
||||
var sizeUnscaled = context.FDiv(context.TypeFP32(), sizeFloat, scale);
|
||||
var sizeUnscaledInt = context.ConvertFToS(context.TypeS32(), sizeUnscaled);
|
||||
|
||||
return context.Select(context.TypeS32(), passthrough, size, sizeUnscaledInt);
|
||||
}
|
||||
|
||||
return size;
|
||||
}
|
||||
}
|
||||
}
|
@ -67,6 +67,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||
public readonly FuncBinaryInstruction GlslSMax;
|
||||
public readonly FuncBinaryInstruction GlslFMin;
|
||||
public readonly FuncBinaryInstruction GlslSMin;
|
||||
public readonly FuncBinaryInstruction FMod;
|
||||
public readonly FuncBinaryInstruction FMul;
|
||||
public readonly FuncBinaryInstruction IMul;
|
||||
public readonly FuncBinaryInstruction FSub;
|
||||
@ -174,6 +175,7 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||
GlslSMax = context.GlslSMax;
|
||||
GlslFMin = context.GlslFMin;
|
||||
GlslSMin = context.GlslSMin;
|
||||
FMod = context.FMod;
|
||||
FMul = context.FMul;
|
||||
IMul = context.IMul;
|
||||
FSub = context.FSub;
|
||||
|
@ -144,10 +144,9 @@ namespace Ryujinx.Graphics.Shader.CodeGen.Spirv
|
||||
|
||||
private static void Generate(CodeGenContext context, StructuredProgramInfo info, int funcIndex)
|
||||
{
|
||||
var function = info.Functions[funcIndex];
|
||||
|
||||
(_, var spvFunc) = context.GetFunction(funcIndex);
|
||||
(var function, var spvFunc) = context.GetFunction(funcIndex);
|
||||
|
||||
context.CurrentFunction = function;
|
||||
context.AddFunction(spvFunc);
|
||||
context.StartFunction();
|
||||
|
||||
|
@ -367,6 +367,15 @@ namespace Ryujinx.Graphics.Shader
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries whether the host supports depth clip control.
|
||||
/// </summary>
|
||||
/// <returns>True if the GPU and driver supports depth clip control, false otherwise</returns>
|
||||
bool QueryHostSupportsDepthClipControl()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Queries the point size from the GPU state, used when it is not explicitly set on the shader.
|
||||
/// </summary>
|
||||
|
@ -187,27 +187,6 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
||||
context.Config.GpuAccessor.Log("Shader instruction Longjmp is not implemented.");
|
||||
}
|
||||
|
||||
public static void P2rR(EmitterContext context)
|
||||
{
|
||||
InstP2rR op = context.GetOp<InstP2rR>();
|
||||
|
||||
context.Config.GpuAccessor.Log("Shader instruction P2rR is not implemented.");
|
||||
}
|
||||
|
||||
public static void P2rI(EmitterContext context)
|
||||
{
|
||||
InstP2rI op = context.GetOp<InstP2rI>();
|
||||
|
||||
context.Config.GpuAccessor.Log("Shader instruction P2rI is not implemented.");
|
||||
}
|
||||
|
||||
public static void P2rC(EmitterContext context)
|
||||
{
|
||||
InstP2rC op = context.GetOp<InstP2rC>();
|
||||
|
||||
context.Config.GpuAccessor.Log("Shader instruction P2rC is not implemented.");
|
||||
}
|
||||
|
||||
public static void Pexit(EmitterContext context)
|
||||
{
|
||||
InstPexit op = context.GetOp<InstPexit>();
|
||||
|
@ -209,21 +209,15 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
||||
return context.ICompareNotEqual(context.BitwiseAnd(value, Const(1 << bit)), Const(0));
|
||||
}
|
||||
|
||||
if (ccpr)
|
||||
int count = ccpr ? RegisterConsts.FlagsCount : RegisterConsts.PredsCount;
|
||||
RegisterType type = ccpr ? RegisterType.Flag : RegisterType.Predicate;
|
||||
int shift = (int)byteSel * 8;
|
||||
|
||||
for (int bit = 0; bit < count; bit++)
|
||||
{
|
||||
// TODO: Support Register to condition code flags copy.
|
||||
context.Config.GpuAccessor.Log("R2P.CC not implemented.");
|
||||
}
|
||||
else
|
||||
{
|
||||
int shift = (int)byteSel * 8;
|
||||
|
||||
for (int bit = 0; bit < RegisterConsts.PredsCount; bit++)
|
||||
{
|
||||
Operand pred = Register(bit, RegisterType.Predicate);
|
||||
Operand res = context.ConditionalSelect(Test(mask, bit), Test(value, bit + shift), pred);
|
||||
context.Copy(pred, res);
|
||||
}
|
||||
Operand flag = Register(bit, type);
|
||||
Operand res = context.ConditionalSelect(Test(mask, bit), Test(value, bit + shift), flag);
|
||||
context.Copy(flag, res);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
using Ryujinx.Graphics.Shader.Decoders;
|
||||
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
|
||||
using Ryujinx.Graphics.Shader.Translation;
|
||||
|
||||
using static Ryujinx.Graphics.Shader.Instructions.InstEmitAluHelper;
|
||||
using static Ryujinx.Graphics.Shader.Instructions.InstEmitHelper;
|
||||
using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
|
||||
@ -50,5 +49,68 @@ namespace Ryujinx.Graphics.Shader.Instructions
|
||||
context.Copy(Register(op.DestPred, RegisterType.Predicate), p0Res);
|
||||
context.Copy(Register(op.DestPredInv, RegisterType.Predicate), p1Res);
|
||||
}
|
||||
|
||||
public static void P2rC(EmitterContext context)
|
||||
{
|
||||
InstP2rC op = context.GetOp<InstP2rC>();
|
||||
|
||||
Operand srcA = GetSrcReg(context, op.SrcA);
|
||||
Operand dest = GetSrcReg(context, op.Dest);
|
||||
Operand mask = GetSrcCbuf(context, op.CbufSlot, op.CbufOffset);
|
||||
|
||||
EmitP2r(context, srcA, dest, mask, op.ByteSel, op.Ccpr);
|
||||
}
|
||||
|
||||
public static void P2rI(EmitterContext context)
|
||||
{
|
||||
InstP2rI op = context.GetOp<InstP2rI>();
|
||||
|
||||
Operand srcA = GetSrcReg(context, op.SrcA);
|
||||
Operand dest = GetSrcReg(context, op.Dest);
|
||||
Operand mask = GetSrcImm(context, op.Imm20);
|
||||
|
||||
EmitP2r(context, srcA, dest, mask, op.ByteSel, op.Ccpr);
|
||||
}
|
||||
|
||||
public static void P2rR(EmitterContext context)
|
||||
{
|
||||
InstP2rR op = context.GetOp<InstP2rR>();
|
||||
|
||||
Operand srcA = GetSrcReg(context, op.SrcA);
|
||||
Operand dest = GetSrcReg(context, op.Dest);
|
||||
Operand mask = GetSrcReg(context, op.SrcB);
|
||||
|
||||
EmitP2r(context, srcA, dest, mask, op.ByteSel, op.Ccpr);
|
||||
}
|
||||
|
||||
private static void EmitP2r(
|
||||
EmitterContext context,
|
||||
Operand srcA,
|
||||
Operand dest,
|
||||
Operand mask,
|
||||
ByteSel byteSel,
|
||||
bool ccpr)
|
||||
{
|
||||
int count = ccpr ? RegisterConsts.FlagsCount : RegisterConsts.PredsCount;
|
||||
int shift = (int)byteSel * 8;
|
||||
mask = context.BitwiseAnd(mask, Const(0xff));
|
||||
|
||||
Operand insert = Const(0);
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
Operand condition = ccpr
|
||||
? Register(i, RegisterType.Flag)
|
||||
: Register(i, RegisterType.Predicate);
|
||||
|
||||
Operand bit = context.ConditionalSelect(condition, Const(1 << (i + shift)), Const(0));
|
||||
insert = context.BitwiseOr(insert, bit);
|
||||
}
|
||||
|
||||
Operand maskShifted = context.ShiftLeft(mask, Const(shift));
|
||||
Operand masked = context.BitwiseAnd(srcA, context.BitwiseNot(maskShifted));
|
||||
Operand res = context.BitwiseOr(masked, context.BitwiseAnd(insert, maskShifted));
|
||||
|
||||
context.Copy(dest, res);
|
||||
}
|
||||
}
|
||||
}
|
@ -97,6 +97,7 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
|
||||
MemoryBarrier,
|
||||
Minimum,
|
||||
MinimumU32,
|
||||
Modulo,
|
||||
Multiply,
|
||||
MultiplyHighS32,
|
||||
MultiplyHighU32,
|
||||
|
@ -8,6 +8,8 @@ namespace Ryujinx.Graphics.Shader.IntermediateRepresentation
|
||||
public Instruction Inst { get; private set; }
|
||||
public StorageKind StorageKind { get; }
|
||||
|
||||
public bool ForcePrecise { get; set; }
|
||||
|
||||
private Operand[] _dests;
|
||||
|
||||
public Operand Dest
|
||||
|
@ -4,10 +4,6 @@
|
||||
<TargetFramework>net7.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Remove="CodeGen\Glsl\HelperFunctions\TexelFetchScale_vp.glsl" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Ryujinx.Common\Ryujinx.Common.csproj" />
|
||||
<ProjectReference Include="..\Spv.Generator\Spv.Generator.csproj" />
|
||||
@ -25,9 +21,6 @@
|
||||
<EmbeddedResource Include="CodeGen\Glsl\HelperFunctions\StoreSharedSmallInt.glsl" />
|
||||
<EmbeddedResource Include="CodeGen\Glsl\HelperFunctions\StoreStorageSmallInt.glsl" />
|
||||
<EmbeddedResource Include="CodeGen\Glsl\HelperFunctions\SwizzleAdd.glsl" />
|
||||
<EmbeddedResource Include="CodeGen\Glsl\HelperFunctions\TexelFetchScale_vp.glsl" />
|
||||
<EmbeddedResource Include="CodeGen\Glsl\HelperFunctions\TexelFetchScale_fp.glsl" />
|
||||
<EmbeddedResource Include="CodeGen\Glsl\HelperFunctions\TexelFetchScale_cp.glsl" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
@ -10,6 +10,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
|
||||
{
|
||||
public Instruction Inst { get; }
|
||||
public StorageKind StorageKind { get; }
|
||||
public bool ForcePrecise { get; }
|
||||
|
||||
public int Index { get; }
|
||||
|
||||
@ -17,10 +18,11 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
|
||||
|
||||
public int SourcesCount => _sources.Length;
|
||||
|
||||
public AstOperation(Instruction inst, StorageKind storageKind, IAstNode[] sources, int sourcesCount)
|
||||
public AstOperation(Instruction inst, StorageKind storageKind, bool forcePrecise, IAstNode[] sources, int sourcesCount)
|
||||
{
|
||||
Inst = inst;
|
||||
StorageKind = storageKind;
|
||||
ForcePrecise = forcePrecise;
|
||||
_sources = sources;
|
||||
|
||||
for (int index = 0; index < sources.Length; index++)
|
||||
@ -38,12 +40,18 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
|
||||
Index = 0;
|
||||
}
|
||||
|
||||
public AstOperation(Instruction inst, StorageKind storageKind, int index, IAstNode[] sources, int sourcesCount) : this(inst, storageKind, sources, sourcesCount)
|
||||
public AstOperation(
|
||||
Instruction inst,
|
||||
StorageKind storageKind,
|
||||
bool forcePrecise,
|
||||
int index,
|
||||
IAstNode[] sources,
|
||||
int sourcesCount) : this(inst, storageKind, forcePrecise, sources, sourcesCount)
|
||||
{
|
||||
Index = index;
|
||||
}
|
||||
|
||||
public AstOperation(Instruction inst, params IAstNode[] sources) : this(inst, StorageKind.None, sources, sources.Length)
|
||||
public AstOperation(Instruction inst, params IAstNode[] sources) : this(inst, StorageKind.None, false, sources, sources.Length)
|
||||
{
|
||||
}
|
||||
|
||||
|
@ -19,7 +19,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
|
||||
int cbufSlot,
|
||||
int handle,
|
||||
int index,
|
||||
params IAstNode[] sources) : base(inst, StorageKind.None, index, sources, sources.Length)
|
||||
params IAstNode[] sources) : base(inst, StorageKind.None, false, index, sources, sources.Length)
|
||||
{
|
||||
Type = type;
|
||||
Format = format;
|
||||
@ -27,10 +27,5 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
|
||||
CbufSlot = cbufSlot;
|
||||
Handle = handle;
|
||||
}
|
||||
|
||||
public AstTextureOperation WithType(SamplerType type)
|
||||
{
|
||||
return new AstTextureOperation(Inst, type, Format, Flags, CbufSlot, Handle, Index);
|
||||
}
|
||||
}
|
||||
}
|
@ -104,6 +104,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
|
||||
Add(Instruction.MaximumU32, AggregateType.U32, AggregateType.U32, AggregateType.U32);
|
||||
Add(Instruction.Minimum, AggregateType.Scalar, AggregateType.Scalar, AggregateType.Scalar);
|
||||
Add(Instruction.MinimumU32, AggregateType.U32, AggregateType.U32, AggregateType.U32);
|
||||
Add(Instruction.Modulo, AggregateType.Scalar, AggregateType.Scalar, AggregateType.Scalar);
|
||||
Add(Instruction.Multiply, AggregateType.Scalar, AggregateType.Scalar, AggregateType.Scalar);
|
||||
Add(Instruction.MultiplyHighS32, AggregateType.S32, AggregateType.S32, AggregateType.S32);
|
||||
Add(Instruction.MultiplyHighU32, AggregateType.U32, AggregateType.U32, AggregateType.U32);
|
||||
|
@ -8,11 +8,11 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
|
||||
{
|
||||
static class StructuredProgram
|
||||
{
|
||||
public static StructuredProgramInfo MakeStructuredProgram(Function[] functions, ShaderConfig config)
|
||||
public static StructuredProgramInfo MakeStructuredProgram(IReadOnlyList<Function> functions, ShaderConfig config)
|
||||
{
|
||||
StructuredProgramContext context = new StructuredProgramContext(config);
|
||||
|
||||
for (int funcIndex = 0; funcIndex < functions.Length; funcIndex++)
|
||||
for (int funcIndex = 0; funcIndex < functions.Count; funcIndex++)
|
||||
{
|
||||
Function function = functions[funcIndex];
|
||||
|
||||
@ -156,7 +156,13 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
|
||||
}
|
||||
else
|
||||
{
|
||||
source = new AstOperation(inst, operation.StorageKind, operation.Index, sources, operation.SourcesCount);
|
||||
source = new AstOperation(
|
||||
inst,
|
||||
operation.StorageKind,
|
||||
operation.ForcePrecise,
|
||||
operation.Index,
|
||||
sources,
|
||||
operation.SourcesCount);
|
||||
}
|
||||
|
||||
AggregateType destElemType = destType;
|
||||
@ -179,7 +185,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
|
||||
|
||||
dest.VarType = destElemType;
|
||||
|
||||
context.AddNode(new AstAssignment(dest, new AstOperation(Instruction.VectorExtract, StorageKind.None, new[] { destVec, index }, 2)));
|
||||
context.AddNode(new AstAssignment(dest, new AstOperation(Instruction.VectorExtract, StorageKind.None, false, new[] { destVec, index }, 2)));
|
||||
}
|
||||
}
|
||||
else if (operation.Dest != null)
|
||||
@ -227,7 +233,13 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
|
||||
}
|
||||
else if (!isCopy)
|
||||
{
|
||||
source = new AstOperation(inst, operation.StorageKind, operation.Index, sources, operation.SourcesCount);
|
||||
source = new AstOperation(
|
||||
inst,
|
||||
operation.StorageKind,
|
||||
operation.ForcePrecise,
|
||||
operation.Index,
|
||||
sources,
|
||||
operation.SourcesCount);
|
||||
}
|
||||
else
|
||||
{
|
||||
@ -248,7 +260,13 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
|
||||
}
|
||||
else
|
||||
{
|
||||
context.AddNode(new AstOperation(inst, operation.StorageKind, operation.Index, sources, operation.SourcesCount));
|
||||
context.AddNode(new AstOperation(
|
||||
inst,
|
||||
operation.StorageKind,
|
||||
operation.ForcePrecise,
|
||||
operation.Index,
|
||||
sources,
|
||||
operation.SourcesCount));
|
||||
}
|
||||
|
||||
// Those instructions needs to be emulated by using helper functions,
|
||||
|
@ -319,7 +319,7 @@ namespace Ryujinx.Graphics.Shader.StructuredIr
|
||||
new AstOperand(OperandType.Constant, elemIndex)
|
||||
};
|
||||
|
||||
return new AstOperation(Instruction.Load, StorageKind.ConstantBuffer, sources, sources.Length);
|
||||
return new AstOperation(Instruction.Load, StorageKind.ConstantBuffer, false, sources, sources.Length);
|
||||
}
|
||||
|
||||
return GetOperand(operand);
|
||||
|
@ -49,13 +49,17 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
private readonly List<Operation> _operations;
|
||||
private readonly Dictionary<ulong, BlockLabel> _labels;
|
||||
|
||||
public EmitterContext(DecodedProgram program, ShaderConfig config, bool isNonMain)
|
||||
public EmitterContext()
|
||||
{
|
||||
_operations = new List<Operation>();
|
||||
_labels = new Dictionary<ulong, BlockLabel>();
|
||||
}
|
||||
|
||||
public EmitterContext(DecodedProgram program, ShaderConfig config, bool isNonMain) : this()
|
||||
{
|
||||
Program = program;
|
||||
Config = config;
|
||||
IsNonMain = isNonMain;
|
||||
_operations = new List<Operation>();
|
||||
_labels = new Dictionary<ulong, BlockLabel>();
|
||||
|
||||
EmitStart();
|
||||
}
|
||||
@ -242,7 +246,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
this.Store(StorageKind.Output, IoVariable.Position, null, Const(1), this.FPFusedMultiplyAdd(y, yScale, negativeOne));
|
||||
}
|
||||
|
||||
if (Config.Options.TargetApi == TargetApi.Vulkan && Config.GpuAccessor.QueryTransformDepthMinusOneToOne())
|
||||
if (Config.GpuAccessor.QueryTransformDepthMinusOneToOne() && !Config.GpuAccessor.QueryHostSupportsDepthClipControl())
|
||||
{
|
||||
Operand z = this.Load(StorageKind.Output, IoVariable.Position, null, Const(2));
|
||||
Operand w = this.Load(StorageKind.Output, IoVariable.Position, null, Const(3));
|
||||
@ -279,7 +283,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
oldYLocal = null;
|
||||
}
|
||||
|
||||
if (Config.Options.TargetApi == TargetApi.Vulkan && Config.GpuAccessor.QueryTransformDepthMinusOneToOne())
|
||||
if (Config.GpuAccessor.QueryTransformDepthMinusOneToOne() && !Config.GpuAccessor.QueryHostSupportsDepthClipControl())
|
||||
{
|
||||
oldZLocal = Local();
|
||||
this.Copy(oldZLocal, this.Load(StorageKind.Output, IoVariable.Position, null, Const(2)));
|
||||
|
@ -307,6 +307,11 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
return context.Add(fpType | Instruction.Minimum, Local(), a, b);
|
||||
}
|
||||
|
||||
public static Operand FPModulo(this EmitterContext context, Operand a, Operand b, Instruction fpType = Instruction.FP32)
|
||||
{
|
||||
return context.Add(fpType | Instruction.Modulo, Local(), a, b);
|
||||
}
|
||||
|
||||
public static Operand FPMultiply(this EmitterContext context, Operand a, Operand b, Instruction fpType = Instruction.FP32)
|
||||
{
|
||||
return context.Add(fpType | Instruction.Multiply, Local(), a, b);
|
||||
@ -656,7 +661,6 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
|
||||
public static void Return(this EmitterContext context, Operand returnValue)
|
||||
{
|
||||
context.PrepareForReturn();
|
||||
context.Add(Instruction.Return, null, returnValue);
|
||||
}
|
||||
|
||||
|
134
src/Ryujinx.Graphics.Shader/Translation/HelperFunctionManager.cs
Normal file
134
src/Ryujinx.Graphics.Shader/Translation/HelperFunctionManager.cs
Normal file
@ -0,0 +1,134 @@
|
||||
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
|
||||
|
||||
namespace Ryujinx.Graphics.Shader.Translation
|
||||
{
|
||||
class HelperFunctionManager
|
||||
{
|
||||
private readonly List<Function> _functionList;
|
||||
private readonly Dictionary<HelperFunctionName, int> _functionIds;
|
||||
private readonly ShaderStage _stage;
|
||||
|
||||
public HelperFunctionManager(List<Function> functionList, ShaderStage stage)
|
||||
{
|
||||
_functionList = functionList;
|
||||
_functionIds = new Dictionary<HelperFunctionName, int>();
|
||||
_stage = stage;
|
||||
}
|
||||
|
||||
public int GetOrCreateFunctionId(HelperFunctionName functionName)
|
||||
{
|
||||
if (_functionIds.TryGetValue(functionName, out int functionId))
|
||||
{
|
||||
return functionId;
|
||||
}
|
||||
|
||||
Function function = GenerateFunction(functionName);
|
||||
functionId = _functionList.Count;
|
||||
_functionList.Add(function);
|
||||
_functionIds.Add(functionName, functionId);
|
||||
|
||||
return functionId;
|
||||
}
|
||||
|
||||
private Function GenerateFunction(HelperFunctionName functionName)
|
||||
{
|
||||
return functionName switch
|
||||
{
|
||||
HelperFunctionName.TexelFetchScale => GenerateTexelFetchScaleFunction(),
|
||||
HelperFunctionName.TextureSizeUnscale => GenerateTextureSizeUnscaleFunction(),
|
||||
_ => throw new ArgumentException($"Invalid function name {functionName}")
|
||||
};
|
||||
}
|
||||
|
||||
private Function GenerateTexelFetchScaleFunction()
|
||||
{
|
||||
EmitterContext context = new EmitterContext();
|
||||
|
||||
Operand input = Argument(0);
|
||||
Operand samplerIndex = Argument(1);
|
||||
Operand index = GetScaleIndex(context, samplerIndex);
|
||||
|
||||
Operand scale = context.Load(StorageKind.ConstantBuffer, 0, Const((int)SupportBufferField.RenderScale), index);
|
||||
|
||||
Operand scaleIsOne = context.FPCompareEqual(scale, ConstF(1f));
|
||||
Operand lblScaleNotOne = Label();
|
||||
|
||||
context.BranchIfFalse(lblScaleNotOne, scaleIsOne);
|
||||
context.Return(input);
|
||||
context.MarkLabel(lblScaleNotOne);
|
||||
|
||||
int inArgumentsCount;
|
||||
|
||||
if (_stage == ShaderStage.Fragment)
|
||||
{
|
||||
Operand scaleIsLessThanZero = context.FPCompareLess(scale, ConstF(0f));
|
||||
Operand lblScaleGreaterOrEqualZero = Label();
|
||||
|
||||
context.BranchIfFalse(lblScaleGreaterOrEqualZero, scaleIsLessThanZero);
|
||||
|
||||
Operand negScale = context.FPNegate(scale);
|
||||
Operand inputScaled = context.FPMultiply(context.IConvertS32ToFP32(input), negScale);
|
||||
Operand fragCoordX = context.Load(StorageKind.Input, IoVariable.FragmentCoord, null, Const(0));
|
||||
Operand fragCoordY = context.Load(StorageKind.Input, IoVariable.FragmentCoord, null, Const(1));
|
||||
Operand fragCoord = context.ConditionalSelect(Argument(2), fragCoordY, fragCoordX);
|
||||
Operand inputBias = context.FPModulo(fragCoord, negScale);
|
||||
Operand inputWithBias = context.FPAdd(inputScaled, inputBias);
|
||||
|
||||
context.Return(context.FP32ConvertToS32(inputWithBias));
|
||||
context.MarkLabel(lblScaleGreaterOrEqualZero);
|
||||
|
||||
inArgumentsCount = 3;
|
||||
}
|
||||
else
|
||||
{
|
||||
inArgumentsCount = 2;
|
||||
}
|
||||
|
||||
Operand inputScaled2 = context.FPMultiply(context.IConvertS32ToFP32(input), scale);
|
||||
|
||||
context.Return(context.FP32ConvertToS32(inputScaled2));
|
||||
|
||||
return new Function(ControlFlowGraph.Create(context.GetOperations()).Blocks, "TexelFetchScale", true, inArgumentsCount, 0);
|
||||
}
|
||||
|
||||
private Function GenerateTextureSizeUnscaleFunction()
|
||||
{
|
||||
EmitterContext context = new EmitterContext();
|
||||
|
||||
Operand input = Argument(0);
|
||||
Operand samplerIndex = Argument(1);
|
||||
Operand index = GetScaleIndex(context, samplerIndex);
|
||||
|
||||
Operand scale = context.FPAbsolute(context.Load(StorageKind.ConstantBuffer, 0, Const((int)SupportBufferField.RenderScale), index));
|
||||
|
||||
Operand scaleIsOne = context.FPCompareEqual(scale, ConstF(1f));
|
||||
Operand lblScaleNotOne = Label();
|
||||
|
||||
context.BranchIfFalse(lblScaleNotOne, scaleIsOne);
|
||||
context.Return(input);
|
||||
context.MarkLabel(lblScaleNotOne);
|
||||
|
||||
Operand inputUnscaled = context.FPDivide(context.IConvertS32ToFP32(input), scale);
|
||||
|
||||
context.Return(context.FP32ConvertToS32(inputUnscaled));
|
||||
|
||||
return new Function(ControlFlowGraph.Create(context.GetOperations()).Blocks, "TextureSizeUnscale", true, 2, 0);
|
||||
}
|
||||
|
||||
private Operand GetScaleIndex(EmitterContext context, Operand index)
|
||||
{
|
||||
switch (_stage)
|
||||
{
|
||||
case ShaderStage.Vertex:
|
||||
Operand fragScaleCount = context.Load(StorageKind.ConstantBuffer, 0, Const((int)SupportBufferField.FragmentRenderScaleCount));
|
||||
return context.IAdd(Const(1), context.IAdd(index, fragScaleCount));
|
||||
default:
|
||||
return context.IAdd(Const(1), index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.Graphics.Shader.Translation
|
||||
{
|
||||
enum HelperFunctionName
|
||||
{
|
||||
TexelFetchScale,
|
||||
TextureSizeUnscale
|
||||
}
|
||||
}
|
@ -17,6 +17,8 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
|
||||
private readonly HashSet<int> _usedConstantBufferBindings;
|
||||
|
||||
public ShaderProperties Properties => _properties;
|
||||
|
||||
public ResourceManager(ShaderStage stage, IGpuAccessor gpuAccessor, ShaderProperties properties)
|
||||
{
|
||||
_gpuAccessor = gpuAccessor;
|
||||
@ -98,19 +100,6 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
_properties.AddConstantBuffer(binding, new BufferDefinition(BufferLayout.Std140, 0, binding, name, type));
|
||||
}
|
||||
|
||||
public void InheritFrom(ResourceManager other)
|
||||
{
|
||||
for (int i = 0; i < other._cbSlotToBindingMap.Length; i++)
|
||||
{
|
||||
int binding = other._cbSlotToBindingMap[i];
|
||||
|
||||
if (binding >= 0)
|
||||
{
|
||||
_cbSlotToBindingMap[i] = binding;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static string GetShaderStagePrefix(ShaderStage stage)
|
||||
{
|
||||
uint index = (uint)stage;
|
||||
|
@ -11,9 +11,10 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
{
|
||||
static class Rewriter
|
||||
{
|
||||
public static void RunPass(BasicBlock[] blocks, ShaderConfig config)
|
||||
public static void RunPass(HelperFunctionManager hfm, BasicBlock[] blocks, ShaderConfig config)
|
||||
{
|
||||
bool isVertexShader = config.Stage == ShaderStage.Vertex;
|
||||
bool isImpreciseFragmentShader = config.Stage == ShaderStage.Fragment && config.GpuAccessor.QueryHostReducedPrecision();
|
||||
bool hasConstantBufferDrawParameters = config.GpuAccessor.QueryHasConstantBufferDrawParameters();
|
||||
bool hasVectorIndexingBug = config.GpuAccessor.QueryHostHasVectorIndexingBug();
|
||||
bool supportsSnormBufferTextureFormat = config.GpuAccessor.QueryHostSupportsSnormBufferTextureFormat();
|
||||
@ -45,6 +46,11 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
}
|
||||
}
|
||||
|
||||
if (isImpreciseFragmentShader)
|
||||
{
|
||||
EnableForcePreciseIfNeeded(operation);
|
||||
}
|
||||
|
||||
if (hasVectorIndexingBug)
|
||||
{
|
||||
InsertVectorComponentSelect(node, config);
|
||||
@ -54,9 +60,14 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
|
||||
if (operation is TextureOperation texOp)
|
||||
{
|
||||
node = InsertTexelFetchScale(hfm, node, config);
|
||||
node = InsertTextureSizeUnscale(hfm, node, config);
|
||||
|
||||
if (texOp.Inst == Instruction.TextureSample)
|
||||
{
|
||||
node = RewriteTextureSample(node, config);
|
||||
node = InsertCoordNormalization(node, config);
|
||||
node = InsertCoordGatherBias(node, config);
|
||||
node = InsertConstOffsets(node, config);
|
||||
|
||||
if (texOp.Type == SamplerType.TextureBuffer && !supportsSnormBufferTextureFormat)
|
||||
{
|
||||
@ -76,6 +87,25 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
}
|
||||
}
|
||||
|
||||
private static void EnableForcePreciseIfNeeded(Operation operation)
|
||||
{
|
||||
// There are some cases where a small bias is added to values to prevent division by zero.
|
||||
// When operating with reduced precision, it is possible for this bias to get rounded to 0
|
||||
// and cause a division by zero.
|
||||
// To prevent that, we force those operations to be precise even if the host wants
|
||||
// imprecise operations for performance.
|
||||
|
||||
if (operation.Inst == (Instruction.FP32 | Instruction.Divide) &&
|
||||
operation.GetSource(0).Type == OperandType.Constant &&
|
||||
operation.GetSource(0).AsFloat() == 1f &&
|
||||
operation.GetSource(1).AsgOp is Operation addOp &&
|
||||
addOp.Inst == (Instruction.FP32 | Instruction.Add) &&
|
||||
addOp.GetSource(1).Type == OperandType.Constant)
|
||||
{
|
||||
addOp.ForcePrecise = true;
|
||||
}
|
||||
}
|
||||
|
||||
private static void InsertVectorComponentSelect(LinkedListNode<INode> node, ShaderConfig config)
|
||||
{
|
||||
Operation operation = (Operation)node.Value;
|
||||
@ -344,10 +374,279 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
return node;
|
||||
}
|
||||
|
||||
private static LinkedListNode<INode> RewriteTextureSample(LinkedListNode<INode> node, ShaderConfig config)
|
||||
private static LinkedListNode<INode> InsertTexelFetchScale(HelperFunctionManager hfm, LinkedListNode<INode> node, ShaderConfig config)
|
||||
{
|
||||
TextureOperation texOp = (TextureOperation)node.Value;
|
||||
|
||||
bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;
|
||||
bool intCoords = (texOp.Flags & TextureFlags.IntCoords) != 0;
|
||||
|
||||
bool isArray = (texOp.Type & SamplerType.Array) != 0;
|
||||
bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0;
|
||||
|
||||
int coordsCount = texOp.Type.GetDimensions();
|
||||
|
||||
int coordsIndex = isBindless || isIndexed ? 1 : 0;
|
||||
|
||||
bool isImage = IsImageInstructionWithScale(texOp.Inst);
|
||||
|
||||
if ((texOp.Inst == Instruction.TextureSample || isImage) &&
|
||||
(intCoords || isImage) &&
|
||||
!isBindless &&
|
||||
!isIndexed &&
|
||||
config.Stage.SupportsRenderScale() &&
|
||||
TypeSupportsScale(texOp.Type))
|
||||
{
|
||||
int functionId = hfm.GetOrCreateFunctionId(HelperFunctionName.TexelFetchScale);
|
||||
int samplerIndex = isImage
|
||||
? config.GetTextureDescriptors().Length + config.FindImageDescriptorIndex(texOp)
|
||||
: config.FindTextureDescriptorIndex(texOp);
|
||||
|
||||
for (int index = 0; index < coordsCount; index++)
|
||||
{
|
||||
Operand scaledCoord = Local();
|
||||
Operand[] callArgs;
|
||||
|
||||
if (config.Stage == ShaderStage.Fragment)
|
||||
{
|
||||
callArgs = new Operand[] { Const(functionId), texOp.GetSource(coordsIndex + index), Const(samplerIndex), Const(index) };
|
||||
}
|
||||
else
|
||||
{
|
||||
callArgs = new Operand[] { Const(functionId), texOp.GetSource(coordsIndex + index), Const(samplerIndex) };
|
||||
}
|
||||
|
||||
node.List.AddBefore(node, new Operation(Instruction.Call, 0, scaledCoord, callArgs));
|
||||
|
||||
texOp.SetSource(coordsIndex + index, scaledCoord);
|
||||
}
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
private static LinkedListNode<INode> InsertTextureSizeUnscale(HelperFunctionManager hfm, LinkedListNode<INode> node, ShaderConfig config)
|
||||
{
|
||||
TextureOperation texOp = (TextureOperation)node.Value;
|
||||
|
||||
bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;
|
||||
bool intCoords = (texOp.Flags & TextureFlags.IntCoords) != 0;
|
||||
|
||||
bool isArray = (texOp.Type & SamplerType.Array) != 0;
|
||||
bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0;
|
||||
|
||||
if (texOp.Inst == Instruction.TextureSize &&
|
||||
texOp.Index < 2 &&
|
||||
!isBindless &&
|
||||
!isIndexed &&
|
||||
config.Stage.SupportsRenderScale() &&
|
||||
TypeSupportsScale(texOp.Type))
|
||||
{
|
||||
int functionId = hfm.GetOrCreateFunctionId(HelperFunctionName.TextureSizeUnscale);
|
||||
int samplerIndex = config.FindTextureDescriptorIndex(texOp, ignoreType: true);
|
||||
|
||||
for (int index = texOp.DestsCount - 1; index >= 0; index--)
|
||||
{
|
||||
Operand dest = texOp.GetDest(index);
|
||||
|
||||
Operand unscaledSize = Local();
|
||||
|
||||
// Replace all uses with the unscaled size value.
|
||||
// This must be done before the call is added, since it also is a use of the original size.
|
||||
foreach (INode useOp in dest.UseOps)
|
||||
{
|
||||
for (int srcIndex = 0; srcIndex < useOp.SourcesCount; srcIndex++)
|
||||
{
|
||||
if (useOp.GetSource(srcIndex) == dest)
|
||||
{
|
||||
useOp.SetSource(srcIndex, unscaledSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Operand[] callArgs = new Operand[] { Const(functionId), dest, Const(samplerIndex) };
|
||||
|
||||
node.List.AddAfter(node, new Operation(Instruction.Call, 0, unscaledSize, callArgs));
|
||||
}
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
private static bool IsImageInstructionWithScale(Instruction inst)
|
||||
{
|
||||
// Currently, we don't support scaling images that are modified,
|
||||
// so we only need to care about the load instruction.
|
||||
return inst == Instruction.ImageLoad;
|
||||
}
|
||||
|
||||
private static bool TypeSupportsScale(SamplerType type)
|
||||
{
|
||||
return (type & SamplerType.Mask) == SamplerType.Texture2D;
|
||||
}
|
||||
|
||||
private static LinkedListNode<INode> InsertCoordNormalization(LinkedListNode<INode> node, ShaderConfig config)
|
||||
{
|
||||
// Emulate non-normalized coordinates by normalizing the coordinates on the shader.
|
||||
// Without normalization, the coordinates are expected to the in the [0, W or H] range,
|
||||
// and otherwise, it is expected to be in the [0, 1] range.
|
||||
// We normalize by dividing the coords by the texture size.
|
||||
|
||||
TextureOperation texOp = (TextureOperation)node.Value;
|
||||
|
||||
bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;
|
||||
bool intCoords = (texOp.Flags & TextureFlags.IntCoords) != 0;
|
||||
|
||||
bool isCoordNormalized = isBindless || config.GpuAccessor.QueryTextureCoordNormalized(texOp.Handle, texOp.CbufSlot);
|
||||
|
||||
if (isCoordNormalized || intCoords)
|
||||
{
|
||||
return node;
|
||||
}
|
||||
|
||||
bool isArray = (texOp.Type & SamplerType.Array) != 0;
|
||||
bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0;
|
||||
|
||||
int coordsCount = texOp.Type.GetDimensions();
|
||||
int coordsIndex = isBindless || isIndexed ? 1 : 0;
|
||||
|
||||
config.SetUsedFeature(FeatureFlags.IntegerSampling);
|
||||
|
||||
int normCoordsCount = (texOp.Type & SamplerType.Mask) == SamplerType.TextureCube ? 2 : coordsCount;
|
||||
|
||||
for (int index = 0; index < normCoordsCount; index++)
|
||||
{
|
||||
Operand coordSize = Local();
|
||||
|
||||
Operand[] texSizeSources;
|
||||
|
||||
if (isBindless || isIndexed)
|
||||
{
|
||||
texSizeSources = new Operand[] { texOp.GetSource(0), Const(0) };
|
||||
}
|
||||
else
|
||||
{
|
||||
texSizeSources = new Operand[] { Const(0) };
|
||||
}
|
||||
|
||||
node.List.AddBefore(node, new TextureOperation(
|
||||
Instruction.TextureSize,
|
||||
texOp.Type,
|
||||
texOp.Format,
|
||||
texOp.Flags,
|
||||
texOp.CbufSlot,
|
||||
texOp.Handle,
|
||||
index,
|
||||
new[] { coordSize },
|
||||
texSizeSources));
|
||||
|
||||
config.SetUsedTexture(Instruction.TextureSize, texOp.Type, texOp.Format, texOp.Flags, texOp.CbufSlot, texOp.Handle);
|
||||
|
||||
Operand source = texOp.GetSource(coordsIndex + index);
|
||||
|
||||
Operand coordNormalized = Local();
|
||||
|
||||
node.List.AddBefore(node, new Operation(Instruction.FP32 | Instruction.Divide, coordNormalized, source, GenerateI2f(node, coordSize)));
|
||||
|
||||
texOp.SetSource(coordsIndex + index, coordNormalized);
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
private static LinkedListNode<INode> InsertCoordGatherBias(LinkedListNode<INode> node, ShaderConfig config)
|
||||
{
|
||||
// The gather behavior when the coordinate sits right in the middle of two texels is not well defined.
|
||||
// To ensure the correct texel is sampled, we add a small bias value to the coordinate.
|
||||
// This value is calculated as the minimum value required to change the texel it will sample from,
|
||||
// and is 0 if the host does not require the bias.
|
||||
|
||||
TextureOperation texOp = (TextureOperation)node.Value;
|
||||
|
||||
bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;
|
||||
bool isGather = (texOp.Flags & TextureFlags.Gather) != 0;
|
||||
|
||||
int gatherBiasPrecision = config.GpuAccessor.QueryHostGatherBiasPrecision();
|
||||
|
||||
if (!isGather || gatherBiasPrecision == 0)
|
||||
{
|
||||
return node;
|
||||
}
|
||||
|
||||
bool intCoords = (texOp.Flags & TextureFlags.IntCoords) != 0;
|
||||
|
||||
bool isArray = (texOp.Type & SamplerType.Array) != 0;
|
||||
bool isIndexed = (texOp.Type & SamplerType.Indexed) != 0;
|
||||
|
||||
int coordsCount = texOp.Type.GetDimensions();
|
||||
int coordsIndex = isBindless || isIndexed ? 1 : 0;
|
||||
|
||||
config.SetUsedFeature(FeatureFlags.IntegerSampling);
|
||||
|
||||
int normCoordsCount = (texOp.Type & SamplerType.Mask) == SamplerType.TextureCube ? 2 : coordsCount;
|
||||
|
||||
for (int index = 0; index < normCoordsCount; index++)
|
||||
{
|
||||
Operand coordSize = Local();
|
||||
Operand scaledSize = Local();
|
||||
Operand bias = Local();
|
||||
|
||||
Operand[] texSizeSources;
|
||||
|
||||
if (isBindless || isIndexed)
|
||||
{
|
||||
texSizeSources = new Operand[] { texOp.GetSource(0), Const(0) };
|
||||
}
|
||||
else
|
||||
{
|
||||
texSizeSources = new Operand[] { Const(0) };
|
||||
}
|
||||
|
||||
node.List.AddBefore(node, new TextureOperation(
|
||||
Instruction.TextureSize,
|
||||
texOp.Type,
|
||||
texOp.Format,
|
||||
texOp.Flags,
|
||||
texOp.CbufSlot,
|
||||
texOp.Handle,
|
||||
index,
|
||||
new[] { coordSize },
|
||||
texSizeSources));
|
||||
|
||||
config.SetUsedTexture(Instruction.TextureSize, texOp.Type, texOp.Format, texOp.Flags, texOp.CbufSlot, texOp.Handle);
|
||||
|
||||
node.List.AddBefore(node, new Operation(
|
||||
Instruction.FP32 | Instruction.Multiply,
|
||||
scaledSize,
|
||||
GenerateI2f(node, coordSize),
|
||||
ConstF((float)(1 << (gatherBiasPrecision + 1)))));
|
||||
node.List.AddBefore(node, new Operation(Instruction.FP32 | Instruction.Divide, bias, ConstF(1f), scaledSize));
|
||||
|
||||
Operand source = texOp.GetSource(coordsIndex + index);
|
||||
|
||||
Operand coordBiased = Local();
|
||||
|
||||
node.List.AddBefore(node, new Operation(Instruction.FP32 | Instruction.Add, coordBiased, source, bias));
|
||||
|
||||
texOp.SetSource(coordsIndex + index, coordBiased);
|
||||
}
|
||||
|
||||
return node;
|
||||
}
|
||||
|
||||
private static LinkedListNode<INode> InsertConstOffsets(LinkedListNode<INode> node, ShaderConfig config)
|
||||
{
|
||||
// Non-constant texture offsets are not allowed (according to the spec),
|
||||
// however some GPUs does support that.
|
||||
// For GPUs where it is not supported, we can replace the instruction with the following:
|
||||
// For texture*Offset, we replace it by texture*, and add the offset to the P coords.
|
||||
// The offset can be calculated as offset / textureSize(lod), where lod = textureQueryLod(coords).
|
||||
// For texelFetchOffset, we replace it by texelFetch and add the offset to the P coords directly.
|
||||
// For textureGatherOffset, we split the operation into up to 4 operations, one for each component
|
||||
// that is accessed, where each textureGather operation has a different offset for each pixel.
|
||||
|
||||
TextureOperation texOp = (TextureOperation)node.Value;
|
||||
|
||||
bool hasOffset = (texOp.Flags & TextureFlags.Offset) != 0;
|
||||
bool hasOffsets = (texOp.Flags & TextureFlags.Offsets) != 0;
|
||||
|
||||
@ -355,9 +654,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
|
||||
bool isBindless = (texOp.Flags & TextureFlags.Bindless) != 0;
|
||||
|
||||
bool isCoordNormalized = isBindless || config.GpuAccessor.QueryTextureCoordNormalized(texOp.Handle, texOp.CbufSlot);
|
||||
|
||||
if (!hasInvalidOffset && isCoordNormalized)
|
||||
if (!hasInvalidOffset)
|
||||
{
|
||||
return node;
|
||||
}
|
||||
@ -454,7 +751,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
|
||||
hasInvalidOffset &= !areAllOffsetsConstant;
|
||||
|
||||
if (!hasInvalidOffset && isCoordNormalized)
|
||||
if (!hasInvalidOffset)
|
||||
{
|
||||
return node;
|
||||
}
|
||||
@ -473,63 +770,6 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
|
||||
int componentIndex = texOp.Index;
|
||||
|
||||
Operand Float(Operand value)
|
||||
{
|
||||
Operand res = Local();
|
||||
|
||||
node.List.AddBefore(node, new Operation(Instruction.ConvertS32ToFP32, res, value));
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
// Emulate non-normalized coordinates by normalizing the coordinates on the shader.
|
||||
// Without normalization, the coordinates are expected to the in the [0, W or H] range,
|
||||
// and otherwise, it is expected to be in the [0, 1] range.
|
||||
// We normalize by dividing the coords by the texture size.
|
||||
if (!isCoordNormalized && !intCoords)
|
||||
{
|
||||
config.SetUsedFeature(FeatureFlags.IntegerSampling);
|
||||
|
||||
int normCoordsCount = (texOp.Type & SamplerType.Mask) == SamplerType.TextureCube ? 2 : coordsCount;
|
||||
|
||||
for (int index = 0; index < normCoordsCount; index++)
|
||||
{
|
||||
Operand coordSize = Local();
|
||||
|
||||
Operand[] texSizeSources;
|
||||
|
||||
if (isBindless || isIndexed)
|
||||
{
|
||||
texSizeSources = new Operand[] { sources[0], Const(0) };
|
||||
}
|
||||
else
|
||||
{
|
||||
texSizeSources = new Operand[] { Const(0) };
|
||||
}
|
||||
|
||||
node.List.AddBefore(node, new TextureOperation(
|
||||
Instruction.TextureSize,
|
||||
texOp.Type,
|
||||
texOp.Format,
|
||||
texOp.Flags,
|
||||
texOp.CbufSlot,
|
||||
texOp.Handle,
|
||||
index,
|
||||
new[] { coordSize },
|
||||
texSizeSources));
|
||||
|
||||
config.SetUsedTexture(Instruction.TextureSize, texOp.Type, texOp.Format, texOp.Flags, texOp.CbufSlot, texOp.Handle);
|
||||
|
||||
Operand source = sources[coordsIndex + index];
|
||||
|
||||
Operand coordNormalized = Local();
|
||||
|
||||
node.List.AddBefore(node, new Operation(Instruction.FP32 | Instruction.Divide, coordNormalized, source, Float(coordSize)));
|
||||
|
||||
sources[coordsIndex + index] = coordNormalized;
|
||||
}
|
||||
}
|
||||
|
||||
Operand[] dests = new Operand[texOp.DestsCount];
|
||||
|
||||
for (int i = 0; i < texOp.DestsCount; i++)
|
||||
@ -541,15 +781,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
|
||||
LinkedListNode<INode> oldNode = node;
|
||||
|
||||
// Technically, non-constant texture offsets are not allowed (according to the spec),
|
||||
// however some GPUs does support that.
|
||||
// For GPUs where it is not supported, we can replace the instruction with the following:
|
||||
// For texture*Offset, we replace it by texture*, and add the offset to the P coords.
|
||||
// The offset can be calculated as offset / textureSize(lod), where lod = textureQueryLod(coords).
|
||||
// For texelFetchOffset, we replace it by texelFetch and add the offset to the P coords directly.
|
||||
// For textureGatherOffset, we split the operation into up to 4 operations, one for each component
|
||||
// that is accessed, where each textureGather operation has a different offset for each pixel.
|
||||
if (hasInvalidOffset && isGather && !isShadow)
|
||||
if (isGather && !isShadow)
|
||||
{
|
||||
config.SetUsedFeature(FeatureFlags.IntegerSampling);
|
||||
|
||||
@ -557,7 +789,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
|
||||
sources.CopyTo(newSources, 0);
|
||||
|
||||
Operand[] texSizes = InsertTextureSize(node, texOp, lodSources, bindlessHandle, coordsCount);
|
||||
Operand[] texSizes = InsertTextureLod(node, texOp, lodSources, bindlessHandle, coordsCount);
|
||||
|
||||
int destIndex = 0;
|
||||
|
||||
@ -576,7 +808,11 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
|
||||
Operand intOffset = offsets[index + (hasOffsets ? compIndex * coordsCount : 0)];
|
||||
|
||||
node.List.AddBefore(node, new Operation(Instruction.FP32 | Instruction.Divide, offset, Float(intOffset), Float(texSizes[index])));
|
||||
node.List.AddBefore(node, new Operation(
|
||||
Instruction.FP32 | Instruction.Divide,
|
||||
offset,
|
||||
GenerateI2f(node, intOffset),
|
||||
GenerateI2f(node, texSizes[index])));
|
||||
|
||||
Operand source = sources[coordsIndex + index];
|
||||
|
||||
@ -603,45 +839,46 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
}
|
||||
else
|
||||
{
|
||||
if (hasInvalidOffset)
|
||||
if (intCoords)
|
||||
{
|
||||
if (intCoords)
|
||||
for (int index = 0; index < coordsCount; index++)
|
||||
{
|
||||
for (int index = 0; index < coordsCount; index++)
|
||||
{
|
||||
Operand source = sources[coordsIndex + index];
|
||||
Operand source = sources[coordsIndex + index];
|
||||
|
||||
Operand coordPlusOffset = Local();
|
||||
Operand coordPlusOffset = Local();
|
||||
|
||||
node.List.AddBefore(node, new Operation(Instruction.Add, coordPlusOffset, source, offsets[index]));
|
||||
node.List.AddBefore(node, new Operation(Instruction.Add, coordPlusOffset, source, offsets[index]));
|
||||
|
||||
sources[coordsIndex + index] = coordPlusOffset;
|
||||
}
|
||||
sources[coordsIndex + index] = coordPlusOffset;
|
||||
}
|
||||
else
|
||||
}
|
||||
else
|
||||
{
|
||||
config.SetUsedFeature(FeatureFlags.IntegerSampling);
|
||||
|
||||
Operand[] texSizes = InsertTextureLod(node, texOp, lodSources, bindlessHandle, coordsCount);
|
||||
|
||||
for (int index = 0; index < coordsCount; index++)
|
||||
{
|
||||
config.SetUsedFeature(FeatureFlags.IntegerSampling);
|
||||
config.SetUsedTexture(Instruction.TextureSize, texOp.Type, texOp.Format, texOp.Flags, texOp.CbufSlot, texOp.Handle);
|
||||
|
||||
Operand[] texSizes = InsertTextureSize(node, texOp, lodSources, bindlessHandle, coordsCount);
|
||||
Operand offset = Local();
|
||||
|
||||
for (int index = 0; index < coordsCount; index++)
|
||||
{
|
||||
config.SetUsedTexture(Instruction.TextureSize, texOp.Type, texOp.Format, texOp.Flags, texOp.CbufSlot, texOp.Handle);
|
||||
Operand intOffset = offsets[index];
|
||||
|
||||
Operand offset = Local();
|
||||
node.List.AddBefore(node, new Operation(
|
||||
Instruction.FP32 | Instruction.Divide,
|
||||
offset,
|
||||
GenerateI2f(node, intOffset),
|
||||
GenerateI2f(node, texSizes[index])));
|
||||
|
||||
Operand intOffset = offsets[index];
|
||||
Operand source = sources[coordsIndex + index];
|
||||
|
||||
node.List.AddBefore(node, new Operation(Instruction.FP32 | Instruction.Divide, offset, Float(intOffset), Float(texSizes[index])));
|
||||
Operand coordPlusOffset = Local();
|
||||
|
||||
Operand source = sources[coordsIndex + index];
|
||||
node.List.AddBefore(node, new Operation(Instruction.FP32 | Instruction.Add, coordPlusOffset, source, offset));
|
||||
|
||||
Operand coordPlusOffset = Local();
|
||||
|
||||
node.List.AddBefore(node, new Operation(Instruction.FP32 | Instruction.Add, coordPlusOffset, source, offset));
|
||||
|
||||
sources[coordsIndex + index] = coordPlusOffset;
|
||||
}
|
||||
sources[coordsIndex + index] = coordPlusOffset;
|
||||
}
|
||||
}
|
||||
|
||||
@ -669,22 +906,13 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
return node;
|
||||
}
|
||||
|
||||
private static Operand[] InsertTextureSize(
|
||||
private static Operand[] InsertTextureLod(
|
||||
LinkedListNode<INode> node,
|
||||
TextureOperation texOp,
|
||||
Operand[] lodSources,
|
||||
Operand bindlessHandle,
|
||||
int coordsCount)
|
||||
{
|
||||
Operand Int(Operand value)
|
||||
{
|
||||
Operand res = Local();
|
||||
|
||||
node.List.AddBefore(node, new Operation(Instruction.ConvertFP32ToS32, res, value));
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
Operand[] texSizes = new Operand[coordsCount];
|
||||
|
||||
Operand lod = Local();
|
||||
@ -708,11 +936,11 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
|
||||
if (bindlessHandle != null)
|
||||
{
|
||||
texSizeSources = new Operand[] { bindlessHandle, Int(lod) };
|
||||
texSizeSources = new Operand[] { bindlessHandle, GenerateF2i(node, lod) };
|
||||
}
|
||||
else
|
||||
{
|
||||
texSizeSources = new Operand[] { Int(lod) };
|
||||
texSizeSources = new Operand[] { GenerateF2i(node, lod) };
|
||||
}
|
||||
|
||||
node.List.AddBefore(node, new TextureOperation(
|
||||
@ -796,6 +1024,24 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
return node;
|
||||
}
|
||||
|
||||
private static Operand GenerateI2f(LinkedListNode<INode> node, Operand value)
|
||||
{
|
||||
Operand res = Local();
|
||||
|
||||
node.List.AddBefore(node, new Operation(Instruction.ConvertS32ToFP32, res, value));
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
private static Operand GenerateF2i(LinkedListNode<INode> node, Operand value)
|
||||
{
|
||||
Operand res = Local();
|
||||
|
||||
node.List.AddBefore(node, new Operation(Instruction.ConvertFP32ToS32, res, value));
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
private static bool ReplaceConstantBufferWithDrawParameters(LinkedListNode<INode> node, Operation operation)
|
||||
{
|
||||
Operand GenerateLoad(IoVariable ioVariable)
|
||||
|
@ -39,9 +39,9 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
|
||||
public TranslationOptions Options { get; }
|
||||
|
||||
public ShaderProperties Properties { get; }
|
||||
public ShaderProperties Properties => ResourceManager.Properties;
|
||||
|
||||
public ResourceManager ResourceManager { get; }
|
||||
public ResourceManager ResourceManager { get; set; }
|
||||
|
||||
public bool TransformFeedbackEnabled { get; }
|
||||
|
||||
@ -159,8 +159,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
_sbSlots = new Dictionary<int, int>();
|
||||
_sbSlotsReverse = new Dictionary<int, int>();
|
||||
|
||||
Properties = new ShaderProperties();
|
||||
ResourceManager = new ResourceManager(stage, gpuAccessor, Properties);
|
||||
ResourceManager = new ResourceManager(stage, gpuAccessor, new ShaderProperties());
|
||||
}
|
||||
|
||||
public ShaderConfig(
|
||||
@ -429,8 +428,6 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
|
||||
public void InheritFrom(ShaderConfig other)
|
||||
{
|
||||
ResourceManager.InheritFrom(other.ResourceManager);
|
||||
|
||||
ClipDistancesWritten |= other.ClipDistancesWritten;
|
||||
UsedFeatures |= other.UsedFeatures;
|
||||
|
||||
@ -860,7 +857,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
return descriptors;
|
||||
}
|
||||
|
||||
public (TextureDescriptor, int) FindTextureDescriptor(AstTextureOperation texOp)
|
||||
public TextureDescriptor FindTextureDescriptor(AstTextureOperation texOp)
|
||||
{
|
||||
TextureDescriptor[] descriptors = GetTextureDescriptors();
|
||||
|
||||
@ -872,11 +869,11 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
descriptor.HandleIndex == texOp.Handle &&
|
||||
descriptor.Format == texOp.Format)
|
||||
{
|
||||
return (descriptor, i);
|
||||
return descriptor;
|
||||
}
|
||||
}
|
||||
|
||||
return (default, -1);
|
||||
return default;
|
||||
}
|
||||
|
||||
private static int FindDescriptorIndex(TextureDescriptor[] array, AstTextureOperation texOp)
|
||||
@ -897,12 +894,30 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
return -1;
|
||||
}
|
||||
|
||||
public int FindTextureDescriptorIndex(AstTextureOperation texOp)
|
||||
private static int FindDescriptorIndex(TextureDescriptor[] array, TextureOperation texOp, bool ignoreType = false)
|
||||
{
|
||||
return FindDescriptorIndex(GetTextureDescriptors(), texOp);
|
||||
for (int i = 0; i < array.Length; i++)
|
||||
{
|
||||
var descriptor = array[i];
|
||||
|
||||
if ((descriptor.Type == texOp.Type || ignoreType) &&
|
||||
descriptor.CbufSlot == texOp.CbufSlot &&
|
||||
descriptor.HandleIndex == texOp.Handle &&
|
||||
descriptor.Format == texOp.Format)
|
||||
{
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
public int FindImageDescriptorIndex(AstTextureOperation texOp)
|
||||
public int FindTextureDescriptorIndex(TextureOperation texOp, bool ignoreType = false)
|
||||
{
|
||||
return FindDescriptorIndex(GetTextureDescriptors(), texOp, ignoreType);
|
||||
}
|
||||
|
||||
public int FindImageDescriptorIndex(TextureOperation texOp)
|
||||
{
|
||||
return FindDescriptorIndex(GetImageDescriptors(), texOp);
|
||||
}
|
||||
|
@ -1,11 +1,11 @@
|
||||
using Ryujinx.Graphics.Shader.IntermediateRepresentation;
|
||||
using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Ryujinx.Graphics.Shader.Translation
|
||||
{
|
||||
static class ShaderIdentifier
|
||||
{
|
||||
public static ShaderIdentification Identify(Function[] functions, ShaderConfig config)
|
||||
public static ShaderIdentification Identify(IReadOnlyList<Function> functions, ShaderConfig config)
|
||||
{
|
||||
if (config.Stage == ShaderStage.Geometry &&
|
||||
config.GpuAccessor.QueryPrimitiveTopology() == InputTopology.Triangles &&
|
||||
@ -20,12 +20,12 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
return ShaderIdentification.None;
|
||||
}
|
||||
|
||||
private static bool IsLayerPassthroughGeometryShader(Function[] functions, out int layerInputAttr)
|
||||
private static bool IsLayerPassthroughGeometryShader(IReadOnlyList<Function> functions, out int layerInputAttr)
|
||||
{
|
||||
bool writesLayer = false;
|
||||
layerInputAttr = 0;
|
||||
|
||||
if (functions.Length != 1)
|
||||
if (functions.Count != 1)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ using Ryujinx.Graphics.Shader.IntermediateRepresentation;
|
||||
using Ryujinx.Graphics.Shader.StructuredIr;
|
||||
using Ryujinx.Graphics.Shader.Translation.Optimizations;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using static Ryujinx.Graphics.Shader.IntermediateRepresentation.OperandHelper;
|
||||
|
||||
@ -44,7 +45,14 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
}
|
||||
}
|
||||
|
||||
Function[] funcs = new Function[functions.Length];
|
||||
List<Function> funcs = new List<Function>(functions.Length);
|
||||
|
||||
for (int i = 0; i < functions.Length; i++)
|
||||
{
|
||||
funcs.Add(null);
|
||||
}
|
||||
|
||||
HelperFunctionManager hfm = new HelperFunctionManager(funcs, config.Stage);
|
||||
|
||||
for (int i = 0; i < functions.Length; i++)
|
||||
{
|
||||
@ -71,7 +79,7 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
Ssa.Rename(cfg.Blocks);
|
||||
|
||||
Optimizer.RunPass(cfg.Blocks, config);
|
||||
Rewriter.RunPass(cfg.Blocks, config);
|
||||
Rewriter.RunPass(hfm, cfg.Blocks, config);
|
||||
}
|
||||
|
||||
funcs[i] = new Function(cfg.Blocks, $"fun{i}", false, inArgumentsCount, outArgumentsCount);
|
||||
|
@ -155,6 +155,9 @@ namespace Ryujinx.Graphics.Shader.Translation
|
||||
{
|
||||
other._config.MergeOutputUserAttributes(_config.UsedOutputAttributes, Enumerable.Empty<int>());
|
||||
|
||||
// We need to share the resource manager since both shaders accesses the same constant buffers.
|
||||
other._config.ResourceManager = _config.ResourceManager;
|
||||
|
||||
FunctionCode[] otherCode = EmitShader(other._program, other._config, initializeOutputs: true, out int aStart);
|
||||
|
||||
code = Combine(otherCode, code, aStart);
|
||||
|
@ -372,8 +372,9 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
private void UpdateAndBind(CommandBufferScoped cbs, int setIndex, PipelineBindPoint pbp)
|
||||
{
|
||||
var program = _program;
|
||||
int stagesCount = program.Bindings[setIndex].Length;
|
||||
if (stagesCount == 0 && setIndex != PipelineBase.UniformSetIndex)
|
||||
var bindingSegments = program.BindingSegments[setIndex];
|
||||
|
||||
if (bindingSegments.Length == 0 && setIndex != PipelineBase.UniformSetIndex)
|
||||
{
|
||||
return;
|
||||
}
|
||||
@ -410,125 +411,113 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
}
|
||||
}
|
||||
|
||||
for (int stageIndex = 0; stageIndex < stagesCount; stageIndex++)
|
||||
foreach (ResourceBindingSegment segment in bindingSegments)
|
||||
{
|
||||
var stageBindings = program.Bindings[setIndex][stageIndex];
|
||||
int bindingsCount = stageBindings.Length;
|
||||
int count;
|
||||
int binding = segment.Binding;
|
||||
int count = segment.Count;
|
||||
|
||||
for (int bindingIndex = 0; bindingIndex < bindingsCount; bindingIndex += count)
|
||||
if (setIndex == PipelineBase.UniformSetIndex)
|
||||
{
|
||||
int binding = stageBindings[bindingIndex];
|
||||
count = 1;
|
||||
|
||||
while (bindingIndex + count < bindingsCount && stageBindings[bindingIndex + count] == binding + count)
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
count++;
|
||||
int index = binding + i;
|
||||
|
||||
if (!_uniformSet[index])
|
||||
{
|
||||
UpdateBuffer(cbs, ref _uniformBuffers[index], _uniformBufferRefs[index], dummyBuffer);
|
||||
|
||||
_uniformSet[index] = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (setIndex == PipelineBase.UniformSetIndex)
|
||||
ReadOnlySpan<DescriptorBufferInfo> uniformBuffers = _uniformBuffers;
|
||||
dsc.UpdateBuffers(0, binding, uniformBuffers.Slice(binding, count), DescriptorType.UniformBuffer);
|
||||
}
|
||||
else if (setIndex == PipelineBase.StorageSetIndex)
|
||||
{
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
int index = binding + i;
|
||||
|
||||
if (!_storageSet[index])
|
||||
{
|
||||
UpdateBuffer(cbs, ref _storageBuffers[index], _storageBufferRefs[index], dummyBuffer);
|
||||
|
||||
_storageSet[index] = true;
|
||||
}
|
||||
}
|
||||
|
||||
ReadOnlySpan<DescriptorBufferInfo> storageBuffers = _storageBuffers;
|
||||
if (program.HasMinimalLayout)
|
||||
{
|
||||
dsc.UpdateBuffers(0, binding, storageBuffers.Slice(binding, count), DescriptorType.StorageBuffer);
|
||||
}
|
||||
else
|
||||
{
|
||||
dsc.UpdateStorageBuffers(0, binding, storageBuffers.Slice(binding, count));
|
||||
}
|
||||
}
|
||||
else if (setIndex == PipelineBase.TextureSetIndex)
|
||||
{
|
||||
if (segment.Type != ResourceType.BufferTexture)
|
||||
{
|
||||
Span<DescriptorImageInfo> textures = _textures;
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
int index = binding + i;
|
||||
ref var texture = ref textures[i];
|
||||
|
||||
if (!_uniformSet[index])
|
||||
texture.ImageView = _textureRefs[binding + i]?.Get(cbs).Value ?? default;
|
||||
texture.Sampler = _samplerRefs[binding + i]?.Get(cbs).Value ?? default;
|
||||
|
||||
if (texture.ImageView.Handle == 0)
|
||||
{
|
||||
UpdateBuffer(cbs, ref _uniformBuffers[index], _uniformBufferRefs[index], dummyBuffer);
|
||||
texture.ImageView = _dummyTexture.GetImageView().Get(cbs).Value;
|
||||
}
|
||||
|
||||
_uniformSet[index] = true;
|
||||
if (texture.Sampler.Handle == 0)
|
||||
{
|
||||
texture.Sampler = _dummySampler.GetSampler().Get(cbs).Value;
|
||||
}
|
||||
}
|
||||
|
||||
ReadOnlySpan<DescriptorBufferInfo> uniformBuffers = _uniformBuffers;
|
||||
dsc.UpdateBuffers(0, binding, uniformBuffers.Slice(binding, count), DescriptorType.UniformBuffer);
|
||||
dsc.UpdateImages(0, binding, textures.Slice(0, count), DescriptorType.CombinedImageSampler);
|
||||
}
|
||||
else if (setIndex == PipelineBase.StorageSetIndex)
|
||||
else
|
||||
{
|
||||
Span<BufferView> bufferTextures = _bufferTextures;
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
int index = binding + i;
|
||||
|
||||
if (!_storageSet[index])
|
||||
{
|
||||
UpdateBuffer(cbs, ref _storageBuffers[index], _storageBufferRefs[index], dummyBuffer);
|
||||
|
||||
_storageSet[index] = true;
|
||||
}
|
||||
bufferTextures[i] = _bufferTextureRefs[binding + i]?.GetBufferView(cbs) ?? default;
|
||||
}
|
||||
|
||||
ReadOnlySpan<DescriptorBufferInfo> storageBuffers = _storageBuffers;
|
||||
if (program.HasMinimalLayout)
|
||||
{
|
||||
dsc.UpdateBuffers(0, binding, storageBuffers.Slice(binding, count), DescriptorType.StorageBuffer);
|
||||
}
|
||||
else
|
||||
{
|
||||
dsc.UpdateStorageBuffers(0, binding, storageBuffers.Slice(binding, count));
|
||||
}
|
||||
dsc.UpdateBufferImages(0, binding, bufferTextures.Slice(0, count), DescriptorType.UniformTexelBuffer);
|
||||
}
|
||||
else if (setIndex == PipelineBase.TextureSetIndex)
|
||||
}
|
||||
else if (setIndex == PipelineBase.ImageSetIndex)
|
||||
{
|
||||
if (segment.Type != ResourceType.BufferImage)
|
||||
{
|
||||
if (((uint)binding % (Constants.MaxTexturesPerStage * 2)) < Constants.MaxTexturesPerStage || program.HasMinimalLayout)
|
||||
Span<DescriptorImageInfo> images = _images;
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
Span<DescriptorImageInfo> textures = _textures;
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
ref var texture = ref textures[i];
|
||||
|
||||
texture.ImageView = _textureRefs[binding + i]?.Get(cbs).Value ?? default;
|
||||
texture.Sampler = _samplerRefs[binding + i]?.Get(cbs).Value ?? default;
|
||||
|
||||
if (texture.ImageView.Handle == 0)
|
||||
{
|
||||
texture.ImageView = _dummyTexture.GetImageView().Get(cbs).Value;
|
||||
}
|
||||
|
||||
if (texture.Sampler.Handle == 0)
|
||||
{
|
||||
texture.Sampler = _dummySampler.GetSampler().Get(cbs).Value;
|
||||
}
|
||||
}
|
||||
|
||||
dsc.UpdateImages(0, binding, textures.Slice(0, count), DescriptorType.CombinedImageSampler);
|
||||
images[i].ImageView = _imageRefs[binding + i]?.Get(cbs).Value ?? default;
|
||||
}
|
||||
else
|
||||
{
|
||||
Span<BufferView> bufferTextures = _bufferTextures;
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
bufferTextures[i] = _bufferTextureRefs[binding + i]?.GetBufferView(cbs) ?? default;
|
||||
}
|
||||
|
||||
dsc.UpdateBufferImages(0, binding, bufferTextures.Slice(0, count), DescriptorType.UniformTexelBuffer);
|
||||
}
|
||||
dsc.UpdateImages(0, binding, images.Slice(0, count), DescriptorType.StorageImage);
|
||||
}
|
||||
else if (setIndex == PipelineBase.ImageSetIndex)
|
||||
else
|
||||
{
|
||||
if (((uint)binding % (Constants.MaxImagesPerStage * 2)) < Constants.MaxImagesPerStage || program.HasMinimalLayout)
|
||||
Span<BufferView> bufferImages = _bufferImages;
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
Span<DescriptorImageInfo> images = _images;
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
images[i].ImageView = _imageRefs[binding + i]?.Get(cbs).Value ?? default;
|
||||
}
|
||||
|
||||
dsc.UpdateImages(0, binding, images.Slice(0, count), DescriptorType.StorageImage);
|
||||
bufferImages[i] = _bufferImageRefs[binding + i]?.GetBufferView(cbs, _bufferImageFormats[binding + i]) ?? default;
|
||||
}
|
||||
else
|
||||
{
|
||||
Span<BufferView> bufferImages = _bufferImages;
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
bufferImages[i] = _bufferImageRefs[binding + i]?.GetBufferView(cbs, _bufferImageFormats[binding + i]) ?? default;
|
||||
}
|
||||
|
||||
dsc.UpdateBufferImages(0, binding, bufferImages.Slice(0, count), DescriptorType.StorageTexelBuffer);
|
||||
}
|
||||
dsc.UpdateBufferImages(0, binding, bufferImages.Slice(0, count), DescriptorType.StorageTexelBuffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -568,9 +557,6 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
private void UpdateAndBindUniformBufferPd(CommandBufferScoped cbs, PipelineBindPoint pbp)
|
||||
{
|
||||
var dummyBuffer = _dummyBuffer?.GetBuffer();
|
||||
int stagesCount = _program.Bindings[PipelineBase.UniformSetIndex].Length;
|
||||
|
||||
if (!_uniformSet[0])
|
||||
{
|
||||
Span<DescriptorBufferInfo> uniformBuffer = stackalloc DescriptorBufferInfo[1];
|
||||
@ -587,41 +573,32 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
UpdateBuffers(cbs, pbp, 0, uniformBuffer, DescriptorType.UniformBuffer);
|
||||
}
|
||||
|
||||
for (int stageIndex = 0; stageIndex < stagesCount; stageIndex++)
|
||||
var bindingSegments = _program.BindingSegments[PipelineBase.UniformSetIndex];
|
||||
var dummyBuffer = _dummyBuffer?.GetBuffer();
|
||||
|
||||
foreach (ResourceBindingSegment segment in bindingSegments)
|
||||
{
|
||||
var stageBindings = _program.Bindings[PipelineBase.UniformSetIndex][stageIndex];
|
||||
int bindingsCount = stageBindings.Length;
|
||||
int count;
|
||||
int binding = segment.Binding;
|
||||
int count = segment.Count;
|
||||
|
||||
for (int bindingIndex = 0; bindingIndex < bindingsCount; bindingIndex += count)
|
||||
bool doUpdate = false;
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
int binding = stageBindings[bindingIndex];
|
||||
count = 1;
|
||||
int index = binding + i;
|
||||
|
||||
while (bindingIndex + count < bindingsCount && stageBindings[bindingIndex + count] == binding + count)
|
||||
if (!_uniformSet[index])
|
||||
{
|
||||
count++;
|
||||
UpdateBuffer(cbs, ref _uniformBuffers[index], _uniformBufferRefs[index], dummyBuffer);
|
||||
_uniformSet[index] = true;
|
||||
doUpdate = true;
|
||||
}
|
||||
}
|
||||
|
||||
bool doUpdate = false;
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
int index = binding + i;
|
||||
|
||||
if (!_uniformSet[index])
|
||||
{
|
||||
UpdateBuffer(cbs, ref _uniformBuffers[index], _uniformBufferRefs[index], dummyBuffer);
|
||||
_uniformSet[index] = true;
|
||||
doUpdate = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (doUpdate)
|
||||
{
|
||||
ReadOnlySpan<DescriptorBufferInfo> uniformBuffers = _uniformBuffers;
|
||||
UpdateBuffers(cbs, pbp, binding, uniformBuffers.Slice(binding, count), DescriptorType.UniformBuffer);
|
||||
}
|
||||
if (doUpdate)
|
||||
{
|
||||
ReadOnlySpan<DescriptorBufferInfo> uniformBuffers = _uniformBuffers;
|
||||
UpdateBuffers(cbs, pbp, binding, uniformBuffers.Slice(binding, count), DescriptorType.UniformBuffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -54,29 +54,29 @@ namespace Ryujinx.Graphics.Vulkan.Effects
|
||||
var scalingShader = EmbeddedResources.Read("Ryujinx.Graphics.Vulkan/Effects/Shaders/FsrScaling.spv");
|
||||
var sharpeningShader = EmbeddedResources.Read("Ryujinx.Graphics.Vulkan/Effects/Shaders/FsrSharpening.spv");
|
||||
|
||||
var computeBindings = new ShaderBindings(
|
||||
new[] { 2 },
|
||||
Array.Empty<int>(),
|
||||
new[] { 1 },
|
||||
new[] { 0 });
|
||||
var scalingResourceLayout = new ResourceLayoutBuilder()
|
||||
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 2)
|
||||
.Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 1)
|
||||
.Add(ResourceStages.Compute, ResourceType.Image, 0).Build();
|
||||
|
||||
var sharpeningBindings = new ShaderBindings(
|
||||
new[] { 2, 3, 4 },
|
||||
Array.Empty<int>(),
|
||||
new[] { 1 },
|
||||
new[] { 0 });
|
||||
var sharpeningResourceLayout = new ResourceLayoutBuilder()
|
||||
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 2)
|
||||
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 3)
|
||||
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 4)
|
||||
.Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 1)
|
||||
.Add(ResourceStages.Compute, ResourceType.Image, 0).Build();
|
||||
|
||||
_sampler = _renderer.CreateSampler(GAL.SamplerCreateInfo.Create(MinFilter.Linear, MagFilter.Linear));
|
||||
|
||||
_scalingProgram = _renderer.CreateProgramWithMinimalLayout(new[]
|
||||
{
|
||||
new ShaderSource(scalingShader, computeBindings, ShaderStage.Compute, TargetLanguage.Spirv)
|
||||
});
|
||||
new ShaderSource(scalingShader, ShaderStage.Compute, TargetLanguage.Spirv)
|
||||
}, scalingResourceLayout);
|
||||
|
||||
_sharpeningProgram = _renderer.CreateProgramWithMinimalLayout(new[]
|
||||
{
|
||||
new ShaderSource(sharpeningShader, sharpeningBindings, ShaderStage.Compute, TargetLanguage.Spirv)
|
||||
});
|
||||
new ShaderSource(sharpeningShader, ShaderStage.Compute, TargetLanguage.Spirv)
|
||||
}, sharpeningResourceLayout);
|
||||
}
|
||||
|
||||
public void Run(
|
||||
@ -160,10 +160,8 @@ namespace Ryujinx.Graphics.Vulkan.Effects
|
||||
_pipeline.ComputeBarrier();
|
||||
|
||||
// Sharpening pass
|
||||
_pipeline.SetCommandBuffer(cbs);
|
||||
_pipeline.SetProgram(_sharpeningProgram);
|
||||
_pipeline.SetTextureAndSampler(ShaderStage.Compute, 1, _intermediaryTexture, _sampler);
|
||||
_pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(2, bufferRanges) });
|
||||
var sharpeningRange = new BufferRange(sharpeningBufferHandle, 0, sizeof(float));
|
||||
_pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(4, sharpeningRange) });
|
||||
_pipeline.SetImage(0, destinationTexture);
|
||||
|
@ -38,18 +38,17 @@ namespace Ryujinx.Graphics.Vulkan.Effects
|
||||
|
||||
var shader = EmbeddedResources.Read("Ryujinx.Graphics.Vulkan/Effects/Shaders/Fxaa.spv");
|
||||
|
||||
var computeBindings = new ShaderBindings(
|
||||
new[] { 2 },
|
||||
Array.Empty<int>(),
|
||||
new[] { 1 },
|
||||
new[] { 0 });
|
||||
var resourceLayout = new ResourceLayoutBuilder()
|
||||
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 2)
|
||||
.Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 1)
|
||||
.Add(ResourceStages.Compute, ResourceType.Image, 0).Build();
|
||||
|
||||
_samplerLinear = _renderer.CreateSampler(GAL.SamplerCreateInfo.Create(MinFilter.Linear, MagFilter.Linear));
|
||||
|
||||
_shaderProgram = _renderer.CreateProgramWithMinimalLayout(new[]
|
||||
{
|
||||
new ShaderSource(shader, computeBindings, ShaderStage.Compute, TargetLanguage.Spirv)
|
||||
});
|
||||
new ShaderSource(shader, ShaderStage.Compute, TargetLanguage.Spirv)
|
||||
}, resourceLayout);
|
||||
}
|
||||
|
||||
public TextureView Run(TextureView view, CommandBufferScoped cbs, int width, int height)
|
||||
|
@ -77,23 +77,23 @@ namespace Ryujinx.Graphics.Vulkan.Effects
|
||||
var blendShader = EmbeddedResources.Read("Ryujinx.Graphics.Vulkan/Effects/Shaders/SmaaBlend.spv");
|
||||
var neighbourShader = EmbeddedResources.Read("Ryujinx.Graphics.Vulkan/Effects/Shaders/SmaaNeighbour.spv");
|
||||
|
||||
var edgeBindings = new ShaderBindings(
|
||||
new[] { 2 },
|
||||
Array.Empty<int>(),
|
||||
new[] { 1 },
|
||||
new[] { 0 });
|
||||
var edgeResourceLayout = new ResourceLayoutBuilder()
|
||||
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 2)
|
||||
.Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 1)
|
||||
.Add(ResourceStages.Compute, ResourceType.Image, 0).Build();
|
||||
|
||||
var blendBindings = new ShaderBindings(
|
||||
new[] { 2 },
|
||||
Array.Empty<int>(),
|
||||
new[] { 1, 3, 4 },
|
||||
new[] { 0 });
|
||||
var blendResourceLayout = new ResourceLayoutBuilder()
|
||||
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 2)
|
||||
.Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 1)
|
||||
.Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 3)
|
||||
.Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 4)
|
||||
.Add(ResourceStages.Compute, ResourceType.Image, 0).Build();
|
||||
|
||||
var neighbourBindings = new ShaderBindings(
|
||||
new[] { 2 },
|
||||
Array.Empty<int>(),
|
||||
new[] { 1, 3 },
|
||||
new[] { 0 });
|
||||
var neighbourResourceLayout = new ResourceLayoutBuilder()
|
||||
.Add(ResourceStages.Compute, ResourceType.UniformBuffer, 2)
|
||||
.Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 1)
|
||||
.Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 3)
|
||||
.Add(ResourceStages.Compute, ResourceType.Image, 0).Build();
|
||||
|
||||
_samplerLinear = _renderer.CreateSampler(GAL.SamplerCreateInfo.Create(MinFilter.Linear, MagFilter.Linear));
|
||||
|
||||
@ -117,18 +117,18 @@ namespace Ryujinx.Graphics.Vulkan.Effects
|
||||
|
||||
_edgeProgram = _renderer.CreateProgramWithMinimalLayout(new[]
|
||||
{
|
||||
new ShaderSource(edgeShader, edgeBindings, ShaderStage.Compute, TargetLanguage.Spirv)
|
||||
}, new[] { specInfo });
|
||||
new ShaderSource(edgeShader, ShaderStage.Compute, TargetLanguage.Spirv)
|
||||
}, edgeResourceLayout, new[] { specInfo });
|
||||
|
||||
_blendProgram = _renderer.CreateProgramWithMinimalLayout(new[]
|
||||
{
|
||||
new ShaderSource(blendShader, blendBindings, ShaderStage.Compute, TargetLanguage.Spirv)
|
||||
}, new[] { specInfo });
|
||||
new ShaderSource(blendShader, ShaderStage.Compute, TargetLanguage.Spirv)
|
||||
}, blendResourceLayout, new[] { specInfo });
|
||||
|
||||
_neighbourProgram = _renderer.CreateProgramWithMinimalLayout(new[]
|
||||
{
|
||||
new ShaderSource(neighbourShader, neighbourBindings, ShaderStage.Compute, TargetLanguage.Spirv)
|
||||
}, new[] { specInfo });
|
||||
new ShaderSource(neighbourShader, ShaderStage.Compute, TargetLanguage.Spirv)
|
||||
}, neighbourResourceLayout, new[] { specInfo });
|
||||
}
|
||||
|
||||
public void DeletePipelines()
|
||||
|
@ -36,6 +36,56 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
};
|
||||
}
|
||||
|
||||
public static ShaderStageFlags Convert(this ResourceStages stages)
|
||||
{
|
||||
ShaderStageFlags stageFlags = stages.HasFlag(ResourceStages.Compute)
|
||||
? ShaderStageFlags.ComputeBit
|
||||
: ShaderStageFlags.None;
|
||||
|
||||
if (stages.HasFlag(ResourceStages.Vertex))
|
||||
{
|
||||
stageFlags |= ShaderStageFlags.VertexBit;
|
||||
}
|
||||
|
||||
if (stages.HasFlag(ResourceStages.TessellationControl))
|
||||
{
|
||||
stageFlags |= ShaderStageFlags.TessellationControlBit;
|
||||
}
|
||||
|
||||
if (stages.HasFlag(ResourceStages.TessellationEvaluation))
|
||||
{
|
||||
stageFlags |= ShaderStageFlags.TessellationEvaluationBit;
|
||||
}
|
||||
|
||||
if (stages.HasFlag(ResourceStages.Geometry))
|
||||
{
|
||||
stageFlags |= ShaderStageFlags.GeometryBit;
|
||||
}
|
||||
|
||||
if (stages.HasFlag(ResourceStages.Fragment))
|
||||
{
|
||||
stageFlags |= ShaderStageFlags.FragmentBit;
|
||||
}
|
||||
|
||||
return stageFlags;
|
||||
}
|
||||
|
||||
public static DescriptorType Convert(this ResourceType type)
|
||||
{
|
||||
return type switch
|
||||
{
|
||||
ResourceType.UniformBuffer => DescriptorType.UniformBuffer,
|
||||
ResourceType.StorageBuffer => DescriptorType.StorageBuffer,
|
||||
ResourceType.Texture => DescriptorType.SampledImage,
|
||||
ResourceType.Sampler => DescriptorType.Sampler,
|
||||
ResourceType.TextureAndSampler => DescriptorType.CombinedImageSampler,
|
||||
ResourceType.Image => DescriptorType.StorageImage,
|
||||
ResourceType.BufferTexture => DescriptorType.UniformTexelBuffer,
|
||||
ResourceType.BufferImage => DescriptorType.StorageTexelBuffer,
|
||||
_ => throw new ArgumentException($"Invalid resource type \"{type}\".")
|
||||
};
|
||||
}
|
||||
|
||||
public static SamplerAddressMode Convert(this AddressMode mode)
|
||||
{
|
||||
return mode switch
|
||||
@ -48,7 +98,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
AddressMode.ClampToBorder => SamplerAddressMode.ClampToBorder,
|
||||
AddressMode.MirroredRepeat => SamplerAddressMode.MirroredRepeat,
|
||||
AddressMode.ClampToEdge => SamplerAddressMode.ClampToEdge,
|
||||
_ => LogInvalidAndReturn(mode, nameof(AddressMode), SamplerAddressMode.ClampToEdge) // TODO: Should be clamp.
|
||||
_ => LogInvalidAndReturn(mode, nameof(AddressMode), SamplerAddressMode.ClampToEdge) // TODO: Should be clamp.
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -65,6 +65,13 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
return (formatFeatureFlags & flags) == flags;
|
||||
}
|
||||
|
||||
public bool BufferFormatSupports(FormatFeatureFlags flags, VkFormat format)
|
||||
{
|
||||
_api.GetPhysicalDeviceFormatProperties(_physicalDevice, format, out var fp);
|
||||
|
||||
return (fp.BufferFeatures & flags) == flags;
|
||||
}
|
||||
|
||||
public bool OptimalFormatSupports(FormatFeatureFlags flags, GAL.Format format)
|
||||
{
|
||||
var formatFeatureFlags = _optimalTable[(int)format];
|
||||
|
@ -168,5 +168,223 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
{
|
||||
return _table[(int)format];
|
||||
}
|
||||
|
||||
public static int GetAttributeFormatSize(VkFormat format)
|
||||
{
|
||||
switch (format)
|
||||
{
|
||||
case VkFormat.R8Unorm:
|
||||
case VkFormat.R8SNorm:
|
||||
case VkFormat.R8Uint:
|
||||
case VkFormat.R8Sint:
|
||||
case VkFormat.R8Uscaled:
|
||||
case VkFormat.R8Sscaled:
|
||||
return 1;
|
||||
|
||||
case VkFormat.R8G8Unorm:
|
||||
case VkFormat.R8G8SNorm:
|
||||
case VkFormat.R8G8Uint:
|
||||
case VkFormat.R8G8Sint:
|
||||
case VkFormat.R8G8Uscaled:
|
||||
case VkFormat.R8G8Sscaled:
|
||||
case VkFormat.R16Sfloat:
|
||||
case VkFormat.R16Unorm:
|
||||
case VkFormat.R16SNorm:
|
||||
case VkFormat.R16Uint:
|
||||
case VkFormat.R16Sint:
|
||||
case VkFormat.R16Uscaled:
|
||||
case VkFormat.R16Sscaled:
|
||||
return 2;
|
||||
|
||||
case VkFormat.R8G8B8Unorm:
|
||||
case VkFormat.R8G8B8SNorm:
|
||||
case VkFormat.R8G8B8Uint:
|
||||
case VkFormat.R8G8B8Sint:
|
||||
case VkFormat.R8G8B8Uscaled:
|
||||
case VkFormat.R8G8B8Sscaled:
|
||||
return 3;
|
||||
|
||||
case VkFormat.R8G8B8A8Unorm:
|
||||
case VkFormat.R8G8B8A8SNorm:
|
||||
case VkFormat.R8G8B8A8Uint:
|
||||
case VkFormat.R8G8B8A8Sint:
|
||||
case VkFormat.R8G8B8A8Srgb:
|
||||
case VkFormat.R8G8B8A8Uscaled:
|
||||
case VkFormat.R8G8B8A8Sscaled:
|
||||
case VkFormat.B8G8R8A8Unorm:
|
||||
case VkFormat.B8G8R8A8Srgb:
|
||||
case VkFormat.R16G16Sfloat:
|
||||
case VkFormat.R16G16Unorm:
|
||||
case VkFormat.R16G16SNorm:
|
||||
case VkFormat.R16G16Uint:
|
||||
case VkFormat.R16G16Sint:
|
||||
case VkFormat.R16G16Uscaled:
|
||||
case VkFormat.R16G16Sscaled:
|
||||
case VkFormat.R32Sfloat:
|
||||
case VkFormat.R32Uint:
|
||||
case VkFormat.R32Sint:
|
||||
case VkFormat.A2B10G10R10UnormPack32:
|
||||
case VkFormat.A2B10G10R10UintPack32:
|
||||
case VkFormat.B10G11R11UfloatPack32:
|
||||
case VkFormat.E5B9G9R9UfloatPack32:
|
||||
case VkFormat.A2B10G10R10SNormPack32:
|
||||
case VkFormat.A2B10G10R10SintPack32:
|
||||
case VkFormat.A2B10G10R10UscaledPack32:
|
||||
case VkFormat.A2B10G10R10SscaledPack32:
|
||||
return 4;
|
||||
|
||||
case VkFormat.R16G16B16Sfloat:
|
||||
case VkFormat.R16G16B16Unorm:
|
||||
case VkFormat.R16G16B16SNorm:
|
||||
case VkFormat.R16G16B16Uint:
|
||||
case VkFormat.R16G16B16Sint:
|
||||
case VkFormat.R16G16B16Uscaled:
|
||||
case VkFormat.R16G16B16Sscaled:
|
||||
return 6;
|
||||
|
||||
case VkFormat.R16G16B16A16Sfloat:
|
||||
case VkFormat.R16G16B16A16Unorm:
|
||||
case VkFormat.R16G16B16A16SNorm:
|
||||
case VkFormat.R16G16B16A16Uint:
|
||||
case VkFormat.R16G16B16A16Sint:
|
||||
case VkFormat.R16G16B16A16Uscaled:
|
||||
case VkFormat.R16G16B16A16Sscaled:
|
||||
case VkFormat.R32G32Sfloat:
|
||||
case VkFormat.R32G32Uint:
|
||||
case VkFormat.R32G32Sint:
|
||||
return 8;
|
||||
|
||||
case VkFormat.R32G32B32Sfloat:
|
||||
case VkFormat.R32G32B32Uint:
|
||||
case VkFormat.R32G32B32Sint:
|
||||
return 12;
|
||||
|
||||
case VkFormat.R32G32B32A32Sfloat:
|
||||
case VkFormat.R32G32B32A32Uint:
|
||||
case VkFormat.R32G32B32A32Sint:
|
||||
return 16;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
public static VkFormat DropLastComponent(VkFormat format)
|
||||
{
|
||||
switch (format)
|
||||
{
|
||||
case VkFormat.R8G8Unorm:
|
||||
return VkFormat.R8Unorm;
|
||||
case VkFormat.R8G8SNorm:
|
||||
return VkFormat.R8SNorm;
|
||||
case VkFormat.R8G8Uint:
|
||||
return VkFormat.R8Uint;
|
||||
case VkFormat.R8G8Sint:
|
||||
return VkFormat.R8Sint;
|
||||
case VkFormat.R8G8Uscaled:
|
||||
return VkFormat.R8Uscaled;
|
||||
case VkFormat.R8G8Sscaled:
|
||||
return VkFormat.R8Sscaled;
|
||||
|
||||
case VkFormat.R8G8B8Unorm:
|
||||
return VkFormat.R8G8Unorm;
|
||||
case VkFormat.R8G8B8SNorm:
|
||||
return VkFormat.R8G8SNorm;
|
||||
case VkFormat.R8G8B8Uint:
|
||||
return VkFormat.R8G8Uint;
|
||||
case VkFormat.R8G8B8Sint:
|
||||
return VkFormat.R8G8Sint;
|
||||
case VkFormat.R8G8B8Uscaled:
|
||||
return VkFormat.R8G8Uscaled;
|
||||
case VkFormat.R8G8B8Sscaled:
|
||||
return VkFormat.R8G8Sscaled;
|
||||
|
||||
case VkFormat.R8G8B8A8Unorm:
|
||||
return VkFormat.R8G8B8Unorm;
|
||||
case VkFormat.R8G8B8A8SNorm:
|
||||
return VkFormat.R8G8B8SNorm;
|
||||
case VkFormat.R8G8B8A8Uint:
|
||||
return VkFormat.R8G8B8Uint;
|
||||
case VkFormat.R8G8B8A8Sint:
|
||||
return VkFormat.R8G8B8Sint;
|
||||
case VkFormat.R8G8B8A8Srgb:
|
||||
return VkFormat.R8G8B8Srgb;
|
||||
case VkFormat.R8G8B8A8Uscaled:
|
||||
return VkFormat.R8G8B8Uscaled;
|
||||
case VkFormat.R8G8B8A8Sscaled:
|
||||
return VkFormat.R8G8B8Sscaled;
|
||||
case VkFormat.B8G8R8A8Unorm:
|
||||
return VkFormat.B8G8R8Unorm;
|
||||
case VkFormat.B8G8R8A8Srgb:
|
||||
return VkFormat.B8G8R8Srgb;
|
||||
|
||||
case VkFormat.R16G16Sfloat:
|
||||
return VkFormat.R16Sfloat;
|
||||
case VkFormat.R16G16Unorm:
|
||||
return VkFormat.R16Unorm;
|
||||
case VkFormat.R16G16SNorm:
|
||||
return VkFormat.R16SNorm;
|
||||
case VkFormat.R16G16Uint:
|
||||
return VkFormat.R16Uint;
|
||||
case VkFormat.R16G16Sint:
|
||||
return VkFormat.R16Sint;
|
||||
case VkFormat.R16G16Uscaled:
|
||||
return VkFormat.R16Uscaled;
|
||||
case VkFormat.R16G16Sscaled:
|
||||
return VkFormat.R16Sscaled;
|
||||
|
||||
case VkFormat.R16G16B16Sfloat:
|
||||
return VkFormat.R16G16Sfloat;
|
||||
case VkFormat.R16G16B16Unorm:
|
||||
return VkFormat.R16G16Unorm;
|
||||
case VkFormat.R16G16B16SNorm:
|
||||
return VkFormat.R16G16SNorm;
|
||||
case VkFormat.R16G16B16Uint:
|
||||
return VkFormat.R16G16Uint;
|
||||
case VkFormat.R16G16B16Sint:
|
||||
return VkFormat.R16G16Sint;
|
||||
case VkFormat.R16G16B16Uscaled:
|
||||
return VkFormat.R16G16Uscaled;
|
||||
case VkFormat.R16G16B16Sscaled:
|
||||
return VkFormat.R16G16Sscaled;
|
||||
|
||||
case VkFormat.R16G16B16A16Sfloat:
|
||||
return VkFormat.R16G16B16Sfloat;
|
||||
case VkFormat.R16G16B16A16Unorm:
|
||||
return VkFormat.R16G16B16Unorm;
|
||||
case VkFormat.R16G16B16A16SNorm:
|
||||
return VkFormat.R16G16B16SNorm;
|
||||
case VkFormat.R16G16B16A16Uint:
|
||||
return VkFormat.R16G16B16Uint;
|
||||
case VkFormat.R16G16B16A16Sint:
|
||||
return VkFormat.R16G16B16Sint;
|
||||
case VkFormat.R16G16B16A16Uscaled:
|
||||
return VkFormat.R16G16B16Uscaled;
|
||||
case VkFormat.R16G16B16A16Sscaled:
|
||||
return VkFormat.R16G16B16Sscaled;
|
||||
|
||||
case VkFormat.R32G32Sfloat:
|
||||
return VkFormat.R32Sfloat;
|
||||
case VkFormat.R32G32Uint:
|
||||
return VkFormat.R32Uint;
|
||||
case VkFormat.R32G32Sint:
|
||||
return VkFormat.R32Sint;
|
||||
|
||||
case VkFormat.R32G32B32Sfloat:
|
||||
return VkFormat.R32G32Sfloat;
|
||||
case VkFormat.R32G32B32Uint:
|
||||
return VkFormat.R32G32Uint;
|
||||
case VkFormat.R32G32B32Sint:
|
||||
return VkFormat.R32G32Sint;
|
||||
|
||||
case VkFormat.R32G32B32A32Sfloat:
|
||||
return VkFormat.R32G32B32Sfloat;
|
||||
case VkFormat.R32G32B32A32Uint:
|
||||
return VkFormat.R32G32B32Uint;
|
||||
case VkFormat.R32G32B32A32Sint:
|
||||
return VkFormat.R32G32B32Sint;
|
||||
}
|
||||
|
||||
return format;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
public uint[] AttachmentSamples { get; }
|
||||
public VkFormat[] AttachmentFormats { get; }
|
||||
public int[] AttachmentIndices { get; }
|
||||
public uint AttachmentIntegerFormatMask { get; }
|
||||
|
||||
public int AttachmentsCount { get; }
|
||||
public int MaxColorAttachmentIndex => AttachmentIndices.Length > 0 ? AttachmentIndices[AttachmentIndices.Length - 1] : -1;
|
||||
@ -74,6 +75,7 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
|
||||
int index = 0;
|
||||
int bindIndex = 0;
|
||||
uint attachmentIntegerFormatMask = 0;
|
||||
|
||||
foreach (ITexture color in colors)
|
||||
{
|
||||
@ -89,6 +91,11 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
AttachmentFormats[index] = texture.VkFormat;
|
||||
AttachmentIndices[index] = bindIndex;
|
||||
|
||||
if (texture.Info.Format.IsInteger())
|
||||
{
|
||||
attachmentIntegerFormatMask |= 1u << bindIndex;
|
||||
}
|
||||
|
||||
width = Math.Min(width, (uint)texture.Width);
|
||||
height = Math.Min(height, (uint)texture.Height);
|
||||
layers = Math.Min(layers, (uint)texture.Layers);
|
||||
@ -102,6 +109,8 @@ namespace Ryujinx.Graphics.Vulkan
|
||||
bindIndex++;
|
||||
}
|
||||
|
||||
AttachmentIntegerFormatMask = attachmentIntegerFormatMask;
|
||||
|
||||
if (depthStencil is TextureView dsTexture && dsTexture.Valid)
|
||||
{
|
||||
_attachments[count - 1] = dsTexture.GetImageViewForAttachment();
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user